<?php
/**
 * Task Comms Portal – Shared Functions (Optimized)
 */

/* ─────────────────────────────────────────────
 * Utility logger
 */

if (!function_exists('wp_get_current_user')) {
    require_once ABSPATH . 'wp-includes/pluggable.php';
}


 
function afb_debug_log($message) {
    $log_file = WP_CONTENT_DIR . '/custom-debug.log';
    $timestamp = date('[Y-m-d H:i:s]');
    file_put_contents($log_file, "{$timestamp} {$message}\n", FILE_APPEND);
}

/* ─────────────────────────────────────────────
 * Token helpers
 */
function afb_get_task_id_by_token($token) {
    return get_transient('afb_token_task_' . sanitize_text_field($token));
}

function afb_gateway_get_pin_hash($token) {
    $token = sanitize_text_field((string) $token);
    if ($token === '') {
        return '';
    }
    return (string) get_transient('afb_token_pin_hash_' . $token);
}

function afb_gateway_has_pin($token) {
    return afb_gateway_get_pin_hash($token) !== '';
}

function afb_gateway_create_pin_for_token($token, $ttl = YEAR_IN_SECONDS) {
    $token = sanitize_text_field((string) $token);
    if ($token === '') {
        return '';
    }

    $pin = (string) random_int(100000, 999999);
    $hash = wp_hash_password($pin);

    set_transient('afb_token_pin_hash_' . $token, $hash, (int) $ttl);
    return $pin;
}

function afb_gateway_verify_pin($token, $pin) {
    $pin = trim((string) $pin);
    if ($pin === '') {
        return false;
    }

    $hash = afb_gateway_get_pin_hash($token);
    if ($hash === '') {
        return false;
    }

    return wp_check_password($pin, $hash);
}

function afb_gateway_pin_client_ip() {
    $raw = (string) ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
    return sanitize_text_field($raw ?: 'unknown');
}

function afb_gateway_pin_attempt_key($token) {
    $token = sanitize_text_field((string) $token);
    $ip = afb_gateway_pin_client_ip();
    return 'afb_pin_attempts_' . md5($token . '|' . $ip);
}

function afb_gateway_pin_is_locked($token, $max_attempts = 5) {
    $attempts = (int) get_transient(afb_gateway_pin_attempt_key($token));
    return $attempts >= (int) $max_attempts;
}

function afb_gateway_pin_record_failure($token, $window = 15 * MINUTE_IN_SECONDS) {
    $key = afb_gateway_pin_attempt_key($token);
    $attempts = (int) get_transient($key);
    $attempts++;
    set_transient($key, $attempts, (int) $window);
    return $attempts;
}

function afb_gateway_pin_clear_failures($token) {
    delete_transient(afb_gateway_pin_attempt_key($token));
}

function afb_gateway_pin_session_key() {
    return 'afb_gateway_pin_auth';
}

function afb_gateway_pin_mark_authorized($token) {
    if (session_status() === PHP_SESSION_NONE) {
        @session_start();
    }
    if (!isset($_SESSION[afb_gateway_pin_session_key()]) || !is_array($_SESSION[afb_gateway_pin_session_key()])) {
        $_SESSION[afb_gateway_pin_session_key()] = [];
    }
    $_SESSION[afb_gateway_pin_session_key()][sanitize_text_field((string) $token)] = time();
}

function afb_gateway_pin_is_authorized($token, $ttl = 12 * HOUR_IN_SECONDS) {
    if (session_status() === PHP_SESSION_NONE) {
        @session_start();
    }
    $bucket = $_SESSION[afb_gateway_pin_session_key()] ?? [];
    if (!is_array($bucket)) {
        return false;
    }
    $token = sanitize_text_field((string) $token);
    $stamp = isset($bucket[$token]) ? (int) $bucket[$token] : 0;
    if ($stamp <= 0) {
        return false;
    }
    return (time() - $stamp) <= (int) $ttl;
}

function afb_gateway_magic_link_signature($token, $task_gid, $expires_at) {
    $token = sanitize_text_field((string) $token);
    $task_gid = preg_replace('/\D+/', '', (string) $task_gid);
    $expires_at = (int) $expires_at;

    if ($token === '' || $task_gid === '' || $expires_at <= 0) {
        return '';
    }

    $payload = $token . '|' . $task_gid . '|' . $expires_at;
    return hash_hmac('sha256', $payload, wp_salt('auth'));
}

function afb_gateway_build_magic_link($gateway_url, $token, $task_gid, $ttl = YEAR_IN_SECONDS) {
    $gateway_url = esc_url_raw((string) $gateway_url);
    $token = sanitize_text_field((string) $token);
    $task_gid = preg_replace('/\D+/', '', (string) $task_gid);

    if ($gateway_url === '' || $token === '' || $task_gid === '') {
        return $gateway_url;
    }

    $ttl = (int) apply_filters('afb_gateway_magic_link_ttl', (int) $ttl, $token, $task_gid);
    if ($ttl <= 0) {
        $ttl = YEAR_IN_SECONDS;
    }

    $expires_at = time() + $ttl;
    $signature = afb_gateway_magic_link_signature($token, $task_gid, $expires_at);
    if ($signature === '') {
        return $gateway_url;
    }

    return add_query_arg([
        'ml' => $signature,
        'ml_exp' => $expires_at,
    ], $gateway_url);
}

function afb_gateway_verify_magic_link($token, $task_gid, $signature, $expires_at) {
    $signature = strtolower(trim((string) $signature));
    $expires_at = (int) $expires_at;

    if ($signature === '' || $expires_at <= 0 || $expires_at < time()) {
        return false;
    }

    $expected = afb_gateway_magic_link_signature($token, $task_gid, $expires_at);
    if ($expected === '') {
        return false;
    }

    return hash_equals($expected, $signature);
}

function afb_get_request_title_by_token($token) {
    $task = afb_get_asana_task_data(afb_get_task_id_by_token($token));
    return $task['name'] ?? '';
}

function afb_get_global_field_map_data() {
    if (function_exists('get_global_field_map')) {
        $map = get_global_field_map();
        if (is_array($map)) return $map;
    }

    if (function_exists('load_global_field_map')) {
        $map = load_global_field_map();
        if (is_array($map)) return $map;
    }

    $path = WP_CONTENT_DIR . '/mu-plugins/global-field-map.json';
    if (!file_exists($path)) {
        return [];
    }

    $raw = @file_get_contents($path);
    if ($raw === false) {
        return [];
    }

    $decoded = json_decode($raw, true);
    return is_array($decoded) ? $decoded : [];
}

function afb_task_custom_field_scalar_value(array $field) {
    $type = strtolower((string) ($field['type'] ?? ''));

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

    if ($type === 'number') {
        return isset($field['number_value']) ? (string) $field['number_value'] : '';
    }

    if ($type === 'enum') {
        return (string) ($field['enum_value']['name'] ?? '');
    }

    if ($type === 'multi_enum') {
        if (!empty($field['multi_enum_values']) && is_array($field['multi_enum_values'])) {
            $names = array_filter(array_map(static function ($item) {
                return is_array($item) ? (string) ($item['name'] ?? '') : '';
            }, $field['multi_enum_values']));
            return implode(', ', $names);
        }
        return '';
    }

    return (string) ($field['text_value'] ?? $field['enum_value']['name'] ?? $field['number_value'] ?? '');
}

function afb_collect_identity_gids_from_map(array $map) {
    $keys = ['first_name', 'last_name', 'work_email', 'email'];
    $out = [
        'first_name' => [],
        'last_name' => [],
        'email' => [],
    ];

    $walk = static function ($node) use (&$walk, &$out, $keys) {
        if (!is_array($node)) {
            return;
        }

        foreach ($node as $k => $v) {
            if (is_array($v)) {
                $walk($v);
                continue;
            }

            if (!in_array((string) $k, $keys, true)) {
                continue;
            }

            $gid = preg_replace('/\D+/', '', (string) $v);
            if ($gid === '') {
                continue;
            }

            if ($k === 'first_name') {
                $out['first_name'][] = $gid;
            } elseif ($k === 'last_name') {
                $out['last_name'][] = $gid;
            } else {
                $out['email'][] = $gid;
            }
        }
    };

    $walk($map);

    $out['first_name'] = array_values(array_unique($out['first_name']));
    $out['last_name'] = array_values(array_unique($out['last_name']));
    $out['email'] = array_values(array_unique($out['email']));

    return $out;
}

function afb_get_submitter_identity_from_task($task_data) {
    $identity = [
        'first_name' => '',
        'last_name' => '',
        'name' => '',
        'email' => '',
    ];

    if (!is_array($task_data) || empty($task_data['custom_fields']) || !is_array($task_data['custom_fields'])) {
        return $identity;
    }

    $by_gid = [];
    $by_label = [];

    foreach ($task_data['custom_fields'] as $field) {
        if (!is_array($field)) {
            continue;
        }

        $value = trim(afb_task_custom_field_scalar_value($field));
        if ($value === '') {
            continue;
        }

        $gid = preg_replace('/\D+/', '', (string) ($field['gid'] ?? ''));
        if ($gid !== '') {
            $by_gid[$gid] = $value;
        }

        $label = strtolower(trim((string) ($field['name'] ?? '')));
        if ($label !== '' && !isset($by_label[$label])) {
            $by_label[$label] = $value;
        }
    }

    $map = afb_get_global_field_map_data();
    $gid_candidates = afb_collect_identity_gids_from_map($map);

    foreach ($gid_candidates['first_name'] as $gid) {
        if (!empty($by_gid[$gid])) {
            $identity['first_name'] = $by_gid[$gid];
            break;
        }
    }

    foreach ($gid_candidates['last_name'] as $gid) {
        if (!empty($by_gid[$gid])) {
            $identity['last_name'] = $by_gid[$gid];
            break;
        }
    }

    foreach ($gid_candidates['email'] as $gid) {
        if (!empty($by_gid[$gid])) {
            $identity['email'] = $by_gid[$gid];
            break;
        }
    }

    $find_by_label = static function (array $labels, $pattern) {
        foreach ($labels as $label => $value) {
            if (preg_match($pattern, $label)) {
                return $value;
            }
        }
        return '';
    };

    if ($identity['first_name'] === '') {
        $identity['first_name'] = $find_by_label($by_label, '/(^|\b)(first\s*name|forename|given\s*name)(\b|$)/i');
    }

    if ($identity['last_name'] === '') {
        $identity['last_name'] = $find_by_label($by_label, '/(^|\b)(last\s*name|surname|family\s*name)(\b|$)/i');
    }

    if ($identity['email'] === '') {
        $identity['email'] = $find_by_label($by_label, '/(^|\b)(work\s*)?e-?mail(\b|$)/i');
    }

    $identity['name'] = trim($identity['first_name'] . ' ' . $identity['last_name']);
    if ($identity['name'] === '') {
        $identity['name'] = $find_by_label($by_label, '/(^|\b)(name|contact\s*name|requestor)(\b|$)/i');
    }

    return $identity;
}

/* ─────────────────────────────────────────────
 * Asana API fetchers
 */
function afb_get_asana_pat_candidates() {
    global $wpdb;

    $candidates = [];

    $push_candidate = static function ($raw_token) use (&$candidates) {
        if (!is_string($raw_token)) {
            return;
        }

        $raw_token = trim($raw_token);
        if ($raw_token === '') {
            return;
        }

        $candidates[] = $raw_token;

        if (function_exists('afb_decrypt')) {
            $decrypted = trim((string) afb_decrypt($raw_token));
            if ($decrypted !== '') {
                $candidates[] = $decrypted;
            }
        }
    };

    if (isset($wpdb) && is_object($wpdb) && isset($wpdb->prefix)) {
        $table = $wpdb->prefix . 'asana_accounts';
        $rows = $wpdb->get_results("SELECT pat FROM {$table} ORDER BY active DESC, id DESC", 'ARRAY_A');
        if (is_array($rows)) {
            foreach ($rows as $row) {
                $push_candidate($row['pat'] ?? '');
            }
        }
    }

    $stored = get_option('asana_accounts');
    $accounts = [];

    if (is_array($stored)) {
        $accounts = $stored;
    } elseif (is_string($stored) && is_serialized($stored)) {
        $decoded = maybe_unserialize($stored);
        if (is_array($decoded)) {
            $accounts = $decoded;
        }
    }

    if (is_array($accounts)) {
        foreach ($accounts as $maybe_pat) {
            $push_candidate($maybe_pat);
        }
    }

    if (function_exists('afb_get_active_pat_token')) {
        $push_candidate((string) afb_get_active_pat_token());
    }

    $push_candidate((string) get_option('asana_access_token'));

    return array_values(array_unique(array_filter($candidates, static function ($token) {
        return is_string($token) && $token !== '';
    })));
}

function afb_get_asana_task_pat_candidates($task_gid, $preferred_token = '') {
    $task_gid = sanitize_text_field((string) $task_gid);
    $cache_key = 'afb_task_pat_' . md5($task_gid);

    $candidates = afb_get_asana_pat_candidates();

    $cached = (string) get_transient($cache_key);
    if ($cached !== '') {
        array_unshift($candidates, $cached);
    }

    if (is_string($preferred_token) && trim($preferred_token) !== '') {
        array_unshift($candidates, trim($preferred_token));
    }

    return array_values(array_unique(array_filter($candidates, static function ($token) {
        return is_string($token) && $token !== '';
    })));
}

function afb_asana_request_for_task($task_gid, $url, $method = 'GET', $data = null, array $success_codes = [200], $preferred_token = '') {
    $task_gid = sanitize_text_field((string) $task_gid);
    $last_result = [
        'success' => false,
        'code' => 0,
        'body' => '',
        'token' => '',
        'response' => null,
    ];

    if ($task_gid === '' || !ctype_digit($task_gid)) {
        $last_result['body'] = 'Invalid task_gid';
        return $last_result;
    }

    $cache_key = 'afb_task_pat_' . md5($task_gid);
    $candidates = afb_get_asana_task_pat_candidates($task_gid, $preferred_token);

    foreach ($candidates as $candidate_token) {
        $args = [
            'method'  => strtoupper((string) $method),
            'headers' => [
                'Authorization' => 'Bearer ' . $candidate_token,
            ],
            'timeout' => 10,
        ];

        if (is_array($data)) {
            $args['headers']['Content-Type'] = 'application/json';
            $args['body'] = wp_json_encode(['data' => $data]);
        }

        $response = wp_remote_request($url, $args);

        if (is_wp_error($response)) {
            $last_result = [
                'success' => false,
                'code' => 0,
                'body' => $response->get_error_message(),
                'token' => (string) $candidate_token,
                'response' => $response,
            ];
            continue;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $body = (string) wp_remote_retrieve_body($response);

        if (in_array($code, $success_codes, true)) {
            set_transient($cache_key, $candidate_token, DAY_IN_SECONDS);
            return [
                'success' => true,
                'code' => $code,
                'body' => $body,
                'token' => (string) $candidate_token,
                'response' => $response,
            ];
        }

        $last_result = [
            'success' => false,
            'code' => $code,
            'body' => $body,
            'token' => (string) $candidate_token,
            'response' => $response,
        ];

        // Try next token only on auth/object visibility errors.
        if (!in_array($code, [401, 403, 404], true)) {
            break;
        }
    }

    return $last_result;
}

function afb_get_asana_task_data($task_gid) {
    $url = "https://app.asana.com/api/1.0/tasks/{$task_gid}?opt_fields=name,assignee.name,completed,due_on,created_at,modified_at,followers.name,followers.photo.image_21x21,custom_fields.name,custom_fields.text_value,custom_fields.enum_value,custom_fields.number_value,projects.name,notes";
    $result = afb_asana_request_for_task($task_gid, $url, 'GET', null, [200]);
    if (!$result['success']) {
        return null;
    }
    return json_decode($result['body'], true)['data'] ?? null;
}

function afb_get_asana_task_comments($task_gid) {
    $url = "https://app.asana.com/api/1.0/tasks/{$task_gid}/stories?opt_fields=text,html_text,created_at,type,created_by.name,created_by.photo.image_21x21";
    $result = afb_asana_request_for_task($task_gid, $url, 'GET', null, [200]);
    if (!$result['success']) {
        afb_debug_log("⚠️ Comments fetch failed | task={$task_gid} http={$result['code']} body={$result['body']}");
        return [];
    }
    return json_decode($result['body'], true)['data'] ?? [];
}


function afb_get_asana_task_attachments($task_gid) {
    $base  = "https://app.asana.com/api/1.0";

    $list_url = "{$base}/tasks/{$task_gid}/attachments";
    $list_result = afb_asana_request_for_task($task_gid, $list_url, 'GET', null, [200]);
    if (!$list_result['success']) {
        return [];
    }

    $attachments = json_decode($list_result['body'], true)['data'] ?? [];
    $token = (string) ($list_result['token'] ?? '');

    foreach ($attachments as &$attachment) {
        $gid = $attachment['gid'];
        $detail_url = "{$base}/attachments/{$gid}?opt_fields=gid,name,size,created_at,created_by.name,download_url,permalink_url,parent";
        $detail_result = afb_asana_request_for_task($task_gid, $detail_url, 'GET', null, [200], $token);
        if (!$detail_result['success']) {
            continue;
        }
        $detail_data = json_decode($detail_result['body'], true);
        if (!empty($detail_data['data'])) {
            $attachment = array_merge($attachment, $detail_data['data']);
        }
    }

    return $attachments;
}

function afb_get_subtask_children($task_gid) {
    $url = "https://app.asana.com/api/1.0/tasks/{$task_gid}/subtasks?opt_fields=name,completed,due_on,assignee.name,assignee.photo.image_21x21";
    $result = afb_asana_request_for_task($task_gid, $url, 'GET', null, [200]);
    if (!$result['success']) {
        return [];
    }
    return json_decode($result['body'], true)['data'] ?? [];
}

function afb_add_asana_comment($task_gid, $text, $access_token = null) {
    $url = "https://app.asana.com/api/1.0/tasks/{$task_gid}/stories";
    $result = afb_asana_request_for_task(
        $task_gid,
        $url,
        'POST',
        ['text' => $text],
        [200, 201],
        is_string($access_token) ? $access_token : ''
    );

    $body = json_decode($result['body'], true);
    return ($result['success'] && isset($body['data']))
        ? ['success' => true, 'data' => $body['data']]
        : ['success' => false, 'error' => $body['errors'][0]['message'] ?? ($result['body'] ?: 'Unknown error'), 'status' => $result['code'], 'raw' => $body];
}

/* ─────────────────────────────────────────────
 * AJAX handlers
 */
add_action('wp_ajax_afb_get_subtask', 'afb_get_subtask');
add_action('wp_ajax_nopriv_afb_get_subtask', 'afb_get_subtask');

function afb_get_subtask() {
    $gid = sanitize_text_field($_GET['gid'] ?? '');
    if (!$gid) wp_send_json_error(['message' => 'Missing GID.']);

    $token = get_option('asana_access_token');
    $headers = ['Authorization' => "Bearer {$token}"];

    $task = json_decode(wp_remote_retrieve_body(wp_remote_get("https://app.asana.com/api/1.0/tasks/{$gid}?opt_fields=name,completed,due_on,assignee.name", ['headers' => $headers])), true)['data'] ?? [];
    $comments = json_decode(wp_remote_retrieve_body(wp_remote_get("https://app.asana.com/api/1.0/tasks/{$gid}/stories?opt_fields=text,created_at", ['headers' => $headers])), true)['data'] ?? [];

    wp_send_json_success(['task' => $task, 'comments' => $comments]);
}

add_action('wp_ajax_afb_toggle_subtask', 'afb_toggle_subtask');
add_action('wp_ajax_nopriv_afb_toggle_subtask', 'afb_toggle_subtask');

function afb_toggle_subtask() {
    try {
        $gid = sanitize_text_field($_POST['gid'] ?? '');
        $completed = ($_POST['completed'] ?? '') === 'true';
        if (!$gid) throw new Exception('Missing GID');

        if (session_status() === PHP_SESSION_NONE) @session_start();
        $token = $_SESSION['asana_access_token'] ?? get_option('asana_access_token');
        if (!$token) throw new Exception('Missing Asana token');

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

        if (wp_remote_retrieve_response_code($response) === 200) {
            wp_send_json_success(['message' => 'Subtask updated']);
        }

        throw new Exception('Asana API error: ' . wp_remote_retrieve_body($response));
    } catch (Throwable $e) {
        error_log('afb_toggle_subtask failed: ' . $e->getMessage());
        wp_send_json_error(['message' => 'Critical error: ' . $e->getMessage()]);
    }
}

add_action('wp_ajax_get_subtask_details', function () {
    $gid = sanitize_text_field($_POST['gid']);
    $token = get_option('asana_access_token');

    $response = wp_remote_get("https://app.asana.com/api/1.0/tasks/{$gid}", ['headers' => ['Authorization' => "Bearer {$token}"]]);
    if (is_wp_error($response)) wp_send_json_error(['message' => 'API request failed']);

    $task = json_decode(wp_remote_retrieve_body($response), true)['data'] ?? null;
    if (!$task) wp_send_json_error(['message' => 'Task not found']);

    wp_send_json_success([
        'name' => $task['name'],
        'assignee' => $task['assignee']['name'] ?? 'Unassigned',
        'due_on' => $task['due_on'] ?? 'No due date',
    ]);
});

/* ─────────────────────────────────────────────
 * Enqueue subtasks.js only on dashboard
 */
add_action('wp_enqueue_scripts', function () {
    if (!get_query_var('asana_comms_token')) return;

    $script_url = plugins_url('assets/subtasks.js', WP_PLUGIN_DIR . '/asana-form-builder/asana-form-builder.php');
    wp_enqueue_script('afb-subtasks', $script_url, [], '1.0.0', true);
    wp_localize_script('afb-subtasks', 'afb_toggle_subtask_vars', ['nonce' => wp_create_nonce('afb_toggle_subtask_nonce')]);
});

/* ─────────────────────────────────────────────
 * Build the Task-Dashboard (comms) link
 */
function afb_get_dashboard_link_by_token($token) {
    return home_url('/task-dashboard/' . sanitize_title($token));
}

function afb_render_fields_html(array $custom_fields, array $exclude = []): string {
    $exclude_lc = array_map('strtolower', $exclude);
    ob_start();
    echo '<ul>';
    foreach ($custom_fields as $field) {
        $label = $field['name'] ?? '';
        $value = $field['text_value']
              ?? $field['enum_value']['name']
              ?? $field['number_value']
              ?? '';
        if ($value === '' || in_array(strtolower($label), $exclude_lc, true)) continue;
        echo '<li><strong>' . esc_html($label) . ':</strong> ' . esc_html(is_array($value) ? implode(', ', $value) : $value) . '</li>';
    }
    echo '</ul>';
    return ob_get_clean();
}

/* ─────────────────────────────────────────────
 * File upload with guest comment
 */
if (
    $_SERVER['REQUEST_METHOD'] === 'POST'
    && isset($_POST['dashboard_upload_token'])
    && (!empty($_FILES['upload_file']) || !empty($_POST['_afb_ajax']))
) {
    require_once AFB_PLUGIN_DIR . '/includes/security.php';
    global $afb_gateway_upload_result;

    $is_ajax_upload_request = !empty($_POST['_afb_ajax']);

    $afb_gateway_upload_result = [
        'success' => false,
        'message' => 'Upload failed. Please try again.',
    ];

    $respond_upload_json = static function () use (&$afb_gateway_upload_result) {
        if (!headers_sent()) {
            header('Content-Type: application/json');
        }
        echo wp_json_encode($afb_gateway_upload_result);
        exit;
    };

    $token    = sanitize_text_field($_POST['dashboard_upload_token']);
    $task_gid = afb_get_task_id_by_token($token);

    $pat = '';

    if (!$task_gid || !ctype_digit($task_gid)) {
        afb_debug_log("❌ Upload skipped: Invalid or missing task_gid for token [{$token}]");
        $afb_gateway_upload_result['message'] = 'Invalid upload request. Please refresh and try again.';
        if ($is_ajax_upload_request) {
            $respond_upload_json();
        }
        return;
    }

    $task_check = afb_asana_request_for_task(
        $task_gid,
        "https://app.asana.com/api/1.0/tasks/{$task_gid}?opt_fields=gid",
        'GET',
        null,
        [200]
    );
    $pat = (string) ($task_check['token'] ?? '');

    if ($pat === '') {
        afb_debug_log("❌ Upload skipped: No Asana PAT can access task {$task_gid}");
        $afb_gateway_upload_result['message'] = 'Unable to access this task with current Asana account settings.';
        if ($is_ajax_upload_request) {
            $respond_upload_json();
        }
        return;
    }

    $file = $_FILES['upload_file'] ?? null;

    if (
        is_array($file)
        && isset($file['error'])
        && (int) $file['error'] === UPLOAD_ERR_OK
        && !empty($file['tmp_name'])
        && is_uploaded_file($file['tmp_name'])
    ) {
        $filename = sanitize_file_name((string) ($file['name'] ?? 'upload.bin'));
        $filetype = mime_content_type($file['tmp_name']) ?: 'application/octet-stream';

        $upload = curl_init("https://app.asana.com/api/1.0/tasks/{$task_gid}/attachments");
        curl_setopt($upload, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($upload, CURLOPT_HTTPHEADER, ["Authorization: Bearer {$pat}"]);
        curl_setopt($upload, CURLOPT_POST, true);
        curl_setopt($upload, CURLOPT_POSTFIELDS, [
            'file' => new CURLFile($file['tmp_name'], $filetype, $filename),
        ]);

        $response  = curl_exec($upload);
        $curl_error = curl_error($upload);
        $httpcode  = curl_getinfo($upload, CURLINFO_HTTP_CODE);
        $resp_data = json_decode($response, true);
        curl_close($upload);

        afb_debug_log("📨 Guest uploaded {$filename} (HTTP {$httpcode}) – Response: {$response}");

        // If upload succeeded, add a lightweight label comment.
        if ($response !== false && $httpcode >= 200 && $httpcode < 300 && isset($resp_data['data']['gid'])) {
            $label_text = "Guest upload: {$filename}";
            $comment = afb_add_asana_comment($task_gid, $label_text, $pat);

            $message = 'File uploaded successfully.';

            if ($comment['success']) {
                afb_debug_log("✅ Guest label comment posted: {$label_text}");
            } else {
                afb_debug_log("⚠️ Failed to post guest label comment: {$comment['error']}");
                $message = 'File uploaded, but the upload note could not be posted.';
            }

            // Send gateway notifications
            afb_send_gateway_notifications( $token, 'upload', [
                'filename' => $filename,
            ] );

            $afb_gateway_upload_result = [
                'success' => true,
                'message' => $message,
                'filename' => $filename,
                'attachment_gid' => (string) ($resp_data['data']['gid'] ?? ''),
            ];
        } else {
            afb_debug_log("⚠️ Upload failed or response missing attachment GID.");

            $api_message = '';
            if (is_array($resp_data) && !empty($resp_data['errors'][0]['message'])) {
                $api_message = sanitize_text_field((string) $resp_data['errors'][0]['message']);
            } elseif (is_string($curl_error) && $curl_error !== '') {
                $api_message = $curl_error;
            }

            $afb_gateway_upload_result['message'] = $api_message !== ''
                ? ('Upload failed: ' . $api_message)
                : 'Upload failed while saving to Asana.';
        }
    } else {
        $upload_error_code = is_array($file) && isset($file['error']) ? (int) $file['error'] : -1;
        $upload_name = is_array($file) ? (string) ($file['name'] ?? '') : '';
        afb_debug_log("❌ File upload failed (code {$upload_error_code}) for {$upload_name}");
        $afb_gateway_upload_result['message'] = 'Upload failed before sending. Please choose a valid file and try again.';
    }

    if ($is_ajax_upload_request) {
        $respond_upload_json();
    }
}

function afb_get_active_pat_token() {
    global $wpdb;

    $row = $wpdb->get_row("
        SELECT pat FROM {$wpdb->prefix}asana_accounts
        WHERE active = 1
        ORDER BY id DESC
        LIMIT 1
    ");

    return $row && !empty($row->pat) ? trim($row->pat) : null;
}

/* ─────────────────────────────────────────────────────────────
 * Gateway Settings helpers
 * Settings stored at: cache/gateway-settings-{token}.json
 * ─────────────────────────────────────────────────────────── */

function afb_client_gateway_field_key(array $field) {
    $gid = sanitize_text_field((string) ($field['gid'] ?? ''));
    if ($gid !== '') {
        return 'gid:' . $gid;
    }

    $name = sanitize_text_field((string) ($field['name'] ?? ''));
    return 'name:' . sanitize_title($name);
}

function afb_client_gateway_hard_skip_names() {
    return [
        'Client Gateway PIN (SCB)',
    ];
}

function afb_get_client_gateway_hidden_field_keys() {
    $raw = get_option('afb_cg_hidden_field_keys', null);
    if (!is_array($raw)) {
        return null;
    }

    $clean = [];
    foreach ($raw as $key) {
        $key = sanitize_text_field((string) $key);
        if ($key !== '') {
            $clean[] = $key;
        }
    }

    return array_values(array_unique($clean));
}

function afb_get_client_gateway_visibility_profiles() {
    $defaults = [
        'global'   => [
            'hidden_keys'    => [],
            'creation_mode'  => 'manual',
        ],
        'projects' => [],
    ];

    $raw = get_option('afb_cg_field_visibility_profiles', null);
    if (!is_array($raw)) {
        // Backward compatibility with previous global-only option.
        $legacy = afb_get_client_gateway_hidden_field_keys();
        if (is_array($legacy)) {
            $defaults['global']['hidden_keys'] = $legacy;
        }
        return $defaults;
    }

    $profiles = array_merge($defaults, $raw);

    $global_keys = $profiles['global']['hidden_keys'] ?? [];
    if (!is_array($global_keys)) {
        $global_keys = [];
    }

    $global_clean = [];
    foreach ($global_keys as $key) {
        $key = sanitize_text_field((string) $key);
        if ($key !== '') {
            $global_clean[] = $key;
        }
    }
    $profiles['global']['hidden_keys'] = array_values(array_unique($global_clean));

    $global_creation_mode = strtolower(sanitize_text_field((string)($profiles['global']['creation_mode'] ?? 'manual')));
    if (!in_array($global_creation_mode, ['manual', 'automatic'], true)) {
        $global_creation_mode = 'manual';
    }
    $profiles['global']['creation_mode'] = $global_creation_mode;

    $projects = $profiles['projects'] ?? [];
    if (!is_array($projects)) {
        $projects = [];
    }

    $project_clean = [];
    foreach ($projects as $project_gid => $project_data) {
        $project_gid = preg_replace('/\D+/', '', (string) $project_gid);
        if ($project_gid === '' || !is_array($project_data)) {
            continue;
        }

        $keys = $project_data['hidden_keys'] ?? [];
        if (!is_array($keys)) {
            $keys = [];
        }

        $keys_clean = [];
        foreach ($keys as $key) {
            $key = sanitize_text_field((string) $key);
            if ($key !== '') {
                $keys_clean[] = $key;
            }
        }

        $project_creation_mode = strtolower(sanitize_text_field((string)($project_data['creation_mode'] ?? 'manual')));
        if (!in_array($project_creation_mode, ['manual', 'automatic'], true)) {
            $project_creation_mode = 'manual';
        }

        $project_clean[$project_gid] = [
            'name' => sanitize_text_field((string) ($project_data['name'] ?? '')),
            'hidden_keys' => array_values(array_unique($keys_clean)),
            'creation_mode' => $project_creation_mode,
        ];
    }

    $profiles['projects'] = $project_clean;
    return $profiles;
}

function afb_get_client_gateway_hidden_field_keys_for_project($project_gid = '') {
    $profiles = afb_get_client_gateway_visibility_profiles();
    $project_gid = preg_replace('/\D+/', '', (string) $project_gid);
    if ($project_gid === '') {
        return [];
    }

    $keys = $profiles['projects'][$project_gid]['hidden_keys'] ?? [];
    if (!is_array($keys)) {
        $keys = [];
    }

    return array_values(array_unique(array_filter(array_map('sanitize_text_field', $keys))));
}

function afb_get_client_gateway_creation_mode_for_project($project_gid = '') {
    $profiles = afb_get_client_gateway_visibility_profiles();

    $project_gid = preg_replace('/\D+/', '', (string) $project_gid);
    if ($project_gid === '') {
        return 'manual';
    }

    $project_mode = strtolower(sanitize_text_field((string)($profiles['projects'][$project_gid]['creation_mode'] ?? 'manual')));
    if (in_array($project_mode, ['manual', 'automatic'], true)) {
        return $project_mode;
    }

    return 'manual';
}

function afb_get_gateway_settings( $token ) {
    $defaults = [
        'email_collaborators_on_comment' => false,
        'email_collaborators_on_upload'  => false,
        'email_assignee_on_comment'      => false,
        'email_assignee_on_upload'       => false,
    ];
    $path = AFB_PLUGIN_DIR . 'cache/gateway-settings-' . sanitize_file_name( $token ) . '.json';
    if ( ! file_exists( $path ) ) return $defaults;
    $data = json_decode( file_get_contents( $path ), true );
    return is_array( $data ) ? array_merge( $defaults, $data ) : $defaults;
}

function afb_save_gateway_settings( $token, $settings ) {
    $path = AFB_PLUGIN_DIR . 'cache/gateway-settings-' . sanitize_file_name( $token ) . '.json';
    file_put_contents( $path, json_encode( $settings, JSON_PRETTY_PRINT ) );
}

/* ─────────────────────────────────────────────────────────────
 * Lookup an Asana user's email by GID (cached 24 h)
 * ─────────────────────────────────────────────────────────── */

function afb_get_user_email_by_gid( $gid, $pat ) {
    if ( ! $gid || ! $pat ) return '';
    $key    = 'afb_uemail_' . md5( $gid );
    $cached = get_transient( $key );
    if ( $cached !== false ) return $cached;

    $resp  = wp_remote_get( "https://app.asana.com/api/1.0/users/{$gid}?opt_fields=email", [
        'headers' => [ 'Authorization' => "Bearer {$pat}" ],
        'timeout' => 8,
    ] );
    if ( is_wp_error( $resp ) ) return '';

    $email = sanitize_email( json_decode( wp_remote_retrieve_body( $resp ), true )['data']['email'] ?? '' );
    if ( $email ) set_transient( $key, $email, DAY_IN_SECONDS );
    return $email;
}

/* ─────────────────────────────────────────────────────────────
 * Fallback recipient for gateway notifications
 * ─────────────────────────────────────────────────────────── */

function afb_get_gateway_fallback_email() {
    $candidates = [];

    $cc  = trim( (string) get_option( 'afb_email_cc', '' ) );
    $bcc = trim( (string) get_option( 'afb_email_bcc', '' ) );

    if ( $cc !== '' ) {
        $parts = preg_split( '/\s*,\s*/', $cc );
        if ( ! empty( $parts[0] ) ) {
            $candidates[] = $parts[0];
        }
    }

    if ( $bcc !== '' ) {
        $parts = preg_split( '/\s*,\s*/', $bcc );
        if ( ! empty( $parts[0] ) ) {
            $candidates[] = $parts[0];
        }
    }

    $candidates[] = get_option( 'admin_email', '' );

    foreach ( $candidates as $candidate ) {
        $email = sanitize_email( $candidate );
        if ( is_email( $email ) ) {
            return $email;
        }
    }

    return '';
}

/* ─────────────────────────────────────────────────────────────
 * Gateway expiry helpers
 * ─────────────────────────────────────────────────────────── */

function afb_get_gateway_client_link_field_gid() {
    // Client Gateway (COPFM) text custom field; override via admin option.
    $gid = sanitize_text_field((string) get_option('afb_client_gateway_link_field_gid', '1211304630299018'));
    return ctype_digit($gid) ? $gid : '1211304630299018';
}

function afb_clear_client_gateway_link_for_task( $task_gid, $pat = null ) {
    if ( ! $task_gid ) {
        return false;
    }

    if ( ! $pat ) {
        $pat = afb_get_active_pat_token();
        if ( ! $pat ) {
            $pat = get_option( 'asana_access_token' );
        }
    }
    if ( ! $pat ) {
        afb_debug_log( '❌ Could not clear Client Gateway field: missing Asana PAT.' );
        return false;
    }

    $field_gid = afb_get_gateway_client_link_field_gid();

    $resp = wp_remote_request( "https://app.asana.com/api/1.0/tasks/{$task_gid}", [
        'method'  => 'PUT',
        'headers' => [
            'Authorization' => "Bearer {$pat}",
            'Content-Type'  => 'application/json',
        ],
        'body'    => wp_json_encode( [
            'data' => [
                'custom_fields' => [
                    $field_gid => '',
                ],
            ],
        ] ),
        'timeout' => 10,
    ] );

    $code = (int) wp_remote_retrieve_response_code( $resp );
    $ok   = ! is_wp_error( $resp ) && $code >= 200 && $code < 300;

    afb_debug_log( sprintf(
        '%s Clear Client Gateway field | task=%s http=%d',
        $ok ? '✅' : '❌',
        $task_gid,
        $code
    ) );

    return $ok;
}

function afb_expire_gateway_token_local( $token, $reason = 'expired' ) {
    $token = sanitize_text_field( (string) $token );
    if ( $token === '' ) {
        return;
    }

    set_transient( "afb_token_reason_{$token}", $reason, DAY_IN_SECONDS );
    delete_transient( "afb_token_task_{$token}" );
    delete_transient( "afb_token_pin_hash_{$token}" );

    $cache_file = AFB_PLUGIN_DIR . 'cache/tasks-summary-' . $token . '.json';
    if ( file_exists( $cache_file ) ) {
        @unlink( $cache_file );
    }

    $settings_file = AFB_PLUGIN_DIR . 'cache/gateway-settings-' . sanitize_file_name( $token ) . '.json';
    if ( file_exists( $settings_file ) ) {
        @unlink( $settings_file );
    }

    afb_debug_log( "ℹ️ Gateway token expired locally | token={$token} reason={$reason}" );
}

function afb_maybe_expire_gateway_if_completed( $token, $task_gid ) {
    if ( ! $token || ! $task_gid ) {
        return false;
    }

    $pat = afb_get_active_pat_token();
    if ( ! $pat ) {
        $pat = get_option( 'asana_access_token' );
    }
    if ( ! $pat ) {
        afb_debug_log( '❌ Completion check skipped: missing Asana PAT.' );
        return false;
    }

    $url  = "https://app.asana.com/api/1.0/tasks/{$task_gid}?opt_fields=completed";
    $resp = wp_remote_get( $url, [
        'headers' => [ 'Authorization' => "Bearer {$pat}" ],
        'timeout' => 8,
    ] );

    if ( is_wp_error( $resp ) ) {
        afb_debug_log( '❌ Completion check failed: ' . $resp->get_error_message() );
        return false;
    }

    $code = (int) wp_remote_retrieve_response_code( $resp );
    if ( $code < 200 || $code >= 300 ) {
        afb_debug_log( '❌ Completion check failed: Asana HTTP ' . $code );
        return false;
    }

    $completed = ! empty( json_decode( wp_remote_retrieve_body( $resp ), true )['data']['completed'] );
    if ( ! $completed ) {
        return false;
    }

    afb_debug_log( "ℹ️ Task completed detected; expiring gateway token {$token}" );
    afb_clear_client_gateway_link_for_task( $task_gid, $pat );
    afb_expire_gateway_token_local( $token, 'completed' );
    return true;
}

/* ─────────────────────────────────────────────────────────────
 * Background cleanup: expire completed gateways on schedule
 * ─────────────────────────────────────────────────────────── */

add_action( 'afb_gateway_completion_cron_hook', 'afb_run_gateway_completion_cron' );
add_action( 'afb_cleanup_cron_hook', 'afb_run_gateway_completion_cron' );

function afb_run_gateway_completion_cron() {
    $lock_key = 'afb_gateway_completion_cron_lock';
    if ( get_transient( $lock_key ) ) {
        afb_debug_log( 'ℹ️ Gateway completion cron skipped: lock exists.' );
        return;
    }
    set_transient( $lock_key, 1, 10 * MINUTE_IN_SECONDS );

    global $wpdb;
    $prefix  = '_transient_afb_token_task_';
    $pattern = $wpdb->esc_like( $prefix ) . '%';

    $option_names = $wpdb->get_col(
        $wpdb->prepare(
            "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
            $pattern
        )
    );

    if ( ! is_array( $option_names ) || empty( $option_names ) ) {
        delete_transient( $lock_key );
        afb_debug_log( 'ℹ️ Gateway completion cron: no active gateway tokens found.' );
        return;
    }

    $checked = 0;

    foreach ( $option_names as $option_name ) {
        if ( strpos( $option_name, $prefix ) !== 0 ) {
            continue;
        }

        $token = substr( $option_name, strlen( $prefix ) );
        if ( ! $token ) {
            continue;
        }

        $task_gid = afb_get_task_id_by_token( $token );
        if ( ! $task_gid ) {
            continue;
        }

        $checked++;
        // NOTE: Gateways no longer auto-expire on task completion.
        // Gateways now stay open so clients can submit feedback after task completion.
        // Gateways expire only after successful feedback submission.
    }

    delete_transient( $lock_key );
    afb_debug_log( sprintf( 'ℹ️ Gateway completion cron done | checked=%d (no auto-expiry; feedback-only expiry)', $checked ) );
}

/* ─────────────────────────────────────────────────────────────
 * Dispatch gateway notifications
 * $event: 'comment' | 'upload'
 * $extra: ['actor_name', 'actor_email', 'comment', 'filename']
 * ─────────────────────────────────────────────────────────── */

function afb_send_gateway_notifications( $token, $event, $extra = [] ) {
    $settings = afb_get_gateway_settings( $token );
    $pat      = afb_get_active_pat_token();
    if ( ! $pat ) {
        $pat = get_option( 'asana_access_token' );
    }

    afb_debug_log( sprintf(
        '📣 Gateway notify start | token=%s event=%s settings=%s',
        $token,
        $event,
        wp_json_encode( $settings )
    ) );

    if ( ! $pat ) {
        afb_debug_log( '❌ Gateway notify aborted: missing Asana PAT.' );
        return;
    }

    $cache_path = AFB_PLUGIN_DIR . 'cache/tasks-summary-' . $token . '.json';
    if ( ! file_exists( $cache_path ) ) {
        afb_debug_log( '❌ Gateway notify aborted: task summary cache not found at ' . $cache_path );
        return;
    }

    $data      = json_decode( file_get_contents( $cache_path ), true );
    $task      = $data['parent'] ?? [];
    $task_name = $task['name'] ?? 'Your request';
    $task_gid  = afb_get_task_id_by_token( $token );
    if ( ! $task_gid ) {
        afb_debug_log( '❌ Gateway notify aborted: could not resolve task gid from token.' );
        return;
    }

    $event_tab = $event === 'upload' ? 'uploads' : 'comments';
    $gateway_url = add_query_arg(
        [
            'task_gid'          => $task_gid,
            'asana_comms_token' => $token,
            'tab'               => $event_tab,
        ],
        home_url( '/task-dashboard/' . $token . '/' )
    );

    $send_collabs  = $event === 'comment'
        ? ! empty( $settings['email_collaborators_on_comment'] )
        : ! empty( $settings['email_collaborators_on_upload'] );

    $send_assignee = $event === 'comment'
        ? ! empty( $settings['email_assignee_on_comment'] )
        : ! empty( $settings['email_assignee_on_upload'] );

    if ( ! $send_collabs && ! $send_assignee ) {
        afb_debug_log( 'ℹ️ Gateway notify skipped: all toggles are off for this event.' );
        return;
    }

    $recipients = [];

    if ( $send_collabs ) {
        foreach ( $task['followers'] ?? [] as $f ) {
            $email = sanitize_email( $f['email'] ?? '' );
            if ( ! is_email( $email ) && ! empty( $f['gid'] ) ) {
                $email = afb_get_user_email_by_gid( $f['gid'], $pat );
            }
            if ( is_email( $email ) ) $recipients[ $email ] = $f['name'] ?? '';
        }
    }

    if ( $send_assignee && ! empty( $task['assignee'] ) ) {
        $email = sanitize_email( $task['assignee']['email'] ?? '' );
        if ( ! is_email( $email ) && ! empty( $task['assignee']['gid'] ) ) {
            $email = afb_get_user_email_by_gid( $task['assignee']['gid'], $pat );
        }
        if ( is_email( $email ) ) $recipients[ $email ] = $task['assignee']['name'] ?? '';
    }

    afb_debug_log( sprintf(
        '📣 Gateway notify recipients resolved | count=%d emails=%s',
        count( $recipients ),
        implode( ',', array_keys( $recipients ) )
    ) );

    if ( empty( $recipients ) ) {
        $fallback_email = afb_get_gateway_fallback_email();
        if ( is_email( $fallback_email ) ) {
            $recipients[ $fallback_email ] = 'Team';
            afb_debug_log( '⚠️ Gateway notify using fallback recipient: ' . $fallback_email );
        } else {
            afb_debug_log( '⚠️ Gateway notify found no valid recipient emails and no fallback email.' );
            return;
        }
    }

    foreach ( $recipients as $email => $name ) {
        $sent = afb_send_gateway_notification_email( array_merge( $extra, [
            'to'          => $email,
            'name'        => $name,
            'event'       => $event,
            'task_name'   => $task_name,
            'gateway_url' => $gateway_url,
        ] ) );

        afb_debug_log( sprintf(
            '%s Gateway notify mail | event=%s to=%s',
            $sent ? '✅' : '❌',
            $event,
            $email
        ) );
    }
}

/* ─────────────────────────────────────────────────────────────
 * Client Feedback Helpers
 * ─────────────────────────────────────────────────────────── */

/**
 * Get Asana custom field GIDs for feedback fields
 * Returns array with keys: rating_gid, reply_gid, feedback_gid, status_gid
 */
function afb_get_feedback_field_gids() {
    $raw_enum_gids     = json_decode(get_option('afb_feedback_rating_enum_gids', '[]'), true);
    $rating_enum_gids  = is_array($raw_enum_gids) ? array_values($raw_enum_gids) : [];

    $fields = [
        'rating_gid'              => get_option('afb_feedback_rating_field_gid', ''),
        'rating_enum_gids'        => $rating_enum_gids, // array [0..4] → enum option GIDs for stars 1–5
        'reply_gid'               => get_option('afb_feedback_reply_field_gid', ''),
        'feedback_gid'            => get_option('afb_feedback_feedback_field_gid', ''),
        'status_gid'              => get_option('afb_feedback_status_field_gid', ''),
        'status_submitted_gid'    => get_option('afb_status_submitted_enum_gid', ''), // enum option GID for "Submitted"
    ];

    // Filter scalar fields; preserve rating_enum_gids only if non-empty array
    return array_filter($fields, function ($v) {
        if (is_array($v)) {
            return !empty(array_filter($v));
        }
        return $v !== '' && $v !== null;
    });
}

/**
 * Check if a task already has feedback submission recorded.
 *
 * Priority order:
 * 1. Status enum GID configured → check for "Submitted" enum option
 * 2. Fallback → check if any configured text/number feedback field has a value
 */
function afb_has_feedback_submission($task_data) {
    if (!is_array($task_data) || empty($task_data['custom_fields'])) {
        return false;
    }

    $feedback_fields = afb_get_feedback_field_gids();
    if (empty($feedback_fields)) {
        return false;
    }

    // Build a map of GID → field data for fast lookup
    $field_map = [];
    foreach ($task_data['custom_fields'] as $field) {
        if (!empty($field['gid'])) {
            $field_map[$field['gid']] = $field;
        }
    }

    // 1. Status enum check (most reliable when configured)
    if (!empty($feedback_fields['status_gid']) && !empty($feedback_fields['status_submitted_gid'])) {
        $status_field = $field_map[$feedback_fields['status_gid']] ?? null;
        if ($status_field && !empty($status_field['enum_value']['gid'])) {
            $status_enum_gid = $status_field['enum_value']['gid'];
            return $status_enum_gid === $feedback_fields['status_submitted_gid'];
        }
    }

    // 2. Fallback: any configured feedback field has a non-empty value
    $check_gids = array_filter([
        $feedback_fields['reply_gid']    ?? '',
        $feedback_fields['feedback_gid'] ?? '',
        $feedback_fields['rating_gid']   ?? '',
    ]);

    foreach ($check_gids as $gid) {
        $f = $field_map[$gid] ?? null;
        if (!$f) continue;

        // Support text, number, and enum field types
        $value = $f['text_value'] ?? $f['number_value'] ?? $f['enum_value']['gid'] ?? '';
        if ($value !== '' && $value !== null) {
            return true;
        }
    }

    return false;
}

