<?php
// includes/api/create-task.php
if (!defined('ABSPATH')) exit;

require_once AFB_PLUGIN_DIR . 'includes/lib/status-store.php';
require_once AFB_PLUGIN_DIR . 'includes/lib/asana-client.php';
require_once AFB_PLUGIN_DIR . 'includes/functions/taskdate/task-date.php';
require_once AFB_PLUGIN_DIR . 'includes/functions/triagesummary/triagesummary.php';

/**
 * AJAX endpoint: afb_create_task_fast
 * Expects (at minimum): project_id
 * Optional: custom_fields (array/JSON), ai_summary (HTML),
 * asana_html_notes (plain text from textarea),
 * submitted_copy_html, subservice, date,
 * submitter_email, triage_state, triage_answers
 */

// --- Safe title sanitizer: strip HTML, collapse spaces, cap length
function afb_sanitize_task_title($title, $max_len = 140) {
    $s = (string) $title;

    $s = wp_strip_all_tags($s);
    $s = html_entity_decode($s, ENT_QUOTES | ENT_HTML5, 'UTF-8');

    $s = preg_replace('/[\r\n\t]+/u', ' ', $s);
    $s = preg_replace('/\s{2,}/u', ' ', $s);
    $s = preg_replace('/\s*–\s*(?:–\s*)+/u', ' – ', $s);
    $s = preg_replace('/(?:\s*–\s*)+$/u', '', $s);
    $s = preg_replace('/(?:\s*[-|,:\/]+\s*)+$/u', '', $s);
    $s = trim($s);

    if (mb_strlen($s) > $max_len) {
        $s = trim(mb_substr($s, 0, $max_len));
    }

    return $s;
}

function afb_get_project_task_name_template($project_cfg) {
    if (!is_array($project_cfg)) {
        return '';
    }

    $templates = $project_cfg['custom_task_name_templates'] ?? [];
    if (!is_array($templates) || empty($templates)) {
        return '';
    }

    $field_order = $project_cfg['field_order'] ?? [];
    if (is_array($field_order)) {
        foreach ($field_order as $ordered_id) {
            $ordered_id = trim((string) $ordered_id);
            if ($ordered_id === '' || strpos($ordered_id, 'task_name_') !== 0) {
                continue;
            }

            $template = trim((string) ($templates[$ordered_id] ?? ''));
            if ($template !== '') {
                return $template;
            }
        }
    }

    foreach ($templates as $template) {
        $template = trim((string) $template);
        if ($template !== '') {
            return $template;
        }
    }

    return '';
}

function afb_condition_matches($condition, $submitted_values) {
    if (!is_array($condition) || empty($condition['rules'])) {
        return true;
    }

    $logic = (string) ($condition['logic'] ?? 'all');
    $rules = (array) ($condition['rules'] ?? []);

    if (empty($rules)) {
        return true;
    }

    $matches = [];
    foreach ($rules as $rule) {
        if (!is_array($rule)) continue;
        
        $depends_on = (string) ($rule['depends_on'] ?? '');
        $expected_values = (array) ($rule['values'] ?? []);
        if ($depends_on === '' || empty($expected_values)) {
            continue;
        }

        $actual_value = $submitted_values[$depends_on] ?? '';
        if (is_array($actual_value)) {
            $actual_value = implode(', ', array_map('strval', array_filter(array_map('trim', array_map('strval', $actual_value)))));
        } else {
            $actual_value = trim((string) $actual_value);
        }

        $rule_match = in_array($actual_value, array_map('strval', $expected_values), true);
        $matches[] = $rule_match;
    }

    if (empty($matches)) {
        return true;
    }

    if ($logic === 'all') {
        return !in_array(false, $matches, true);
    }

    return in_array(true, $matches, true);
}

function afb_get_task_name_template_by_condition($project_cfg, $submitted_values) {
    if (!is_array($project_cfg)) {
        return '';
    }

    $templates = $project_cfg['custom_task_name_templates'] ?? [];
    $conditions = $project_cfg['conditions'] ?? [];
    $field_order = $project_cfg['field_order'] ?? [];

    if (!is_array($templates) || empty($templates)) {
        return '';
    }

    if (!is_array($field_order)) {
        $field_order = [];
    }

    foreach ($field_order as $ordered_id) {
        $ordered_id = trim((string) $ordered_id);
        if ($ordered_id === '' || strpos($ordered_id, 'task_name_') !== 0) {
            continue;
        }

        $template = trim((string) ($templates[$ordered_id] ?? ''));
        if ($template === '') {
            continue;
        }

        $condition = $conditions[$ordered_id] ?? null;
        if (is_array($condition)) {
            if (afb_condition_matches($condition, $submitted_values)) {
                return $template;
            }
        } else {
            return $template;
        }
    }

    foreach ($templates as $template) {
        $template = trim((string) $template);
        if ($template !== '') {
            return $template;
        }
    }

    return '';
}

function afb_task_name_value_to_string($value) {
    if (is_array($value)) {
        if (isset($value['date']) && is_string($value['date'])) {
            return trim($value['date']);
        }

        $parts = [];
        foreach ($value as $entry) {
            $flat = afb_task_name_value_to_string($entry);
            if ($flat !== '') {
                $parts[] = $flat;
            }
        }

        return implode(', ', $parts);
    }

    if (is_bool($value)) {
        return $value ? 'Yes' : 'No';
    }

    if (is_scalar($value)) {
        return trim((string) $value);
    }

    return '';
}

function afb_task_name_format_date_value($value) {
    $raw = afb_task_name_value_to_string($value);
    if ($raw === '') {
        return '';
    }

    if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw)) {
        $dt = DateTime::createFromFormat('Y-m-d', $raw);
        if ($dt instanceof DateTime) {
            return $dt->format('d/m/Y');
        }
    }

    return $raw;
}

function afb_task_name_sync_aliases_from_config($project_cfg) {
    $aliases = [];
    if (!is_array($project_cfg)) {
        return $aliases;
    }

    foreach (['custom_text_sync_fields', 'custom_single_input_sync_fields', 'custom_dropdown_sync_fields', 'custom_calendar_sync_fields'] as $map_key) {
        $raw_map = $project_cfg[$map_key] ?? [];
        if (!is_array($raw_map)) {
            continue;
        }

        foreach ($raw_map as $source_id => $target_gid) {
            $source_id = trim((string) $source_id);
            $target_gid = trim((string) $target_gid);
            if ($source_id === '' || $target_gid === '') {
                continue;
            }

            $aliases[$source_id] = $target_gid;
        }
    }

    return $aliases;
}

function afb_count_words_from_text($text) {
    $text = trim((string) $text);
    if ($text === '') {
        return 0;
    }

    $parts = preg_split('/\s+/u', $text);
    if (!is_array($parts)) {
        return 0;
    }

    $parts = array_values(array_filter($parts, static function ($part) {
        return $part !== '';
    }));

    return count($parts);
}

function afb_get_single_input_max_word_rules($project_cfg) {
    $rules = [];
    if (!is_array($project_cfg)) {
        return $rules;
    }

    $single_modes = is_array($project_cfg['custom_single_input_modes'] ?? null)
        ? $project_cfg['custom_single_input_modes']
        : [];
    $single_labels = is_array($project_cfg['custom_single_input_labels'] ?? null)
        ? $project_cfg['custom_single_input_labels']
        : [];
    $single_sync_fields = is_array($project_cfg['custom_single_input_sync_fields'] ?? null)
        ? $project_cfg['custom_single_input_sync_fields']
        : [];
    $single_max_words = is_array($project_cfg['custom_single_input_max_words'] ?? null)
        ? $project_cfg['custom_single_input_max_words']
        : [];

    $enabled_ids = [];
    if (!empty($project_cfg['custom_single_input_max_words_enabled']) && is_array($project_cfg['custom_single_input_max_words_enabled'])) {
        foreach ($project_cfg['custom_single_input_max_words_enabled'] as $single_id) {
            $single_id = sanitize_text_field((string) $single_id);
            if ($single_id === '') {
                continue;
            }

            $enabled_ids[$single_id] = true;
        }
    }

    foreach ($single_max_words as $single_id => $max_words_raw) {
        $single_id = sanitize_text_field((string) $single_id);
        if ($single_id === '' || empty($enabled_ids[$single_id])) {
            continue;
        }

        $mode = sanitize_text_field((string) ($single_modes[$single_id] ?? 'text'));
        if ($mode !== 'textarea') {
            continue;
        }

        $max_words = max(0, absint($max_words_raw));
        if ($max_words <= 0) {
            continue;
        }

        $label = sanitize_text_field((string) ($single_labels[$single_id] ?? ''));
        if ($label === '') {
            $label = 'Additional detail';
        }

        $sync_field = sanitize_text_field((string) ($single_sync_fields[$single_id] ?? ''));

        $rules[$single_id] = [
            'label'      => $label,
            'max_words'  => $max_words,
            'sync_field' => $sync_field,
        ];
    }

    return $rules;
}

function afb_resolve_task_name_template($template, $values_map, $field_types = []) {
    $template = trim((string) $template);
    if ($template === '') {
        return '';
    }

    $values = is_array($values_map) ? $values_map : [];
    $types = is_array($field_types) ? $field_types : [];

    $resolved = preg_replace_callback('/\{\{\s*([a-zA-Z0-9_:-]+)\s*\}\}/', static function ($matches) use ($values, $types) {
        $token = isset($matches[1]) ? trim((string) $matches[1]) : '';
        if ($token === '') {
            return '';
        }

        $token_lc = strtolower($token);

        if (in_array($token_lc, ['job_number', 'submitter_name', 'submitter_email'], true)) {
            return afb_task_name_value_to_string($values[$token_lc] ?? '');
        }

        if ($token_lc === 'due_date') {
            return afb_task_name_format_date_value($values['due_date'] ?? '');
        }

        $field_key = $token;
        if (strpos($token_lc, 'field:') === 0) {
            $field_key = substr($token, 6);
        }

        $field_key = trim((string) $field_key);
        if ($field_key === '') {
            return '';
        }

        $raw_value = $values[$field_key] ?? '';
        if (strpos($field_key, 'calendar_') === 0) {
            return afb_task_name_format_date_value($raw_value);
        }

        $field_type = strtolower((string) ($types[$field_key] ?? ''));
        if ($field_type === 'date') {
            return afb_task_name_format_date_value($raw_value);
        }

        return afb_task_name_value_to_string($raw_value);
    }, $template);

    if (!is_string($resolved)) {
        return '';
    }

    $resolved = preg_replace('/\s{2,}/u', ' ', $resolved);
    $resolved = preg_replace('/(?:\s*[-|,:\/]+\s*)+$/u', '', $resolved);

    return trim((string) $resolved);
}

function afb_task_name_render_field_value($field_gid, $raw_value, $field_types = [], $enum_option_labels = []) {
    $gid = trim((string) $field_gid);
    $type = strtolower((string) ($field_types[$gid] ?? ''));

    if ($type === 'enum') {
        $option_gid = afb_task_name_value_to_string($raw_value);
        if ($option_gid === '') {
            return '';
        }

        return trim((string) ($enum_option_labels[$gid][$option_gid] ?? $option_gid));
    }

    if ($type === 'multi_enum') {
        $values = is_array($raw_value) ? $raw_value : [$raw_value];
        $labels = [];

        foreach ($values as $value) {
            $option_gid = afb_task_name_value_to_string($value);
            if ($option_gid === '') {
                continue;
            }

            $labels[] = trim((string) ($enum_option_labels[$gid][$option_gid] ?? $option_gid));
        }

        $labels = array_values(array_unique(array_filter($labels, static function ($label) {
            return $label !== '';
        })));

        return implode(', ', $labels);
    }

    if ($type === 'date') {
        return afb_task_name_format_date_value($raw_value);
    }

    return afb_task_name_value_to_string($raw_value);
}

function afb_condition_known_field_ids_from_project_cfg($project_cfg) {
    $ids = [];
    if (!is_array($project_cfg)) {
        return $ids;
    }

    $list_keys = [
        'field_order',
        'used_fields',
        'agreement_required_fields',
        'submit_block_fields',
    ];
    foreach ($list_keys as $key) {
        $vals = $project_cfg[$key] ?? [];
        if (!is_array($vals)) {
            continue;
        }

        foreach ($vals as $val) {
            $id = sanitize_text_field((string) $val);
            if ($id !== '') {
                $ids[$id] = true;
            }
        }
    }

    $map_keys = [
        'custom_paragraphs',
        'custom_paragraph_titles',
        'custom_sections',
        'custom_section_titles',
        'custom_agreement_labels',
        'custom_upload_titles',
        'custom_text_sync_fields',
        'custom_single_input_labels',
        'custom_dropdown_labels',
        'custom_calendar_labels',
        'custom_repeater_labels',
    ];
    foreach ($map_keys as $key) {
        $vals = $project_cfg[$key] ?? [];
        if (!is_array($vals)) {
            continue;
        }

        foreach (array_keys($vals) as $map_key) {
            $id = sanitize_text_field((string) $map_key);
            if ($id !== '') {
                $ids[$id] = true;
            }
        }
    }

    return $ids;
}

function afb_condition_matches_rule_value($current, $expected_values) {
    $expected = [];
    if (is_array($expected_values)) {
        foreach ($expected_values as $value) {
            $value = sanitize_text_field((string) $value);
            if ($value !== '') {
                $expected[] = $value;
            }
        }
    }

    $expected = array_values(array_unique($expected));
    if (empty($expected)) {
        return false;
    }

    if (is_array($current)) {
        foreach ($current as $value) {
            $value = sanitize_text_field((string) $value);
            if ($value !== '' && in_array($value, $expected, true)) {
                return true;
            }
        }
        return false;
    }

    $current_value = sanitize_text_field((string) $current);
    return $current_value !== '' && in_array($current_value, $expected, true);
}

function afb_condition_set_matches_submitted_values($condition, $submitted_values, $known_field_ids = []) {
    if (!is_array($condition)) {
        return false;
    }

    $raw_rules = isset($condition['rules']) && is_array($condition['rules'])
        ? $condition['rules']
        : [];

    if (empty($raw_rules) && !empty($condition['depends_on'])) {
        $raw_rules = [[
            'depends_on' => $condition['depends_on'],
            'values'     => $condition['values'] ?? (($condition['value'] ?? '') !== '' ? [$condition['value']] : []),
        ]];
    }

    if (empty($raw_rules)) {
        return false;
    }

    $logic = sanitize_text_field((string) ($condition['logic'] ?? 'all'));
    if ($logic !== 'any') {
        $logic = 'all';
    }

    $results = [];
    foreach ($raw_rules as $rule) {
        if (!is_array($rule)) {
            continue;
        }

        $source = sanitize_text_field((string) ($rule['depends_on'] ?? ''));
        if ($source === '') {
            $results[] = true;
            continue;
        }

        if (!isset($known_field_ids[$source])) {
            // Match frontend behavior: stale/missing sources should not hide or block.
            $results[] = true;
            continue;
        }

        $rule_values = [];
        if (isset($rule['values']) && is_array($rule['values'])) {
            $rule_values = $rule['values'];
        } elseif (isset($rule['value']) && $rule['value'] !== '') {
            $rule_values = [$rule['value']];
        }

        $current = $submitted_values[$source] ?? '';
        $results[] = afb_condition_matches_rule_value($current, $rule_values);
    }

    if (empty($results)) {
        return false;
    }

    return $logic === 'any'
        ? in_array(true, $results, true)
        : !in_array(false, $results, true);
}

function afb_find_submission_blocking_target($project_cfg, $submitted_values) {
    if (!is_array($project_cfg) || !is_array($submitted_values)) {
        return null;
    }

    $blocked_targets = isset($project_cfg['submit_block_fields']) && is_array($project_cfg['submit_block_fields'])
        ? $project_cfg['submit_block_fields']
        : [];
    if (empty($blocked_targets)) {
        return null;
    }

    $conditions = isset($project_cfg['conditions']) && is_array($project_cfg['conditions'])
        ? $project_cfg['conditions']
        : [];
    $target_titles = [];
    foreach (['labels', 'custom_paragraph_titles', 'custom_section_titles', 'custom_agreement_labels', 'custom_upload_titles', 'custom_single_input_labels', 'custom_dropdown_labels', 'custom_calendar_labels', 'custom_repeater_labels'] as $title_key) {
        $raw_titles = isset($project_cfg[$title_key]) && is_array($project_cfg[$title_key])
            ? $project_cfg[$title_key]
            : [];
        foreach ($raw_titles as $target_id => $title) {
            $target_id = sanitize_text_field((string) $target_id);
            $title = sanitize_text_field((string) $title);
            if ($target_id === '' || $title === '' || isset($target_titles[$target_id])) {
                continue;
            }

            $target_titles[$target_id] = $title;
        }
    }

    $known_ids = afb_condition_known_field_ids_from_project_cfg($project_cfg);

    foreach ($blocked_targets as $raw_target_id) {
        $target_id = sanitize_text_field((string) $raw_target_id);
        if ($target_id === '') {
            continue;
        }

        $condition = $conditions[$target_id] ?? null;
        if (!is_array($condition)) {
            continue;
        }

        if (afb_condition_set_matches_submitted_values($condition, $submitted_values, $known_ids)) {
            $title = sanitize_text_field((string) ($target_titles[$target_id] ?? ''));
            if ($title === '') {
                $title = 'this field';
            }

            return [
                'target_id' => $target_id,
                'title'     => $title,
            ];
        }
    }

    return null;
}

function afb_get_project_due_date_tool_config($project_cfg) {
    if (!is_array($project_cfg)) {
        return null;
    }

    $configs = isset($project_cfg['custom_due_date_configs']) && is_array($project_cfg['custom_due_date_configs'])
        ? $project_cfg['custom_due_date_configs']
        : [];
    if (empty($configs)) {
        return null;
    }

    $field_order = isset($project_cfg['field_order']) && is_array($project_cfg['field_order'])
        ? $project_cfg['field_order']
        : [];

    foreach ($field_order as $ordered_id) {
        $ordered_id = sanitize_text_field((string) $ordered_id);
        if ($ordered_id === '' || strpos($ordered_id, 'due_date_') !== 0) {
            continue;
        }

        if (isset($configs[$ordered_id]) && is_array($configs[$ordered_id])) {
            return $configs[$ordered_id];
        }
    }

    foreach ($configs as $cfg) {
        if (is_array($cfg)) {
            return $cfg;
        }
    }

    return null;
}

function afb_due_date_normalize_formula($raw_formula) {
    $formula = is_array($raw_formula) ? $raw_formula : [];

    $base_raw = sanitize_text_field((string) ($formula['base'] ?? 'today'));
    $base = in_array($base_raw, ['today', 'field'], true) ? $base_raw : 'today';

    $base_field = sanitize_text_field((string) ($formula['base_field'] ?? ''));

    $offset = (int) ($formula['offset'] ?? 0);
    if ($offset > 3650) {
        $offset = 3650;
    } elseif ($offset < -3650) {
        $offset = -3650;
    }

    $mode_raw = sanitize_text_field((string) ($formula['mode'] ?? 'business'));
    $mode = in_array($mode_raw, ['business', 'calendar'], true) ? $mode_raw : 'business';

    $allow_weekends = !empty($formula['allow_weekends']) ? 1 : 0;

    return [
        'base'           => $base,
        'base_field'     => $base_field,
        'offset'         => $offset,
        'mode'           => $mode,
        'allow_weekends' => $allow_weekends,
    ];
}

function afb_due_date_value_to_iso($value) {
    if (is_array($value)) {
        if (isset($value['date']) && is_string($value['date'])) {
            return afb_parse_date_to_iso($value['date']);
        }

        foreach ($value as $entry) {
            $iso = afb_due_date_value_to_iso($entry);
            if ($iso !== null) {
                return $iso;
            }
        }

        return null;
    }

    if (!is_scalar($value)) {
        return null;
    }

    $raw = sanitize_text_field((string) $value);
    if ($raw === '') {
        return null;
    }

    return afb_parse_date_to_iso($raw);
}

function afb_due_date_formula_base_iso($formula, $submitted_values) {
    if (!is_array($formula)) {
        return null;
    }

    $base = sanitize_text_field((string) ($formula['base'] ?? 'today'));
    if ($base === 'field') {
        $base_field = sanitize_text_field((string) ($formula['base_field'] ?? ''));
        if ($base_field === '') {
            return null;
        }

        if (!is_array($submitted_values) || !array_key_exists($base_field, $submitted_values)) {
            return null;
        }

        return afb_due_date_value_to_iso($submitted_values[$base_field]);
    }

    return current_time('Y-m-d');
}

function afb_due_date_apply_offset_formula($base_iso, $offset, $mode = 'business', $allow_weekends = false) {
    $base_iso = sanitize_text_field((string) $base_iso);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $base_iso)) {
        return null;
    }

    $tz = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC');
    $date = DateTimeImmutable::createFromFormat('!Y-m-d', $base_iso, $tz);
    if (!($date instanceof DateTimeImmutable)) {
        return null;
    }

    $offset = (int) $offset;
    if ($offset > 3650) {
        $offset = 3650;
    } elseif ($offset < -3650) {
        $offset = -3650;
    }

    $mode = in_array((string) $mode, ['business', 'calendar'], true) ? (string) $mode : 'business';
    $allow_weekends = !empty($allow_weekends);

    if ($mode === 'business') {
        if ($offset !== 0) {
            $step = $offset >= 0 ? 1 : -1;
            $remaining = abs($offset);
            while ($remaining > 0) {
                $date = $date->modify($step > 0 ? '+1 day' : '-1 day');
                $dow = (int) $date->format('N');
                if ($dow < 6) {
                    $remaining--;
                }
            }
        }

        if (!$allow_weekends) {
            $step = $offset < 0 ? -1 : 1;
            while ((int) $date->format('N') >= 6) {
                $date = $date->modify($step > 0 ? '+1 day' : '-1 day');
            }
        }
    } else {
        if ($offset !== 0) {
            if ($offset > 0) {
                $date = $date->modify('+' . $offset . ' day');
            } else {
                $date = $date->modify($offset . ' day');
            }
        }

        if (!$allow_weekends) {
            $dow = (int) $date->format('N');
            if ($dow === 6) {
                $date = $date->modify('+2 day');
            } elseif ($dow === 7) {
                $date = $date->modify('+1 day');
            }
        }
    }

    return $date->format('Y-m-d');
}

function afb_due_date_rule_matches_submitted_values($rule, $submitted_values) {
    if (!is_array($rule) || !is_array($submitted_values)) {
        return false;
    }

    $source = sanitize_text_field((string) ($rule['depends_on'] ?? ''));
    if ($source === '' || !array_key_exists($source, $submitted_values)) {
        return false;
    }

    $rule_values = [];
    if (isset($rule['values']) && is_array($rule['values'])) {
        $rule_values = $rule['values'];
    } elseif (isset($rule['value']) && $rule['value'] !== '') {
        $rule_values = [$rule['value']];
    }

    return afb_condition_matches_rule_value($submitted_values[$source], $rule_values);
}

function afb_resolve_due_date_from_tool_config($tool_cfg, $submitted_values) {
    if (!is_array($tool_cfg)) {
        return null;
    }

    $rules = isset($tool_cfg['rules']) && is_array($tool_cfg['rules'])
        ? $tool_cfg['rules']
        : [];

    $chosen_formula = null;
    $matched_rule = false;
    foreach ($rules as $rule) {
        if (!is_array($rule)) {
            continue;
        }

        if (!afb_due_date_rule_matches_submitted_values($rule, $submitted_values)) {
            continue;
        }

        $chosen_formula = $rule;
        $matched_rule = true;
        break;
    }

    if (!is_array($chosen_formula)) {
        $chosen_formula = isset($tool_cfg['default']) && is_array($tool_cfg['default'])
            ? $tool_cfg['default']
            : null;
    }

    if (!is_array($chosen_formula)) {
        return null;
    }

    $formula = afb_due_date_normalize_formula($chosen_formula);
    $base_iso = afb_due_date_formula_base_iso($formula, $submitted_values);
    if ($base_iso === null && $matched_rule && isset($tool_cfg['default']) && is_array($tool_cfg['default'])) {
        $formula = afb_due_date_normalize_formula($tool_cfg['default']);
        $base_iso = afb_due_date_formula_base_iso($formula, $submitted_values);
    }

    if ($base_iso === null) {
        return null;
    }

    return afb_due_date_apply_offset_formula(
        $base_iso,
        (int) ($formula['offset'] ?? 0),
        (string) ($formula['mode'] ?? 'business'),
        !empty($formula['allow_weekends'])
    );
}

function afb_get_project_assignee_tool_config($project_cfg) {
    if (!is_array($project_cfg)) {
        return null;
    }

    $configs = isset($project_cfg['custom_assignee_configs']) && is_array($project_cfg['custom_assignee_configs'])
        ? $project_cfg['custom_assignee_configs']
        : [];
    if (empty($configs)) {
        return null;
    }

    $field_order = isset($project_cfg['field_order']) && is_array($project_cfg['field_order'])
        ? $project_cfg['field_order']
        : [];

    foreach ($field_order as $ordered_id) {
        $ordered_id = sanitize_text_field((string) $ordered_id);
        if ($ordered_id === '' || strpos($ordered_id, 'assignee_') !== 0) {
            continue;
        }

        if (isset($configs[$ordered_id]) && is_array($configs[$ordered_id])) {
            return $configs[$ordered_id];
        }
    }

    foreach ($configs as $cfg) {
        if (is_array($cfg)) {
            return $cfg;
        }
    }

    return null;
}

function afb_assignee_tool_normalize_gid($raw_gid) {
    $gid = sanitize_text_field((string) $raw_gid);
    $gid = preg_replace('/\D+/', '', $gid);
    return is_string($gid) ? $gid : '';
}

function afb_assignee_tool_collect_unique_gids($raw_values, $exclude_gid = '') {
    $exclude = afb_assignee_tool_normalize_gid($exclude_gid);
    $vals = is_array($raw_values) ? $raw_values : [];

    $out = [];
    $seen = [];
    foreach ($vals as $raw_gid) {
        $gid = afb_assignee_tool_normalize_gid($raw_gid);
        if ($gid === '' || $gid === $exclude || isset($seen[$gid])) {
            continue;
        }

        $seen[$gid] = true;
        $out[] = $gid;
    }

    return $out;
}

function afb_assignee_rule_matches_submitted_values($rule, $submitted_values) {
    if (!is_array($rule) || !is_array($submitted_values)) {
        return false;
    }

    $source = sanitize_text_field((string) ($rule['depends_on'] ?? ''));
    if ($source === '' || !array_key_exists($source, $submitted_values)) {
        return false;
    }

    $rule_values = [];
    if (isset($rule['values']) && is_array($rule['values'])) {
        $rule_values = $rule['values'];
    } elseif (isset($rule['value']) && $rule['value'] !== '') {
        $rule_values = [$rule['value']];
    }

    return afb_condition_matches_rule_value($submitted_values[$source], $rule_values);
}

function afb_assignee_tool_resolve_candidate_chain($tool_cfg, $submitted_values) {
    if (!is_array($tool_cfg)) {
        return [];
    }

    $rules = isset($tool_cfg['rules']) && is_array($tool_cfg['rules'])
        ? $tool_cfg['rules']
        : [];
    foreach ($rules as $rule) {
        if (!is_array($rule) || !afb_assignee_rule_matches_submitted_values($rule, $submitted_values)) {
            continue;
        }

        $primary = afb_assignee_tool_normalize_gid($rule['assignee'] ?? '');
        if ($primary === '') {
            continue;
        }

        $fallbacks = afb_assignee_tool_collect_unique_gids($rule['fallback_assignees'] ?? [], $primary);
        return array_merge([$primary], $fallbacks);
    }

    $default_primary = afb_assignee_tool_normalize_gid($tool_cfg['default_assignee'] ?? '');
    if ($default_primary === '') {
        return [];
    }

    $default_fallbacks_raw = [];
    if (isset($tool_cfg['default_fallback_assignees']) && is_array($tool_cfg['default_fallback_assignees'])) {
        $default_fallbacks_raw = $tool_cfg['default_fallback_assignees'];
    } elseif (isset($tool_cfg['fallback_assignees']) && is_array($tool_cfg['fallback_assignees'])) {
        $default_fallbacks_raw = $tool_cfg['fallback_assignees'];
    }

    $default_fallbacks = afb_assignee_tool_collect_unique_gids($default_fallbacks_raw, $default_primary);
    return array_merge([$default_primary], $default_fallbacks);
}

function afb_assignee_ooo_extract_iso($entry, $date_keys, $datetime_keys) {
    if (!is_array($entry)) {
        return null;
    }

    foreach ((array) $date_keys as $key) {
        $raw = sanitize_text_field((string) ($entry[$key] ?? ''));
        if ($raw === '') {
            continue;
        }

        $iso = afb_parse_date_to_iso($raw);
        if (is_string($iso) && $iso !== '') {
            return $iso;
        }
    }

    foreach ((array) $datetime_keys as $key) {
        $raw = sanitize_text_field((string) ($entry[$key] ?? ''));
        if ($raw === '') {
            continue;
        }

        $ts = strtotime($raw);
        if ($ts !== false) {
            return gmdate('Y-m-d', $ts);
        }
    }

    return null;
}

function afb_assignee_user_is_ooo_on_date($pat, $workspace_gid, $user_gid, $date_iso, &$ooo_available = true) {
    static $cache = [];

    $workspace_gid = afb_assignee_tool_normalize_gid($workspace_gid);
    $user_gid = afb_assignee_tool_normalize_gid($user_gid);
    $date_iso = sanitize_text_field((string) $date_iso);
    if ($workspace_gid === '' || $user_gid === '' || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_iso)) {
        $ooo_available = false;
        return false;
    }

    $cache_key = $workspace_gid . '|' . $user_gid . '|' . $date_iso;
    if (isset($cache[$cache_key])) {
        $cached = $cache[$cache_key];
        $ooo_available = !empty($cached['available']);
        return !empty($cached['is_ooo']);
    }

    $url = 'https://app.asana.com/api/1.0/ooo_entries?workspace=' . rawurlencode($workspace_gid)
        . '&user=' . rawurlencode($user_gid)
        . '&start_date=' . rawurlencode($date_iso)
        . '&end_date=' . rawurlencode($date_iso);

    $resp = afb_asana_request('GET', $url, $pat);
    if ((int) ($resp['code'] ?? 0) !== 200) {
        $cache[$cache_key] = [
            'available' => false,
            'is_ooo'    => false,
        ];
        $ooo_available = false;
        return false;
    }

    $body = json_decode($resp['body'] ?? '', true);
    if (!is_array($body)) {
        $cache[$cache_key] = [
            'available' => false,
            'is_ooo'    => false,
        ];
        $ooo_available = false;
        return false;
    }

    $entries = isset($body['data']) && is_array($body['data']) ? $body['data'] : [];
    $is_ooo = false;

    foreach ($entries as $entry) {
        if (!is_array($entry)) {
            continue;
        }

        $start_iso = afb_assignee_ooo_extract_iso($entry, ['start_on', 'start_date'], ['start_at']);
        $end_iso = afb_assignee_ooo_extract_iso($entry, ['end_on', 'end_date'], ['end_at']);

        if ($start_iso === null && $end_iso === null) {
            continue;
        }

        if ($start_iso === null) {
            $start_iso = $end_iso;
        }
        if ($end_iso === null) {
            $end_iso = $start_iso;
        }

        if ($start_iso !== null && $end_iso !== null && $start_iso <= $date_iso && $date_iso <= $end_iso) {
            $is_ooo = true;
            break;
        }
    }

    $cache[$cache_key] = [
        'available' => true,
        'is_ooo'    => $is_ooo,
    ];
    $ooo_available = true;
    return $is_ooo;
}

function afb_resolve_assignee_from_tool_config($tool_cfg, $submitted_values, $pat, $workspace_gid) {
    if (!is_array($tool_cfg)) {
        return null;
    }

    $candidates = afb_assignee_tool_resolve_candidate_chain($tool_cfg, $submitted_values);
    if (empty($candidates)) {
        return null;
    }

    $check_ooo_today = !array_key_exists('check_ooo_today', $tool_cfg) || !empty($tool_cfg['check_ooo_today']);
    if (!$check_ooo_today) {
        return $candidates[0];
    }

    $workspace_gid = afb_assignee_tool_normalize_gid($workspace_gid);
    if ($workspace_gid === '') {
        return $candidates[0];
    }

    $check_date = current_time('Y-m-d');
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $check_date)) {
        return $candidates[0];
    }

    foreach ($candidates as $candidate_gid) {
        $ooo_available = true;
        $is_ooo = afb_assignee_user_is_ooo_on_date($pat, $workspace_gid, $candidate_gid, $check_date, $ooo_available);

        if (!$ooo_available) {
            // User-selected policy: continue with normal assignment when OOO API is unavailable.
            return $candidates[0];
        }

        if (!$is_ooo) {
            return $candidate_gid;
        }
    }

    // User-selected policy: if all candidates are out-of-office, leave task unassigned.
    return null;
}

function afb_get_project_recipient_tool_configs($project_cfg) {
    if (!is_array($project_cfg)) {
        return [];
    }

    $configs = isset($project_cfg['custom_recipient_configs']) && is_array($project_cfg['custom_recipient_configs'])
        ? $project_cfg['custom_recipient_configs']
        : [];
    if (empty($configs)) {
        return [];
    }

    $ordered = [];
    $field_order = isset($project_cfg['field_order']) && is_array($project_cfg['field_order'])
        ? $project_cfg['field_order']
        : [];

    foreach ($field_order as $ordered_id_raw) {
        $ordered_id = sanitize_text_field((string) $ordered_id_raw);
        if ($ordered_id === '' || strpos($ordered_id, 'recipient_') !== 0) {
            continue;
        }

        $cfg = isset($configs[$ordered_id]) && is_array($configs[$ordered_id])
            ? $configs[$ordered_id]
            : [];
        if (empty($cfg)) {
            continue;
        }

        $source_fields_raw = isset($cfg['source_fields']) && is_array($cfg['source_fields'])
            ? $cfg['source_fields']
            : [];
        $source_fields = array_values(array_unique(array_filter(array_map('sanitize_text_field', $source_fields_raw), static function ($val) {
            return $val !== '';
        })));
        $manual_emails = afb_collect_emails_from_raw_value($cfg['manual_emails'] ?? []);

        $mode_raw = sanitize_text_field((string) ($cfg['mode'] ?? 'separate'));
        $mode = in_array($mode_raw, ['separate', 'cc'], true) ? $mode_raw : 'separate';

        $ordered[] = [
            'id'                => $ordered_id,
            'enabled'           => !empty($cfg['enabled']),
            'include_submitter' => !empty($cfg['include_submitter']),
            'mode'              => $mode,
            'source_fields'     => $source_fields,
            'manual_emails'     => $manual_emails,
            'subject_template'  => sanitize_text_field((string) ($cfg['subject_template'] ?? '')),
            'body_template'     => wp_kses_post((string) ($cfg['body_template'] ?? '')),
        ];
    }

    foreach ($configs as $cfg_id => $cfg) {
        $cfg_id = sanitize_text_field((string) $cfg_id);
        if ($cfg_id === '' || strpos($cfg_id, 'recipient_') !== 0 || !is_array($cfg)) {
            continue;
        }

        $already_added = false;
        foreach ($ordered as $entry) {
            if (($entry['id'] ?? '') === $cfg_id) {
                $already_added = true;
                break;
            }
        }
        if ($already_added) {
            continue;
        }

        $source_fields_raw = isset($cfg['source_fields']) && is_array($cfg['source_fields'])
            ? $cfg['source_fields']
            : [];
        $source_fields = array_values(array_unique(array_filter(array_map('sanitize_text_field', $source_fields_raw), static function ($val) {
            return $val !== '';
        })));
        $manual_emails = afb_collect_emails_from_raw_value($cfg['manual_emails'] ?? []);

        $mode_raw = sanitize_text_field((string) ($cfg['mode'] ?? 'separate'));
        $mode = in_array($mode_raw, ['separate', 'cc'], true) ? $mode_raw : 'separate';

        $ordered[] = [
            'id'                => $cfg_id,
            'enabled'           => !empty($cfg['enabled']),
            'include_submitter' => !empty($cfg['include_submitter']),
            'mode'              => $mode,
            'source_fields'     => $source_fields,
            'manual_emails'     => $manual_emails,
            'subject_template'  => sanitize_text_field((string) ($cfg['subject_template'] ?? '')),
            'body_template'     => wp_kses_post((string) ($cfg['body_template'] ?? '')),
        ];
    }

    return $ordered;
}

function afb_collect_emails_from_raw_value($raw_value) {
    $out = [];

    $walk = static function ($value) use (&$walk, &$out) {
        if (is_array($value)) {
            foreach ($value as $nested) {
                $walk($nested);
            }
            return;
        }

        if (!is_scalar($value)) {
            return;
        }

        $text = trim((string) $value);
        if ($text === '') {
            return;
        }

        if (preg_match_all('/[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}/i', $text, $matches) && !empty($matches[0])) {
            foreach ($matches[0] as $candidate) {
                $email = sanitize_email((string) $candidate);
                if (is_email($email)) {
                    $out[] = $email;
                }
            }
            return;
        }

        $email = sanitize_email($text);
        if (is_email($email)) {
            $out[] = $email;
        }
    };

    $walk($raw_value);

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

function afb_find_project_access_context($project_gid, $accounts) {
    $project_gid = trim((string) $project_gid);
    if ($project_gid === '' || !is_array($accounts)) {
        return null;
    }

    foreach ($accounts as $acct) {
        $pat = afb_decrypt($acct['pat'] ?? '');
        if ($pat === '') {
            continue;
        }

        $portfolio_gid = isset($acct['portfolio_gid']) ? (string) $acct['portfolio_gid'] : '';
        $projects = afb_get_all_projects($pat, $portfolio_gid);
        foreach ((array) $projects as $project) {
            $gid = (string) ($project['gid'] ?? '');
            if ($gid !== $project_gid) {
                continue;
            }

            return [
                'pat'          => $pat,
                'account_id'   => (int) ($acct['id'] ?? 0),
                'project_name' => (string) ($project['name'] ?? ''),
            ];
        }
    }

    return null;
}

function afb_collect_project_field_meta($project_id, $pat) {
    $meta = [
        'field_gids'          => [],
        'field_types'         => [],
        'enum_option_labels'  => [],
        'job_number_gid'      => '',
    ];

    $project_id = trim((string) $project_id);
    $pat = trim((string) $pat);
    if ($project_id === '' || $pat === '') {
        return $meta;
    }

    $job_number_gid_by_name = '';
    $job_number_gid_candidates = [
        '1211338377069778', // Job Number (COPFM) - current
    ];

    try {
        $project_field_settings = (array) afb_get_project_fields($project_id, $pat);
        foreach ($project_field_settings as $setting) {
            $gid = isset($setting['custom_field']['gid']) ? (string) $setting['custom_field']['gid'] : '';
            if ($gid === '') {
                continue;
            }

            $meta['field_gids'][$gid] = true;

            $field_type = isset($setting['custom_field']['type']) ? strtolower((string) $setting['custom_field']['type']) : '';
            if ($field_type !== '') {
                $meta['field_types'][$gid] = $field_type;
            }

            if (in_array($field_type, ['enum', 'multi_enum'], true)) {
                $enum_options = isset($setting['custom_field']['enum_options']) && is_array($setting['custom_field']['enum_options'])
                    ? $setting['custom_field']['enum_options']
                    : [];

                foreach ($enum_options as $enum_option) {
                    if (!is_array($enum_option)) {
                        continue;
                    }

                    $enum_gid = trim((string) ($enum_option['gid'] ?? ''));
                    if ($enum_gid === '') {
                        continue;
                    }

                    $meta['enum_option_labels'][$gid][$enum_gid] = trim((string) ($enum_option['name'] ?? $enum_gid));
                }
            }

            $field_name = isset($setting['custom_field']['name']) ? strtolower(trim((string) $setting['custom_field']['name'])) : '';
            if ($field_name !== '' && strpos($field_name, 'job number') !== false && $job_number_gid_by_name === '') {
                $job_number_gid_by_name = $gid;
            }
        }
    } catch (Throwable $e) {
        // Non-fatal for metadata helper.
    }

    foreach ($job_number_gid_candidates as $candidate_gid) {
        if (!empty($meta['field_gids'][$candidate_gid])) {
            $meta['job_number_gid'] = $candidate_gid;
            break;
        }
    }

    if ($meta['job_number_gid'] === '' && $job_number_gid_by_name !== '') {
        $meta['job_number_gid'] = $job_number_gid_by_name;
    }

    return $meta;
}

function afb_create_task_with_retries($pat, $task_payload) {
    $result = [
        'response' => [],
        'payload'  => is_array($task_payload) ? $task_payload : [],
    ];

    if (!is_array($task_payload)) {
        return $result;
    }

    $response = afb_asana_request(
        'POST',
        'https://app.asana.com/api/1.0/tasks',
        $pat,
        ['json' => $task_payload]
    );

    // Retry once without html_notes if rich notes are rejected.
    if (($response['code'] ?? 0) === 400 && !empty($task_payload['html_notes'])) {
        unset($task_payload['html_notes']);
        $response = afb_asana_request(
            'POST',
            'https://app.asana.com/api/1.0/tasks',
            $pat,
            ['json' => $task_payload]
        );
    }

    // Strip any custom fields that are not attached to the destination project and retry.
    $retry_guard = 0;
    while (($response['code'] ?? 0) === 400 && $retry_guard < 3 && !empty($task_payload['custom_fields']) && is_array($task_payload['custom_fields'])) {
        $retry_guard++;
        $body = isset($response['body']) ? (string) $response['body'] : '';
        preg_match_all('/Custom field with ID\s+(\d+)\s+is not on given object/i', $body, $matches);
        $bad_ids = array_values(array_unique($matches[1] ?? []));
        if (!$bad_ids) {
            break;
        }

        foreach ($bad_ids as $bad_gid) {
            unset($task_payload['custom_fields'][(string) $bad_gid]);
        }

        $response = afb_asana_request(
            'POST',
            'https://app.asana.com/api/1.0/tasks',
            $pat,
            ['json' => $task_payload]
        );
    }

    $result['response'] = $response;
    $result['payload'] = $task_payload;
    return $result;
}

function afb_repeater_value_to_string($value) {
    if (is_bool($value)) {
        return $value ? 'Yes' : '';
    }

    if (is_array($value)) {
        $parts = [];
        foreach ($value as $entry) {
            $flat = afb_repeater_value_to_string($entry);
            if ($flat !== '') {
                $parts[] = $flat;
            }
        }

        $parts = array_values(array_unique(array_filter($parts, static function ($part) {
            return $part !== '';
        })));

        return implode(', ', $parts);
    }

    if (!is_scalar($value)) {
        return '';
    }

    return sanitize_text_field((string) $value);
}

function afb_repeater_sanitize_custom_field_value($value) {
    if (is_array($value)) {
        $out = [];
        foreach ($value as $entry) {
            if (is_int($entry) || is_float($entry)) {
                $out[] = $entry;
                continue;
            }

            if (is_bool($entry)) {
                if ($entry) {
                    $out[] = '1';
                }
                continue;
            }

            if (!is_scalar($entry)) {
                continue;
            }

            $flat = sanitize_text_field((string) $entry);
            if ($flat === '') {
                continue;
            }

            $out[] = $flat;
        }

        $out = array_values(array_unique($out, SORT_REGULAR));
        return empty($out) ? null : $out;
    }

    if (is_int($value) || is_float($value)) {
        return $value;
    }

    if (is_bool($value)) {
        return $value ? '1' : null;
    }

    if (!is_scalar($value)) {
        return null;
    }

    $flat = sanitize_text_field((string) $value);
    return $flat === '' ? null : $flat;
}

function afb_repeater_normalize_token($raw) {
    $token = strtolower((string) $raw);
    $token = preg_replace('/[^a-z0-9]+/', '', $token);
    return is_string($token) ? $token : '';
}

function afb_repeater_row_find_value($row, $candidate_tokens) {
    $entries = isset($row['entries']) && is_array($row['entries'])
        ? $row['entries']
        : [];
    if (empty($entries)) {
        return '';
    }

    $candidates = [];
    foreach ((array) $candidate_tokens as $candidate) {
        $candidate_norm = afb_repeater_normalize_token($candidate);
        if ($candidate_norm !== '') {
            $candidates[$candidate_norm] = true;
        }
    }
    if (empty($candidates)) {
        return '';
    }

    foreach ($entries as $entry) {
        if (!is_array($entry)) {
            continue;
        }

        $value = sanitize_text_field((string) ($entry['value'] ?? ''));
        if ($value === '') {
            continue;
        }

        $entry_tokens = [
            afb_repeater_normalize_token($entry['key'] ?? ''),
            afb_repeater_normalize_token($entry['label'] ?? ''),
        ];

        foreach ($entry_tokens as $entry_token) {
            if ($entry_token === '') {
                continue;
            }

            foreach (array_keys($candidates) as $candidate_token) {
                if (
                    $entry_token === $candidate_token
                    || strpos($entry_token, $candidate_token) !== false
                    || strpos($candidate_token, $entry_token) !== false
                ) {
                    return $value;
                }
            }
        }
    }

    return '';
}

function afb_repeater_row_to_subtask_name($main_task_name, $row) {
    $row_number = max(1, absint($row['row_number'] ?? 1));

    $full_name = afb_repeater_row_find_value($row, ['full_name', 'fullname', 'person_name', 'personname']);
    $first_name = afb_repeater_row_find_value($row, ['first_name', 'firstname', 'given_name', 'givenname', 'name']);
    $last_name = afb_repeater_row_find_value($row, ['last_name', 'lastname', 'surname', 'family_name', 'familyname']);
    $email = afb_repeater_row_find_value($row, ['work_email', 'email', 'email_address', 'emailaddress']);

    $person_fragment = '';
    if ($full_name !== '') {
        $person_fragment = $full_name;
    } elseif ($first_name !== '' || $last_name !== '') {
        $person_fragment = trim($first_name . ' ' . $last_name);
    } elseif ($email !== '') {
        $person_fragment = $email;
    }

    if ($person_fragment === '') {
        $person_fragment = 'Person ' . $row_number;
    }

    $base_name = afb_sanitize_task_title((string) $main_task_name);
    if ($base_name === '') {
        $base_name = 'Request';
    }

    return afb_sanitize_task_title($base_name . ' - ' . $person_fragment);
}

function afb_repeater_row_to_subtask_payload($main_task_name, $row, $assignee_gid = '', $due_on = null, $project_gid = '', $project_field_types = []) {
    $entries = isset($row['entries']) && is_array($row['entries'])
        ? $row['entries']
        : [];
    $raw_custom_fields = isset($row['custom_fields']) && is_array($row['custom_fields'])
        ? $row['custom_fields']
        : [];
    $field_types = is_array($project_field_types) ? $project_field_types : [];
    $row_number = max(1, absint($row['row_number'] ?? 1));
    $repeater_label = sanitize_text_field((string) ($row['repeater_label'] ?? ''));

    $lines = [];
    foreach ($entries as $entry) {
        if (!is_array($entry)) {
            continue;
        }

        $label = sanitize_text_field((string) ($entry['label'] ?? ''));
        $value = sanitize_text_field((string) ($entry['value'] ?? ''));
        if ($label === '' || $value === '') {
            continue;
        }

        $lines[] = '- ' . $label . ': ' . $value;
    }

    $section_label = $repeater_label !== '' ? $repeater_label : 'Repeater entry';
    $notes_plain = $section_label . ' (Row ' . $row_number . ')';
    if (!empty($lines)) {
        $notes_plain .= "\n" . implode("\n", $lines);
    }

    $payload = [
        'name'  => afb_repeater_row_to_subtask_name($main_task_name, $row),
        'notes' => $notes_plain,
    ];

    $custom_fields = [];
    foreach ($raw_custom_fields as $raw_gid => $raw_value) {
        $cf_gid = sanitize_text_field((string) $raw_gid);
        if ($cf_gid === '' || !ctype_digit($cf_gid)) {
            continue;
        }

        $normalized_value = afb_normalize_value_for_target_field_type($raw_value, $field_types[$cf_gid] ?? '');
        if ($normalized_value === null || $normalized_value === '') {
            continue;
        }

        $custom_fields[$cf_gid] = $normalized_value;
    }

    if (!empty($custom_fields)) {
        $payload['custom_fields'] = $custom_fields;
    }

    $project_gid = sanitize_text_field((string) $project_gid);
    if ($project_gid !== '' && ctype_digit($project_gid)) {
        $payload['projects'] = [$project_gid];
    }

    $assignee_gid = sanitize_text_field((string) $assignee_gid);
    if ($assignee_gid !== '' && ctype_digit($assignee_gid)) {
        $payload['assignee'] = $assignee_gid;
    }

    $due_on = is_string($due_on) ? sanitize_text_field($due_on) : '';
    if ($due_on !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $due_on)) {
        $payload['due_on'] = $due_on;
    }

    return $payload;
}

function afb_normalize_repeater_save_mode($raw_mode) {
    $mode = sanitize_text_field((string) $raw_mode);
    return $mode === 'description' ? 'description' : 'subtasks';
}

function afb_get_project_repeater_save_modes($project_cfg) {
    if (!is_array($project_cfg)) {
        return [];
    }

    $raw_modes = isset($project_cfg['custom_repeater_save_mode']) && is_array($project_cfg['custom_repeater_save_mode'])
        ? $project_cfg['custom_repeater_save_mode']
        : [];

    $out = [];
    foreach ($raw_modes as $repeater_id_raw => $mode_raw) {
        $repeater_id = sanitize_text_field((string) $repeater_id_raw);
        if ($repeater_id === '') {
            continue;
        }

        $out[$repeater_id] = afb_normalize_repeater_save_mode($mode_raw);
    }

    return $out;
}

function afb_get_repeater_row_save_mode($row, $save_mode_map = []) {
    $repeater_id = sanitize_text_field((string) ($row['repeater_id'] ?? ''));
    if ($repeater_id !== '' && isset($save_mode_map[$repeater_id])) {
        return afb_normalize_repeater_save_mode($save_mode_map[$repeater_id]);
    }

    return afb_normalize_repeater_save_mode($row['save_mode'] ?? 'subtasks');
}

function afb_repeater_rows_to_description_sections($rows) {
    if (!is_array($rows) || empty($rows)) {
        return [];
    }

    $sections = [];

    foreach ($rows as $row) {
        if (!is_array($row)) {
            continue;
        }

        $entries = isset($row['entries']) && is_array($row['entries'])
            ? $row['entries']
            : [];
        if (empty($entries)) {
            continue;
        }

        $section_label = sanitize_text_field((string) ($row['repeater_label'] ?? ''));
        if ($section_label === '') {
            $section_label = 'Repeater entries';
        }
        $section_key = sanitize_text_field((string) ($row['repeater_id'] ?? ''));
        if ($section_key === '') {
            $section_key = $section_label;
        }

        $row_number = max(1, absint($row['row_number'] ?? 1));

        $parts_plain = [];
        $parts_html = [];
        foreach ($entries as $entry) {
            if (!is_array($entry)) {
                continue;
            }

            $label = sanitize_text_field((string) ($entry['label'] ?? ''));
            $value = sanitize_text_field((string) ($entry['value'] ?? ''));
            if ($label === '' || $value === '') {
                continue;
            }

            $parts_plain[] = $label . ': ' . $value;
            $parts_html[] = '<strong>' . esc_html($label) . ':</strong> ' . esc_html($value);
        }

        if (empty($parts_plain)) {
            continue;
        }

        if (!isset($sections[$section_key])) {
            $sections[$section_key] = [
                'label' => $section_label,
                'plain' => [],
                'html' => [],
            ];
        }

        $sections[$section_key]['plain'][] = '- Row ' . $row_number . ': ' . implode(' | ', $parts_plain);
        $sections[$section_key]['html'][] = '<li><strong>Row ' . $row_number . ':</strong> ' . implode('; ', $parts_html) . '</li>';
    }

    return array_values($sections);
}

function afb_parse_repeater_rows_from_request($raw_rows) {
    if (is_string($raw_rows)) {
        $decoded_rows = json_decode(wp_unslash($raw_rows), true);
        if (json_last_error() === JSON_ERROR_NONE) {
            $raw_rows = $decoded_rows;
        }
    }

    if (!is_array($raw_rows)) {
        return [];
    }

    $rows = [];
    $max_rows = 250;

    foreach ($raw_rows as $row_index => $raw_row) {
        if (!is_array($raw_row)) {
            continue;
        }

        $repeater_id = sanitize_text_field((string) ($raw_row['repeater_id'] ?? ''));
        $repeater_label = sanitize_text_field((string) ($raw_row['repeater_label'] ?? ''));
        $save_mode = afb_normalize_repeater_save_mode($raw_row['save_mode'] ?? 'subtasks');
        $row_number = absint($raw_row['row_number'] ?? ($row_index + 1));
        if ($row_number <= 0) {
            $row_number = $row_index + 1;
        }

        $entries = [];
        $entry_keys = [];
        $legacy_custom_fields = [];

        $display_map = isset($raw_row['display']) && is_array($raw_row['display'])
            ? $raw_row['display']
            : [];
        foreach ($display_map as $raw_label => $raw_value) {
            $label = sanitize_text_field((string) $raw_label);
            $value = afb_repeater_value_to_string($raw_value);
            if ($label === '' || $value === '') {
                continue;
            }

            if (ctype_digit($label)) {
                $legacy_custom_value = afb_repeater_sanitize_custom_field_value($raw_value);
                if ($legacy_custom_value !== null && $legacy_custom_value !== '') {
                    $legacy_custom_fields[$label] = $legacy_custom_value;
                }
            }

            $entry_key = sanitize_key(str_replace(' ', '_', strtolower($label)));
            if ($entry_key === '') {
                $entry_key = 'value_' . (string) (count($entries) + 1);
            }
            if (isset($entry_keys[$entry_key])) {
                continue;
            }

            $entry_keys[$entry_key] = true;
            $entries[] = [
                'key'   => $entry_key,
                'label' => $label,
                'value' => $value,
            ];
        }

        $values_map = isset($raw_row['values']) && is_array($raw_row['values'])
            ? $raw_row['values']
            : [];
        foreach ($values_map as $raw_key => $raw_value) {
            $raw_key_string = sanitize_text_field((string) $raw_key);
            if ($raw_key_string !== '' && ctype_digit($raw_key_string)) {
                $legacy_custom_value = afb_repeater_sanitize_custom_field_value($raw_value);
                if ($legacy_custom_value !== null && $legacy_custom_value !== '') {
                    $legacy_custom_fields[$raw_key_string] = $legacy_custom_value;
                }
            }

            $entry_key = sanitize_key((string) $raw_key);
            $value = afb_repeater_value_to_string($raw_value);
            if ($entry_key === '' || $value === '' || isset($entry_keys[$entry_key])) {
                continue;
            }

            $entry_keys[$entry_key] = true;
            $entries[] = [
                'key'   => $entry_key,
                'label' => ucwords(str_replace(['_', '-'], ' ', $entry_key)),
                'value' => $value,
            ];

            if (count($entries) >= 50) {
                break;
            }
        }

        $custom_fields_raw = isset($raw_row['custom_fields']) && is_array($raw_row['custom_fields'])
            ? $raw_row['custom_fields']
            : [];

        $custom_fields = $legacy_custom_fields;
        foreach ($custom_fields_raw as $raw_gid => $raw_value) {
            $cf_gid = sanitize_text_field((string) $raw_gid);
            if ($cf_gid === '' || !ctype_digit($cf_gid)) {
                continue;
            }

            $normalized_value = afb_repeater_sanitize_custom_field_value($raw_value);
            if ($normalized_value === null || $normalized_value === '') {
                continue;
            }

            $custom_fields[$cf_gid] = $normalized_value;
        }

        if (empty($entries) && empty($custom_fields)) {
            continue;
        }

        $row_payload = [
            'repeater_id'    => $repeater_id,
            'repeater_label' => $repeater_label,
            'save_mode'      => $save_mode,
            'row_number'     => $row_number,
            'entries'        => $entries,
        ];

        if (!empty($custom_fields)) {
            $row_payload['custom_fields'] = $custom_fields;
        }

        $rows[] = $row_payload;

        if (count($rows) >= $max_rows) {
            break;
        }
    }

    return $rows;
}

function afb_create_subtask_with_retries($pat, $parent_task_gid, $subtask_payload) {
    $parent_task_gid = sanitize_text_field((string) $parent_task_gid);
    $result = [
        'response' => [],
        'payload'  => is_array($subtask_payload) ? $subtask_payload : [],
    ];

    if (!ctype_digit($parent_task_gid) || !is_array($subtask_payload)) {
        return $result;
    }

    $endpoint = 'https://app.asana.com/api/1.0/tasks/' . rawurlencode($parent_task_gid) . '/subtasks';
    $response = afb_asana_request(
        'POST',
        $endpoint,
        $pat,
        ['json' => $subtask_payload]
    );

    if (($response['code'] ?? 0) === 400 && !empty($subtask_payload['html_notes'])) {
        unset($subtask_payload['html_notes']);
        $response = afb_asana_request(
            'POST',
            $endpoint,
            $pat,
            ['json' => $subtask_payload]
        );
    }

    $retry_guard = 0;
    while (($response['code'] ?? 0) === 400 && $retry_guard < 3 && !empty($subtask_payload['custom_fields']) && is_array($subtask_payload['custom_fields'])) {
        $retry_guard++;
        $body = isset($response['body']) ? (string) $response['body'] : '';
        preg_match_all('/Custom field with ID\s+(\d+)\s+is not on given object/i', $body, $matches);
        $bad_ids = array_values(array_unique($matches[1] ?? []));
        if (!$bad_ids) {
            break;
        }

        foreach ($bad_ids as $bad_gid) {
            unset($subtask_payload['custom_fields'][(string) $bad_gid]);
        }

        $response = afb_asana_request(
            'POST',
            $endpoint,
            $pat,
            ['json' => $subtask_payload]
        );
    }

    if (($response['code'] ?? 0) === 400 && !empty($subtask_payload['projects'])) {
        $body = isset($response['body']) ? (string) $response['body'] : '';
        if (preg_match('/\bprojects?\b.*(invalid|unknown|not\s+allowed|cannot|unsupported)|\b(unknown|invalid)\b.*\bprojects?\b/i', $body)) {
            unset($subtask_payload['projects']);
            $response = afb_asana_request(
                'POST',
                $endpoint,
                $pat,
                ['json' => $subtask_payload]
            );
        }
    }

    $result['response'] = $response;
    $result['payload'] = $subtask_payload;
    return $result;
}

function afb_link_task_dependency_with_available_tokens($dependent_task_gid, $dependency_task_gid, $candidate_tokens = []) {
    $dependent_task_gid = sanitize_text_field((string) $dependent_task_gid);
    $dependency_task_gid = sanitize_text_field((string) $dependency_task_gid);

    $result = [
        'ok' => false,
        'status' => 'invalid',
        'http' => 0,
        'body' => '',
    ];

    if (!ctype_digit($dependent_task_gid) || !ctype_digit($dependency_task_gid)) {
        return $result;
    }

    $tokens = [];
    foreach ((array) $candidate_tokens as $candidate_token) {
        $candidate_token = trim((string) $candidate_token);
        if ($candidate_token === '' || in_array($candidate_token, $tokens, true)) {
            continue;
        }

        $tokens[] = $candidate_token;
    }

    if (empty($tokens)) {
        $result['status'] = 'no_token';
        return $result;
    }

    $max_attempts_per_token = 4; // Initial request + up to 3 short retries.
    $retry_delay_micros = 350000;
    $dependency_endpoint = 'https://app.asana.com/api/1.0/tasks/' . rawurlencode($dependent_task_gid) . '/addDependencies';

    foreach ($tokens as $candidate_token) {
        for ($attempt = 1; $attempt <= $max_attempts_per_token; $attempt++) {
            $response = afb_asana_request(
                'POST',
                $dependency_endpoint,
                $candidate_token,
                ['json' => ['dependencies' => [$dependency_task_gid]]]
            );

            $http_code = (int) ($response['code'] ?? 0);
            $response_body = (string) ($response['body'] ?? '');

            if (in_array($http_code, [200, 201], true)) {
                return [
                    'ok' => true,
                    'status' => 'linked',
                    'http' => $http_code,
                    'body' => $response_body,
                ];
            }

            if ($http_code === 400 && preg_match('/already.*dependenc|dependenc.*already/i', $response_body)) {
                return [
                    'ok' => true,
                    'status' => 'already_linked',
                    'http' => $http_code,
                    'body' => $response_body,
                ];
            }

            $result = [
                'ok' => false,
                'status' => 'failed',
                'http' => $http_code,
                'body' => $response_body,
            ];

            // Newly-created tasks can briefly 404 in Asana; retry the same token a few times first.
            $looks_like_fresh_task_race = $http_code === 404 && (
                preg_match('/Not a recognized ID:\s*' . preg_quote($dependent_task_gid, '/') . '\b/i', $response_body)
                || preg_match('/Could not find object with ID\s*' . preg_quote($dependent_task_gid, '/') . '\b/i', $response_body)
            );

            if ($looks_like_fresh_task_race && $attempt < $max_attempts_per_token) {
                usleep($retry_delay_micros);
                continue;
            }

            // Auth/access errors can still succeed with another token, so keep trying tokens.
            if (!in_array($http_code, [401, 403, 404], true)) {
                return $result;
            }

            break;
        }
    }

    return $result;
}

function afb_normalize_value_for_target_field_type($value, $target_type) {
    $type = strtolower(trim((string) $target_type));

    if ($type === 'number') {
        if (is_int($value) || is_float($value)) {
            return $value;
        }

        if (is_string($value) && is_numeric(trim($value))) {
            $raw = trim($value);
            return (strpos($raw, '.') !== false) ? (float) $raw : (int) $raw;
        }

        return null;
    }

    if ($type === 'date') {
        if (is_array($value) && isset($value['date']) && preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $value['date'])) {
            return ['date' => (string) $value['date']];
        }

        $flat = afb_task_name_value_to_string($value);
        if ($flat === '') {
            return null;
        }

        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $flat)) {
            return ['date' => $flat];
        }

        $iso = afb_parse_date_to_iso($flat);
        if ($iso !== null && $iso !== '') {
            return ['date' => $iso];
        }

        return null;
    }

    if ($type === 'multi_enum') {
        $values = is_array($value) ? $value : [$value];
        $out = [];
        foreach ($values as $entry) {
            $entry = sanitize_text_field((string) $entry);
            if ($entry === '') {
                continue;
            }
            $out[] = $entry;
        }

        $out = array_values(array_unique($out));
        return empty($out) ? null : $out;
    }

    if ($type === 'enum') {
        if (is_array($value)) {
            foreach ($value as $entry) {
                $entry = sanitize_text_field((string) $entry);
                if ($entry !== '') {
                    return $entry;
                }
            }
            return null;
        }

        $val = sanitize_text_field((string) $value);
        return $val === '' ? null : $val;
    }

    if (is_array($value)) {
        $flattened = [];
        foreach ($value as $entry) {
            $flat = afb_task_name_value_to_string($entry);
            if ($flat !== '') {
                $flattened[] = $flat;
            }
        }

        $flattened = array_values(array_unique(array_filter($flattened, static function ($entry) {
            return $entry !== '';
        })));

        if (empty($flattened)) {
            return null;
        }

        return sanitize_text_field(implode(', ', $flattened));
    }

    $scalar = sanitize_text_field((string) $value);
    return $scalar === '' ? null : $scalar;
}

function afb_send_json_success_clean($data) {
    $stray_output = '';
    while (ob_get_level() > 0) {
        $stray_output .= (string) ob_get_clean();
    }

    if (trim($stray_output) !== '' && function_exists('afb_log')) {
        afb_log('[DEBUG] stripped pre-json output (success): ' . substr(trim(preg_replace('/\s+/', ' ', wp_strip_all_tags($stray_output))), 0, 800), 'afb-debug.log');
    }

    wp_send_json_success($data);
}

function afb_send_json_error_clean($data, $status_code = null) {
    $stray_output = '';
    while (ob_get_level() > 0) {
        $stray_output .= (string) ob_get_clean();
    }

    if (trim($stray_output) !== '' && function_exists('afb_log')) {
        afb_log('[DEBUG] stripped pre-json output (error): ' . substr(trim(preg_replace('/\s+/', ' ', wp_strip_all_tags($stray_output))), 0, 800), 'afb-debug.log');
    }

    if ($status_code === null) {
        wp_send_json_error($data);
    }

    wp_send_json_error($data, (int) $status_code);
}

function afb_api_create_task_fast() {
    if (ob_get_level() === 0) {
        ob_start();
    }

    $request_id = isset($_POST['request_id'])
        ? sanitize_text_field($_POST['request_id'])
        : uniqid('srv_', true);

    // Idempotency – reuse if already created for this request_id
    if ($existing = afb_idem_lookup($request_id)) {
        afb_send_json_success_clean([
            'request_id' => $request_id,
            'task_gid'   => $existing,
            'phase'      => 'upload',
            'reused'     => true,
        ]);
    }

    $project_id    = sanitize_text_field($_POST['project_id'] ?? '');
    $task_name_raw = sanitize_text_field($_POST['task_name'] ?? '');
    $task_name     = afb_sanitize_task_title($task_name_raw);

    // ─────────────────────────────────────────────────────────────
    // triage_state / answers (single source of truth for context)
    // ─────────────────────────────────────────────────────────────
    $triage_state   = [];
    $triage_answers = [];

    // 1) Full triage_state JSON from the form (preferred)
    if (!empty($_POST['triage_state']) && is_string($_POST['triage_state'])) {
        $ts = json_decode(stripslashes($_POST['triage_state']), true);
        if (json_last_error() === JSON_ERROR_NONE && is_array($ts)) {
            $triage_state = $ts;
            if (!empty($ts['answers']) && is_array($ts['answers'])) {
                $triage_answers = $ts['answers'];
            }
        }
    }

    // 2) Or a direct triage_answers JSON payload
    if (empty($triage_answers) && !empty($_POST['triage_answers'])) {
        $ta = is_string($_POST['triage_answers'])
            ? json_decode(stripslashes($_POST['triage_answers']), true)
            : $_POST['triage_answers'];

        if (json_last_error() === JSON_ERROR_NONE && is_array($ta)) {
            $triage_answers = $ta;
        }
    }

    if (!$project_id) {
        afb_send_json_error_clean([
            'message'    => 'Missing project_id',
            'request_id' => $request_id
        ]);
    }

    $all_project_cfg = function_exists('afb_get_project_field_config')
        ? afb_get_project_field_config()
        : get_option('afb_project_field_config', []);
    $project_cfg = (is_array($all_project_cfg) && isset($all_project_cfg[$project_id]) && is_array($all_project_cfg[$project_id]))
        ? $all_project_cfg[$project_id]
        : [];
    $private_data_companion_enabled = !empty($project_cfg['private_data_companion_enabled']);
    $private_companion_depends_on_main = !empty($project_cfg['private_companion_depends_on_main']);
    $private_data_project_gid = sanitize_text_field((string) ($project_cfg['private_data_project_gid'] ?? ''));
    $private_companion_upload_mode = sanitize_text_field((string) ($project_cfg['private_companion_upload_mode'] ?? 'main_only'));
    if (!in_array($private_companion_upload_mode, ['main_only', 'copy_to_companion', 'companion_only'], true)) {
        $private_companion_upload_mode = 'main_only';
    }
    $private_companion_upload_modes_by_field = [];
    if (!empty($project_cfg['custom_upload_private_modes']) && is_array($project_cfg['custom_upload_private_modes'])) {
        foreach ($project_cfg['custom_upload_private_modes'] as $upload_id_raw => $upload_mode_raw) {
            $upload_id = sanitize_text_field((string) $upload_id_raw);
            if ($upload_id === '' || strpos($upload_id, 'upload_') !== 0) {
                continue;
            }

            $upload_mode = sanitize_text_field((string) $upload_mode_raw);
            if (!in_array($upload_mode, ['main_only', 'copy_to_companion', 'companion_only'], true)) {
                continue;
            }

            $private_companion_upload_modes_by_field[$upload_id] = $upload_mode;
        }
    }
    $upload_accept_types_by_field = [];
    if (!empty($project_cfg['custom_upload_accept_types']) && is_array($project_cfg['custom_upload_accept_types'])) {
        foreach ($project_cfg['custom_upload_accept_types'] as $upload_id_raw => $upload_accept_raw) {
            $upload_id = sanitize_text_field((string) $upload_id_raw);
            if ($upload_id === '' || strpos($upload_id, 'upload_') !== 0) {
                continue;
            }

            $accept_parts = preg_split('/\s*,\s*/', strtolower((string) $upload_accept_raw));
            if (!is_array($accept_parts)) {
                continue;
            }

            $accept_tokens = [];
            foreach ($accept_parts as $accept_part) {
                $accept_token = trim((string) $accept_part);
                if ($accept_token === '') {
                    continue;
                }

                $accept_token = preg_replace('/\s+/', '', $accept_token);
                if ($accept_token === '') {
                    continue;
                }

                if (strpos($accept_token, '.') === 0) {
                    $ext = preg_replace('/[^a-z0-9]/', '', substr($accept_token, 1));
                    if ($ext === '') {
                        continue;
                    }

                    $accept_token = '.' . $ext;
                } elseif (preg_match('/^[a-z0-9!#$&^_.+-]+\/(?:[a-z0-9!#$&^_.+-]+|\*)$/', $accept_token)) {
                    // MIME token is valid as-is.
                } elseif (preg_match('/^[a-z0-9]{1,16}$/', $accept_token)) {
                    $accept_token = '.' . $accept_token;
                } else {
                    continue;
                }

                $accept_tokens[$accept_token] = true;
            }

            if (!empty($accept_tokens)) {
                $upload_accept_types_by_field[$upload_id] = implode(',', array_keys($accept_tokens));
            }
        }
    }
    $upload_max_file_mb_by_field = [];
    if (!empty($project_cfg['custom_upload_max_file_mb']) && is_array($project_cfg['custom_upload_max_file_mb'])) {
        foreach ($project_cfg['custom_upload_max_file_mb'] as $upload_id_raw => $max_file_mb_raw) {
            $upload_id = sanitize_text_field((string) $upload_id_raw);
            if ($upload_id === '' || strpos($upload_id, 'upload_') !== 0) {
                continue;
            }

            $max_file_mb = (float) $max_file_mb_raw;
            if ($max_file_mb <= 0) {
                continue;
            }

            $upload_max_file_mb_by_field[$upload_id] = min(512, round($max_file_mb, 2));
        }
    }
    $private_companion_field_targets = [];
    if (!empty($project_cfg['private_companion_field_targets']) && is_array($project_cfg['private_companion_field_targets'])) {
        foreach ($project_cfg['private_companion_field_targets'] as $source_id => $target_gid_raw) {
            $source_id = sanitize_text_field((string) $source_id);
            $target_gid = sanitize_text_field((string) $target_gid_raw);
            if ($source_id === '' || $target_gid === '' || !ctype_digit($target_gid)) {
                continue;
            }

            $private_companion_field_targets[$source_id] = $target_gid;
        }
    }
    if (!ctype_digit($private_data_project_gid) || $private_data_project_gid === $project_id) {
        $private_data_project_gid = '';
    }
    if (!$private_data_companion_enabled) {
        $private_data_project_gid = '';
    }
    if (!$private_data_companion_enabled || $private_data_project_gid === '') {
        $private_companion_upload_mode = 'main_only';
        $private_companion_upload_modes_by_field = [];
        $private_companion_depends_on_main = false;
    }
    $private_companion_only_source_ids = [];
    $private_companion_only_primary_field_gids = [];
    if ($private_data_companion_enabled && !empty($private_companion_field_targets)) {
        $text_sync_map = isset($project_cfg['custom_text_sync_fields']) && is_array($project_cfg['custom_text_sync_fields'])
            ? $project_cfg['custom_text_sync_fields']
            : [];
        $single_sync_map = isset($project_cfg['custom_single_input_sync_fields']) && is_array($project_cfg['custom_single_input_sync_fields'])
            ? $project_cfg['custom_single_input_sync_fields']
            : [];
        $dropdown_sync_map = isset($project_cfg['custom_dropdown_sync_fields']) && is_array($project_cfg['custom_dropdown_sync_fields'])
            ? $project_cfg['custom_dropdown_sync_fields']
            : [];
        $calendar_sync_map = isset($project_cfg['custom_calendar_sync_fields']) && is_array($project_cfg['custom_calendar_sync_fields'])
            ? $project_cfg['custom_calendar_sync_fields']
            : [];
        $calendar_sync_modes = isset($project_cfg['custom_calendar_sync_modes']) && is_array($project_cfg['custom_calendar_sync_modes'])
            ? $project_cfg['custom_calendar_sync_modes']
            : [];

        foreach ($private_companion_field_targets as $source_id => $_target_gid) {
            $source_id = sanitize_text_field((string) $source_id);
            if ($source_id === '') {
                continue;
            }

            $private_companion_only_source_ids[$source_id] = true;

            if (ctype_digit($source_id)) {
                $private_companion_only_primary_field_gids[$source_id] = true;
            }

            $text_sync_gid = sanitize_text_field((string) ($text_sync_map[$source_id] ?? ''));
            if ($text_sync_gid !== '' && ctype_digit($text_sync_gid)) {
                $private_companion_only_primary_field_gids[$text_sync_gid] = true;
            }

            $single_sync_gid = sanitize_text_field((string) ($single_sync_map[$source_id] ?? ''));
            if ($single_sync_gid !== '' && ctype_digit($single_sync_gid)) {
                $private_companion_only_primary_field_gids[$single_sync_gid] = true;
            }

            $dropdown_sync_gid = sanitize_text_field((string) ($dropdown_sync_map[$source_id] ?? ''));
            if ($dropdown_sync_gid !== '' && ctype_digit($dropdown_sync_gid)) {
                $private_companion_only_primary_field_gids[$dropdown_sync_gid] = true;
            }

            $calendar_sync_gid = sanitize_text_field((string) ($calendar_sync_map[$source_id] ?? ''));
            $calendar_sync_mode = sanitize_text_field((string) ($calendar_sync_modes[$source_id] ?? ''));
            if ($calendar_sync_gid !== '' && ctype_digit($calendar_sync_gid) && ($calendar_sync_mode === '' || $calendar_sync_mode === 'text')) {
                $private_companion_only_primary_field_gids[$calendar_sync_gid] = true;
            }
        }
    }
    $agreement_description_fields = isset($project_cfg['agreement_description_fields']) && is_array($project_cfg['agreement_description_fields'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', $project_cfg['agreement_description_fields']), static function ($value) {
            return $value !== '';
        })))
        : [];
    $task_name_template = afb_get_project_task_name_template($project_cfg);

    // ─────────────────────────────────────────────────────────────
    // Resolve PAT for the target project
    // ─────────────────────────────────────────────────────────────
    global $wpdb;
    $pat           = '';
    $matched_account_id = 0;
    $account_table = $wpdb->prefix . 'asana_accounts';
    $accounts      = (array) $wpdb->get_results("SELECT * FROM $account_table", ARRAY_A);

    $source_project_ctx = afb_find_project_access_context($project_id, $accounts);
    if (is_array($source_project_ctx)) {
        $pat = (string) ($source_project_ctx['pat'] ?? '');
        $matched_account_id = (int) ($source_project_ctx['account_id'] ?? 0);
    }

    if (!$pat) {
        afb_send_json_error_clean([
            'message'    => 'Asana account not found for project',
            'request_id' => $request_id
        ]);
    }

    // ─────────────────────────────────────────────────────────────
    // Collect / normalize custom_fields from POST
    // ─────────────────────────────────────────────────────────────
    $custom_fields = $_POST['custom_fields'] ?? [];
    if (is_string($custom_fields)) {
        $decoded = json_decode(stripslashes($custom_fields), true);
        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
            $custom_fields = $decoded;
        }
    }
    if (!is_array($custom_fields)) {
        $custom_fields = [];
    }

    // Sanitize values (scalar or array; arrays imply multi_enum)
    foreach ($custom_fields as $k => $v) {
        if (is_array($v)) {
            $custom_fields[$k] = array_map('sanitize_text_field', $v);
        } else {
            $custom_fields[$k] = sanitize_text_field($v);
        }
    }

    // DEBUG: snapshot of inputs / CF keys
    if (function_exists('afb_log')) {
        try {
            afb_log(
                '[DEBUG] POST snapshot → ' . json_encode(
                    [
                        'request_id' => $request_id,
                        'project_id' => $project_id,
                        'task_name'  => $task_name,
                        'subservice' => ($_POST['subservice'] ?? null),
                        'date'       => ($_POST['date'] ?? null),
                    ],
                    JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
                ),
                'afb-debug.log'
            );
            afb_log(
                '[DEBUG] custom_fields pre-map keys → ' . implode(',', array_keys($custom_fields)),
                'afb-debug.log'
            );
        } catch (Throwable $e) {
            afb_log('[DEBUG] snapshot failed: ' . $e->getMessage(), 'afb-debug.log');
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Job Number: resolve per-project GID (supports legacy and current COPFM IDs)
    // ─────────────────────────────────────────────────────────────
    $project_field_meta = afb_collect_project_field_meta($project_id, $pat);
    $job_number_gid = (string) ($project_field_meta['job_number_gid'] ?? '');
    $project_field_gids = is_array($project_field_meta['field_gids'] ?? null)
        ? $project_field_meta['field_gids']
        : [];
    $project_field_types = is_array($project_field_meta['field_types'] ?? null)
        ? $project_field_meta['field_types']
        : [];
    $project_enum_option_labels = is_array($project_field_meta['enum_option_labels'] ?? null)
        ? $project_field_meta['enum_option_labels']
        : [];

    $posted_job_number = isset($_POST['job_number']) ? sanitize_text_field($_POST['job_number']) : '';
    $job_number_supported = ($job_number_gid !== '' && !empty($project_field_gids[$job_number_gid]));

    if ($job_number_supported) {
        if ($posted_job_number) {
            $custom_fields[$job_number_gid] = $posted_job_number;
        } elseif (empty($custom_fields[$job_number_gid])) {
            $prefix_map = get_option('afb_prefix_map', []);
            $prefix     = (!empty($prefix_map[$project_id])) ? $prefix_map[$project_id] : 'TASK';
            $stamp      = date('YmdHis', current_time('timestamp')); // WP tz
            $custom_fields[$job_number_gid] = $prefix . '-' . $stamp;
        }
    } else {
        // Ensure we never send this CF if it's not attached to the selected project.
        if ($job_number_gid !== '') {
            unset($custom_fields[$job_number_gid]);
        }
    }

    // If no explicit task name was provided, use the job number as the task name.
    if ($task_name === '' && !empty($custom_fields[$job_number_gid])) {
        $task_name = afb_sanitize_task_title((string)$custom_fields[$job_number_gid]);
    }

    // ─────────────────────────────────────────────────────────────
    // Normalize payload for Asana typed custom fields.
    // Avoid empty/invalid values (e.g. number_value: "") that cause HTTP 400.
    // ─────────────────────────────────────────────────────────────
    foreach ($custom_fields as $cf_gid => $cf_val) {
        $gid = (string) $cf_gid;
        $type = strtolower((string) ($project_field_types[$gid] ?? ''));

        // Defensive: ignore custom fields not attached to the selected project.
        if (!empty($project_field_gids) && empty($project_field_gids[$gid])) {
            unset($custom_fields[$cf_gid]);
            continue;
        }

        // Multi-value payloads (typically multi_enum)
        if (is_array($cf_val)) {
            $vals = array_values(array_filter(array_map('sanitize_text_field', $cf_val), static function ($v) {
                return $v !== '';
            }));

            if (empty($vals)) {
                unset($custom_fields[$cf_gid]);
                continue;
            }

            $custom_fields[$cf_gid] = $vals;
            continue;
        }

        $val = is_string($cf_val) ? trim($cf_val) : (string) $cf_val;

        if ($val === '') {
            unset($custom_fields[$cf_gid]);
            continue;
        }

        if ($type === 'number') {
            if (!is_numeric($val)) {
                unset($custom_fields[$cf_gid]);
                continue;
            }
            // Keep integers as ints; decimal values as floats.
            $custom_fields[$cf_gid] = (strpos($val, '.') !== false) ? (float) $val : (int) $val;
            continue;
        }

        if ($type === 'date') {
            // Asana DayAndDateTime fields require an object payload.
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $val)) {
                $custom_fields[$cf_gid] = ['date' => $val];
            } else {
                unset($custom_fields[$cf_gid]);
            }
            continue;
        }

        $custom_fields[$cf_gid] = sanitize_text_field($val);
    }

    // ─────────────────────────────────────────────────────────────
    // Triage summary + request details + submitted copy (HTML/plain)
    // ─────────────────────────────────────────────────────────────
    $summary_html_structured = function_exists('afb_generate_structured_triage_summary_html')
        ? afb_generate_structured_triage_summary_html($triage_state, $triage_answers)
        : '';

    $summary_html = $summary_html_structured;

    // “Further Request Details (SCB)” CF
    $request_details_gid  = '1209687341852846';
    $request_details_text = !empty($custom_fields[$request_details_gid])
        ? sanitize_text_field(
            is_array($custom_fields[$request_details_gid])
                ? reset($custom_fields[$request_details_gid])
                : $custom_fields[$request_details_gid]
        )
        : '';

    // Submitted copy from triage session
    $submitted_copy_html  = (string) ($_POST['submitted_copy_html']  ?? '');
    $submitted_copy_plain = sanitize_textarea_field($_POST['submitted_copy_plain'] ?? '');
    // Notes textarea (plain text from your new "Notes" field)
    $notes_user_plain = sanitize_textarea_field($_POST['asana_html_notes'] ?? '');
    $notes_user_html  = $notes_user_plain !== ''
        ? nl2br(esc_html($notes_user_plain))
        : '';

    $description_inputs = [];
    if (isset($_POST['description_inputs'])) {
        $raw_description_inputs = wp_unslash($_POST['description_inputs']);
        $decoded_description_inputs = is_string($raw_description_inputs)
            ? json_decode($raw_description_inputs, true)
            : $raw_description_inputs;

        if (is_array($decoded_description_inputs)) {
            foreach ($decoded_description_inputs as $entry_key => $entry_value) {
                $desc_id = '';
                $desc_label = '';
                $desc_value = '';

                if (is_array($entry_value)) {
                    $desc_id = sanitize_text_field((string) ($entry_value['id'] ?? $entry_key));
                    $desc_label = sanitize_text_field((string) ($entry_value['label'] ?? ''));
                    $desc_value = sanitize_textarea_field((string) ($entry_value['value'] ?? ''));
                } else {
                    $desc_id = sanitize_text_field((string) $entry_key);
                    $desc_value = sanitize_textarea_field((string) $entry_value);
                }

                if ($desc_value === '') {
                    continue;
                }

                if ($desc_label === '') {
                    $fallback_label = $desc_id !== '' ? str_replace(['_', '-'], ' ', $desc_id) : 'Additional detail';
                    $desc_label = ucwords(trim((string) $fallback_label));
                    if ($desc_label === '') {
                        $desc_label = 'Additional detail';
                    }
                }

                $description_inputs[] = [
                    'id'    => $desc_id,
                    'label' => $desc_label,
                    'value' => $desc_value,
                ];
            }
        }
    }

    $description_input_map = [];
    foreach ($description_inputs as $desc_input) {
        $desc_id = trim((string) ($desc_input['id'] ?? ''));
        $desc_value = trim((string) ($desc_input['value'] ?? ''));
        if ($desc_id === '' || $desc_value === '') {
            continue;
        }

        $description_input_map[$desc_id] = $desc_value;
    }

    $single_input_value_map = [];
    if (isset($_POST['task_name_field_values'])) {
        $raw_single_values = wp_unslash($_POST['task_name_field_values']);
        $decoded_single_values = is_string($raw_single_values)
            ? json_decode($raw_single_values, true)
            : $raw_single_values;

        if (is_array($decoded_single_values)) {
            foreach ($decoded_single_values as $field_id => $field_value) {
                $field_id = sanitize_text_field((string) $field_id);
                if ($field_id === '' || strpos($field_id, 'single_') !== 0) {
                    continue;
                }

                $normalized_value = afb_task_name_value_to_string($field_value);
                if ($normalized_value === '') {
                    continue;
                }

                $single_input_value_map[$field_id] = sanitize_textarea_field($normalized_value);
            }
        }
    }

    $single_input_word_rules = afb_get_single_input_max_word_rules($project_cfg);
    foreach ($single_input_word_rules as $single_id => $word_rule) {
        $submitted_value = '';

        if (isset($description_input_map[$single_id])) {
            $submitted_value = (string) $description_input_map[$single_id];
        } elseif (isset($single_input_value_map[$single_id])) {
            $submitted_value = (string) $single_input_value_map[$single_id];
        } else {
            $sync_field = trim((string) ($word_rule['sync_field'] ?? ''));
            if ($sync_field !== '' && isset($custom_fields[$sync_field]) && !is_array($custom_fields[$sync_field])) {
                $submitted_value = (string) $custom_fields[$sync_field];
            }
        }

        if ($submitted_value === '') {
            continue;
        }

        $max_words = max(0, (int) ($word_rule['max_words'] ?? 0));
        if ($max_words <= 0) {
            continue;
        }

        $word_count = afb_count_words_from_text($submitted_value);
        if ($word_count <= $max_words) {
            continue;
        }

        $over_by = $word_count - $max_words;
        $suffix = $over_by === 1 ? '' : 's';
        $field_label = sanitize_text_field((string) ($word_rule['label'] ?? 'Additional detail'));

        afb_send_json_error_clean([
            'message'    => sprintf('"%s" is over the word limit by %d word%s (max %d).', $field_label, $over_by, $suffix, $max_words),
            'request_id' => $request_id,
            'code'       => 'single_input_word_limit_exceeded',
            'field_id'   => $single_id,
        ], 400);
    }

    $agreement_checks = [];
    $raw_agreement_checks = $_POST['agreement_checks'] ?? [];
    if (is_string($raw_agreement_checks)) {
        $decoded_agreement_checks = json_decode(wp_unslash($raw_agreement_checks), true);
        if (is_array($decoded_agreement_checks)) {
            $raw_agreement_checks = $decoded_agreement_checks;
        }
    }
    if (is_array($raw_agreement_checks)) {
        foreach ($raw_agreement_checks as $agreement_id => $agreement_value) {
            $agreement_id = sanitize_text_field((string) $agreement_id);
            if ($agreement_id === '') {
                continue;
            }

            $is_checked = !empty($agreement_value) && (string) $agreement_value !== '0';
            $agreement_checks[$agreement_id] = $is_checked ? '1' : '';
        }
    }

    $submitted_condition_values = [];
    foreach ($custom_fields as $field_id => $field_value) {
        $field_id = sanitize_text_field((string) $field_id);
        if ($field_id === '') {
            continue;
        }

        $submitted_condition_values[$field_id] = $field_value;
    }

    foreach ($description_input_map as $field_id => $field_value) {
        $field_id = sanitize_text_field((string) $field_id);
        if ($field_id === '') {
            continue;
        }

        $submitted_condition_values[$field_id] = sanitize_text_field((string) $field_value);
    }

    foreach ($single_input_value_map as $field_id => $field_value) {
        $field_id = sanitize_text_field((string) $field_id);
        if ($field_id === '') {
            continue;
        }

        $submitted_condition_values[$field_id] = sanitize_text_field((string) $field_value);
    }

    foreach ($agreement_checks as $field_id => $field_value) {
        $field_id = sanitize_text_field((string) $field_id);
        if ($field_id === '') {
            continue;
        }

        $submitted_condition_values[$field_id] = sanitize_text_field((string) $field_value);
    }

    $sync_aliases = afb_task_name_sync_aliases_from_config($project_cfg);
    foreach ($sync_aliases as $source_id => $target_gid) {
        $source_id = sanitize_text_field((string) $source_id);
        $target_gid = sanitize_text_field((string) $target_gid);
        if ($source_id === '' || $target_gid === '') {
            continue;
        }

        if (!isset($submitted_condition_values[$source_id]) && isset($submitted_condition_values[$target_gid])) {
            $submitted_condition_values[$source_id] = $submitted_condition_values[$target_gid];
        }
        if (!isset($submitted_condition_values[$target_gid]) && isset($submitted_condition_values[$source_id])) {
            $submitted_condition_values[$target_gid] = $submitted_condition_values[$source_id];
        }
    }

    $blocked_target = afb_find_submission_blocking_target($project_cfg, $submitted_condition_values);
    if (is_array($blocked_target) && !empty($blocked_target['target_id'])) {
        afb_send_json_error_clean([
            'message'    => sprintf('This request cannot be submitted while "%s" is active.', sanitize_text_field((string) ($blocked_target['title'] ?? 'this field'))),
            'request_id' => $request_id,
            'code'       => 'submission_blocked_by_condition',
            'field_id'   => sanitize_text_field((string) $blocked_target['target_id']),
        ], 400);
    }

    $task_name_field_values = [];
    if (isset($_POST['task_name_field_values'])) {
        $raw_task_name_fields = wp_unslash($_POST['task_name_field_values']);
        $decoded_task_name_fields = is_string($raw_task_name_fields)
            ? json_decode($raw_task_name_fields, true)
            : $raw_task_name_fields;

        if (is_array($decoded_task_name_fields)) {
            foreach ($decoded_task_name_fields as $field_id => $field_value) {
                $field_id = sanitize_text_field((string) $field_id);
                if ($field_id === '') {
                    continue;
                }

                $normalized_value = afb_task_name_value_to_string($field_value);
                if ($normalized_value === '') {
                    continue;
                }

                $task_name_field_values[$field_id] = sanitize_text_field($normalized_value);
            }
        }
    }

    $repeater_rows = afb_parse_repeater_rows_from_request($_POST['repeater_rows'] ?? []);
    $repeater_save_modes = afb_get_project_repeater_save_modes($project_cfg);
    $repeater_subtask_rows = [];
    $repeater_description_rows = [];
    foreach ($repeater_rows as $row) {
        if (!is_array($row)) {
            continue;
        }

        $row_mode = afb_get_repeater_row_save_mode($row, $repeater_save_modes);
        if ($row_mode === 'description') {
            $repeater_description_rows[] = $row;
        } else {
            $repeater_subtask_rows[] = $row;
        }
    }
    $repeater_description_sections = afb_repeater_rows_to_description_sections($repeater_description_rows);

    $agreement_description_plain_lines = [];
    $agreement_description_html_lines = [];
    if (!empty($agreement_description_fields)) {
        $agreement_label_map = isset($project_cfg['custom_agreement_labels']) && is_array($project_cfg['custom_agreement_labels'])
            ? $project_cfg['custom_agreement_labels']
            : [];

        foreach ($agreement_description_fields as $agreement_id) {
            $agreement_id = sanitize_text_field((string) $agreement_id);
            if ($agreement_id === '') {
                continue;
            }

            if (($agreement_checks[$agreement_id] ?? '') !== '1') {
                continue;
            }

            $agreement_label = sanitize_text_field((string) ($agreement_label_map[$agreement_id] ?? ''));
            if ($agreement_label === '') {
                $agreement_label = 'Agreement';
            }

            $agreement_description_plain_lines[] = '- ' . $agreement_label . ': Checked';
            $agreement_description_html_lines[] = '<li><strong>' . esc_html($agreement_label) . ':</strong> Checked</li>';
        }
    }

    $description_plain_lines = [];
    $description_html_lines = [];
    foreach ($description_inputs as $desc_input) {
        $desc_id = sanitize_text_field((string) ($desc_input['id'] ?? ''));
        if ($desc_id !== '' && !empty($private_companion_only_source_ids[$desc_id])) {
            continue;
        }

        $desc_label = (string) ($desc_input['label'] ?? 'Additional detail');
        $desc_value = (string) ($desc_input['value'] ?? '');
        if ($desc_value === '') {
            continue;
        }

        $description_plain_lines[] = '- ' . $desc_label . ': ' . $desc_value;
        $description_html_lines[] = '<li><strong>' . esc_html($desc_label) . ':</strong> ' . esc_html($desc_value) . '</li>';
    }

    // Notes (PLAIN): triage summary + request details + user notes
    $notes_plain = afb_generate_combined_summary_plain(
        $summary_html,
        $request_details_text
    );

    if ($notes_user_plain !== '') {
        $notes_plain .= "\n\nNotes\n" . $notes_user_plain;
    }

    if (!empty($agreement_description_plain_lines)) {
        $notes_plain .= "\n\nAgreements\n" . implode("\n", $agreement_description_plain_lines);
    }

    if (!empty($description_plain_lines)) {
        $notes_plain .= "\n\nAdditional Details\n" . implode("\n", $description_plain_lines);
    }

    if (!empty($repeater_description_sections)) {
        $repeater_plain_blocks = [];
        foreach ($repeater_description_sections as $section) {
            $section_label = sanitize_text_field((string) ($section['label'] ?? ''));
            if ($section_label === '') {
                $section_label = 'Repeater entries';
            }

            $rows_plain = [];
            foreach ((array) ($section['plain'] ?? []) as $plain_line) {
                $plain_line = sanitize_text_field((string) $plain_line);
                if ($plain_line !== '') {
                    $rows_plain[] = $plain_line;
                }
            }

            if (empty($rows_plain)) {
                continue;
            }

            $repeater_plain_blocks[] = $section_label . "\n" . implode("\n", $rows_plain);
        }

        if (!empty($repeater_plain_blocks)) {
            $notes_plain .= "\n\nRepeater Entries\n" . implode("\n\n", $repeater_plain_blocks);
        }
    }

    // If plain-text copy was submitted (no HTML), append to description
    if ($submitted_copy_plain !== '' && $submitted_copy_html === '') {
        $notes_plain .= "\n\nSubmitted Copy\n" . $submitted_copy_plain;
    }

    // Notes (HTML): combined + user notes appended, wrapped for Asana html_notes
    $notes_html = afb_generate_combined_summary_html(
        $summary_html,
        $request_details_text
    );

    if ($notes_user_html !== '') {
        $notes_html .= '<hr><h3>Notes</h3><p>' . $notes_user_html . '</p>';
    }

    if (!empty($agreement_description_html_lines)) {
        $notes_html .= '<hr><h3>Agreements</h3><ul>' . implode('', $agreement_description_html_lines) . '</ul>';
    }

    if (!empty($description_html_lines)) {
        $notes_html .= '<hr><h3>Additional Details</h3><ul>' . implode('', $description_html_lines) . '</ul>';
    }

    if (!empty($repeater_description_sections)) {
        $repeater_html_blocks = [];
        foreach ($repeater_description_sections as $section) {
            $section_label = sanitize_text_field((string) ($section['label'] ?? ''));
            if ($section_label === '') {
                $section_label = 'Repeater entries';
            }

            $rows_html = [];
            foreach ((array) ($section['html'] ?? []) as $html_line) {
                $html_line = (string) $html_line;
                if ($html_line !== '') {
                    $rows_html[] = $html_line;
                }
            }

            if (empty($rows_html)) {
                continue;
            }

            $repeater_html_blocks[] = '<h4>' . esc_html($section_label) . '</h4><ul>' . implode('', $rows_html) . '</ul>';
        }

        if (!empty($repeater_html_blocks)) {
            $notes_html .= '<hr><h3>Repeater Entries</h3>' . implode('', $repeater_html_blocks);
        }
    }

    // If plain-text copy was submitted (no HTML), append to HTML description too
    if ($submitted_copy_plain !== '' && $submitted_copy_html === '') {
        $notes_html .= '<hr><h3>Submitted Copy</h3><p>' . nl2br(esc_html($submitted_copy_plain)) . '</p>';
    }

    if ($notes_html && !preg_match('/<body\b/i', $notes_html)) {
        $notes_html = '<body>' . $notes_html . '</body>';
    }

    // ─────────────────────────────────────────────────────────────
    // Due date resolver (accepts DD/MM/YY in $_POST['date'])
    // ─────────────────────────────────────────────────────────────
    $manual_due_iso = afb_parse_date_to_iso(sanitize_text_field($_POST['date'] ?? ''));
    $resolved = afb_resolve_due_date($_POST, $custom_fields, $task_name);
    $due_on   = !empty($resolved['date']) ? $resolved['date'] : null;
    $due_date_source_field_id = sanitize_text_field($_POST['due_date_field_id'] ?? '');

    $due_tool_cfg = afb_get_project_due_date_tool_config($project_cfg);
    if (is_array($due_tool_cfg)) {
        $due_tool_override_manual = !empty($due_tool_cfg['override_manual']);
        if ($due_tool_override_manual || $manual_due_iso === null) {
            $due_tool_date = afb_resolve_due_date_from_tool_config($due_tool_cfg, $submitted_condition_values);
            if (is_string($due_tool_date) && $due_tool_date !== '') {
                $due_on = $due_tool_date;
            }
        }
    }

    $workspace_gid = sanitize_text_field($_POST['workspace_gid'] ?? '');
    if ($workspace_gid === '') {
        $workspace_gid = '9536715027185';
    }

    $assignee_gid = null;
    $assignee_tool_cfg = afb_get_project_assignee_tool_config($project_cfg);
    if (is_array($assignee_tool_cfg)) {
        $assignee_gid = afb_resolve_assignee_from_tool_config(
            $assignee_tool_cfg,
            $submitted_condition_values,
            $pat,
            $workspace_gid
        );
    }

    // ─────────────────────────────────────────────────────────────
    // Follower (submitter) lookup by email
    // ─────────────────────────────────────────────────────────────
    $submitter_email = sanitize_email($_POST['submitter_email'] ?? '');

    if (!$submitter_email && !empty($triage_answers['work_email'])) {
        $submitter_email = sanitize_email($triage_answers['work_email']);
    }

    $user_gid = null;
    if ($submitter_email) {
        $u  = afb_asana_request(
            'GET',
            'https://app.asana.com/api/1.0/users/' . rawurlencode($submitter_email) . '?workspace=' . rawurlencode($workspace_gid),
            $pat
        );
        $jd = json_decode($u['body'] ?? '', true);
        if (($u['code'] ?? 0) === 200 && !empty($jd['data']['gid'])) {
            $user_gid = $jd['data']['gid'];
        }
    }

    if ($task_name_template !== '') {
        // Re-evaluate template with conditions now that we have all submitted values
        $task_name_template_with_conditions = afb_get_task_name_template_by_condition($project_cfg, $submitted_condition_values);
        if ($task_name_template_with_conditions !== '') {
            $task_name_template = $task_name_template_with_conditions;
        }
        
        $task_name_values = [];

        foreach ($custom_fields as $cf_gid => $cf_val) {
            $gid = trim((string) $cf_gid);
            if ($gid === '') {
                continue;
            }

            $flat_value = afb_task_name_render_field_value($gid, $cf_val, $project_field_types, $project_enum_option_labels);
            if ($flat_value !== '') {
                $task_name_values[$gid] = $flat_value;
            }
        }

        foreach ($description_input_map as $desc_id => $desc_value) {
            if (!isset($task_name_values[$desc_id])) {
                $task_name_values[$desc_id] = $desc_value;
            }
        }

        foreach ($task_name_field_values as $field_id => $field_value) {
            if (!isset($task_name_values[$field_id]) || $task_name_values[$field_id] === '') {
                $task_name_values[$field_id] = $field_value;
            }
        }

        $calendar_sync_modes = is_array($project_cfg['custom_calendar_sync_modes'] ?? null)
            ? $project_cfg['custom_calendar_sync_modes']
            : [];
        if (!empty($calendar_sync_modes)) {
            $due_token_value = $due_on !== null ? (string) $due_on : sanitize_text_field($_POST['date'] ?? '');
            $due_date_calendar_ids = [];
            foreach ($calendar_sync_modes as $calendar_id => $sync_mode) {
                $calendar_id = trim((string) $calendar_id);
                if ($calendar_id === '' || strpos($calendar_id, 'calendar_') !== 0) {
                    continue;
                }

                if (trim((string) $sync_mode) === 'due_date') {
                    $due_date_calendar_ids[] = $calendar_id;
                }
            }

            $due_date_calendar_ids = array_values(array_unique($due_date_calendar_ids));

            $due_target_calendar_id = '';
            if ($due_date_source_field_id !== '' && in_array($due_date_source_field_id, $due_date_calendar_ids, true)) {
                $due_target_calendar_id = $due_date_source_field_id;
            } elseif (count($due_date_calendar_ids) === 1) {
                $due_target_calendar_id = $due_date_calendar_ids[0];
            }

            if ($due_target_calendar_id !== '' && $due_token_value !== '' && (!isset($task_name_values[$due_target_calendar_id]) || $task_name_values[$due_target_calendar_id] === '')) {
                $task_name_values[$due_target_calendar_id] = $due_token_value;
            }
        }

        $sync_aliases = afb_task_name_sync_aliases_from_config($project_cfg);
        foreach ($sync_aliases as $block_id => $target_gid) {
            if (isset($task_name_values[$block_id])) {
                continue;
            }
            if (empty($task_name_values[$target_gid])) {
                continue;
            }

            $task_name_values[$block_id] = $task_name_values[$target_gid];
        }

        $task_name_values['job_number'] = $job_number_gid !== ''
            ? afb_task_name_value_to_string($custom_fields[$job_number_gid] ?? '')
            : '';
        $task_name_values['submitter_name'] = sanitize_text_field($_POST['submitter_name'] ?? '');
        $task_name_values['submitter_email'] = $submitter_email;
        $task_name_values['due_date'] = $due_on !== null
            ? (string) $due_on
            : sanitize_text_field($_POST['date'] ?? '');

        $templated_name = afb_resolve_task_name_template($task_name_template, $task_name_values, $project_field_types);
        if ($templated_name !== '') {
            $task_name = afb_sanitize_task_title($templated_name);
            if ($due_on === null) {
                $template_due = afb_get_due_date_from_task_name($task_name);
                if ($template_due !== null) {
                    $due_on = $template_due;
                }
            }
        }
    }

    $companion_source_custom_fields = $custom_fields;
    if (!empty($private_companion_only_primary_field_gids)) {
        foreach (array_keys($private_companion_only_primary_field_gids) as $excluded_gid) {
            $excluded_gid = sanitize_text_field((string) $excluded_gid);
            if ($excluded_gid === '' || ($job_number_gid !== '' && $excluded_gid === $job_number_gid)) {
                continue;
            }

            unset($custom_fields[$excluded_gid]);
        }
    }

    // ─────────────────────────────────────────────────────────────
    // Create task
    // ─────────────────────────────────────────────────────────────
    $task_payload = [
        'name'          => $task_name,
        'projects'      => [$project_id],
        'custom_fields' => $custom_fields,
        'notes'         => $notes_plain, // fallback plain text
    ];

    if ($due_on)   $task_payload['due_on']    = $due_on;
    if ($assignee_gid) $task_payload['assignee'] = $assignee_gid;
    if ($user_gid) $task_payload['followers'] = [$user_gid];

    // Prefer rich HTML notes if we have any
    if (!empty($notes_html)) {
        $task_payload['html_notes'] = $notes_html;
    }

    // DEBUG: payload preview (key CFs only)
    if (function_exists('afb_log')) {
        $preview_cf = [
            'job_number_gid'   => $job_number_gid,
            'job_number_value' => $custom_fields[$job_number_gid] ?? null,
        ];
        afb_log(
            '[DEBUG] creating task payload → ' . json_encode(
                [
                    'name'          => $task_name,
                    'project_id'    => $project_id,
                    'due_on'        => $due_on,
                    'assignee'      => $assignee_gid,
                    'custom_fields' => $preview_cf,
                    'has_html_notes'=> !empty($task_payload['html_notes']),
                ],
                JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
            ),
            'afb-debug.log'
        );
    }

    $create_result = afb_create_task_with_retries($pat, $task_payload);
    $r = is_array($create_result) ? ($create_result['response'] ?? []) : [];
    $create_http_code = (int) ($r['code'] ?? 0);
    $create_ok = in_array($create_http_code, [200, 201], true);

    // DEBUG: response
    if (function_exists('afb_log')) {
        afb_log(
            '[DEBUG] Asana create http=' . ($r['code'] ?? 0) . ' ms=' . ($r['ms'] ?? 0),
            'afb-debug.log'
        );
        if (!$create_ok) {
            $body = isset($r['body'])
                ? (is_string($r['body']) ? $r['body'] : json_encode($r['body']))
                : '';
            afb_log('[DEBUG] Asana create failure body → ' . $body, 'afb-debug.log');
        }
    }

    if (!$create_ok) {
        $asana_error = '';
        $err_json = json_decode($r['body'] ?? '', true);
        if (is_array($err_json) && !empty($err_json['errors'][0]['message'])) {
            $asana_error = sanitize_text_field((string) $err_json['errors'][0]['message']);
        }

        $message = 'Task create failed';
        if ($asana_error !== '') {
            $message .= ': ' . $asana_error;
        }

        afb_send_json_error_clean([
            'message'    => $message,
            'http_code'  => $create_http_code,
            'asana_error'=> $asana_error,
            'request_id' => $request_id
        ]);
    }

    $task_gid = (string) (json_decode($r['body'] ?? '', true)['data']['gid'] ?? '');
    if (!$task_gid) {
        afb_send_json_error_clean([
            'message'    => 'No task_gid from Asana',
            'request_id' => $request_id
        ]);
    }

    $subtask_summary = [
        'total' => is_array($repeater_subtask_rows) ? count($repeater_subtask_rows) : 0,
        'created' => 0,
        'failed' => 0,
        'created_task_gids' => [],
        'failed_rows' => [],
    ];

    // Idempotency for create requests.
    afb_idem_remember($request_id, $task_gid);

    // Cache the winning PAT for this task so task-comms endpoints can resolve quickly.
    set_transient('afb_task_pat_' . md5($task_gid), (string) $pat, DAY_IN_SECONDS);

    // Seed status immediately so clients can recover from proxy/gateway timeouts
    // while this endpoint continues post-create work.
    afb_status_init($request_id, [
        'task_gid' => $task_gid,
        'phase'    => 'create',
        'uploads'  => [
            'total'  => (int) ($_POST['upload_count'] ?? 0),
            'done'   => 0,
            'failed' => [],
        ],
        'subtasks' => [
            'total' => $subtask_summary['total'],
            'created' => 0,
            'failed' => 0,
            'failed_rows' => [],
        ],
        'steps'    => [
            [
                'k'  => 'create',
                'ok' => true,
                'ms' => ($r['ms'] ?? 0),
            ],
        ],
    ]);

    // DEBUG: created GID + key CFs
    if (function_exists('afb_log')) {
        afb_log(
            '[DEBUG] Task created gid=' . $task_gid .
            ' job_number=' . ($custom_fields[$job_number_gid] ?? '(unset)'),
            'afb-debug.log'
        );
    }

    if ($subtask_summary['total'] > 0) {
        foreach ($repeater_subtask_rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $row_number = max(1, absint($row['row_number'] ?? ($subtask_summary['created'] + $subtask_summary['failed'] + 1)));
            $subtask_payload = afb_repeater_row_to_subtask_payload(
                $task_name,
                $row,
                (string) $assignee_gid,
                $due_on,
                $project_id,
                $project_field_types
            );
            $subtask_result = afb_create_subtask_with_retries($pat, $task_gid, $subtask_payload);
            $subtask_response = is_array($subtask_result) ? ($subtask_result['response'] ?? []) : [];
            $subtask_http_code = (int) ($subtask_response['code'] ?? 0);

            if (in_array($subtask_http_code, [200, 201], true)) {
                $subtask_gid = (string) (json_decode($subtask_response['body'] ?? '', true)['data']['gid'] ?? '');
                $subtask_summary['created']++;

                if ($subtask_gid !== '' && count($subtask_summary['created_task_gids']) < 250) {
                    $subtask_summary['created_task_gids'][] = $subtask_gid;
                }

                continue;
            }

            $subtask_summary['failed']++;
            $error_message = '';
            $subtask_error_json = json_decode($subtask_response['body'] ?? '', true);
            if (is_array($subtask_error_json) && !empty($subtask_error_json['errors'][0]['message'])) {
                $error_message = sanitize_text_field((string) $subtask_error_json['errors'][0]['message']);
            }

            if ($error_message === '') {
                $error_message = 'Subtask create failed';
            }

            if (count($subtask_summary['failed_rows']) < 50) {
                $subtask_summary['failed_rows'][] = [
                    'row_number' => $row_number,
                    'http_code'  => $subtask_http_code,
                    'message'    => $error_message,
                ];
            }
        }

        if (function_exists('afb_log')) {
            afb_log(
                '[DEBUG] repeater subtask summary=' . wp_json_encode([
                    'total' => $subtask_summary['total'],
                    'created' => $subtask_summary['created'],
                    'failed' => $subtask_summary['failed'],
                ]),
                'afb-debug.log'
            );
        }

        afb_status_merge($request_id, [
            'subtasks' => [
                'total' => $subtask_summary['total'],
                'created' => $subtask_summary['created'],
                'failed' => $subtask_summary['failed'],
                'failed_rows' => $subtask_summary['failed_rows'],
            ],
        ]);
    }

    $private_task_gid = '';
    $private_task_status = 'disabled';
    $private_task_dependency_status = $private_companion_depends_on_main ? 'waiting_for_companion' : 'disabled';
    $private_task_dependency_http = 0;
    $private_task_dependency_body = '';
    if ($private_data_companion_enabled) {
        if ($private_data_project_gid === '') {
            $private_task_status = 'missing_project';
            if ($private_companion_depends_on_main) {
                $private_task_dependency_status = 'missing_project';
            }
        } else {
            $private_ctx = afb_find_project_access_context($private_data_project_gid, $accounts);
            if (!is_array($private_ctx) || empty($private_ctx['pat'])) {
                $private_task_status = 'project_not_found';
                if ($private_companion_depends_on_main) {
                    $private_task_dependency_status = 'project_not_found';
                }
            } else {
                $private_pat = (string) ($private_ctx['pat'] ?? '');
                $private_project_name = sanitize_text_field((string) ($private_ctx['project_name'] ?? ''));

                $private_field_meta = afb_collect_project_field_meta($private_data_project_gid, $private_pat);
                $private_field_gids = is_array($private_field_meta['field_gids'] ?? null)
                    ? $private_field_meta['field_gids']
                    : [];
                $private_field_types = is_array($private_field_meta['field_types'] ?? null)
                    ? $private_field_meta['field_types']
                    : [];

                $private_custom_fields = [];
                foreach ($custom_fields as $cf_gid => $cf_val) {
                    $cf_gid = (string) $cf_gid;
                    if ($cf_gid === '' || empty($private_field_gids[$cf_gid])) {
                        continue;
                    }

                    $private_custom_fields[$cf_gid] = $cf_val;
                }

                $private_job_number_gid = (string) ($private_field_meta['job_number_gid'] ?? '');
                $primary_job_number_value = ($job_number_gid !== '' && isset($custom_fields[$job_number_gid]))
                    ? $custom_fields[$job_number_gid]
                    : '';

                if ($private_job_number_gid !== '' && $primary_job_number_value !== '' && !isset($private_custom_fields[$private_job_number_gid])) {
                    $private_custom_fields[$private_job_number_gid] = $primary_job_number_value;
                }

                $mapped_private_target_count = 0;
                if (!empty($private_companion_field_targets)) {
                    foreach ($private_companion_field_targets as $source_id => $target_gid) {
                        $source_id = sanitize_text_field((string) $source_id);
                        $target_gid = sanitize_text_field((string) $target_gid);
                        if ($source_id === '' || $target_gid === '' || empty($private_field_gids[$target_gid])) {
                            continue;
                        }

                        $has_source_value = false;
                        $source_value = null;

                        if (array_key_exists($source_id, $companion_source_custom_fields)) {
                            $source_value = $companion_source_custom_fields[$source_id];
                            $has_source_value = true;
                        } elseif (array_key_exists($source_id, $task_name_field_values)) {
                            $source_value = $task_name_field_values[$source_id];
                            $has_source_value = true;
                        } elseif (array_key_exists($source_id, $submitted_condition_values)) {
                            $source_value = $submitted_condition_values[$source_id];
                            $has_source_value = true;
                        }

                        if (!$has_source_value) {
                            unset($private_custom_fields[$target_gid]);
                            continue;
                        }

                        $normalized_value = afb_normalize_value_for_target_field_type($source_value, $private_field_types[$target_gid] ?? '');
                        if ($normalized_value === null) {
                            unset($private_custom_fields[$target_gid]);
                            continue;
                        }

                        $private_custom_fields[$target_gid] = $normalized_value;
                        $mapped_private_target_count++;
                    }
                }

                // Determine companion task name according to per-project mode.
                $private_task_name_to_use = $task_name;
                $mode = sanitize_text_field((string) ($project_cfg['private_companion_task_name_mode'] ?? 'use_main'));
                if (!in_array($mode, ['use_main', 'use_job_number', 'use_template'], true)) {
                    $mode = 'use_main';
                }

                if ($mode === 'use_job_number') {
                    $job_val = '';
                    if ($private_job_number_gid !== '' && isset($private_custom_fields[$private_job_number_gid]) && $private_custom_fields[$private_job_number_gid] !== '') {
                        $job_val = (string) $private_custom_fields[$private_job_number_gid];
                    } elseif ($primary_job_number_value !== '') {
                        $job_val = (string) $primary_job_number_value;
                    }

                    if ($job_val !== '') {
                        $private_task_name_to_use = afb_sanitize_task_title($job_val);
                    }
                } elseif ($mode === 'use_template') {
                    $private_companion_template = trim((string) ($project_cfg['private_companion_task_name_template'] ?? ''));
                    if ($private_companion_template !== '') {
                        $private_values = [];
                        foreach ($custom_fields as $cf_gid => $cf_val) {
                            $gid = trim((string) $cf_gid);
                            if ($gid === '') continue;

                            $flat_value = afb_task_name_render_field_value($gid, $cf_val, $project_field_types, $project_enum_option_labels);
                            if ($flat_value !== '') {
                                $private_values[$gid] = $flat_value;
                            }
                        }

                        foreach ($description_input_map as $desc_id => $desc_value) {
                            if (!isset($private_values[$desc_id])) {
                                $private_values[$desc_id] = $desc_value;
                            }
                        }

                        foreach ($task_name_field_values as $field_id => $field_value) {
                            if (!isset($private_values[$field_id]) || $private_values[$field_id] === '') {
                                $private_values[$field_id] = $field_value;
                            }
                        }

                        $private_values['job_number'] = $job_number_gid !== '' ? afb_task_name_value_to_string($custom_fields[$job_number_gid] ?? '') : '';
                        $private_values['submitter_name'] = sanitize_text_field($_POST['submitter_name'] ?? '');
                        $private_values['submitter_email'] = $submitter_email;
                        $private_values['due_date'] = $due_on !== null ? (string) $due_on : sanitize_text_field($_POST['date'] ?? '');
                        $private_values['task_name'] = $task_name;

                        $resolved_private_name = afb_resolve_task_name_template($private_companion_template, $private_values, $project_field_types);
                        if ($resolved_private_name !== '') {
                            $private_task_name_to_use = afb_sanitize_task_title($resolved_private_name);
                        } elseif ($primary_job_number_value !== '') {
                            $private_task_name_to_use = afb_sanitize_task_title((string) $primary_job_number_value);
                        }
                    } elseif ($primary_job_number_value !== '') {
                        $private_task_name_to_use = afb_sanitize_task_title((string) $primary_job_number_value);
                    }
                }

                $private_task_payload = [
                    'name'     => $private_task_name_to_use,
                    'projects' => [$private_data_project_gid],
                ];

                if (!empty($private_custom_fields)) {
                    $private_task_payload['custom_fields'] = $private_custom_fields;
                }
                if ($due_on) {
                    $private_task_payload['due_on'] = $due_on;
                }

                $private_create_result = afb_create_task_with_retries($private_pat, $private_task_payload);
                $private_r = is_array($private_create_result) ? ($private_create_result['response'] ?? []) : [];
                $private_code = (int) ($private_r['code'] ?? 0);
                $primary_privacy_notice_status = 'skipped';
                $primary_privacy_notice_http = 0;

                if (in_array($private_code, [200, 201], true)) {
                    $private_task_gid = (string) (json_decode($private_r['body'] ?? '', true)['data']['gid'] ?? '');
                    $private_task_status = $private_task_gid !== '' ? 'created' : 'created_no_gid';

                    if ($private_companion_depends_on_main) {
                        if ($private_task_gid !== '') {
                            $dependency_link_result = afb_link_task_dependency_with_available_tokens(
                                $private_task_gid,
                                $task_gid,
                                [$private_pat, $pat]
                            );
                            $private_task_dependency_status = (string) ($dependency_link_result['status'] ?? 'failed');
                            $private_task_dependency_http = (int) ($dependency_link_result['http'] ?? 0);
                            $private_task_dependency_body = (string) ($dependency_link_result['body'] ?? '');
                        } else {
                            $private_task_dependency_status = 'companion_missing_gid';
                        }
                    }

                    if ($private_task_gid !== '') {
                        $private_project_label = $private_project_name !== '' ? $private_project_name : $private_data_project_gid;
                        $private_task_url = 'https://app.asana.com/0/' . rawurlencode($private_data_project_gid) . '/' . rawurlencode($private_task_gid) . '/f';

                        $primary_notice_plain = 'Confidential data notice: This task has private/confidential data stored in ' . $private_project_label . ' (' . $private_data_project_gid . ').';
                        $primary_notice_plain .= "\nPrivate companion task: " . $private_task_url;

                        $main_notes_plain = trim((string) $notes_plain);
                        if ($main_notes_plain !== '') {
                            $main_notes_plain .= "\n\n-----\n\n" . $primary_notice_plain;
                        } else {
                            $main_notes_plain = $primary_notice_plain;
                        }

                        $main_notes_html_inner = trim((string) $notes_html);
                        if ($main_notes_html_inner !== '') {
                            $main_notes_html_inner = (string) preg_replace('/^\s*<body[^>]*>/i', '', $main_notes_html_inner);
                            $main_notes_html_inner = (string) preg_replace('/<\/body>\s*$/i', '', $main_notes_html_inner);
                        }

                        $main_notice_html = '<p><strong>Confidential data notice:</strong> This task has private/confidential data stored in <strong>' . esc_html($private_project_label) . '</strong> (' . esc_html($private_data_project_gid) . ').</p>';
                        $main_notice_html .= '<p><strong>Private companion task:</strong> <a href="' . esc_url($private_task_url) . '">' . esc_html($private_task_gid) . '</a></p>';

                        if (stripos($main_notes_html_inner, 'Confidential data notice:') !== false || stripos($main_notes_html_inner, 'Private companion task:') !== false) {
                            $primary_privacy_notice_status = 'already_present';
                        } else {
                            $main_notes_html = '<body>';
                            if ($main_notes_html_inner !== '') {
                                $main_notes_html .= $main_notes_html_inner . '<hr>';
                            }
                            $main_notes_html .= $main_notice_html;
                            $main_notes_html .= '</body>';

                            $main_task_update_payload = [
                                'notes' => $main_notes_plain,
                                'html_notes' => $main_notes_html,
                            ];

                            $main_update_response = afb_asana_request(
                                'PUT',
                                'https://app.asana.com/api/1.0/tasks/' . rawurlencode($task_gid),
                                $pat,
                                ['json' => $main_task_update_payload]
                            );

                            if (($main_update_response['code'] ?? 0) === 400) {
                                unset($main_task_update_payload['html_notes']);
                                $main_update_response = afb_asana_request(
                                    'PUT',
                                    'https://app.asana.com/api/1.0/tasks/' . rawurlencode($task_gid),
                                    $pat,
                                    ['json' => $main_task_update_payload]
                                );
                            }

                            $primary_privacy_notice_http = (int) ($main_update_response['code'] ?? 0);
                            $primary_privacy_notice_status = $primary_privacy_notice_http === 200
                                ? 'updated'
                                : 'update_failed';
                        }
                    }
                } else {
                    $private_task_status = 'create_failed';
                    if ($private_companion_depends_on_main) {
                        $private_task_dependency_status = 'companion_create_failed';
                    }
                }

                if (function_exists('afb_log')) {
                    $private_preview_cf = [
                        'count' => is_array($private_custom_fields) ? count($private_custom_fields) : 0,
                        'has_job_number' => $private_job_number_gid !== '' && isset($private_custom_fields[$private_job_number_gid]),
                        'mapped_targets' => $mapped_private_target_count,
                        'dependency' => $private_task_dependency_status,
                        'dependency_http' => $private_task_dependency_http,
                        'primary_notice' => $primary_privacy_notice_status,
                        'primary_notice_http' => $primary_privacy_notice_http,
                    ];
                    afb_log(
                        '[DEBUG] private companion create status=' . $private_task_status .
                        ' http=' . $private_code .
                        ' source_project=' . $project_id .
                        ' private_project=' . $private_data_project_gid .
                        ' private_project_name=' . $private_project_name .
                        ' private_task_gid=' . ($private_task_gid !== '' ? $private_task_gid : '(none)') .
                        ' custom_fields=' . wp_json_encode($private_preview_cf),
                        'afb-debug.log'
                    );

                    if (!in_array($private_code, [200, 201], true)) {
                        $private_body = isset($private_r['body'])
                            ? (is_string($private_r['body']) ? $private_r['body'] : wp_json_encode($private_r['body']))
                            : '';
                        afb_log('[DEBUG] private companion failure body → ' . $private_body, 'afb-debug.log');
                    }

                    if ($private_companion_depends_on_main && $private_task_dependency_status === 'failed' && $private_task_dependency_body !== '') {
                        afb_log('[DEBUG] private companion dependency failure body → ' . $private_task_dependency_body, 'afb-debug.log');
                    }
                }
            }
        }
    }

    $recipient_tool_dispatch = [
        'tools' => 0,
        'separate' => 0,
        'cc' => 0,
        'skipped_condition' => 0,
        'recipients' => 0,
    ];

    if (function_exists('afb_send_submission_email')) {
        $recipient_tool_cfgs = afb_get_project_recipient_tool_configs($project_cfg);
        if (!empty($recipient_tool_cfgs)) {
            $task_link = 'https://app.asana.com/0/' . rawurlencode($project_id) . '/' . rawurlencode($task_gid) . '/f';
            $job_number_value = ($job_number_gid !== '' && isset($custom_fields[$job_number_gid]))
                ? sanitize_text_field((string) $custom_fields[$job_number_gid])
                : '';
            $submitter_name_value = sanitize_text_field($_POST['submitter_name'] ?? '');
            $recipient_conditions = isset($project_cfg['conditions']) && is_array($project_cfg['conditions'])
                ? $project_cfg['conditions']
                : [];
            $known_condition_ids = afb_condition_known_field_ids_from_project_cfg($project_cfg);

            $fields_html_for_email = '';
            if (trim((string) $submitted_copy_html) !== '') {
                $fields_html_for_email = wp_kses_post((string) $submitted_copy_html);
            } elseif (trim((string) $submitted_copy_plain) !== '') {
                $fields_html_for_email = '<p>' . nl2br(esc_html((string) $submitted_copy_plain)) . '</p>';
            } else {
                $parts = [];
                if (!empty($agreement_description_html_lines)) {
                    $parts[] = '<h3>Agreements</h3><ul>' . implode('', $agreement_description_html_lines) . '</ul>';
                }
                if (!empty($description_html_lines)) {
                    $parts[] = '<h3>Additional Details</h3><ul>' . implode('', $description_html_lines) . '</ul>';
                }
                $fields_html_for_email = implode('', $parts);
            }

            $recipient_template_tokens = [];
            foreach ($custom_fields as $field_gid => $field_value) {
                $field_gid = sanitize_text_field((string) $field_gid);
                if ($field_gid === '') {
                    continue;
                }

                $rendered_value = afb_task_name_render_field_value($field_gid, $field_value, $project_field_types, $project_enum_option_labels);
                if ($rendered_value === '') {
                    continue;
                }

                $recipient_template_tokens[$field_gid] = $rendered_value;
                $recipient_template_tokens['field:' . $field_gid] = $rendered_value;
            }

            foreach ($submitted_condition_values as $field_id => $field_value) {
                $field_id = sanitize_text_field((string) $field_id);
                if ($field_id === '' || !empty($recipient_template_tokens[$field_id])) {
                    continue;
                }

                $rendered_value = strpos($field_id, 'calendar_') === 0
                    ? afb_task_name_format_date_value($field_value)
                    : afb_task_name_value_to_string($field_value);
                if ($rendered_value === '') {
                    continue;
                }

                $recipient_template_tokens[$field_id] = $rendered_value;
                $recipient_template_tokens['field:' . $field_id] = $rendered_value;
            }

            $sync_aliases = afb_task_name_sync_aliases_from_config($project_cfg);
            foreach ($sync_aliases as $source_id => $target_gid) {
                $source_id = sanitize_text_field((string) $source_id);
                $target_gid = sanitize_text_field((string) $target_gid);
                if ($source_id === '' || $target_gid === '') {
                    continue;
                }

                if (empty($recipient_template_tokens[$source_id]) && !empty($recipient_template_tokens[$target_gid])) {
                    $recipient_template_tokens[$source_id] = $recipient_template_tokens[$target_gid];
                    $recipient_template_tokens['field:' . $source_id] = $recipient_template_tokens[$target_gid];
                }

                if (empty($recipient_template_tokens[$target_gid]) && !empty($recipient_template_tokens[$source_id])) {
                    $recipient_template_tokens[$target_gid] = $recipient_template_tokens[$source_id];
                    $recipient_template_tokens['field:' . $target_gid] = $recipient_template_tokens[$source_id];
                }
            }

            $recipient_template_tokens['job_number'] = $job_number_value;
            $recipient_template_tokens['submitter_name'] = $submitter_name_value;
            $recipient_template_tokens['submitter_email'] = $submitter_email;
            $recipient_template_tokens['due_date'] = afb_task_name_format_date_value($due_on !== null
                ? (string) $due_on
                : sanitize_text_field($_POST['date'] ?? ''));
            $recipient_template_tokens['task_name'] = $task_name;
            $recipient_template_tokens['request_number'] = $job_number_value !== '' ? $job_number_value : $task_gid;
            $recipient_template_tokens['comms_link'] = $task_link;
            $recipient_template_tokens['fields_html'] = $fields_html_for_email;

            foreach ($recipient_tool_cfgs as $tool_cfg) {
                if (empty($tool_cfg['enabled'])) {
                    continue;
                }

                $recipient_tool_dispatch['tools']++;

                $tool_id = sanitize_text_field((string) ($tool_cfg['id'] ?? ''));
                $tool_condition = ($tool_id !== '' && isset($recipient_conditions[$tool_id]) && is_array($recipient_conditions[$tool_id]))
                    ? $recipient_conditions[$tool_id]
                    : [];
                if (!empty($tool_condition) && !afb_condition_set_matches_submitted_values($tool_condition, $submitted_condition_values, $known_condition_ids)) {
                    $recipient_tool_dispatch['skipped_condition']++;
                    continue;
                }

                $recipient_pool = [];
                $source_fields = isset($tool_cfg['source_fields']) && is_array($tool_cfg['source_fields'])
                    ? $tool_cfg['source_fields']
                    : [];

                foreach ($source_fields as $source_field_id) {
                    $source_field_id = sanitize_text_field((string) $source_field_id);
                    if ($source_field_id === '' || !array_key_exists($source_field_id, $submitted_condition_values)) {
                        continue;
                    }

                    $value_emails = afb_collect_emails_from_raw_value($submitted_condition_values[$source_field_id]);
                    if (!empty($value_emails)) {
                        $recipient_pool = array_merge($recipient_pool, $value_emails);
                    }
                }

                $manual_emails = afb_collect_emails_from_raw_value($tool_cfg['manual_emails'] ?? []);
                if (!empty($manual_emails)) {
                    $recipient_pool = array_merge($recipient_pool, $manual_emails);
                }

                if (!empty($tool_cfg['include_submitter']) && is_email($submitter_email)) {
                    $recipient_pool[] = $submitter_email;
                }

                $recipient_pool = array_values(array_unique(array_filter(array_map('sanitize_email', $recipient_pool), static function ($email) {
                    return is_email($email);
                })));

                if (empty($recipient_pool)) {
                    continue;
                }

                $mail_args_base = [
                    'task_name' => $task_name,
                    'request_number' => $task_gid,
                    'job_number' => $job_number_value,
                    'comms_link' => $task_link,
                    'fields_html' => $fields_html_for_email,
                    'template_tokens' => $recipient_template_tokens,
                ];

                $subject_template = sanitize_text_field((string) ($tool_cfg['subject_template'] ?? ''));
                if ($subject_template !== '') {
                    $mail_args_base['subject_template'] = $subject_template;
                }

                $body_template = wp_kses_post((string) ($tool_cfg['body_template'] ?? ''));
                if (trim($body_template) !== '') {
                    $mail_args_base['body_template'] = $body_template;
                }

                $mode = in_array((string) ($tool_cfg['mode'] ?? 'separate'), ['separate', 'cc'], true)
                    ? (string) $tool_cfg['mode']
                    : 'separate';

                if ($mode === 'cc') {
                    $primary_to = is_email($submitter_email) ? $submitter_email : (string) ($recipient_pool[0] ?? '');
                    if (!is_email($primary_to)) {
                        continue;
                    }

                    $cc_list = array_values(array_filter($recipient_pool, static function ($email) use ($primary_to) {
                        return is_email($email) && strcasecmp((string) $email, (string) $primary_to) !== 0;
                    }));

                    $mail_args = $mail_args_base;
                    $mail_args['to'] = $primary_to;
                    if (!empty($cc_list)) {
                        $mail_args['cc'] = $cc_list;
                    }

                    if (afb_send_submission_email($mail_args)) {
                        $recipient_tool_dispatch['cc']++;
                        $recipient_tool_dispatch['recipients'] += 1 + count($cc_list);
                    }
                    continue;
                }

                foreach ($recipient_pool as $recipient_email) {
                    if (!is_email($recipient_email)) {
                        continue;
                    }

                    $mail_args = $mail_args_base;
                    $mail_args['to'] = $recipient_email;

                    if (afb_send_submission_email($mail_args)) {
                        $recipient_tool_dispatch['separate']++;
                        $recipient_tool_dispatch['recipients']++;
                    }
                }
            }

            if (function_exists('afb_log') && $recipient_tool_dispatch['tools'] > 0) {
                afb_log(
                    '[DEBUG] recipient tool dispatch ' . wp_json_encode($recipient_tool_dispatch),
                    'afb-debug.log'
                );
            }
        }
    }

    // Keep secrets server-side for enrich
    $multi_home = [];
    $has_per_account_override = false;
    $multi_home_by_account = get_option('afb_multi_home_projects_by_account', []);
    if (is_array($multi_home_by_account) && $matched_account_id > 0) {
        $account_key = (string) $matched_account_id;
        if (array_key_exists($account_key, $multi_home_by_account) && is_array($multi_home_by_account[$account_key])) {
            $has_per_account_override = true;
            $multi_home = $multi_home_by_account[$account_key];
        }
    }
    if (!$has_per_account_override) {
        // Backward compatibility for older global-only setting.
        $multi_home = (array) get_option('afb_multi_home_projects', []);
    }
    $multi_home = array_values(array_filter(array_map('sanitize_text_field', $multi_home)));
    $dashboard_cf_gid = sanitize_text_field((string) get_option('afb_client_gateway_create_field_gid', '1211304630299020'));
    if (!ctype_digit($dashboard_cf_gid)) {
        $dashboard_cf_gid = '1211304630299020';
    }
    $hmac_key         = hash_hmac('sha256', $task_gid, AUTH_SALT);
    $dashboard_url    = site_url("/create-dashboard?task_gid={$task_gid}&key={$hmac_key}");
    $gateway_creation_mode = function_exists('afb_get_client_gateway_creation_mode_for_project')
        ? afb_get_client_gateway_creation_mode_for_project($project_id)
        : 'manual';
    if (!in_array($gateway_creation_mode, ['manual', 'automatic'], true)) {
        $gateway_creation_mode = 'manual';
    }

    afb_secret_set($request_id, [
        'pat'                 => $pat,
        'task_gid'            => $task_gid,
        'subtask_gids'        => $subtask_summary['created_task_gids'],
        'private_task_gid'    => $private_task_gid,
        'task_name'           => $task_name,
        'project_id'          => $project_id, // ✅ store so enrich can read it
        'private_project_id'  => $private_data_project_gid,
        'private_companion_depends_on_main' => $private_companion_depends_on_main ? 1 : 0,
        'private_companion_upload_mode' => $private_companion_upload_mode,
        'private_companion_upload_modes_by_field' => $private_companion_upload_modes_by_field,
        'upload_accept_types_by_field' => $upload_accept_types_by_field,
        'upload_max_file_mb_by_field' => $upload_max_file_mb_by_field,
        'copy_html'           => $submitted_copy_html, // legacy alias
        'submitted_copy_html' => $submitted_copy_html,
        'multi_home'          => $multi_home,
        'dashboard_cf_gid'    => $dashboard_cf_gid,
        'dashboard_url'       => $dashboard_url,
        'gateway_creation_mode' => $gateway_creation_mode,

        // Optional: keep user notes around
        'asana_html_notes'    => $notes_user_plain,
    ]);

    afb_status_init($request_id, [
        'task_gid' => $task_gid,
        'phase'    => 'upload',
        'uploads'  => [
            'total'  => (int) ($_POST['upload_count'] ?? 0),
            'done'   => 0,
            'failed' => [],
        ],
        'subtasks' => [
            'total' => $subtask_summary['total'],
            'created' => $subtask_summary['created'],
            'failed' => $subtask_summary['failed'],
            'failed_rows' => $subtask_summary['failed_rows'],
        ],
        'steps'    => [
            [
                'k'  => 'create',
                'ok' => true,
                'ms' => ($r['ms'] ?? 0),
            ],
            [
                'k'       => 'subtasks',
                'ok'      => ($subtask_summary['failed'] === 0),
                'rows'    => $subtask_summary['total'],
                'created' => $subtask_summary['created'],
                'failed'  => $subtask_summary['failed'],
            ],
        ],
    ]);

    // Schedule enrich worker
    $payload = [
        'request_id'          => $request_id,
        'task_gid'            => $task_gid,
        'pat'                 => $pat,
        'task_name'           => $task_name,
        'project_id'          => $project_id,
        'submitted_copy_html' => $submitted_copy_html,
        'multi_home'          => $multi_home,
        'dashboard_cf_gid'    => $dashboard_cf_gid,
        'dashboard_url'       => $dashboard_url,
        'gateway_creation_mode' => $gateway_creation_mode,
    ];

    if (function_exists('as_schedule_single_action')) {
        as_schedule_single_action(time() + 5, 'afb_enrich_task', [$payload], 'afb');
    } else {
        wp_schedule_single_event(time() + 5, 'afb_enrich_task_event', [$payload]);
    }

    // If this task came from a triage flow, clear the saved chat for this browser
    if ((!empty($triage_state) || !empty($triage_answers)) && function_exists('triage_gpt_reset_state')) {
        triage_gpt_reset_state();
    }

    // Compact success payload
    afb_send_json_success_clean([
        'request_id' => $request_id,
        'task_gid'   => $task_gid,
        'subtasks'   => [
            'total' => $subtask_summary['total'],
            'created' => $subtask_summary['created'],
            'failed' => $subtask_summary['failed'],
            'failed_rows' => $subtask_summary['failed_rows'],
            'task_gids' => $subtask_summary['created_task_gids'],
        ],
        'private_task_gid' => $private_task_gid,
        'private_project_gid' => $private_data_project_gid,
        'private_companion_depends_on_main' => $private_companion_depends_on_main ? 1 : 0,
        'private_task_dependency_status' => $private_task_dependency_status,
        'private_companion_upload_mode' => $private_companion_upload_mode,
        'private_task_status' => $private_task_status,
        'phase'      => 'upload',
        'gateway_creation_mode' => $gateway_creation_mode,
        'submitted_fields' => array_map(
            function ($v) {
                if (is_array($v)) return '[array]';
                $s = (string) $v;
                return (mb_strlen($s) > 200) ? mb_substr($s, 0, 200) . '…' : $s;
            },
            $custom_fields
        ),
        'job_number' => $custom_fields[$job_number_gid] ?? '',
        'task_name'  => $task_name,
    ]);
}

add_action('wp_ajax_afb_create_task_fast',        'afb_api_create_task_fast');
add_action('wp_ajax_nopriv_afb_create_task_fast', 'afb_api_create_task_fast');
