<?php

/* force OPcache refresh on each deploy */
if (function_exists('opcache_invalidate')) {
    opcache_invalidate(__FILE__, /*force=*/true);
}

if (!defined('ABSPATH')) {
    exit;
}

/* ======================================================================
 *  PUBLIC API
 * ==================================================================== */

/**
 * Run every saved rule (wp-cron or “Run all”)
 */
function asana_automator_dispatch_rules() {
    


    /* ── cron diagnostics ───────────────────────────── */
    file_put_contents(WP_CONTENT_DIR.'/cron-test.log',
        date('c')." 📋 Dispatch start\n", FILE_APPEND);

    $rules = get_option('asana_automator_rules', []);
    file_put_contents(WP_CONTENT_DIR.'/cron-test.log',
        date('c')." 📦 Found ".count($rules)." rules\n", FILE_APPEND);
        
            /* ── DEBUG: dump rule array once ─────────────────────────── */
file_put_contents(
    WP_CONTENT_DIR . '/cron-test.log',
    date('c') . " 🐞 Rules payload: " . json_encode($rules, JSON_UNESCAPED_SLASHES) . "\n",
    FILE_APPEND
);
/* ────────────────────────────────────────────────────────── */


    $pat_accounts = asana_automator_get_pat_accounts();
    file_put_contents(WP_CONTENT_DIR.'/cron-test.log',
        date('c')." 🔐 Found ".count($pat_accounts)." PAT accounts\n", FILE_APPEND);
    /* ───────────────────────────────────────────────── */

    $automations = asana_automator_get_registered_automations();

    foreach ($rules as $rule) {
        file_put_contents(WP_CONTENT_DIR.'/cron-test.log',
            date('c')." 🎯 Attempting rule: ".($rule['rule_label'] ?? 'Unnamed')."\n",
            FILE_APPEND);

        _asana_automator_execute_rule($rule, $pat_accounts, $automations);
    }
}


/**
 * Run **one** rule by its array index (used by the ▶️ Run links).
 *
 * @param int $index
 * @return bool  True = executed, False = invalid index.
 */
function asana_automator_run_rule_by_index($index) {
    $rules        = get_option('asana_automator_rules', []);
    $pat_accounts = asana_automator_get_pat_accounts();
    $automations  = asana_automator_get_registered_automations();

    if (!isset($rules[$index])) {
        return false;
    }
    _asana_automator_execute_rule($rules[$index], $pat_accounts, $automations);
    return true;
}

/* ======================================================================
 *  INTERNAL EXECUTOR
 * ==================================================================== */

function _asana_automator_execute_rule($rule, $pat_accounts, $automations) {
    
    // cron tests
file_put_contents(WP_CONTENT_DIR . '/cron-test.log',
    date('c') . " 🧪 Executing rule: " . ($rule['rule_label'] ?? 'Unnamed') . "\n", FILE_APPEND);


    $log_file = WP_CONTENT_DIR . '/custom-debug.log';

    /* ---------- Validate rule ---------- */
    $action_key = $rule['action'] ?? '';
    if (!isset($automations[$action_key])) {
        file_put_contents($log_file, "⚠️ Unknown action: {$action_key}\n", FILE_APPEND);
        return;
    }
    $handler_fn = $automations[$action_key]['handler'];
    if (!function_exists($handler_fn)) {
        file_put_contents($log_file, "⚠️ Missing handler function: {$handler_fn}\n", FILE_APPEND);
        return;
    }

    /* ---------- Select PAT token ---------- */
    $pat_label = $rule['pat_label'] ?? '';
    $token     = $pat_accounts[$pat_label] ?? reset($pat_accounts);
    if (empty($token)) {
        file_put_contents($log_file, "⚠️ No PAT accounts configured.\n", FILE_APPEND);
        return;
    }

    /* ---------- Rule scan guardrails ---------- */
    $lookback_days = max(0, absint($rule['lookback_days'] ?? 14));
    $max_tasks_per_run = max(1, min(1000, absint($rule['max_tasks_per_run'] ?? 100)));
    $processed_tasks = 0;
    $hit_task_cap = false;

    file_put_contents(
        $log_file,
        "🛡 Rule scan guardrails: lookback_days={$lookback_days}, max_tasks_per_run={$max_tasks_per_run}\n",
        FILE_APPEND
    );

    /* ---------- Query params ---------- */
    $query_args_base = [];

    // Completion-focused handlers need completed tasks in section queries.
    // Asana defaults to incomplete tasks unless completed_since is provided.
    $completion_actions = [
        'delete_completed_tasks',
        'clear_field_after_done',
        'cleanup_on_completion',
        'email_client_gateway_on_completion',
    ];
    $completion_state = strtolower(trim((string)($rule['completion_state'] ?? '')));
    if (
        in_array($action_key, $completion_actions, true)
        || ($action_key === 'email_on_section_entry' && $completion_state === 'complete')
    ) {
        if ($lookback_days > 0) {
            $completed_since_ts = max(0, time() - ($lookback_days * DAY_IN_SECONDS));
            $query_args_base['completed_since'] = gmdate('Y-m-d\\TH:i:s.000\\Z', $completed_since_ts);
        } else {
            $query_args_base['completed_since'] = '1970-01-01T00:00:00.000Z';
        }
    }

    $seen_task_ids = [];

    /* ---------- Iterate each configured section ---------- */
    file_put_contents(WP_CONTENT_DIR.'/cron-test.log',
    date('c') . " 🧭 Rule sections: " . json_encode($rule['section_gids']) . "\n", FILE_APPEND);

    foreach ($rule['section_gids'] ?? [] as $section_gid) {
        $remaining = $max_tasks_per_run - $processed_tasks;
        if ($remaining <= 0) {
            $hit_task_cap = true;
            break;
        }

        $query_args = $query_args_base;
        $query_args['limit'] = min(100, max(1, $remaining));
        $query = '?' . http_build_query($query_args, '', '&', PHP_QUERY_RFC3986);

        file_put_contents(
            $log_file,
            "🔍 Fetching tasks for section {$section_gid} using {$query}\n",
            FILE_APPEND
        );

        $res = wp_remote_get(
            "https://app.asana.com/api/1.0/sections/{$section_gid}/tasks{$query}",
            ['headers' => ['Authorization' => "Bearer {$token}"]]
        );
        
        // cron tests
if (is_wp_error($res)) {
    file_put_contents(WP_CONTENT_DIR . '/cron-test.log',
        date('c') . " ❌ HTTP Error: " . $res->get_error_message() . "\n", FILE_APPEND);
} else {
    file_put_contents(WP_CONTENT_DIR . '/cron-test.log',
        date('c') . " 📡 API response: " . wp_remote_retrieve_response_code($res) . "\n", FILE_APPEND);
}


        if (is_wp_error($res)) {
            file_put_contents(
                $log_file,
                "❌ Error fetching tasks for section {$section_gid}: " .
                $res->get_error_message() . "\n",
                FILE_APPEND
            );
            continue;
        }

       $tasks = json_decode(wp_remote_retrieve_body($res), true)['data'] ?? [];
file_put_contents(WP_CONTENT_DIR . '/cron-test.log',
    date('c') . " 🧾 Section {$section_gid}: " . count($tasks) . " tasks retrieved\n",
    FILE_APPEND
);

        file_put_contents(
            $log_file,
            "🔢 Retrieved " . count($tasks) . " tasks from section {$section_gid}\n",
            FILE_APPEND
        );

        /* ---------- Process each task ---------- */
        foreach ($tasks as $summary) {

            if ($processed_tasks >= $max_tasks_per_run) {
                $hit_task_cap = true;
                break 2;
            }

            $task_id = trim((string)($summary['gid'] ?? ''));
            if ($task_id === '') {
                continue;
            }
            if (isset($seen_task_ids[$task_id])) {
                continue;
            }
            $seen_task_ids[$task_id] = true;
            $processed_tasks++;
            $detail  = wp_remote_get(
                "https://app.asana.com/api/1.0/tasks/{$task_id}?opt_fields=name,notes,completed,tags.gid,tags.name,custom_fields.gid,custom_fields.name,custom_fields.text_value,custom_fields.number_value,custom_fields.enum_value.gid,custom_fields.enum_value.name,custom_fields.date_value.date,custom_fields.multi_enum_values.gid,custom_fields.display_value,assignee.name,due_on,created_at,modified_at,projects.name,memberships.section.name,memberships.project.gid",
                ['headers' => ['Authorization' => "Bearer {$token}"]]
            );

            if (is_wp_error($detail)) {
                file_put_contents(
                    $log_file,
                    "❌ Failed to load task {$task_id}: " .
                    $detail->get_error_message() . "\n",
                    FILE_APPEND
                );
                continue;
            }

            $task = json_decode(wp_remote_retrieve_body($detail), true)['data'] ?? [];
            if (empty($task)) {
                continue;
            }

            file_put_contents(
                $log_file,
                "➡️ Passing task {$task_id} ({$task['name']}) to handler {$handler_fn}\n",
                FILE_APPEND
            );

            try {
                $handler_fn($rule, $token, $task);
            } catch (Throwable $e) {
                file_put_contents(
                    $log_file,
                    "❌ Automation error in {$action_key} for task {$task_id}: " .
                    $e->getMessage() . "\n",
                    FILE_APPEND
                );
            }
        }
    }

    file_put_contents(
        $log_file,
        "🏁 Rule scan summary: processed_tasks={$processed_tasks}, max_tasks_per_run={$max_tasks_per_run}" . ($hit_task_cap ? ' (cap reached)' : '') . "\n",
        FILE_APPEND
    );
}

/* ======================================================================
 *  HANDLER FUNCTIONS
 * ==================================================================== */

/* ---------- Email on section entry ---------------------------------- */
function asana_automator_email_on_section_entry($config, $token, $task) {
    $log_file = WP_CONTENT_DIR . '/custom-debug.log';

    $email_gid        = trim($config['email_field_gid'] ?? '');
    $first_gid        = trim($config['first_name'] ?? '');
    $last_gid         = trim($config['last_name'] ?? '');
    $marker_label     = trim($config['marker_label'] ?? '');
    $project_gid      = trim($config['project_gid'] ?? '');
    $completion_state = trim($config['completion_state'] ?? 'any');
    $task_gid         = $task['gid'] ?? '';

    if (!$email_gid || !$task_gid || !$marker_label) {
        file_put_contents($log_file, "⚠️ Missing required config values — skipping\n", FILE_APPEND);
        return;
    }

    $is_completed = !empty($task['completed']);
    if ($completion_state === 'complete' && !$is_completed) return;
    if ($completion_state === 'incomplete' && $is_completed) return;

    $stories = wp_remote_get("https://app.asana.com/api/1.0/tasks/{$task_gid}/stories", [
        'headers' => ['Authorization' => 'Bearer ' . $token],
    ]);
    if (!is_wp_error($stories)) {
        foreach (json_decode(wp_remote_retrieve_body($stories), true)['data'] ?? [] as $s) {
            if (strpos($s['text'], "EMAIL SENT: {$marker_label}") !== false) {
                file_put_contents($log_file, "⏭ Marker exists – skipping task {$task_gid}\n", FILE_APPEND);
                return;
            }
        }
    }

    $email = $first = $last = '';
    foreach ($task['custom_fields'] ?? [] as $field) {
        if ($field['gid'] === $email_gid) $email = $field['text_value'] ?? '';
        if ($field['gid'] === $first_gid) $first = $field['text_value'] ?? '';
        if ($field['gid'] === $last_gid)  $last  = $field['text_value'] ?? '';
    }

    if (!$email || !is_email($email)) {
        file_put_contents($log_file, "🚫 Invalid or missing email for task {$task_gid} — skipping\n", FILE_APPEND);
        return;
    }

    $fullName = trim("$first $last") ?: 'there';

    $section_names = [];
    foreach ($task['memberships'] ?? [] as $m) {
        if (($m['project']['gid'] ?? '') === $project_gid && !empty($m['section']['name']))
            $section_names[] = $m['section']['name'];
    }

    $repl = [
        '{task_name}'     => $task['name'],
        '{task_link}'     => "https://app.asana.com/0/{$task_gid}",
        '{full_name}'     => $fullName,
        '{assignee_name}' => $task['assignee']['name'] ?? '(unassigned)',
        '{due_date}'      => $task['due_on'] ?? '(no due date)',
        '{created_at}'    => $task['created_at'] ?? '',
        '{modified_at}'   => $task['modified_at'] ?? '',
        '{project_name}'  => $task['projects'][0]['name'] ?? '(unknown project)',
        '{section_names}' => implode(', ', $section_names),
    ];

    $subject = str_replace(array_keys($repl), array_values($repl),
        $config['email_subject_template'] ?? '🛎️ Update: {task_name}');
    $body    = str_replace(array_keys($repl), array_values($repl),
        $config['email_body_template']    ?? "Hi {full_name},\n\nTask {task_name} updated.\n\n{task_link}");

    // Render as styled HTML using submission template
    $task_name      = $task['name'] ?? '';
    $request_number = $task['gid'] ?? '';
    $comms_link     = "https://app.asana.com/0/{$task_gid}";
    $fields_html    = '';

    $subject_tpl = (string) $subject;
    if (function_exists('wp_kses_post')) {
        $body_tpl = wp_kses_post(nl2br(esc_html((string)$body)));
    } else {
        $body_tpl = nl2br(htmlspecialchars((string)$body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
    }

    ob_start();
    @include plugin_dir_path(__FILE__) . '../../../admin/emails/submission-template.php';
    $html_body = ob_get_clean();

    $headers = function_exists('afb_get_email_headers') ? afb_get_email_headers() : ['Content-Type: text/html; charset=UTF-8'];
    $sent = wp_mail($email, sanitize_text_field((string)$subject), $html_body, $headers);
    file_put_contents($log_file, "📧 Email to {$email} for task {$task_gid}: " . ($sent ? 'SUCCESS' : 'FAIL') . "\n", FILE_APPEND);

    $marker_txt = $sent
        ? "EMAIL SENT: {$marker_label}"
        : "Email failed for marker: {$marker_label}";

    wp_remote_post("https://app.asana.com/api/1.0/tasks/{$task_gid}/stories", [
        'headers' => ['Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json', 'Asana-Disable' => 'notifications',], 
        'body'    => json_encode(['data' => ['text' => $marker_txt]]),
    ]);
}

function asana_automator_should_skip_comment_notification_text(string $comment_text): bool {
    $text = strtolower(ltrim($comment_text));
    if ($text === '') {
        return true;
    }

    $blocked_prefixes = [
        '[guest]',
        'email sent:',
        'email failed for marker:',
        'automation applied:',
        'automation dry run:',
        'invalid or missing email',
    ];

    foreach ($blocked_prefixes as $prefix) {
        if (strpos($text, $prefix) === 0) {
            return true;
        }
    }

    return false;
}

function asana_automator_email_client_on_new_team_comment($config, $token, $task, array $story = []) {
    $log_file = WP_CONTENT_DIR . '/custom-debug.log';

    $task_gid  = trim((string)($task['gid'] ?? ''));
    $story_gid = trim((string)($story['gid'] ?? ''));
    if ($task_gid === '' || $story_gid === '') {
        // This automation is webhook-driven and requires a concrete story event.
        return false;
    }

    $subtype = strtolower(trim((string)($story['resource_subtype'] ?? $story['type'] ?? '')));
    if ($subtype !== '' && strpos($subtype, 'comment') === false) {
        return false;
    }

    $comment_text = trim((string)($story['text'] ?? ''));
    if (asana_automator_should_skip_comment_notification_text($comment_text)) {
        return false;
    }

    $email_gid = trim((string)($config['email_field_gid'] ?? ''));
    if ($email_gid === '') {
        file_put_contents($log_file, "⚠️ Comment email rule missing email_field_gid for task {$task_gid}\n", FILE_APPEND);
        return false;
    }

    $first_gid = trim((string)($config['first_name'] ?? ''));
    $last_gid  = trim((string)($config['last_name'] ?? ''));

    $email = '';
    $first = '';
    $last  = '';

    foreach ((array)($task['custom_fields'] ?? []) as $field) {
        $gid = (string)($field['gid'] ?? '');
        if ($gid === $email_gid) {
            $email = trim((string)($field['text_value'] ?? $field['display_value'] ?? ''));
        }
        if ($gid === $first_gid) {
            $first = trim((string)($field['text_value'] ?? $field['display_value'] ?? ''));
        }
        if ($gid === $last_gid) {
            $last = trim((string)($field['text_value'] ?? $field['display_value'] ?? ''));
        }
    }

    if (!is_email($email)) {
        file_put_contents($log_file, "🚫 Comment email skipped for task {$task_gid}: invalid client email\n", FILE_APPEND);
        return false;
    }

    $rule_id = trim((string)($config['rule_id'] ?? ''));
    if ($rule_id === '') {
        $rule_id = md5(wp_json_encode([
            (string)($config['project_gid'] ?? ''),
            (array)($config['section_gids'] ?? []),
            $email_gid,
            (string)($config['marker_label'] ?? ''),
        ]));
    }

    $dedupe_key = 'asana_automator_comment_email_' . md5($task_gid . '|' . $story_gid . '|' . $rule_id);
    if (get_transient($dedupe_key)) {
        return true;
    }

    $full_name = trim($first . ' ' . $last);
    if ($full_name === '') {
        $full_name = 'there';
    }

    $rule_project_gid = trim((string)($config['project_gid'] ?? ''));
    $section_names = [];
    foreach ((array)($task['memberships'] ?? []) as $membership) {
        $membership_project_gid = trim((string)($membership['project']['gid'] ?? ''));
        if ($rule_project_gid !== '' && $membership_project_gid !== $rule_project_gid) {
            continue;
        }

        $section_name = trim((string)($membership['section']['name'] ?? ''));
        if ($section_name !== '') {
            $section_names[] = $section_name;
        }
    }
    $section_names = array_values(array_unique($section_names));

    $task_name = (string)($task['name'] ?? 'Task update');
    $author_name = trim((string)($story['created_by']['name'] ?? 'Team'));
    if ($author_name === '') {
        $author_name = 'Team';
    }

    $task_link = "https://app.asana.com/0/{$task_gid}";
    $replacements = [
        '{task_name}'      => $task_name,
        '{task_link}'      => $task_link,
        '{full_name}'      => $full_name,
        '{first_name}'     => $first !== '' ? $first : $full_name,
        '{last_name}'      => $last,
        '{comment_author}' => $author_name,
        '{comment_text}'   => $comment_text,
        '{story_gid}'      => $story_gid,
        '{project_name}'   => (string)($task['projects'][0]['name'] ?? '(unknown project)'),
        '{section_names}'  => implode(', ', $section_names),
    ];

    $subject_template = trim((string)($config['email_subject_template'] ?? 'Update on {task_name}'));
    if ($subject_template === '') {
        $subject_template = 'Update on {task_name}';
    }

    $body_template = (string)($config['email_body_template'] ?? "Hi {full_name},\n\n{comment_author} added a comment on {task_name}:\n\n{comment_text}\n\nView task: {task_link}");
    if (trim($body_template) === '') {
        $body_template = "Hi {full_name},\n\n{comment_author} added a comment on {task_name}:\n\n{comment_text}\n\nView task: {task_link}";
    }

    $subject = str_replace(array_keys($replacements), array_values($replacements), $subject_template);
    $body    = str_replace(array_keys($replacements), array_values($replacements), $body_template);

    // Render styled HTML using submission template
    $task_name      = $task['name'] ?? '';
    $request_number = $task['gid'] ?? '';
    $comms_link     = $task_link;
    $fields_html    = '';

    $subject_tpl = (string) $subject;
    if (function_exists('wp_kses_post')) {
        $body_tpl = wp_kses_post(nl2br(esc_html((string)$body)));
    } else {
        $body_tpl = nl2br(htmlspecialchars((string)$body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
    }

    ob_start();
    @include plugin_dir_path(__FILE__) . '../../../admin/emails/submission-template.php';
    $html_body = ob_get_clean();

    $headers = function_exists('afb_get_email_headers') ? afb_get_email_headers() : ['Content-Type: text/html; charset=UTF-8'];
    $sent = wp_mail($email, sanitize_text_field((string)$subject), $html_body, $headers);
    if ($sent) {
        set_transient($dedupe_key, 1, WEEK_IN_SECONDS);
    }

    file_put_contents(
        $log_file,
        "📧 Comment email " . ($sent ? 'SUCCESS' : 'FAIL') . " for task {$task_gid}, story {$story_gid}, to {$email}\n",
        FILE_APPEND
    );

    return $sent;
}

function asana_automator_email_client_gateway_on_completion($config, $token, $task) {
    $log_file = WP_CONTENT_DIR . '/custom-debug.log';

    $task_gid = trim((string)($task['gid'] ?? ''));
    if ($task_gid === '' || empty($task['completed'])) {
        return false;
    }

    $email_gid = trim((string)($config['email_field_gid'] ?? ''));
    if ($email_gid === '') {
        file_put_contents($log_file, "⚠️ Completion gateway email rule missing email_field_gid for task {$task_gid}\n", FILE_APPEND);
        return false;
    }

    $marker_label = trim((string)($config['marker_label'] ?? 'gateway_completion_email'));
    if ($marker_label === '') {
        $marker_label = 'gateway_completion_email';
    }

    $marker_text = "EMAIL SENT: {$marker_label}";
    if (function_exists('asana_automator_task_has_story_marker') && asana_automator_task_has_story_marker($task_gid, $token, $marker_text)) {
        return true;
    }

    $first_gid = trim((string)($config['first_name'] ?? ''));
    $last_gid  = trim((string)($config['last_name'] ?? ''));

    $gateway_link_gid = sanitize_text_field((string) get_option('afb_client_gateway_link_field_gid', ''));
    $gateway_pin_gid  = sanitize_text_field((string) get_option('afb_client_gateway_pin_field_gid', ''));

    $email = '';
    $first = '';
    $last  = '';
    $gateway_url = '';
    $gateway_pin = '';
    $job_number = '';

    foreach ((array)($task['custom_fields'] ?? []) as $field) {
        $gid = trim((string)($field['gid'] ?? ''));
        $name = trim((string)($field['name'] ?? ''));
        $name_lc = strtolower($name);

        $value = trim((string)($field['text_value'] ?? $field['display_value'] ?? ''));
        if ($value === '' && isset($field['number_value']) && $field['number_value'] !== null && $field['number_value'] !== '') {
            $value = trim((string)$field['number_value']);
        }

        if ($gid !== '' && $gid === $email_gid) {
            $email = $value;
        }
        if ($first_gid !== '' && $gid === $first_gid) {
            $first = $value;
        }
        if ($last_gid !== '' && $gid === $last_gid) {
            $last = $value;
        }

        if ($job_number === '' && in_array($name, ['Job Number (SCB)', 'Job Number (COPFM)', 'Job Number'], true)) {
            $job_number = $value;
        }

        if ($gateway_url === '' && $gateway_link_gid !== '' && $gid === $gateway_link_gid) {
            $gateway_url = $value;
        }
        if ($gateway_pin === '' && $gateway_pin_gid !== '' && $gid === $gateway_pin_gid) {
            $gateway_pin = $value;
        }

        if ($gateway_url === '' && $name_lc !== '' && str_contains($name_lc, 'client gateway') && !str_contains($name_lc, 'create') && !str_contains($name_lc, 'pin')) {
            $gateway_url = $value;
        }
        if ($gateway_pin === '' && $name_lc !== '' && str_contains($name_lc, 'gateway pin')) {
            $gateway_pin = $value;
        }
    }

    if (!is_email($email)) {
        file_put_contents($log_file, "🚫 Completion gateway email skipped for task {$task_gid}: invalid client email\n", FILE_APPEND);
        return false;
    }

    if ($gateway_url === '') {
        file_put_contents($log_file, "🚫 Completion gateway email skipped for task {$task_gid}: missing gateway URL\n", FILE_APPEND);
        return false;
    }

    $auto_refresh_gateway = !array_key_exists('auto_refresh_gateway', (array)$config)
        || !empty($config['auto_refresh_gateway']);
    if ($auto_refresh_gateway) {
        $gateway_url = add_query_arg(['refresh' => '1'], $gateway_url);
    }

    $task_gid_digits = preg_replace('/\D+/', '', $task_gid);
    $gateway_path = (string) parse_url($gateway_url, PHP_URL_PATH);
    $gateway_token = '';
    if (preg_match('~/task-dashboard/([^/\?#]+)~i', $gateway_path, $m)) {
        $gateway_token = sanitize_text_field((string)$m[1]);
    }

    if ($gateway_token !== '' && $task_gid_digits !== '' && function_exists('afb_gateway_build_magic_link')) {
        $gateway_url = (string) afb_gateway_build_magic_link($gateway_url, $gateway_token, $task_gid_digits);
    }

    $include_gateway_pin = !empty($config['include_gateway_pin']) && (string)$config['include_gateway_pin'] === '1';
    $gateway_pin_for_email = $include_gateway_pin ? $gateway_pin : '';

    $full_name = trim($first . ' ' . $last);
    if ($full_name === '') {
        $full_name = $first !== '' ? $first : 'there';
    }

    $first_name = $first !== '' ? $first : $full_name;
    $subject_template = trim((string)($config['email_subject_template'] ?? ''));
    if ($subject_template === '') {
        $subject_template = trim((string) get_option('afb_cg_completion_email_subject', 'Request complete: {task_name}'));
    }
    if ($subject_template === '') {
        $subject_template = 'Request complete: {task_name}';
    }

    $default_completion_body = '<p>Hi {first_name},</p><p>Your request for "<strong>{task_name}</strong>" is now complete.</p><p>Please review the completed request and submit feedback using your Client Gateway below.</p>';
    $body_template = (string)($config['email_body_template'] ?? '');
    if (trim($body_template) === '') {
        $body_template = (string) get_option('afb_cg_completion_email_body', $default_completion_body);
    }
    if (trim((string)$body_template) === '') {
        $body_template = $default_completion_body;
    }

    $sent = false;
    if (function_exists('afb_send_client_gateway_created_email')) {
        $sent = (bool) afb_send_client_gateway_created_email([
            'to' => $email,
            'name' => $full_name,
            'task_name' => (string)($task['name'] ?? ''),
            'task_gid' => $task_gid,
            'job_number' => $job_number,
            'gateway_url' => $gateway_url,
            'gateway_pin' => $gateway_pin_for_email,
            'heading' => 'Request Complete',
            'subject_template' => $subject_template,
            'body_template' => $body_template,
        ]);
    } else {
        $tokens = [
            'task_name' => (string)($task['name'] ?? ''),
            'task_gid' => $task_gid,
            'job_number' => $job_number,
            'request_number' => $job_number,
            'gateway_url' => $gateway_url,
            'gateway_pin' => $gateway_pin_for_email,
            'full_name' => $full_name,
            'first_name' => $first_name,
            'last_name' => $last,
            'name' => $full_name,
            'workspace_name' => sanitize_text_field((string) get_bloginfo('name')),
        ];

        if (function_exists('afb_email_expand_placeholders')) {
            $subject = afb_email_expand_placeholders($subject_template, $tokens);
            $body = afb_email_expand_placeholders($body_template, $tokens);
        } else {
            $map = [];
            foreach ($tokens as $k => $v) {
                $map['{{' . $k . '}}'] = (string)$v;
                $map['{' . $k . '}'] = (string)$v;
            }
            $subject = strtr($subject_template, $map);
            $body = strtr($body_template, $map);
        }

        // Render as styled HTML using submission template
        $task_name      = (string)($task['name'] ?? '');
        $request_number = $job_number;
        $comms_link     = $gateway_url;
        $fields_html    = '';

        $subject_tpl = (string) $subject;
        if (function_exists('wp_kses_post')) {
            $body_tpl = wp_kses_post((string)$body);
        } else {
            $body_tpl = htmlspecialchars((string)$body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        }

        ob_start();
        @include plugin_dir_path(__FILE__) . '../../../admin/emails/submission-template.php';
        $html_body = ob_get_clean();

        $headers = function_exists('afb_get_email_headers') ? afb_get_email_headers() : ['Content-Type: text/html; charset=UTF-8'];
        $sent = (bool) wp_mail($email, sanitize_text_field((string)$subject), (string)$html_body, $headers);
    }

    $marker_outcome = $sent ? $marker_text : "Email failed for marker: {$marker_label}";
    if (function_exists('asana_automator_post_story_marker')) {
        asana_automator_post_story_marker($task_gid, $token, $marker_outcome);
    } else {
        wp_remote_post("https://app.asana.com/api/1.0/tasks/{$task_gid}/stories", [
            'headers' => [
                'Authorization' => "Bearer {$token}",
                'Content-Type' => 'application/json',
                'Asana-Disable' => 'notifications',
            ],
            'body' => json_encode(['data' => ['text' => $marker_outcome]]),
        ]);
    }

    file_put_contents(
        $log_file,
        "📧 Completion gateway email " . ($sent ? 'SUCCESS' : 'FAIL') . " for task {$task_gid} to {$email}\n",
        FILE_APPEND
    );

    return $sent;
}


/* ---------- Tag on section entry ----------------------------------- */
function asana_automator_tag_on_section_entry($config, $token, $task) {
    $tag_gid  = trim($config['tag_gid'] ?? '');
    $task_gid = $task['gid'] ?? '';
    if (!$tag_gid || !$task_gid) return;

    wp_remote_post(
        "https://app.asana.com/api/1.0/tasks/{$task_gid}/addTag",
        [
            'headers' => ['Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json'],
            'body'    => json_encode(['data' => ['tag' => $tag_gid]]),
        ]
    );
}

/* ---------- Clear field after done --------------------------------- */
function asana_automator_clear_field_after_done($config, $token, $task) {
    $field_gid = trim($config['field_gid'] ?? '');
    $task_gid  = $task['gid'] ?? '';
    if (!$field_gid || !$task_gid) return;

    if (!empty($task['completed'])) {
        wp_remote_request(
            "https://app.asana.com/api/1.0/tasks/{$task_gid}",
            [
                'method'  => 'PUT',
                'headers' => ['Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json'],
                'body'    => json_encode(['data' => ['custom_fields' => [$field_gid => null]]]),
            ]
        );
    }
}

function asana_automator_parse_redact_phrases($raw) {
    $raw = (string) $raw;
    if ($raw === '') {
        return [];
    }

    $parts = preg_split('/[\r\n,]+/', $raw);
    $parts = array_map('trim', (array)$parts);
    return array_values(array_filter($parts, static function ($v) {
        return $v !== '';
    }));
}

function asana_automator_extract_private_companion_task_gid_from_notes(string $notes): string {
    $notes = trim($notes);
    if ($notes === '') {
        return '';
    }

    if (preg_match('/Private\s+companion\s+task:\s*https?:\/\/app\.asana\.com\/0\/\d+\/(\d+)\/f/i', $notes, $matches)) {
        return (string)($matches[1] ?? '');
    }

    return '';
}

function asana_automator_strip_private_companion_references_from_notes(string $notes): string {
    $notes = (string)$notes;
    if (trim($notes) === '') {
        return '';
    }

    $lines = preg_split('/\r\n|\r|\n/', $notes);
    if (!is_array($lines)) {
        return trim($notes);
    }

    $kept_lines = [];
    foreach ($lines as $line) {
        $line_trim = trim((string)$line);
        if ($line_trim !== '' && preg_match('/^confidential\s+data\s+notice\s*:/i', $line_trim)) {
            continue;
        }
        if ($line_trim !== '' && preg_match('/^private\s+companion\s+task\s*:/i', $line_trim)) {
            continue;
        }

        $kept_lines[] = (string)$line;
    }

    return trim((string)preg_replace('/\n{3,}/', "\n\n", implode("\n", $kept_lines)));
}

function asana_automator_complete_task_with_available_tokens(string $task_gid, string $primary_token): array {
    $task_gid = trim($task_gid);
    if ($task_gid === '') {
        return [
            'ok' => false,
            'http' => 0,
            'reason' => 'missing_gid',
            'error' => 'Missing task gid.',
        ];
    }

    $tokens = [];
    $primary_token = trim($primary_token);
    if ($primary_token !== '') {
        $tokens[] = $primary_token;
    }

    $pat_accounts = asana_automator_get_pat_accounts();
    if (is_array($pat_accounts)) {
        foreach ($pat_accounts as $candidate_token) {
            $candidate_token = trim((string)$candidate_token);
            if ($candidate_token === '' || in_array($candidate_token, $tokens, true)) {
                continue;
            }

            $tokens[] = $candidate_token;
        }
    }

    $last_http = 0;
    $last_error = '';

    foreach ($tokens as $candidate_token) {
        $response = wp_remote_request(
            "https://app.asana.com/api/1.0/tasks/{$task_gid}",
            [
                'method'  => 'PUT',
                'headers' => [
                    'Authorization' => "Bearer {$candidate_token}",
                    'Content-Type'  => 'application/json',
                ],
                'body' => json_encode(['data' => ['completed' => true]]),
            ]
        );

        if (is_wp_error($response)) {
            $last_error = $response->get_error_message();
            continue;
        }

        $http_code = (int) wp_remote_retrieve_response_code($response);
        $response_body = (string) wp_remote_retrieve_body($response);

        if ($http_code >= 200 && $http_code < 300) {
            return [
                'ok' => true,
                'http' => $http_code,
                'reason' => 'completed',
                'error' => '',
            ];
        }

        if ($http_code === 404) {
            return [
                'ok' => true,
                'http' => $http_code,
                'reason' => 'already_missing',
                'error' => '',
            ];
        }

        $last_http = $http_code;
        $last_error = $response_body;

        if ($http_code !== 401 && $http_code !== 403) {
            return [
                'ok' => false,
                'http' => $http_code,
                'reason' => 'failed',
                'error' => $response_body,
            ];
        }
    }

    return [
        'ok' => false,
        'http' => $last_http,
        'reason' => 'failed',
        'error' => $last_error,
    ];
}

function asana_automator_extract_gateway_token_from_task(array $task): string {
    $gateway_link_gid = sanitize_text_field((string) get_option('afb_client_gateway_link_field_gid', ''));
    $gateway_url = '';

    foreach ((array)($task['custom_fields'] ?? []) as $field) {
        $gid = trim((string)($field['gid'] ?? ''));
        $name_lc = strtolower(trim((string)($field['name'] ?? '')));

        $value = trim((string)($field['text_value'] ?? $field['display_value'] ?? ''));
        if ($value === '' && isset($field['number_value']) && $field['number_value'] !== null && $field['number_value'] !== '') {
            $value = trim((string)$field['number_value']);
        }

        if ($gateway_url === '' && $gateway_link_gid !== '' && $gid === $gateway_link_gid) {
            $gateway_url = $value;
        }

        if ($gateway_url === '' && $name_lc !== '' && str_contains($name_lc, 'client gateway') && !str_contains($name_lc, 'create') && !str_contains($name_lc, 'pin')) {
            $gateway_url = $value;
        }
    }

    if ($gateway_url === '') {
        return '';
    }

    $gateway_path = (string) parse_url($gateway_url, PHP_URL_PATH);
    if (preg_match('~/task-dashboard/([^/\?#]+)~i', $gateway_path, $matches)) {
        return sanitize_text_field((string)($matches[1] ?? ''));
    }

    if (preg_match('~/task-dashboard/([^/\?#]+)~i', $gateway_url, $matches)) {
        return sanitize_text_field((string)($matches[1] ?? ''));
    }

    return '';
}

function asana_automator_gateway_exists_locally(string $token): bool {
    $token = sanitize_text_field((string)$token);
    if ($token === '') {
        return false;
    }

    $has_task_transient = get_transient("afb_token_task_{$token}") !== false;
    $has_pin_transient = get_transient("afb_token_pin_hash_{$token}") !== false;

    $has_summary_file = false;
    $has_settings_file = false;
    if (defined('AFB_PLUGIN_DIR')) {
        $summary_file = AFB_PLUGIN_DIR . 'cache/tasks-summary-' . $token . '.json';
        $settings_file = AFB_PLUGIN_DIR . 'cache/gateway-settings-' . sanitize_file_name($token) . '.json';
        $has_summary_file = file_exists($summary_file);
        $has_settings_file = file_exists($settings_file);
    }

    return $has_task_transient || $has_pin_transient || $has_summary_file || $has_settings_file;
}

function asana_automator_expire_local_gateway_for_task(array $task, bool $dry_run = false): array {
    $token = asana_automator_extract_gateway_token_from_task($task);
    if ($token === '') {
        return [
            'ok' => false,
            'token' => '',
            'reason' => 'missing_token',
            'exists_before' => false,
            'exists_after' => false,
        ];
    }

    $exists_before = asana_automator_gateway_exists_locally($token);
    if (!$exists_before) {
        return [
            'ok' => true,
            'token' => $token,
            'reason' => 'not_found',
            'exists_before' => false,
            'exists_after' => false,
        ];
    }

    if ($dry_run) {
        return [
            'ok' => true,
            'token' => $token,
            'reason' => 'would_expire',
            'exists_before' => true,
            'exists_after' => true,
        ];
    }

    if (!function_exists('afb_expire_gateway_token_local')) {
        return [
            'ok' => false,
            'token' => $token,
            'reason' => 'helper_unavailable',
            'exists_before' => true,
            'exists_after' => true,
        ];
    }

    afb_expire_gateway_token_local($token, 'automation_cleanup_completed');
    $exists_after = asana_automator_gateway_exists_locally($token);

    return [
        'ok' => !$exists_after,
        'token' => $token,
        'reason' => $exists_after ? 'expire_incomplete' : 'expired',
        'exists_before' => true,
        'exists_after' => $exists_after,
    ];
}

function asana_automator_cleanup_on_completion($config, $token, $task) {
    $log_file = WP_CONTENT_DIR . '/custom-debug.log';
    $task_gid = (string)($task['gid'] ?? '');
    if ($task_gid === '' || empty($task['completed'])) {
        return;
    }

    $dry_run = !empty($config['dry_run']) && (string)($config['dry_run']) === '1';
    $dry_run_marker_prefix = 'AUTOMATION DRY RUN: cleanup_on_completion';
    $skip_dry_run_marker = false;

    if (
        $dry_run
        && function_exists('asana_automator_task_has_story_marker')
        && asana_automator_task_has_story_marker($task_gid, $token, $dry_run_marker_prefix)
    ) {
        $skip_dry_run_marker = true;
    }

    $delete_attachments = !array_key_exists('delete_attachments_on_complete', (array)$config)
        || !empty($config['delete_attachments_on_complete']);

    $attachment_count = 0;
    if ($delete_attachments && $dry_run) {
        $attachments_res = wp_remote_get(
            "https://app.asana.com/api/1.0/attachments?parent={$task_gid}&opt_fields=gid",
            ['headers' => ['Authorization' => 'Bearer ' . $token]]
        );

        if (is_wp_error($attachments_res)) {
            file_put_contents(
                $log_file,
                "⚠️ cleanup_on_completion dry-run attachment fetch failed for {$task_gid}: " . $attachments_res->get_error_message() . "\n",
                FILE_APPEND
            );
        } else {
            $attachments_code = (int) wp_remote_retrieve_response_code($attachments_res);
            if ($attachments_code >= 200 && $attachments_code < 300) {
                $attachment_count = count((array)(json_decode(wp_remote_retrieve_body($attachments_res), true)['data'] ?? []));
            } else {
                file_put_contents(
                    $log_file,
                    "⚠️ cleanup_on_completion dry-run attachment fetch HTTP {$attachments_code} for {$task_gid}: " . wp_remote_retrieve_body($attachments_res) . "\n",
                    FILE_APPEND
                );
            }
        }
    }

    if (!$dry_run && $delete_attachments && function_exists('asana_automator_delete_attachments_on_complete')) {
        asana_automator_delete_attachments_on_complete($config, $token, $task);
    }

    $payload_data = [];

    $cleanup_field_gids = isset($config['cleanup_field_gids']) ? (array)$config['cleanup_field_gids'] : [];
    $cleanup_field_gids = array_values(array_filter(array_map('trim', $cleanup_field_gids)));
    if (!empty($cleanup_field_gids)) {
        $payload_data['custom_fields'] = [];
        foreach ($cleanup_field_gids as $gid) {
            $payload_data['custom_fields'][$gid] = null;
        }
    }

    $clear_notes = !empty($config['clear_notes_on_complete']);
    $notes       = (string)($task['notes'] ?? '');
    $phrases     = asana_automator_parse_redact_phrases($config['notes_redact_phrases'] ?? '');
    $remove_private_companion_refs = !array_key_exists('remove_private_companion_references_on_complete', (array)$config)
        || !empty($config['remove_private_companion_references_on_complete']);
    $private_companion_refs_removed = false;
    $delete_local_gateway = !empty($config['delete_local_client_gateway_on_complete']);
    $gateway_local_result = $delete_local_gateway
        ? asana_automator_expire_local_gateway_for_task((array)$task, $dry_run)
        : [];
    $complete_private_companion = !empty($config['complete_private_companion_on_complete']);
    $companion_task_gid = $complete_private_companion
        ? asana_automator_extract_private_companion_task_gid_from_notes($notes)
        : '';

    if ($clear_notes) {
        $payload_data['notes'] = '';
    } elseif ($notes !== '') {
        $updated_notes = $notes;
        if ($remove_private_companion_refs) {
            $updated_notes = asana_automator_strip_private_companion_references_from_notes($updated_notes);
            $private_companion_refs_removed = $updated_notes !== $notes;
        }

        foreach ($phrases as $phrase) {
            $updated_notes = str_ireplace($phrase, '', $updated_notes);
        }

        $updated_notes = trim((string)preg_replace('/\n{3,}/', "\n\n", $updated_notes));
        if ($updated_notes !== $notes) {
            $payload_data['notes'] = $updated_notes;
        }
    }

    if ($dry_run) {
        $dry_run_summary = [
            'task_gid' => $task_gid,
            'delete_attachments' => $delete_attachments ? '1' : '0',
            'attachment_count' => $attachment_count,
            'cleanup_field_gids' => $cleanup_field_gids,
            'clear_notes_on_complete' => $clear_notes ? '1' : '0',
            'notes_redact_phrases' => $phrases,
            'remove_private_companion_references_on_complete' => $remove_private_companion_refs ? '1' : '0',
            'private_companion_refs_removed' => $private_companion_refs_removed ? '1' : '0',
            'delete_local_client_gateway_on_complete' => $delete_local_gateway ? '1' : '0',
            'gateway_local_result' => $gateway_local_result,
            'complete_private_companion_on_complete' => $complete_private_companion ? '1' : '0',
            'companion_task_gid' => $companion_task_gid,
            'payload_preview' => $payload_data,
        ];

        file_put_contents(
            $log_file,
            "🧪 cleanup_on_completion dry-run: " . wp_json_encode($dry_run_summary, JSON_UNESCAPED_SLASHES) . "\n",
            FILE_APPEND
        );

        if ($skip_dry_run_marker) {
            file_put_contents(
                $log_file,
                "ℹ️ cleanup_on_completion dry-run marker already exists for task {$task_gid}; skipping duplicate story marker\n",
                FILE_APPEND
            );
        } elseif (function_exists('asana_automator_post_story_marker')) {
            $marker_bits = [];
            if ($delete_attachments) {
                $marker_bits[] = 'attachments=' . $attachment_count;
            }
            if (!empty($cleanup_field_gids)) {
                $marker_bits[] = 'fields=' . count($cleanup_field_gids);
            }
            if (array_key_exists('notes', $payload_data)) {
                $marker_bits[] = 'notes=1';
            }
            if ($remove_private_companion_refs) {
                $marker_bits[] = 'main_refs=' . ($private_companion_refs_removed ? 'removed' : 'none');
            }
            if ($complete_private_companion) {
                $marker_bits[] = $companion_task_gid !== '' ? 'companion=1' : 'companion=missing';
            }
            if ($delete_local_gateway) {
                $gateway_reason = trim((string)($gateway_local_result['reason'] ?? 'unknown'));
                if ($gateway_reason === '') {
                    $gateway_reason = 'unknown';
                }
                $marker_bits[] = 'wp_gateway=' . $gateway_reason;
            }
            if (empty($marker_bits)) {
                $marker_bits[] = 'no-op';
            }

            asana_automator_post_story_marker(
                $task_gid,
                $token,
                $dry_run_marker_prefix . ' [' . implode(', ', $marker_bits) . ']'
            );
        }

        return;
    }

    $main_task_update_ok = true;
    if (empty($payload_data)) {
        file_put_contents(
            $log_file,
            "ℹ️ cleanup_on_completion: no payload changes for task {$task_gid}\n",
            FILE_APPEND
        );
    } else {
        $update_response = wp_remote_request(
            "https://app.asana.com/api/1.0/tasks/{$task_gid}",
            [
                'method'  => 'PUT',
                'headers' => [
                    'Authorization' => "Bearer {$token}",
                    'Content-Type' => 'application/json',
                ],
                'body' => json_encode(['data' => $payload_data]),
            ]
        );

        if (is_wp_error($update_response)) {
            file_put_contents(
                $log_file,
                "❌ cleanup_on_completion failed for task {$task_gid}: " . $update_response->get_error_message() . "\n",
                FILE_APPEND
            );
            $main_task_update_ok = false;
        } else {
            $update_code = (int) wp_remote_retrieve_response_code($update_response);
            if ($update_code < 200 || $update_code >= 300) {
                file_put_contents(
                    $log_file,
                    "❌ cleanup_on_completion HTTP {$update_code} for task {$task_gid}: " . wp_remote_retrieve_body($update_response) . "\n",
                    FILE_APPEND
                );
                $main_task_update_ok = false;
            } else {
                file_put_contents(
                    $log_file,
                    "✅ cleanup_on_completion updated task {$task_gid} payload=" . wp_json_encode($payload_data, JSON_UNESCAPED_SLASHES) . "\n",
                    FILE_APPEND
                );
            }
        }
    }

    if ($complete_private_companion) {
        if ($companion_task_gid === '') {
            file_put_contents(
                $log_file,
                "ℹ️ cleanup_on_completion: no linked private companion task found in notes for {$task_gid}\n",
                FILE_APPEND
            );
        } else {
            $companion_sync = asana_automator_complete_task_with_available_tokens($companion_task_gid, (string)$token);
            $companion_http = (int)($companion_sync['http'] ?? 0);
            $companion_reason = (string)($companion_sync['reason'] ?? '');
            if (!empty($companion_sync['ok'])) {
                file_put_contents(
                    $log_file,
                    "✅ cleanup_on_completion companion sync {$companion_reason} for main {$task_gid} → companion {$companion_task_gid} (HTTP {$companion_http})\n",
                    FILE_APPEND
                );
            } else {
                $companion_error = trim((string)($companion_sync['error'] ?? ''));
                file_put_contents(
                    $log_file,
                    "❌ cleanup_on_completion companion sync failed for main {$task_gid} → companion {$companion_task_gid} (HTTP {$companion_http})" . ($companion_error !== '' ? ': ' . $companion_error : '') . "\n",
                    FILE_APPEND
                );
            }
        }
    }

    if ($delete_local_gateway) {
        $gateway_reason = trim((string)($gateway_local_result['reason'] ?? 'unknown'));
        $gateway_token = trim((string)($gateway_local_result['token'] ?? ''));
        $gateway_ok = !empty($gateway_local_result['ok']);

        if ($gateway_reason === 'missing_token') {
            file_put_contents(
                $log_file,
                "ℹ️ cleanup_on_completion: no Client Gateway token found on task {$task_gid}; local gateway delete skipped\n",
                FILE_APPEND
            );
        } elseif ($gateway_reason === 'not_found') {
            file_put_contents(
                $log_file,
                "ℹ️ cleanup_on_completion: no local WordPress gateway record found for task {$task_gid}" . ($gateway_token !== '' ? " (token {$gateway_token})" : '') . "\n",
                FILE_APPEND
            );
        } elseif ($gateway_ok) {
            file_put_contents(
                $log_file,
                "✅ cleanup_on_completion: deleted local WordPress gateway for task {$task_gid}" . ($gateway_token !== '' ? " (token {$gateway_token})" : '') . "\n",
                FILE_APPEND
            );
        } else {
            file_put_contents(
                $log_file,
                "❌ cleanup_on_completion: failed to delete local WordPress gateway for task {$task_gid}" . ($gateway_token !== '' ? " (token {$gateway_token})" : '') . " reason={$gateway_reason}\n",
                FILE_APPEND
            );
        }
    }

    if (!$main_task_update_ok) {
        return;
    }
}

/* ---------- Comment on section move -------------------------------- */
function asana_automator_comment_on_section_move($config, $token, $task) {
    $text     = trim($config['comment_text'] ?? '');
    $task_gid = $task['gid'] ?? '';
    if (!$text || !$task_gid) return;

    wp_remote_post(
        "https://app.asana.com/api/1.0/tasks/{$task_gid}/stories",
        [
            'headers' => ['Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json'],
            'body'    => json_encode(['data' => ['text' => $text]]),
        ]
    );
}

/* ---------- Map custom fields from description --------------------- */
function asana_automator_normalize_match_text(string $value): string {
    $value = strtolower(trim($value));
    $value = preg_replace('/[^a-z0-9\s]+/', ' ', $value);
    $value = preg_replace('/\s+/', ' ', (string)$value);
    return trim((string)$value);
}

function asana_automator_extract_description_pairs(string $notes): array {
    $pairs = [];
    $lines = preg_split('/\r\n|\r|\n/', $notes);
    foreach ($lines as $line) {
        if (strpos($line, ':') === false) {
            continue;
        }

        [$label, $value] = array_pad(explode(':', $line, 2), 2, '');
        $label = trim((string)$label);
        $value = trim((string)$value);
        if ($label === '' || $value === '') {
            continue;
        }

        $pairs[asana_automator_normalize_match_text($label)] = $value;
    }

    return $pairs;
}

function asana_automator_parse_mapping_rules(string $raw): array {
    $decoded = json_decode($raw, true);
    if (!is_array($decoded)) {
        return [];
    }

    // Accept full Asana custom_field_settings payloads and derive mapping rules automatically.
    // Supported shapes:
    // 1) {"data":[{"custom_field":{...}}, ...]}
    // 2) [{"custom_field":{...}}, ...]
    // 3) [{"gid":"...","name":"...","type":"..."}, ...]
    $extract_fields = function ($payload): array {
        $rows = [];

        if (is_array($payload) && isset($payload['data']) && is_array($payload['data'])) {
            $rows = $payload['data'];
        } elseif (is_array($payload)) {
            $rows = $payload;
        }

        $fields = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            if (!empty($row['custom_field']) && is_array($row['custom_field'])) {
                $fields[] = $row['custom_field'];
                continue;
            }

            if (!empty($row['gid']) && !empty($row['name'])) {
                $fields[] = $row;
            }
        }

        return $fields;
    };

    $bootstrap_fields = $extract_fields($decoded);
    if (!empty($bootstrap_fields) && function_exists('asana_automator_bootstrap_build_rules')) {
        $derived = asana_automator_bootstrap_build_rules($bootstrap_fields);
        if (!empty($derived)) {
            return $derived;
        }
    }

    $rules = [];
    foreach ($decoded as $item) {
        if (!is_array($item)) {
            continue;
        }

        $source_labels = [];
        if (!empty($item['source_labels']) && is_array($item['source_labels'])) {
            foreach ($item['source_labels'] as $label) {
                if (!is_scalar($label)) {
                    continue;
                }
                $s = trim((string)$label);
                if ($s !== '') {
                    $source_labels[] = $s;
                }
            }
        }

        if (empty($source_labels) && !empty($item['source_label']) && is_scalar($item['source_label'])) {
            $source_labels[] = trim((string)$item['source_label']);
        }

        $target_field_gid = trim((string)($item['target_field_gid'] ?? ''));
        if (empty($source_labels) || $target_field_gid === '') {
            continue;
        }

        $target_type = strtolower(trim((string)($item['target_type'] ?? 'enum')));
        if (!in_array($target_type, ['enum', 'text', 'number', 'date'], true)) {
            $target_type = 'enum';
        }

        $aliases = [];
        if (!empty($item['aliases']) && is_array($item['aliases'])) {
            foreach ($item['aliases'] as $from => $to) {
                if (!is_scalar($from) || !is_scalar($to)) {
                    continue;
                }
                $aliases[trim((string)$from)] = trim((string)$to);
            }
        }

        $rules[] = [
            'source_labels' => array_values(array_unique($source_labels)),
            'target_field_gid' => $target_field_gid,
            'target_type' => $target_type,
            'aliases' => $aliases,
        ];
    }

    return $rules;
}

function asana_automator_task_custom_field_has_value(array $task, string $field_gid): bool {
    foreach ((array)($task['custom_fields'] ?? []) as $field) {
        if ((string)($field['gid'] ?? '') !== $field_gid) {
            continue;
        }

        if (!empty($field['enum_value']['gid'])) {
            return true;
        }
        if (!empty($field['multi_enum_values']) && is_array($field['multi_enum_values'])) {
            return true;
        }
        if (isset($field['number_value']) && $field['number_value'] !== null && $field['number_value'] !== '') {
            return true;
        }
        if (!empty($field['date_value']['date'])) {
            return true;
        }

        $text = trim((string)($field['text_value'] ?? ''));
        if ($text !== '') {
            return true;
        }

        $display = trim((string)($field['display_value'] ?? ''));
        if ($display !== '') {
            return true;
        }
    }

    return false;
}

function asana_automator_fetch_enum_options(string $field_gid, string $token): array {
    $cache_key = 'asana_automator_enum_options_' . md5($field_gid);
    $cached = get_transient($cache_key);
    if (is_array($cached)) {
        return $cached;
    }

    $res = wp_remote_get("https://app.asana.com/api/1.0/custom_fields/{$field_gid}?opt_fields=enum_options.gid,enum_options.name,enum_options.enabled", [
        'headers' => ['Authorization' => "Bearer {$token}"],
    ]);

    if (is_wp_error($res)) {
        return [];
    }

    $body = json_decode(wp_remote_retrieve_body($res), true);
    $options = (array)($body['data']['enum_options'] ?? []);
    $options = array_values(array_filter($options, function ($opt) {
        return !isset($opt['enabled']) || !empty($opt['enabled']);
    }));

    set_transient($cache_key, $options, 15 * MINUTE_IN_SECONDS);
    return $options;
}

function asana_automator_pick_enum_gid(string $incoming, array $options, array $aliases = []): string {
    $incoming = trim($incoming);
    if ($incoming === '' || empty($options)) {
        return '';
    }

    $incoming_norm = asana_automator_normalize_match_text($incoming);

    foreach ($aliases as $from => $to) {
        if (asana_automator_normalize_match_text((string)$from) === $incoming_norm) {
            $incoming = trim((string)$to);
            foreach ($options as $opt) {
                if ((string)($opt['gid'] ?? '') === $incoming) {
                    return $incoming;
                }
            }
            $incoming_norm = asana_automator_normalize_match_text($incoming);
            break;
        }
    }

    foreach ($options as $opt) {
        $name = (string)($opt['name'] ?? '');
        $gid = (string)($opt['gid'] ?? '');
        if ($gid === '' || $name === '') {
            continue;
        }
        if (asana_automator_normalize_match_text($name) === $incoming_norm) {
            return $gid;
        }
    }

    $in_tokens = array_values(array_filter(explode(' ', $incoming_norm)));
    $best_gid = '';
    $best_score = 0.0;

    foreach ($options as $opt) {
        $name = (string)($opt['name'] ?? '');
        $gid = (string)($opt['gid'] ?? '');
        if ($gid === '' || $name === '') {
            continue;
        }

        $name_norm = asana_automator_normalize_match_text($name);
        $score = 0.0;

        if (strpos($name_norm, $incoming_norm) !== false || strpos($incoming_norm, $name_norm) !== false) {
            $score = 0.85;
        }

        $name_tokens = array_values(array_filter(explode(' ', $name_norm)));
        if (!empty($in_tokens) && !empty($name_tokens)) {
            $common = array_intersect($in_tokens, $name_tokens);
            $token_score = count($common) / max(count($in_tokens), count($name_tokens));
            $score = max($score, $token_score);
        }

        if ($score > $best_score) {
            $best_score = $score;
            $best_gid = $gid;
        }
    }

    return $best_score >= 0.55 ? $best_gid : '';
}

function asana_automator_task_has_story_marker(string $task_gid, string $token, string $marker): bool {
    if ($task_gid === '' || $marker === '') {
        return false;
    }

    $stories = wp_remote_get("https://app.asana.com/api/1.0/tasks/{$task_gid}/stories?limit=100&opt_fields=text", [
        'headers' => ['Authorization' => 'Bearer ' . $token],
    ]);

    if (is_wp_error($stories)) {
        return false;
    }

    foreach ((array)(json_decode(wp_remote_retrieve_body($stories), true)['data'] ?? []) as $story) {
        $text = (string)($story['text'] ?? '');
        if ($text !== '' && strpos($text, $marker) !== false) {
            return true;
        }
    }

    return false;
}

function asana_automator_post_story_marker(string $task_gid, string $token, string $text): void {
    if ($task_gid === '' || $text === '') {
        return;
    }

    wp_remote_post("https://app.asana.com/api/1.0/tasks/{$task_gid}/stories", [
        'headers' => [
            'Authorization' => "Bearer {$token}",
            'Content-Type' => 'application/json',
            'Asana-Disable' => 'notifications',
        ],
        'body' => json_encode(['data' => ['text' => $text]]),
    ]);
}

function asana_automator_map_fields_from_description($config, $token, $task) {
    $log_file = WP_CONTENT_DIR . '/custom-debug.log';

    $task_gid = (string)($task['gid'] ?? '');
    $notes = (string)($task['notes'] ?? '');
    if ($task_gid === '' || trim($notes) === '') {
        return;
    }

    $trigger_phrase = trim((string)($config['trigger_phrase'] ?? 'This request was submitted through QH Forms'));
    if ($trigger_phrase !== '' && stripos($notes, $trigger_phrase) === false) {
        return;
    }

    $marker_label = trim((string)($config['marker_label'] ?? 'map_fields_from_description'));
    $dry_run = !empty($config['dry_run']) && (string)$config['dry_run'] === '1';
    $marker_text = ($dry_run ? 'AUTOMATION DRY RUN: ' : 'AUTOMATION APPLIED: ') . $marker_label;
    if (asana_automator_task_has_story_marker($task_gid, $token, $marker_text)) {
        return;
    }

    $mapping_rules_json = (string)($config['mapping_rules_json'] ?? '');
    if (trim($mapping_rules_json) === '') {
        $profile_key = sanitize_key((string)($config['mapping_profile_key'] ?? ''));
        if ($profile_key !== '') {
            $profiles = get_option('asana_automator_mapping_profiles', []);
            if (!empty($profiles[$profile_key]['mapping_rules_json'])) {
                $mapping_rules_json = (string)$profiles[$profile_key]['mapping_rules_json'];
            }
        }
    }

    $mapping_rules = asana_automator_parse_mapping_rules($mapping_rules_json);
    if (empty($mapping_rules)) {
        file_put_contents($log_file, "⚠️ map_fields_from_description: no valid mapping rules for task {$task_gid}\n", FILE_APPEND);
        return;
    }

    $pairs = asana_automator_extract_description_pairs($notes);
    if (empty($pairs)) {
        return;
    }

    $overwrite_existing = !empty($config['overwrite_existing']) && (string)$config['overwrite_existing'] === '1';
    $updates = [];
    $map_debug = [
        'task_gid' => $task_gid,
        'task_name' => (string)($task['name'] ?? ''),
        'trigger_phrase' => $trigger_phrase,
        'overwrite_existing' => $overwrite_existing ? '1' : '0',
        'dry_run' => $dry_run ? '1' : '0',
        'attempted' => [],
        'updated_field_gids' => [],
    ];

    foreach ($mapping_rules as $rule) {
        $target_field_gid = (string)$rule['target_field_gid'];
        $target_type = (string)$rule['target_type'];
        $source_value = '';
        $rule_source_labels = [];
        if (!empty($rule['source_labels']) && is_array($rule['source_labels'])) {
            $rule_source_labels = array_values(array_filter(array_map('strval', $rule['source_labels'])));
        } elseif (!empty($rule['source_label']) && is_scalar($rule['source_label'])) {
            $rule_source_labels = [trim((string)$rule['source_label'])];
        }

        $attempt = [
            'target_field_gid' => $target_field_gid,
            'target_type' => $target_type,
            'source_labels' => $rule_source_labels,
            'status' => 'pending',
            'reason' => '',
            'source_value' => '',
        ];

        if (!$overwrite_existing && asana_automator_task_custom_field_has_value($task, $target_field_gid)) {
            $attempt['status'] = 'skipped';
            $attempt['reason'] = 'existing_value';
            $map_debug['attempted'][] = $attempt;
            continue;
        }

        foreach ($rule_source_labels as $label) {
            $key = asana_automator_normalize_match_text((string)$label);
            if (!empty($pairs[$key])) {
                $source_value = (string)$pairs[$key];
                break;
            }
        }

        if ($source_value === '') {
            $attempt['status'] = 'skipped';
            $attempt['reason'] = 'source_label_not_found';
            $map_debug['attempted'][] = $attempt;
            continue;
        }
        $attempt['source_value'] = $source_value;

        if ($target_type === 'enum') {
            $options = asana_automator_fetch_enum_options($target_field_gid, $token);
            $enum_gid = asana_automator_pick_enum_gid($source_value, $options, (array)$rule['aliases']);
            if ($enum_gid === '') {
                $attempt['status'] = 'skipped';
                $attempt['reason'] = 'enum_no_match';
                $map_debug['attempted'][] = $attempt;
                continue;
            }
            $updates[$target_field_gid] = $enum_gid;
            $attempt['status'] = 'mapped';
            $attempt['reason'] = 'enum_match';
            $map_debug['attempted'][] = $attempt;
            $map_debug['updated_field_gids'][] = $target_field_gid;
            continue;
        }

        if ($target_type === 'number') {
            $clean = preg_replace('/[^0-9.\-]/', '', $source_value);
            if ($clean === '' || !is_numeric($clean)) {
                $attempt['status'] = 'skipped';
                $attempt['reason'] = 'invalid_number';
                $map_debug['attempted'][] = $attempt;
                continue;
            }
            $updates[$target_field_gid] = (float)$clean;
            $attempt['status'] = 'mapped';
            $attempt['reason'] = 'number_parsed';
            $map_debug['attempted'][] = $attempt;
            $map_debug['updated_field_gids'][] = $target_field_gid;
            continue;
        }

        if ($target_type === 'date') {
            $ts = strtotime($source_value);
            if ($ts === false) {
                $attempt['status'] = 'skipped';
                $attempt['reason'] = 'invalid_date';
                $map_debug['attempted'][] = $attempt;
                continue;
            }
            $updates[$target_field_gid] = gmdate('Y-m-d', $ts);
            $attempt['status'] = 'mapped';
            $attempt['reason'] = 'date_parsed';
            $map_debug['attempted'][] = $attempt;
            $map_debug['updated_field_gids'][] = $target_field_gid;
            continue;
        }

        $updates[$target_field_gid] = $source_value;
        $attempt['status'] = 'mapped';
        $attempt['reason'] = 'text_mapped';
        $map_debug['attempted'][] = $attempt;
        $map_debug['updated_field_gids'][] = $target_field_gid;
    }

    if (empty($updates)) {
        file_put_contents(
            $log_file,
            "ℹ️ map_fields_from_description no-updates: " . wp_json_encode($map_debug, JSON_UNESCAPED_SLASHES) . "\n",
            FILE_APPEND
        );
        return;
    }

    if ($dry_run) {
        $dry_run_marker = $marker_text . ' [would update ' . count($updates) . ' fields; no changes applied]';
        file_put_contents(
            $log_file,
            "🧪 map_fields_from_description dry-run task {$task_gid} updates=" . wp_json_encode($updates, JSON_UNESCAPED_SLASHES) . " debug=" . wp_json_encode($map_debug, JSON_UNESCAPED_SLASHES) . "\n",
            FILE_APPEND
        );
        asana_automator_post_story_marker($task_gid, $token, $dry_run_marker);
        return;
    }

    $response = wp_remote_request("https://app.asana.com/api/1.0/tasks/{$task_gid}", [
        'method' => 'PUT',
        'headers' => ['Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json'],
        'body' => json_encode(['data' => ['custom_fields' => $updates]]),
    ]);

    if (is_wp_error($response)) {
        file_put_contents($log_file, "❌ map_fields_from_description failed for {$task_gid}: " . $response->get_error_message() . "\n", FILE_APPEND);
        return;
    }

    $code = wp_remote_retrieve_response_code($response);
    if (in_array($code, [200, 201], true)) {
        file_put_contents(
            $log_file,
            "✅ map_fields_from_description updated task {$task_gid} fields=" . wp_json_encode(array_keys($updates), JSON_UNESCAPED_SLASHES) . " debug=" . wp_json_encode($map_debug, JSON_UNESCAPED_SLASHES) . "\n",
            FILE_APPEND
        );
        asana_automator_post_story_marker($task_gid, $token, $marker_text . ' [' . count($updates) . ' fields]');
    } else {
        $body = wp_remote_retrieve_body($response);
        file_put_contents($log_file, "❌ map_fields_from_description HTTP {$code} for {$task_gid}: {$body}\n", FILE_APPEND);
    }
}

/* ---------- Delete completed tasks --------------------------------- */
function asana_automator_delete_completed_tasks($config, $token, $task) {

    $log_file  = WP_CONTENT_DIR . '/custom-debug.log';
    $task_gid  = $task['gid'] ?? '';
    $task_name = $task['name'] ?? '(unnamed)';
    $completed = !empty($task['completed']);

    file_put_contents(
        $log_file,
        "🧪 Delete handler: {$task_gid} ({$task_name}) completed = " .
        ($completed ? 'YES' : 'NO') . "\n",
        FILE_APPEND
    );

    if (!$completed) return;

    $del = wp_remote_request(
        "https://app.asana.com/api/1.0/tasks/{$task_gid}",
        [
            'method'  => 'DELETE',
            'headers' => ['Authorization' => "Bearer {$token}"],
        ]
    );

    if (is_wp_error($del)) {
        file_put_contents(
            $log_file,
            "❌ Failed to delete task {$task_gid}: " . $del->get_error_message() . "\n",
            FILE_APPEND
        );
    } else {
        $code = wp_remote_retrieve_response_code($del);
        file_put_contents(
            $log_file,
            "✅ Deleted task {$task_gid} ({$task_name}) — HTTP {$code}\n",
            FILE_APPEND
        );
    }
}


/* ---------- Email preview helpers (admin-only) ------------------ */
/**
 * Render the styled email HTML using the existing submission template.
 *
 * @param string $subject_tpl
 * @param string $body_tpl
 * @param array  $task
 * @param string $comms_link
 * @return string
 */
function asana_automator_render_email_preview_html($subject_tpl, $body_tpl, array $task = [], $comms_link = '') {
    $task_name      = $task['name'] ?? '';
    $request_number = $task['gid'] ?? '';
    $comms_link     = $comms_link ?: ($request_number ? "https://app.asana.com/0/{$request_number}" : '');
    $fields_html    = '';

    $subject_tpl = (string) $subject_tpl;
    $body_tpl = (string) $body_tpl;

    ob_start();
    // reuse the main submission template so styling stays consistent
    @include plugin_dir_path(__FILE__) . '../../../admin/emails/submission-template.php';
    $html = ob_get_clean();
    return $html;
}


/**
 * Write an HTML preview file to WP_CONTENT_DIR and return the full path.
 *
 * @param string $html
 * @param string $filename
 * @return string|false
 */
function asana_automator_write_email_preview_file($html, $filename = 'email-preview.html') {
    $filename = preg_replace('/[^A-Za-z0-9_.-]+/', '-', (string)$filename);
    $path = rtrim(WP_CONTENT_DIR, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename;
    $ok = @file_put_contents($path, (string)$html);
    return $ok === false ? false : $path;
}


/**
 * AJAX handler for admins to generate an email preview for a saved rule.
 * Expects POST param `rule_index` (int). Returns JSON with `path` and `url`.
 */
function asana_automator_preview_rule_ajax() {
    if (!is_user_logged_in() || !current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $index = isset($_POST['rule_index']) ? intval($_POST['rule_index']) : null;
    if ($index === null) {
        wp_send_json_error(['message' => 'Missing rule_index'], 400);
    }

    $rules = get_option('asana_automator_rules', []);
    if (!isset($rules[$index])) {
        wp_send_json_error(['message' => 'Rule not found'], 404);
    }

    $rule = $rules[$index];

    $task = [
        'name' => 'Preview: ' . ($rule['rule_label'] ?? "Rule {$index}"),
        'gid'  => 'preview-' . $index,
    ];

    $subject_template = trim((string)($rule['email_subject_template'] ?? ($rule['email_subject_template'] ?? 'Test: {task_name}')));
    $body_template    = (string)($rule['email_body_template'] ?? "Hi {full_name},\n\nThis is a preview for {task_name}.\n\n{task_link}");

    $replacements = [
        '{task_name}' => $task['name'],
        '{task_link}' => "https://app.asana.com/0/{$task['gid']}",
        '{full_name}' => 'Preview User',
    ];

    $subject = str_replace(array_keys($replacements), array_values($replacements), $subject_template);
    $body    = str_replace(array_keys($replacements), array_values($replacements), $body_template);

    $html = asana_automator_render_email_preview_html($subject, $body, $task, $replacements['{task_link}']);
    $filename = 'email-preview-rule-' . $index . '.html';
    $path = asana_automator_write_email_preview_file($html, $filename);
    if ($path === false) {
        wp_send_json_error(['message' => 'Failed to write preview file'], 500);
    }

    $url = content_url('/' . basename($path));
    wp_send_json_success(['path' => $path, 'url' => $url]);
}

add_action('wp_ajax_asana_automator_preview_rule', 'asana_automator_preview_rule_ajax');


/**
 * AJAX handler to send a test email for a saved rule.
 * POST params: rule_index (int), to_email (optional), nonce
 */
function asana_automator_send_test_email_ajax() {
    // Verify nonce and capability
    check_ajax_referer('asana_automator_send_test_email', 'nonce');
    if (!is_user_logged_in() || !current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $index = isset($_POST['rule_index']) ? intval($_POST['rule_index']) : null;
    if ($index === null) {
        wp_send_json_error(['message' => 'Missing rule_index'], 400);
    }

    $rules = get_option('asana_automator_rules', []);
    if (!isset($rules[$index])) {
        wp_send_json_error(['message' => 'Rule not found'], 404);
    }

    $rule = $rules[$index];

    $to_email = isset($_POST['to_email']) ? sanitize_email($_POST['to_email']) : '';
    if (!is_email($to_email)) {
        $current = wp_get_current_user();
        $to_email = $current->user_email ?: get_option('admin_email');
    }

    $task = [
        'name' => 'Test: ' . ($rule['rule_label'] ?? "Rule {$index}"),
        'gid'  => 'preview-' . $index,
    ];

    $subject_template = trim((string)($rule['email_subject_template'] ?? 'Test: {task_name}'));
    $body_template    = (string)($rule['email_body_template'] ?? "Hi {full_name},\n\nThis is a preview for {task_name}.\n\n{task_link}");

    $replacements = [
        '{task_name}' => $task['name'],
        '{task_link}' => "https://app.asana.com/0/{$task['gid']}",
        '{full_name}' => wp_get_current_user()->display_name ?: 'Preview User',
        '{project_name}' => $rule['project_name'] ?? '',
        '{section_names}' => implode(', ', (array)($rule['section_names'] ?? [])),
    ];

    $subject = str_replace(array_keys($replacements), array_values($replacements), $subject_template);
    $body    = str_replace(array_keys($replacements), array_values($replacements), $body_template);

    // Render HTML using existing submission template
    $task_name      = $task['name'];
    $request_number = $task['gid'];
    $comms_link     = $replacements['{task_link}'];
    $fields_html    = '';

    $subject_tpl = (string) $subject;
    if (function_exists('wp_kses_post')) {
        $body_tpl = wp_kses_post(nl2br(esc_html((string)$body)));
    } else {
        $body_tpl = nl2br(htmlspecialchars((string)$body, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
    }

    ob_start();
    @include plugin_dir_path(__FILE__) . '../../../admin/emails/submission-template.php';
    $html_body = ob_get_clean();

    $headers = function_exists('afb_get_email_headers') ? afb_get_email_headers() : ['Content-Type: text/html; charset=UTF-8'];

    $sent = wp_mail($to_email, sanitize_text_field((string)$subject), $html_body, $headers);

    // Log outcome
    $log_file = WP_CONTENT_DIR . '/custom-debug.log';
    file_put_contents($log_file, "📧 Test email to {$to_email} for rule {$index}: " . ($sent ? 'SENT' : 'FAILED') . "\n", FILE_APPEND);

    if ($sent) {
        wp_send_json_success(['message' => 'Email sent', 'to' => $to_email]);
    }

    wp_send_json_error(['message' => 'wp_mail returned false; check logs (custom-debug.log)']);
}

add_action('wp_ajax_asana_automator_send_test_email', 'asana_automator_send_test_email_ajax');

