<?php
// admin/ajax-handlers.php

function afb_maybe_decrypt( $token ) {
    $plain = afb_decrypt( $token );
    return $plain ?: $token;
}

add_action('wp_ajax_afb_send_email_settings_test', 'afb_send_email_settings_test_ajax');

function afb_send_email_settings_test_ajax() {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Unauthorized.'], 403);
    }

    check_ajax_referer('afb_email_settings_test_nonce', 'nonce');

    $template = sanitize_key((string)($_POST['template'] ?? ''));
    $requested_to = sanitize_email((string)($_POST['to'] ?? ''));

    $current_user = wp_get_current_user();
    $fallback_user_to = sanitize_email((string)($current_user->user_email ?? ''));
    $admin_to = sanitize_email((string)get_option('admin_email', ''));

    $to = is_email($requested_to)
        ? $requested_to
        : (is_email($fallback_user_to) ? $fallback_user_to : $admin_to);

    if (!is_email($to)) {
        wp_send_json_error(['message' => 'No valid recipient email found.'], 400);
    }

    $display_name = sanitize_text_field((string)($current_user->display_name ?? 'Admin User'));
    if ($display_name === '') {
        $display_name = 'Admin User';
    }

    $task_name = 'Email Template Test Request';
    $task_gid = 'TEST-001';
    $gateway_url = home_url('/task-dashboard/test/');

    $sent = false;
    $buffered_output = '';

    ob_start();
    try {
        if ($template === 'submission') {
            if (!function_exists('afb_send_submission_email')) {
                wp_send_json_error(['message' => 'Submission email sender is unavailable.'], 500);
            }

            $fields_html = '<ul>'
                . '<li><strong>Job Number:</strong> TEST-001</li>'
                . '<li><strong>First Name:</strong> Test</li>'
                . '<li><strong>Last Name:</strong> User</li>'
                . '<li><strong>Email Address:</strong> ' . esc_html($to) . '</li>'
                . '</ul>';

            $sent = afb_send_submission_email([
                'to' => $to,
                'task_name' => $task_name,
                'request_number' => $task_gid,
                'job_number' => $task_gid,
                'comms_link' => home_url('/task-dashboard/test/?task_gid=' . rawurlencode($task_gid)),
                'fields_html' => $fields_html,
                'attachments' => [],
            ]);
        } elseif ($template === 'client_gateway_created') {
            if (!function_exists('afb_send_client_gateway_created_email')) {
                wp_send_json_error(['message' => 'Client Gateway email sender is unavailable.'], 500);
            }

            $sent = afb_send_client_gateway_created_email([
                'to' => $to,
                'name' => $display_name,
                'task_name' => $task_name,
                'task_gid' => $task_gid,
                'job_number' => $task_gid,
                'gateway_url' => $gateway_url,
                'gateway_pin' => '123456',
                'attachments' => [],
            ]);
        } elseif ($template === 'client_gateway_completed') {
            if (!function_exists('afb_send_client_gateway_created_email')) {
                wp_send_json_error(['message' => 'Client Gateway email sender is unavailable.'], 500);
            }

            $completion_subject = (string) get_option('afb_cg_completion_email_subject', 'Request complete: {{task_name}}');
            $completion_body = (string) get_option(
                'afb_cg_completion_email_body',
                '<p>Hi {{first_name}},</p><p>Your request for "<strong>{{task_name}}</strong>" is now complete.</p><p>Please review the completed request and submit feedback using your Client Gateway below.</p>'
            );

            $sent = afb_send_client_gateway_created_email([
                'to' => $to,
                'name' => $display_name,
                'task_name' => $task_name,
                'task_gid' => $task_gid,
                'job_number' => $task_gid,
                'gateway_url' => add_query_arg(['refresh' => '1'], $gateway_url),
                'gateway_pin' => '123456',
                'heading' => 'Request Complete',
                'subject_template' => $completion_subject,
                'body_template' => $completion_body,
                'attachments' => [],
            ]);
        } elseif ($template === 'gateway_comment') {
            if (!function_exists('afb_send_gateway_notification_email')) {
                wp_send_json_error(['message' => 'Gateway notification sender is unavailable.'], 500);
            }

            $sent = afb_send_gateway_notification_email([
                'to' => $to,
                'name' => $display_name,
                'event' => 'comment',
                'task_name' => $task_name,
                'gateway_url' => $gateway_url,
                'actor_name' => 'Client Tester',
                'actor_email' => 'client@example.com',
                'comment' => 'This is a test comment generated from Email Notifications settings.',
            ]);
        } elseif ($template === 'gateway_upload') {
            if (!function_exists('afb_send_gateway_notification_email')) {
                wp_send_json_error(['message' => 'Gateway notification sender is unavailable.'], 500);
            }

            $sent = afb_send_gateway_notification_email([
                'to' => $to,
                'name' => $display_name,
                'event' => 'upload',
                'task_name' => $task_name,
                'gateway_url' => $gateway_url,
                'actor_name' => 'Client Tester',
                'actor_email' => 'client@example.com',
                'filename' => 'test-upload.pdf',
            ]);
        } else {
            wp_send_json_error(['message' => 'Unknown email template requested.'], 400);
        }
    } catch (Throwable $e) {
        $buffered_output = (string) ob_get_clean();

        if (function_exists('afb_debug_log')) {
            afb_debug_log('❌ Email settings test exception [' . $template . ']: ' . $e->getMessage());
            if (trim($buffered_output) !== '') {
                afb_debug_log('⚠️ Email settings test buffered output [' . $template . ']: ' . substr(trim(wp_strip_all_tags($buffered_output)), 0, 400));
            }
        }

        wp_send_json_error(['message' => 'Test email exception: ' . $e->getMessage()], 500);
    }

    $buffered_output = (string) ob_get_clean();
    if (trim($buffered_output) !== '' && function_exists('afb_debug_log')) {
        afb_debug_log('⚠️ Email settings test buffered output [' . $template . ']: ' . substr(trim(wp_strip_all_tags($buffered_output)), 0, 400));
    }

    if (!$sent) {
        $phpmailer_error = '';
        if (isset($GLOBALS['phpmailer']) && !empty($GLOBALS['phpmailer']->ErrorInfo)) {
            $phpmailer_error = (string) $GLOBALS['phpmailer']->ErrorInfo;
        }

        $message = 'wp_mail returned false while sending the test email.';
        if ($phpmailer_error !== '') {
            $message .= ' PHPMailer: ' . $phpmailer_error;
        }

        wp_send_json_error(['message' => $message], 500);
    }

    wp_send_json_success([
        'message' => 'Test email sent to ' . $to,
        'to' => $to,
        'template' => $template,
    ]);
}



add_action('wp_ajax_afb_gf_editor_context', function () {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Unauthorized.'], 403);
    }

    check_ajax_referer('afb_gf_editor_nonce', 'nonce');

    $pat_id = isset($_POST['pat_id']) ? (int)$_POST['pat_id'] : 0;
    if ($pat_id <= 0) {
        wp_send_json_error(['message' => 'Missing PAT account id.'], 400);
    }

    $workspace_gid = preg_replace('/\D+/', '', (string)($_POST['workspace_gid'] ?? ''));
    $target = ((string)($_POST['target'] ?? 'workspace') === 'portfolio') ? 'portfolio' : 'workspace';
    $portfolio_gid = preg_replace('/\D+/', '', (string)($_POST['portfolio_gid'] ?? ''));

    $context = afb_gf_load_editor_context($pat_id, $workspace_gid, $target, $portfolio_gid);
    if (!empty($context['error'])) {
        wp_send_json_error(['message' => (string)$context['error']], 400);
    }

    wp_send_json_success($context);
});

add_action('wp_ajax_afb_gf_editor_save', function () {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Unauthorized.'], 403);
    }

    check_ajax_referer('afb_gf_editor_nonce', 'nonce');

    $result = afb_gf_save_mapping([
        'pat_id' => (int)($_POST['pat_id'] ?? 0),
        'workspace_gid' => (string)($_POST['workspace_gid'] ?? ''),
        'target' => (string)($_POST['target'] ?? 'workspace'),
        'portfolio_gid' => (string)($_POST['portfolio_gid'] ?? ''),
        'first_name_gid' => (string)($_POST['first_name_gid'] ?? ''),
        'last_name_gid' => (string)($_POST['last_name_gid'] ?? ''),
        'email_gid' => (string)($_POST['email_gid'] ?? ''),
        'gateway_create_gid' => (string)($_POST['gateway_create_gid'] ?? ''),
        'gateway_link_gid' => (string)($_POST['gateway_link_gid'] ?? ''),
        'gateway_pin_gid' => (string)($_POST['gateway_pin_gid'] ?? ''),
    ]);

    if (empty($result['ok'])) {
        wp_send_json_error(['message' => (string)($result['message'] ?? 'Save failed.')], 400);
    }

    wp_send_json_success(['message' => (string)($result['message'] ?? 'Mapping saved.')]);
});
require_once plugin_dir_path(__FILE__) . '/../includes/security.php'; // Ensure afb_decrypt is available
require_once plugin_dir_path(__FILE__) . '/global-field-map-helpers.php';

// ✅ Secure AJAX: Fetch sections for a selected project (uses POST)
add_action( 'wp_ajax_afb_get_project_sections', function () {

    // ── 1. capability check ──────────────────────────────────
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( 'Unauthorized', 403 );
    }

    // ── 2. grab & sanitise POST vars ─────────────────────────
    $project_gid   = isset( $_POST['project_gid'] ) ? sanitize_text_field( $_POST['project_gid'] ) : '';
    $pat_encrypted = isset( $_POST['pat'] )        ? sanitize_text_field( $_POST['pat'] )        : '';

    if ( empty( $project_gid ) || empty( $pat_encrypted ) ) {
        wp_send_json_error( 'Missing parameters', 400 );
    }

    // ── 3. decrypt the PAT (raw token if already plain) ──────
    $pat = afb_maybe_decrypt( $pat_encrypted );
    if ( empty( $pat ) ) {
        wp_send_json_error( 'PAT decrypt failed', 403 );
    }

    // ── 4. call the API helper ───────────────────────────────
    $sections = afb_get_project_sections( $project_gid, $pat );

    // ── 5. always send back a valid json object ──────────────
    if ( empty( $sections ) ) {
        wp_send_json_success( [] );          // ← empty array keeps JS happy
    }

    wp_send_json_success( $sections );
} );


// ✅ Fetch custom fields for a given project (multi-PAT aware + encrypted)
add_action('wp_ajax_afb_get_project_fields', function () {
    if (!isset($_POST['project_id'])) {
        wp_send_json_error(['message' => 'No project ID provided.']);
    }

    global $wpdb;
    $project_id = sanitize_text_field($_POST['project_id']);
    $table      = $wpdb->prefix . 'asana_accounts';
    $accounts   = $wpdb->get_results("SELECT * FROM $table WHERE active = 1", ARRAY_A);

    $pat_to_use = null;

    foreach ($accounts as $account) {
        $decrypted_pat = afb_decrypt($account['pat']);
        $projects = afb_get_all_projects($decrypted_pat, $account['portfolio_gid']);
        foreach ($projects as $project) {
            if ($project['gid'] === $project_id) {
                $pat_to_use = $decrypted_pat;
                break 2;
            }
        }
    }

    if (!$pat_to_use) {
        wp_send_json_error(['message' => 'Project not found in any account.']);
    }

    $fields = afb_get_project_fields($project_id, $pat_to_use);
    if ($fields === false) {
        wp_send_json_error(['message' => 'Failed to fetch fields from Asana.']);
    }

    $config         = afb_get_project_field_config();
    $allowed_fields = $config[$project_id]['used_fields'] ?? [];

    if (empty($allowed_fields)) {
        wp_send_json_success(['fields' => $fields]);
    }

    $filtered = array_values(array_filter($fields, function ($setting) use ($allowed_fields) {
        return in_array($setting['custom_field']['gid'], $allowed_fields, true);
    }));

    wp_send_json_success(['fields' => $filtered]);
});

// ✅ Delete a single afb_task entry via AJAX
add_action('wp_ajax_afb_delete_task_comms_entry', function () {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Not authorized.']);
    }

    $post_id = intval($_POST['post_id'] ?? 0);
    if ($post_id && get_post_type($post_id) === 'afb_task') {
        wp_delete_post($post_id, true);
        wp_send_json_success();
    } else {
        wp_send_json_error(['message' => 'Invalid post ID.']);
    }
});

add_action('wp_ajax_afb_rewrite_single_input_copy', 'afb_rewrite_single_input_copy_ajax');
add_action('wp_ajax_nopriv_afb_rewrite_single_input_copy', 'afb_rewrite_single_input_copy_ajax');

function afb_rewrite_single_input_copy_ajax() {
    if (false === check_ajax_referer('afb_ai_rewrite', 'nonce', false)) {
        wp_send_json_error(['message' => 'Invalid request token. Please refresh and try again.'], 403);
    }

    $raw_text = isset($_POST['text']) ? wp_unslash((string) $_POST['text']) : '';
    $text = sanitize_textarea_field($raw_text);
    if ($text === '') {
        wp_send_json_error(['message' => 'Please enter text before using AI rewrite.'], 400);
    }

    $text_len = function_exists('mb_strlen') ? mb_strlen($text) : strlen($text);
    if ($text_len > 8000) {
        wp_send_json_error(['message' => 'This text is too long for AI rewrite. Please shorten it and try again.'], 400);
    }

    $mode_raw = isset($_POST['mode']) ? sanitize_text_field((string) $_POST['mode']) : 'textarea';
    $mode = ($mode_raw === 'text') ? 'text' : 'textarea';
    $max_words = isset($_POST['max_words']) ? max(0, absint($_POST['max_words'])) : 0;
    $preset_raw = isset($_POST['preset']) ? sanitize_key((string) $_POST['preset']) : 'improve';
    $preset = in_array($preset_raw, ['improve', 'shorter', 'formal', 'friendly', 'plain'], true)
        ? $preset_raw
        : 'improve';

    $ai_enabled_raw = function_exists('triage_get_setting') ? triage_get_setting('triage_gpt_ai_enabled', '0') : '0';
    $api_key_raw = function_exists('triage_get_setting') ? triage_get_setting('triage_gpt_openai_api_key', '') : '';
    $model_raw = function_exists('triage_get_setting') ? triage_get_setting('triage_gpt_model', 'gpt-4o-mini') : 'gpt-4o-mini';

    $ai_enabled = in_array($ai_enabled_raw, ['1', 1, true, 'true'], true);
    $api_key = is_string($api_key_raw) ? trim($api_key_raw) : '';
    $model = is_string($model_raw) && trim($model_raw) !== '' ? trim($model_raw) : 'gpt-4o-mini';

    if (!$ai_enabled || $api_key === '') {
        wp_send_json_error(['message' => 'AI rewrite is not available right now. Please contact support.'], 503);
    }

    $openai_url = defined('TRIAGE_GPT_OPENAI_URL')
        ? TRIAGE_GPT_OPENAI_URL
        : 'https://api.openai.com/v1/chat/completions';

    $mode_instruction = $mode === 'text'
        ? 'Keep the output concise and suitable for a single-line style input.'
        : 'Keep paragraph structure where helpful and improve readability.';

    $preset_instructions = [
        'improve'  => 'Improve clarity and flow while preserving the original intent.',
        'shorter'  => 'Make it shorter and more concise while preserving key meaning.',
        'formal'   => 'Use a professional and formal tone.',
        'friendly' => 'Use a warm and friendly tone.',
        'plain'    => 'Use plain English with simple wording.',
    ];
    $preset_instruction = $preset_instructions[$preset] ?? $preset_instructions['improve'];

    $limit_instruction = $max_words > 0
        ? 'Return no more than ' . $max_words . ' words. Do not exceed this limit.'
        : '';

    $system_prompt = 'You are a writing assistant that rewrites user-provided copy for clarity, grammar, tone, and readability while preserving intent and factual meaning. Follow all explicit constraints exactly. Return only the rewritten copy with no prefacing labels.';
    $user_prompt = "Rewrite the following copy.\n"
        . $mode_instruction . "\n"
        . $preset_instruction . "\n"
        . $limit_instruction . "\n\n"
        . "Original copy:\n" . $text;

    $count_words = static function ($value) {
        $tokens = preg_split('/\s+/', trim((string) $value));
        if (!is_array($tokens)) {
            return 0;
        }

        $tokens = array_values(array_filter($tokens, static function ($token) {
            return $token !== '';
        }));

        return count($tokens);
    };

    $truncate_to_words = static function ($value, $max_word_count) {
        $max = max(0, (int) $max_word_count);
        $text_value = trim((string) $value);
        if ($max <= 0 || $text_value === '') {
            return $text_value;
        }

        $tokens = preg_split('/\s+/', $text_value);
        if (!is_array($tokens)) {
            return $text_value;
        }

        $tokens = array_values(array_filter($tokens, static function ($token) {
            return $token !== '';
        }));

        if (count($tokens) <= $max) {
            return $text_value;
        }

        return implode(' ', array_slice($tokens, 0, $max));
    };

    $run_completion = static function (array $messages, $temperature, $max_tokens) use ($openai_url, $api_key, $model) {
        $payload = [
            'model' => $model,
            'messages' => $messages,
            'temperature' => (float) $temperature,
            'max_tokens' => max(32, (int) $max_tokens),
        ];

        $response = wp_remote_post($openai_url, [
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json',
            ],
            'body' => wp_json_encode($payload),
            'timeout' => 30,
        ]);

        if (is_wp_error($response)) {
            return ['ok' => false, 'message' => 'AI rewrite failed to connect. Please try again.'];
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        if ($code !== 200) {
            return ['ok' => false, 'message' => 'AI rewrite returned an error. Please try again.'];
        }

        $body = wp_remote_retrieve_body($response);
        $decoded = json_decode($body, true);
        $text_out = trim((string) ($decoded['choices'][0]['message']['content'] ?? ''));
        $text_out = preg_replace('/^```(?:[a-zA-Z0-9_-]+)?\s*|\s*```$/', '', $text_out);
        $text_out = trim((string) $text_out);

        if ($text_out === '') {
            return ['ok' => false, 'message' => 'AI rewrite returned an empty response. Please try again.'];
        }

        return ['ok' => true, 'text' => $text_out];
    };

    $initial = $run_completion([
        ['role' => 'system', 'content' => $system_prompt],
        ['role' => 'user', 'content' => $user_prompt],
    ], 0.5, ($mode === 'text') ? 240 : 800);

    if (empty($initial['ok'])) {
        wp_send_json_error(['message' => (string) ($initial['message'] ?? 'AI rewrite failed. Please try again.')], 502);
    }

    $rewritten = trim((string) ($initial['text'] ?? ''));
    $initial_word_count = $count_words($rewritten);
    $used_compression_pass = false;
    $used_hard_trim = false;

    // Hard-enforce max words when the field has max-word mode enabled.
    if ($max_words > 0 && $count_words($rewritten) > $max_words) {
        $compress_prompt = 'Rewrite the following text to no more than ' . $max_words . ' words. Preserve meaning and tone. Return only the rewritten text.'
            . "\n\nText:\n" . $rewritten;

        $compressed = $run_completion([
            ['role' => 'system', 'content' => $system_prompt],
            ['role' => 'user', 'content' => $compress_prompt],
        ], 0.2, ($mode === 'text') ? 180 : 500);

        if (!empty($compressed['ok'])) {
            $used_compression_pass = true;
            $rewritten = trim((string) ($compressed['text'] ?? $rewritten));
        }

        if ($count_words($rewritten) > $max_words) {
            $used_hard_trim = true;
            $rewritten = $truncate_to_words($rewritten, $max_words);
        }
    }

    $final_word_count = $count_words($rewritten);
    $limit_applied = $max_words > 0;
    $within_limit = $limit_applied ? ($final_word_count <= $max_words) : true;

    wp_send_json_success([
        'rewritten' => trim((string) $rewritten),
        'word_count' => $final_word_count,
        'max_words' => $max_words,
        'limit_applied' => $limit_applied,
        'within_limit' => $within_limit,
        'used_compression_pass' => $used_compression_pass,
        'used_hard_trim' => $used_hard_trim,
        'was_shortened' => $final_word_count < $initial_word_count,
        'preset' => $preset,
    ]);
}

// Save field-flow config without reloading the admin page.
add_action('wp_ajax_afb_save_field_flow_config', function () {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Unauthorized.'], 403);
    }

    check_ajax_referer('afb_field_flow_save', 'afb_field_flow_nonce');

    $gid = isset($_POST['config_project_gid']) ? sanitize_text_field($_POST['config_project_gid']) : '';
    if ($gid === '') {
        wp_send_json_error(['message' => 'Missing project id.'], 400);
    }

    $field_config             = get_option('afb_project_field_config', []);
    $used_fields              = isset($_POST['used_fields']) ? array_map('sanitize_text_field', (array) $_POST['used_fields']) : [];
    $field_order              = isset($_POST['field_order']) ? array_map('sanitize_text_field', (array) $_POST['field_order']) : [];
    $required_fields          = isset($_POST['required_fields']) ? array_map('sanitize_text_field', (array) $_POST['required_fields']) : [];
    $hidden_fields            = isset($_POST['hidden_fields']) ? array_map('sanitize_text_field', (array) $_POST['hidden_fields']) : [];
    $readonly_fields          = isset($_POST['readonly_fields']) ? array_map('sanitize_text_field', (array) $_POST['readonly_fields']) : [];
    $paragraph_fields         = isset($_POST['paragraph_fields']) ? array_map('sanitize_text_field', (array) $_POST['paragraph_fields']) : [];
    $custom_paragraphs        = [];
    if (!empty($_POST['custom_paragraphs']) && is_array($_POST['custom_paragraphs'])) {
        foreach ($_POST['custom_paragraphs'] as $paragraph_id => $paragraph_html) {
            $paragraph_id = sanitize_text_field((string) $paragraph_id);
            if ($paragraph_id === '') {
                continue;
            }
            $custom_paragraphs[$paragraph_id] = wp_kses_post(wp_unslash((string) $paragraph_html));
        }
    }
    $custom_paragraph_titles  = isset($_POST['custom_paragraph_titles']) ? array_map('sanitize_text_field', $_POST['custom_paragraph_titles']) : [];
    $custom_sections          = isset($_POST['custom_sections']) ? array_map('sanitize_textarea_field', $_POST['custom_sections']) : [];
    $custom_section_titles    = isset($_POST['custom_section_titles']) ? array_map('sanitize_text_field', $_POST['custom_section_titles']) : [];
    $submit_block_fields      = isset($_POST['submit_block_fields'])
        ? array_map('sanitize_text_field', (array) $_POST['submit_block_fields'])
        : [];
    $custom_agreement_labels  = isset($_POST['custom_agreement_labels']) ? array_map('sanitize_text_field', $_POST['custom_agreement_labels']) : [];
    $custom_agreement_details = isset($_POST['custom_agreement_details']) ? array_map('sanitize_textarea_field', $_POST['custom_agreement_details']) : [];
    $agreement_required_fields = isset($_POST['agreement_required_fields']) ? array_map('sanitize_text_field', (array) $_POST['agreement_required_fields']) : [];
    $agreement_description_fields = isset($_POST['agreement_description_fields']) ? array_map('sanitize_text_field', (array) $_POST['agreement_description_fields']) : [];
    $custom_upload_titles     = isset($_POST['custom_upload_titles']) ? array_map('sanitize_text_field', $_POST['custom_upload_titles']) : [];
    $custom_upload_details    = isset($_POST['custom_upload_details']) ? array_map('sanitize_textarea_field', $_POST['custom_upload_details']) : [];
    $custom_upload_min_files  = isset($_POST['custom_upload_min_files']) ? array_map('absint', $_POST['custom_upload_min_files']) : [];
    $custom_upload_private_modes = [];
    if (!empty($_POST['custom_upload_private_modes']) && is_array($_POST['custom_upload_private_modes'])) {
        foreach ($_POST['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;
            }

            $custom_upload_private_modes[$upload_id] = $upload_mode;
        }
    }
    $custom_upload_accept_types = [];
    if (!empty($_POST['custom_upload_accept_types']) && is_array($_POST['custom_upload_accept_types'])) {
        foreach ($_POST['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)) {
                $custom_upload_accept_types[$upload_id] = implode(',', array_keys($accept_tokens));
            }
        }
    }
    $custom_upload_compress_images = isset($_POST['custom_upload_compress_images'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', (array) $_POST['custom_upload_compress_images']), static function ($upload_id) {
            return $upload_id !== '' && strpos($upload_id, 'upload_') === 0;
        })))
        : [];
    $custom_upload_max_file_mb = [];
    if (!empty($_POST['custom_upload_max_file_mb']) && is_array($_POST['custom_upload_max_file_mb'])) {
        foreach ($_POST['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_text = str_replace(',', '.', trim((string) $max_file_mb_raw));
            if ($max_file_mb_text === '' || !is_numeric($max_file_mb_text)) {
                continue;
            }

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

            $custom_upload_max_file_mb[$upload_id] = min(512, round($max_file_mb, 2));
        }
    }
    $custom_upload_max_width_px = [];
    if (!empty($_POST['custom_upload_max_width_px']) && is_array($_POST['custom_upload_max_width_px'])) {
        foreach ($_POST['custom_upload_max_width_px'] as $upload_id_raw => $max_width_raw) {
            $upload_id = sanitize_text_field((string) $upload_id_raw);
            if ($upload_id === '' || strpos($upload_id, 'upload_') !== 0) {
                continue;
            }

            $max_width_text = str_replace(',', '.', trim((string) $max_width_raw));
            if ($max_width_text === '' || !is_numeric($max_width_text)) {
                continue;
            }

            $max_width = (int) round((float) $max_width_text);
            if ($max_width <= 0) {
                continue;
            }

            $custom_upload_max_width_px[$upload_id] = max(100, min(4000, $max_width));
        }
    }
    $custom_upload_compress_quality = [];
    if (!empty($_POST['custom_upload_compress_quality']) && is_array($_POST['custom_upload_compress_quality'])) {
        foreach ($_POST['custom_upload_compress_quality'] as $upload_id_raw => $quality_raw) {
            $upload_id = sanitize_text_field((string) $upload_id_raw);
            if ($upload_id === '' || strpos($upload_id, 'upload_') !== 0) {
                continue;
            }

            $quality_text = trim((string) $quality_raw);
            if ($quality_text === '' || !is_numeric($quality_text)) {
                continue;
            }

            $custom_upload_compress_quality[$upload_id] = max(1, min(100, (int) round((float) $quality_text)));
        }
    }
    $custom_single_input_labels = isset($_POST['custom_single_input_labels']) ? array_map('sanitize_text_field', $_POST['custom_single_input_labels']) : [];
    $custom_single_input_descriptions = isset($_POST['custom_single_input_descriptions']) ? array_map('sanitize_textarea_field', $_POST['custom_single_input_descriptions']) : [];
    $custom_single_input_placeholders = isset($_POST['custom_single_input_placeholders']) ? array_map('sanitize_text_field', $_POST['custom_single_input_placeholders']) : [];
    $custom_single_input_sync_fields = isset($_POST['custom_single_input_sync_fields']) ? array_map('sanitize_text_field', $_POST['custom_single_input_sync_fields']) : [];
    $custom_single_input_modes = [];
    if (!empty($_POST['custom_single_input_modes']) && is_array($_POST['custom_single_input_modes'])) {
        foreach ($_POST['custom_single_input_modes'] as $single_id => $single_mode_raw) {
            $single_id = sanitize_text_field((string) $single_id);
            if ($single_id === '') {
                continue;
            }

            $single_mode = sanitize_text_field((string) $single_mode_raw);
            if (!in_array($single_mode, ['text', 'textarea'], true)) {
                $single_mode = 'text';
            }

            $custom_single_input_modes[$single_id] = $single_mode;
        }
    }
    $custom_single_input_max_words_enabled = isset($_POST['custom_single_input_max_words_enabled'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', (array) $_POST['custom_single_input_max_words_enabled']), static function ($val) {
            return $val !== '';
        })))
        : [];
    $custom_single_input_max_words = [];
    if (!empty($_POST['custom_single_input_max_words']) && is_array($_POST['custom_single_input_max_words'])) {
        foreach ($_POST['custom_single_input_max_words'] as $single_id => $max_words_raw) {
            $single_id = sanitize_text_field((string) $single_id);
            if ($single_id === '') {
                continue;
            }

            $custom_single_input_max_words[$single_id] = max(0, absint($max_words_raw));
        }
    }
    $custom_single_input_ai_enabled = isset($_POST['custom_single_input_ai_enabled'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', (array) $_POST['custom_single_input_ai_enabled']), static function ($val) {
            return $val !== '';
        })))
        : [];
    $custom_dropdown_labels = isset($_POST['custom_dropdown_labels']) ? array_map('sanitize_text_field', $_POST['custom_dropdown_labels']) : [];
    $custom_dropdown_descriptions = isset($_POST['custom_dropdown_descriptions']) ? array_map('sanitize_textarea_field', $_POST['custom_dropdown_descriptions']) : [];
    $custom_dropdown_placeholders = isset($_POST['custom_dropdown_placeholders']) ? array_map('sanitize_text_field', $_POST['custom_dropdown_placeholders']) : [];
    $custom_dropdown_options = isset($_POST['custom_dropdown_options']) ? array_map('sanitize_textarea_field', $_POST['custom_dropdown_options']) : [];
    $custom_dropdown_selection_modes = isset($_POST['custom_dropdown_selection_modes']) ? array_map('sanitize_text_field', $_POST['custom_dropdown_selection_modes']) : [];
    $custom_dropdown_sync_fields = isset($_POST['custom_dropdown_sync_fields']) ? array_map('sanitize_text_field', $_POST['custom_dropdown_sync_fields']) : [];
    $custom_calendar_labels = isset($_POST['custom_calendar_labels']) ? array_map('sanitize_text_field', $_POST['custom_calendar_labels']) : [];
    $custom_calendar_descriptions = isset($_POST['custom_calendar_descriptions']) ? array_map('sanitize_textarea_field', $_POST['custom_calendar_descriptions']) : [];
    $custom_calendar_placeholders = isset($_POST['custom_calendar_placeholders']) ? array_map('sanitize_text_field', $_POST['custom_calendar_placeholders']) : [];
    $custom_calendar_sync_modes = isset($_POST['custom_calendar_sync_modes']) ? array_map('sanitize_text_field', $_POST['custom_calendar_sync_modes']) : [];
    $custom_calendar_sync_fields = isset($_POST['custom_calendar_sync_fields']) ? array_map('sanitize_text_field', $_POST['custom_calendar_sync_fields']) : [];
    $custom_repeater_labels = isset($_POST['custom_repeater_labels']) ? array_map('sanitize_text_field', $_POST['custom_repeater_labels']) : [];
    $custom_repeater_descriptions = isset($_POST['custom_repeater_descriptions']) ? array_map('sanitize_textarea_field', $_POST['custom_repeater_descriptions']) : [];
    $custom_repeater_save_mode = [];
    if (!empty($_POST['custom_repeater_save_mode']) && is_array($_POST['custom_repeater_save_mode'])) {
        foreach ($_POST['custom_repeater_save_mode'] as $repeater_id_raw => $save_mode_raw) {
            $repeater_id = sanitize_text_field((string) $repeater_id_raw);
            if ($repeater_id === '') {
                continue;
            }

            $save_mode = sanitize_text_field((string) $save_mode_raw);
            $custom_repeater_save_mode[$repeater_id] = ($save_mode === 'description') ? 'description' : 'subtasks';
        }
    }
    $custom_repeater_columns = [];
    if (!empty($_POST['custom_repeater_columns']) && is_array($_POST['custom_repeater_columns'])) {
        foreach ($_POST['custom_repeater_columns'] as $repeater_id_raw => $repeater_columns_raw) {
            $repeater_id = sanitize_text_field((string) $repeater_id_raw);
            if ($repeater_id === '') {
                continue;
            }

            $repeater_columns = sanitize_textarea_field((string) $repeater_columns_raw);
            if ($repeater_columns === '') {
                continue;
            }

            $custom_repeater_columns[$repeater_id] = $repeater_columns;
        }
    }
    $custom_repeater_min_rows = [];
    if (!empty($_POST['custom_repeater_min_rows']) && is_array($_POST['custom_repeater_min_rows'])) {
        foreach ($_POST['custom_repeater_min_rows'] as $repeater_id_raw => $min_rows_raw) {
            $repeater_id = sanitize_text_field((string) $repeater_id_raw);
            if ($repeater_id === '') {
                continue;
            }

            $custom_repeater_min_rows[$repeater_id] = max(0, min(250, absint($min_rows_raw)));
        }
    }
    $custom_repeater_max_rows = [];
    if (!empty($_POST['custom_repeater_max_rows']) && is_array($_POST['custom_repeater_max_rows'])) {
        foreach ($_POST['custom_repeater_max_rows'] as $repeater_id_raw => $max_rows_raw) {
            $repeater_id = sanitize_text_field((string) $repeater_id_raw);
            if ($repeater_id === '') {
                continue;
            }

            $max_rows = max(0, min(250, absint($max_rows_raw)));
            if ($max_rows <= 0) {
                continue;
            }

            $custom_repeater_max_rows[$repeater_id] = $max_rows;
        }
    }
    foreach ($custom_repeater_min_rows as $repeater_id => $min_rows) {
        if (!isset($custom_repeater_max_rows[$repeater_id])) {
            continue;
        }

        if ((int) $custom_repeater_max_rows[$repeater_id] < (int) $min_rows) {
            $custom_repeater_max_rows[$repeater_id] = (int) $min_rows;
        }
    }
    $custom_repeater_field_map = [];
    if (!empty($_POST['custom_repeater_field_map']) && is_array($_POST['custom_repeater_field_map'])) {
        foreach ($_POST['custom_repeater_field_map'] as $repeater_id_raw => $mapped_field_ids_raw) {
            $repeater_id = sanitize_text_field((string) $repeater_id_raw);
            if ($repeater_id === '') {
                continue;
            }

            $mapped_field_ids = is_array($mapped_field_ids_raw)
                ? $mapped_field_ids_raw
                : [$mapped_field_ids_raw];

            $seen_mapped = [];
            $cleaned_mapped = [];
            foreach ($mapped_field_ids as $mapped_gid_raw) {
                $mapped_gid = sanitize_text_field((string) $mapped_gid_raw);
                if ($mapped_gid === '' || !ctype_digit($mapped_gid) || isset($seen_mapped[$mapped_gid])) {
                    continue;
                }

                $seen_mapped[$mapped_gid] = true;
                $cleaned_mapped[] = $mapped_gid;
            }

            if (!empty($cleaned_mapped)) {
                $custom_repeater_field_map[$repeater_id] = $cleaned_mapped;
            }
        }
    }
    $custom_text_sync_fields = [];
    if (!empty($_POST['custom_text_sync_fields']) && is_array($_POST['custom_text_sync_fields'])) {
        foreach ($_POST['custom_text_sync_fields'] as $source_gid_raw => $target_gid_raw) {
            $source_gid = sanitize_text_field((string) $source_gid_raw);
            $target_gid = sanitize_text_field((string) $target_gid_raw);
            if ($source_gid === '' || $target_gid === '' || !ctype_digit($source_gid) || !ctype_digit($target_gid) || $source_gid === $target_gid) {
                continue;
            }

            $custom_text_sync_fields[$source_gid] = $target_gid;
        }
    }
    $custom_calendar_preset_modes = isset($_POST['custom_calendar_preset_modes']) ? array_map('sanitize_text_field', $_POST['custom_calendar_preset_modes']) : [];
    $custom_task_name_labels = isset($_POST['custom_task_name_labels']) ? array_map('sanitize_text_field', $_POST['custom_task_name_labels']) : [];
    $custom_task_name_templates = [];
    if (!empty($_POST['custom_task_name_templates']) && is_array($_POST['custom_task_name_templates'])) {
        foreach ($_POST['custom_task_name_templates'] as $task_name_id => $task_name_template) {
            $task_name_id = sanitize_text_field((string) $task_name_id);
            if ($task_name_id === '') {
                continue;
            }

            $custom_task_name_templates[$task_name_id] = sanitize_text_field((string) $task_name_template);
        }
    }
    $private_companion_task_name_template = sanitize_text_field((string) ($_POST['private_companion_task_name_template'] ?? ''));
    $private_companion_task_name_mode = sanitize_text_field((string) ($_POST['private_companion_task_name_mode'] ?? 'use_main'));
    if (!in_array($private_companion_task_name_mode, ['use_main', 'use_job_number', 'use_template'], true)) {
        $private_companion_task_name_mode = 'use_main';
    }
    $private_data_companion_enabled = !empty($_POST['private_data_companion_enabled']) ? 1 : 0;
    $private_companion_depends_on_main = !empty($_POST['private_companion_depends_on_main']) ? 1 : 0;
    $private_data_project_gid = sanitize_text_field((string) ($_POST['private_data_project_gid'] ?? ''));
    if (!ctype_digit($private_data_project_gid)) {
        $private_data_project_gid = '';
    }
    $private_companion_upload_mode = sanitize_text_field((string) ($_POST['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_field_targets = [];
    if (!empty($_POST['private_companion_field_targets']) && is_array($_POST['private_companion_field_targets'])) {
        foreach ($_POST['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;
        }
    }

    $custom_due_date_default_base = isset($_POST['custom_due_date_default_base']) && is_array($_POST['custom_due_date_default_base'])
        ? array_map('sanitize_text_field', $_POST['custom_due_date_default_base'])
        : [];
    $custom_due_date_default_base_field = isset($_POST['custom_due_date_default_base_field']) && is_array($_POST['custom_due_date_default_base_field'])
        ? array_map('sanitize_text_field', $_POST['custom_due_date_default_base_field'])
        : [];
    $custom_due_date_default_offset = isset($_POST['custom_due_date_default_offset']) && is_array($_POST['custom_due_date_default_offset'])
        ? $_POST['custom_due_date_default_offset']
        : [];
    $custom_due_date_default_mode = isset($_POST['custom_due_date_default_mode']) && is_array($_POST['custom_due_date_default_mode'])
        ? array_map('sanitize_text_field', $_POST['custom_due_date_default_mode'])
        : [];
    $custom_due_date_default_allow_weekends = isset($_POST['custom_due_date_default_allow_weekends'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', (array) $_POST['custom_due_date_default_allow_weekends']), static function ($val) {
            return $val !== '';
        })))
        : [];
    $custom_due_date_override_manual = isset($_POST['custom_due_date_override_manual'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', (array) $_POST['custom_due_date_override_manual']), static function ($val) {
            return $val !== '';
        })))
        : [];
    $custom_due_date_rules_raw = isset($_POST['custom_due_date_rules']) && is_array($_POST['custom_due_date_rules'])
        ? $_POST['custom_due_date_rules']
        : [];

    $custom_due_date_configs = [];
    $due_date_block_ids = array_values(array_unique(array_filter(array_merge(
        array_map('strval', array_keys((array) $custom_due_date_default_base)),
        array_map('strval', array_keys((array) $custom_due_date_default_base_field)),
        array_map('strval', array_keys((array) $custom_due_date_default_offset)),
        array_map('strval', array_keys((array) $custom_due_date_default_mode)),
        array_map('strval', array_keys((array) $custom_due_date_rules_raw)),
        array_map('strval', (array) $custom_due_date_default_allow_weekends),
        array_map('strval', (array) $custom_due_date_override_manual),
        array_values(array_filter((array) $field_order, static function ($id) {
            return is_string($id) && strpos($id, 'due_date_') === 0;
        }))
    ), static function ($id) {
        return is_string($id) && strpos($id, 'due_date_') === 0;
    })));

    foreach ($due_date_block_ids as $due_id) {
        $default_base_raw = (string) ($custom_due_date_default_base[$due_id] ?? 'today');
        $default_base = in_array($default_base_raw, ['today', 'field'], true) ? $default_base_raw : 'today';
        $default_base_field = sanitize_text_field((string) ($custom_due_date_default_base_field[$due_id] ?? ''));
        $default_offset = (int) ($custom_due_date_default_offset[$due_id] ?? 0);
        if ($default_offset > 3650) {
            $default_offset = 3650;
        } elseif ($default_offset < -3650) {
            $default_offset = -3650;
        }

        $default_mode_raw = (string) ($custom_due_date_default_mode[$due_id] ?? 'business');
        $default_mode = in_array($default_mode_raw, ['business', 'calendar'], true) ? $default_mode_raw : 'business';
        $default_allow_weekends = in_array($due_id, $custom_due_date_default_allow_weekends, true) ? 1 : 0;
        $override_manual = in_array($due_id, $custom_due_date_override_manual, true) ? 1 : 0;

        $rules = [];
        $rules_json_raw = wp_unslash((string) ($custom_due_date_rules_raw[$due_id] ?? '[]'));
        $decoded_rules = json_decode($rules_json_raw, true);
        if (is_array($decoded_rules)) {
            foreach ($decoded_rules as $rule) {
                if (!is_array($rule)) {
                    continue;
                }

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

                $posted_vals = $rule['values'] ?? [];
                $rule_vals = is_array($posted_vals)
                    ? $posted_vals
                    : (($posted_vals !== '' && $posted_vals !== null) ? [$posted_vals] : []);
                $rule_vals = array_values(array_unique(array_filter(array_map('sanitize_text_field', $rule_vals), static function ($val) {
                    return $val !== '';
                })));
                if (empty($rule_vals)) {
                    continue;
                }

                $rule_base_raw = sanitize_text_field((string) ($rule['base'] ?? 'today'));
                $rule_base = in_array($rule_base_raw, ['today', 'field'], true) ? $rule_base_raw : 'today';
                $rule_base_field = sanitize_text_field((string) ($rule['base_field'] ?? ''));
                $rule_offset = (int) ($rule['offset'] ?? 0);
                if ($rule_offset > 3650) {
                    $rule_offset = 3650;
                } elseif ($rule_offset < -3650) {
                    $rule_offset = -3650;
                }

                $rule_mode_raw = sanitize_text_field((string) ($rule['mode'] ?? 'business'));
                $rule_mode = in_array($rule_mode_raw, ['business', 'calendar'], true) ? $rule_mode_raw : 'business';
                $rule_allow_weekends = !empty($rule['allow_weekends']) ? 1 : 0;

                $rules[] = [
                    'depends_on'      => $depends_on,
                    'value'           => $rule_vals[0],
                    'values'          => $rule_vals,
                    'base'            => $rule_base,
                    'base_field'      => $rule_base_field,
                    'offset'          => $rule_offset,
                    'mode'            => $rule_mode,
                    'allow_weekends'  => $rule_allow_weekends,
                ];
            }
        }

        $custom_due_date_configs[$due_id] = [
            'rules' => $rules,
            'default' => [
                'base'           => $default_base,
                'base_field'     => $default_base_field,
                'offset'         => $default_offset,
                'mode'           => $default_mode,
                'allow_weekends' => $default_allow_weekends,
            ],
            'override_manual' => $override_manual,
        ];
    }

    $custom_assignee_labels = isset($_POST['custom_assignee_labels']) && is_array($_POST['custom_assignee_labels'])
        ? array_map('sanitize_text_field', $_POST['custom_assignee_labels'])
        : [];
    $custom_assignee_default = isset($_POST['custom_assignee_default']) && is_array($_POST['custom_assignee_default'])
        ? array_map('sanitize_text_field', $_POST['custom_assignee_default'])
        : [];
    $custom_assignee_default_fallbacks_raw = isset($_POST['custom_assignee_default_fallbacks']) && is_array($_POST['custom_assignee_default_fallbacks'])
        ? $_POST['custom_assignee_default_fallbacks']
        : [];
    $custom_assignee_rules_raw = isset($_POST['custom_assignee_rules']) && is_array($_POST['custom_assignee_rules'])
        ? $_POST['custom_assignee_rules']
        : [];
    $custom_assignee_check_ooo = isset($_POST['custom_assignee_check_ooo'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', (array) $_POST['custom_assignee_check_ooo']), static function ($val) {
            return $val !== '';
        })))
        : [];

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

    $custom_assignee_configs = [];
    $assignee_block_ids = array_values(array_unique(array_filter(array_merge(
        array_map('strval', array_keys((array) $custom_assignee_default)),
        array_map('strval', array_keys((array) $custom_assignee_default_fallbacks_raw)),
        array_map('strval', array_keys((array) $custom_assignee_rules_raw)),
        array_map('strval', (array) $custom_assignee_check_ooo),
        array_values(array_filter((array) $field_order, static function ($id) {
            return is_string($id) && strpos($id, 'assignee_') === 0;
        }))
    ), static function ($id) {
        return is_string($id) && strpos($id, 'assignee_') === 0;
    })));

    foreach ($assignee_block_ids as $assignee_id) {
        $default_assignee = $normalize_assignee_gid($custom_assignee_default[$assignee_id] ?? '');

        $default_fallback_vals = isset($custom_assignee_default_fallbacks_raw[$assignee_id]) && is_array($custom_assignee_default_fallbacks_raw[$assignee_id])
            ? $custom_assignee_default_fallbacks_raw[$assignee_id]
            : [];
        $default_fallbacks = array_values(array_unique(array_filter(array_map($normalize_assignee_gid, $default_fallback_vals), static function ($gid) use ($default_assignee) {
            return $gid !== '' && $gid !== $default_assignee;
        })));

        $rules = [];
        $rules_json_raw = wp_unslash((string) ($custom_assignee_rules_raw[$assignee_id] ?? '[]'));
        $decoded_rules = json_decode($rules_json_raw, true);
        if (is_array($decoded_rules)) {
            foreach ($decoded_rules as $rule) {
                if (!is_array($rule)) {
                    continue;
                }

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

                $posted_vals = $rule['values'] ?? [];
                $rule_vals = is_array($posted_vals)
                    ? $posted_vals
                    : (($posted_vals !== '' && $posted_vals !== null) ? [$posted_vals] : []);
                $rule_vals = array_values(array_unique(array_filter(array_map('sanitize_text_field', $rule_vals), static function ($val) {
                    return $val !== '';
                })));
                if (empty($rule_vals)) {
                    continue;
                }

                $rule_assignee = $normalize_assignee_gid($rule['assignee'] ?? '');
                if ($rule_assignee === '') {
                    continue;
                }

                $raw_rule_fallbacks = [];
                if (isset($rule['fallback_assignees']) && is_array($rule['fallback_assignees'])) {
                    $raw_rule_fallbacks = $rule['fallback_assignees'];
                } elseif (isset($rule['fallback']) && $rule['fallback'] !== '') {
                    $raw_rule_fallbacks = [$rule['fallback']];
                }

                $rule_fallbacks = array_values(array_unique(array_filter(array_map($normalize_assignee_gid, $raw_rule_fallbacks), static function ($gid) use ($rule_assignee) {
                    return $gid !== '' && $gid !== $rule_assignee;
                })));

                $rules[] = [
                    'depends_on'         => $depends_on,
                    'value'              => $rule_vals[0],
                    'values'             => $rule_vals,
                    'assignee'           => $rule_assignee,
                    'fallback_assignees' => $rule_fallbacks,
                ];
            }
        }

        $custom_assignee_configs[$assignee_id] = [
            'default_assignee'           => $default_assignee,
            'default_fallback_assignees' => $default_fallbacks,
            'rules'                      => $rules,
            'check_ooo_today'            => in_array($assignee_id, $custom_assignee_check_ooo, true) ? 1 : 0,
            'all_ooo_action'             => 'leave_unassigned',
            'ooo_unavailable_action'     => 'continue_normal',
        ];
    }

    $custom_recipient_labels = isset($_POST['custom_recipient_labels']) && is_array($_POST['custom_recipient_labels'])
        ? array_map('sanitize_text_field', $_POST['custom_recipient_labels'])
        : [];
    $custom_recipient_enabled = isset($_POST['custom_recipient_enabled'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', (array) $_POST['custom_recipient_enabled']), static function ($val) {
            return $val !== '';
        })))
        : [];
    $custom_recipient_include_submitter = isset($_POST['custom_recipient_include_submitter'])
        ? array_values(array_unique(array_filter(array_map('sanitize_text_field', (array) $_POST['custom_recipient_include_submitter']), static function ($val) {
            return $val !== '';
        })))
        : [];
    $custom_recipient_mode = isset($_POST['custom_recipient_mode']) && is_array($_POST['custom_recipient_mode'])
        ? array_map('sanitize_text_field', $_POST['custom_recipient_mode'])
        : [];
    $custom_recipient_subject_templates = isset($_POST['custom_recipient_subject_templates']) && is_array($_POST['custom_recipient_subject_templates'])
        ? array_map('sanitize_text_field', $_POST['custom_recipient_subject_templates'])
        : [];
    $custom_recipient_body_templates = isset($_POST['custom_recipient_body_templates']) && is_array($_POST['custom_recipient_body_templates'])
        ? array_map('wp_kses_post', wp_unslash($_POST['custom_recipient_body_templates']))
        : [];
    $custom_recipient_source_fields_raw = isset($_POST['custom_recipient_source_fields']) && is_array($_POST['custom_recipient_source_fields'])
        ? $_POST['custom_recipient_source_fields']
        : [];
    $custom_recipient_manual_emails_raw = isset($_POST['custom_recipient_manual_emails']) && is_array($_POST['custom_recipient_manual_emails'])
        ? $_POST['custom_recipient_manual_emails']
        : [];

    $extract_recipient_manual_emails = static function ($raw_value) {
        if (is_array($raw_value)) {
            $raw_value = implode("\n", array_map('strval', $raw_value));
        }

        $text = wp_unslash((string) $raw_value);
        if ($text === '') {
            return [];
        }

        $emails = [];
        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)) {
                    $emails[] = $email;
                }
            }
        } else {
            $email = sanitize_email($text);
            if (is_email($email)) {
                $emails[] = $email;
            }
        }

        return array_values(array_unique($emails));
    };

    $custom_recipient_configs = [];
    $recipient_block_ids = array_values(array_unique(array_filter(array_merge(
        array_map('strval', array_keys((array) $custom_recipient_mode)),
        array_map('strval', array_keys((array) $custom_recipient_subject_templates)),
        array_map('strval', array_keys((array) $custom_recipient_body_templates)),
        array_map('strval', array_keys((array) $custom_recipient_source_fields_raw)),
        array_map('strval', array_keys((array) $custom_recipient_manual_emails_raw)),
        array_map('strval', (array) $custom_recipient_enabled),
        array_map('strval', (array) $custom_recipient_include_submitter),
        array_values(array_filter((array) $field_order, static function ($id) {
            return is_string($id) && strpos($id, 'recipient_') === 0;
        }))
    ), static function ($id) {
        return is_string($id) && strpos($id, 'recipient_') === 0;
    })));

    foreach ($recipient_block_ids as $recipient_id) {
        $mode_raw = sanitize_text_field((string) ($custom_recipient_mode[$recipient_id] ?? 'separate'));
        $mode = in_array($mode_raw, ['separate', 'cc'], true) ? $mode_raw : 'separate';

        $source_vals = isset($custom_recipient_source_fields_raw[$recipient_id]) && is_array($custom_recipient_source_fields_raw[$recipient_id])
            ? $custom_recipient_source_fields_raw[$recipient_id]
            : [];
        $source_fields = array_values(array_unique(array_filter(array_map('sanitize_text_field', $source_vals), static function ($val) {
            return $val !== '';
        })));
        $manual_emails = $extract_recipient_manual_emails($custom_recipient_manual_emails_raw[$recipient_id] ?? '');

        $subject_tpl = sanitize_text_field((string) ($custom_recipient_subject_templates[$recipient_id] ?? ''));
        $body_tpl = wp_kses_post((string) ($custom_recipient_body_templates[$recipient_id] ?? ''));

        $custom_recipient_configs[$recipient_id] = [
            'enabled'           => in_array($recipient_id, $custom_recipient_enabled, true) ? 1 : 0,
            'include_submitter' => in_array($recipient_id, $custom_recipient_include_submitter, true) ? 1 : 0,
            'mode'              => $mode,
            'source_fields'     => $source_fields,
            'manual_emails'     => $manual_emails,
            'subject_template'  => $subject_tpl,
            'body_template'     => $body_tpl,
        ];
    }

    $visible_enum_options     = [];
    if (!empty($_POST['visible_enum_options']) && is_array($_POST['visible_enum_options'])) {
        foreach ($_POST['visible_enum_options'] as $field_gid => $option_gids) {
            $field_gid = sanitize_text_field((string) $field_gid);
            if ($field_gid === '') {
                continue;
            }

            $vals = is_array($option_gids) ? $option_gids : [];
            $vals = array_values(array_unique(array_filter(array_map('sanitize_text_field', $vals), static function ($val) {
                return $val !== '';
            })));

            if (!empty($vals)) {
                $visible_enum_options[$field_gid] = $vals;
            }
        }
    }
    $option_visibility_rules = [];
    if (!empty($_POST['option_filter_depends_on']) && is_array($_POST['option_filter_depends_on'])) {
        foreach ($_POST['option_filter_depends_on'] as $target_gid => $source_gid) {
            $target_gid = sanitize_text_field((string) $target_gid);
            $source_gid = sanitize_text_field((string) $source_gid);
            if ($target_gid === '' || $source_gid === '') {
                continue;
            }

            $raw_map = '';
            if (!empty($_POST['option_filter_map']) && is_array($_POST['option_filter_map'])) {
                $raw_map = wp_unslash((string) ($_POST['option_filter_map'][$target_gid] ?? ''));
            }

            $decoded_map = json_decode($raw_map, true);
            if (!is_array($decoded_map)) {
                continue;
            }

            $sanitized_map = [];
            foreach ($decoded_map as $source_value_gid => $target_values) {
                $source_value_gid = sanitize_text_field((string) $source_value_gid);
                if ($source_value_gid === '') {
                    continue;
                }

                $vals = is_array($target_values) ? $target_values : [];
                $vals = array_values(array_unique(array_filter(array_map('sanitize_text_field', $vals), static function ($val) {
                    return $val !== '';
                })));

                if (!empty($vals)) {
                    $sanitized_map[$source_value_gid] = $vals;
                }
            }

            if (!empty($sanitized_map)) {
                $option_visibility_rules[$target_gid] = [
                    'depends_on' => $source_gid,
                    'map'        => $sanitized_map,
                ];
            }
        }
    }
    // Deprecated: uploads are now configured through upload blocks only.
    $allow_uploads            = false;
    $form_title               = isset($_POST['form_title']) ? sanitize_text_field($_POST['form_title']) : '';
    $half_width_fields        = isset($_POST['half_width_fields']) ? array_map('sanitize_text_field', (array) $_POST['half_width_fields']) : [];
    $third_width_fields       = isset($_POST['third_width_fields']) ? array_map('sanitize_text_field', (array) $_POST['third_width_fields']) : [];
    if (!empty($half_width_fields) && !empty($third_width_fields)) {
        $half_width_fields = array_values(array_diff($half_width_fields, $third_width_fields));
    }

    $conditions = [];
    if (!empty($_POST['condition_rules']) && is_array($_POST['condition_rules'])) {
        $posted_logic = isset($_POST['condition_logic']) && is_array($_POST['condition_logic'])
            ? $_POST['condition_logic']
            : [];

        foreach ($_POST['condition_rules'] as $target => $rules_json) {
            $target = sanitize_text_field((string) $target);
            if ($target === '') continue;

            $raw = wp_unslash((string) $rules_json);
            $decoded = json_decode($raw, true);
            if (!is_array($decoded)) continue;

            $rules = [];
            foreach ($decoded as $rule) {
                if (!is_array($rule)) continue;
                $source = sanitize_text_field((string) ($rule['depends_on'] ?? ''));
                if ($source === '') continue;

                $posted_vals = $rule['values'] ?? [];
                $vals = is_array($posted_vals)
                    ? $posted_vals
                    : (($posted_vals !== '' && $posted_vals !== null) ? [$posted_vals] : []);
                $vals = array_values(array_unique(array_filter(array_map('sanitize_text_field', $vals), static function ($val) {
                    return $val !== '';
                })));

                if (!empty($vals)) {
                    $rules[] = [
                        'depends_on' => $source,
                        'value'      => $vals[0],
                        'values'     => $vals,
                    ];
                }
            }

            if (!empty($rules)) {
                $logic = sanitize_text_field((string) ($posted_logic[$target] ?? 'all'));
                if (!in_array($logic, ['all', 'any'], true)) {
                    $logic = 'all';
                }

                $conditions[$target] = [
                    'logic'      => $logic,
                    'rules'      => $rules,
                    // Legacy single-rule compatibility keys.
                    'depends_on' => $rules[0]['depends_on'],
                    'value'      => $rules[0]['value'],
                    'values'     => $rules[0]['values'],
                ];
            }
        }
    }

    // Backward compatibility for legacy single-condition form posts.
    if (!empty($_POST['depends_on']) && is_array($_POST['depends_on'])) {
        foreach ($_POST['depends_on'] as $target => $source) {
            $target = sanitize_text_field((string) $target);
            if ($target === '' || isset($conditions[$target])) continue;

            $source = sanitize_text_field((string) $source);
            if ($source === '') continue;

            $posted = $_POST['expected_value'][$target] ?? [];
            $vals   = is_array($posted) ? $posted : (($posted !== '' && $posted !== null) ? [$posted] : []);
            $vals   = array_values(array_unique(array_filter(array_map('sanitize_text_field', $vals), static function ($val) {
                return $val !== '';
            })));

            if (!empty($vals)) {
                $rule = [
                    'depends_on' => $source,
                    'value'      => $vals[0],
                    'values'     => $vals,
                ];
                $conditions[$target] = [
                    'logic'      => 'all',
                    'rules'      => [$rule],
                    'depends_on' => $source,
                    'value'      => $vals[0],
                    'values'     => $vals,
                ];
            }
        }
    }

    $labels       = isset($_POST['labels']) ? array_map('sanitize_text_field', $_POST['labels']) : [];
    $descriptions = isset($_POST['descriptions']) ? array_map('sanitize_text_field', $_POST['descriptions']) : [];

    $field_config[$gid] = [
        'used_fields'             => $used_fields,
        'field_order'             => $field_order,
        'conditions'              => $conditions,
        'required_fields'         => $required_fields,
        'hidden_fields'           => $hidden_fields,
        'readonly_fields'         => $readonly_fields,
        'labels'                  => $labels,
        'descriptions'            => $descriptions,
        'paragraph_fields'        => $paragraph_fields,
        'custom_paragraphs'       => $custom_paragraphs,
        'custom_paragraph_titles' => $custom_paragraph_titles,
        'custom_sections'         => $custom_sections,
        'custom_section_titles'   => $custom_section_titles,
        'submit_block_fields'     => $submit_block_fields,
        'custom_agreement_labels' => $custom_agreement_labels,
        'custom_agreement_details'=> $custom_agreement_details,
        'agreement_required_fields' => $agreement_required_fields,
        'agreement_description_fields' => $agreement_description_fields,
        'custom_upload_titles'    => $custom_upload_titles,
        'custom_upload_details'   => $custom_upload_details,
        'custom_upload_min_files' => $custom_upload_min_files,
        'custom_upload_private_modes' => $custom_upload_private_modes,
        'custom_upload_accept_types' => $custom_upload_accept_types,
        'custom_upload_compress_images' => $custom_upload_compress_images,
        'custom_upload_max_file_mb' => $custom_upload_max_file_mb,
        'custom_upload_max_width_px' => $custom_upload_max_width_px,
        'custom_upload_compress_quality' => $custom_upload_compress_quality,
        'custom_single_input_labels' => $custom_single_input_labels,
        'custom_single_input_descriptions' => $custom_single_input_descriptions,
        'custom_single_input_placeholders' => $custom_single_input_placeholders,
        'custom_single_input_sync_fields' => $custom_single_input_sync_fields,
        'custom_single_input_modes' => $custom_single_input_modes,
        'custom_single_input_max_words_enabled' => $custom_single_input_max_words_enabled,
        'custom_single_input_max_words' => $custom_single_input_max_words,
        'custom_single_input_ai_enabled' => $custom_single_input_ai_enabled,
        'custom_dropdown_labels' => $custom_dropdown_labels,
        'custom_dropdown_descriptions' => $custom_dropdown_descriptions,
        'custom_dropdown_placeholders' => $custom_dropdown_placeholders,
        'custom_dropdown_options' => $custom_dropdown_options,
        'custom_dropdown_selection_modes' => $custom_dropdown_selection_modes,
        'custom_dropdown_sync_fields' => $custom_dropdown_sync_fields,
        'custom_calendar_labels' => $custom_calendar_labels,
        'custom_calendar_descriptions' => $custom_calendar_descriptions,
        'custom_calendar_placeholders' => $custom_calendar_placeholders,
        'custom_calendar_sync_modes' => $custom_calendar_sync_modes,
        'custom_calendar_sync_fields' => $custom_calendar_sync_fields,
        'custom_repeater_labels' => $custom_repeater_labels,
        'custom_repeater_descriptions' => $custom_repeater_descriptions,
        'custom_repeater_save_mode' => $custom_repeater_save_mode,
        'custom_repeater_columns' => $custom_repeater_columns,
        'custom_repeater_min_rows' => $custom_repeater_min_rows,
        'custom_repeater_max_rows' => $custom_repeater_max_rows,
        'custom_repeater_field_map' => $custom_repeater_field_map,
        'custom_text_sync_fields' => $custom_text_sync_fields,
        'custom_calendar_preset_modes' => $custom_calendar_preset_modes,
        'custom_task_name_labels' => $custom_task_name_labels,
        'custom_task_name_templates' => $custom_task_name_templates,
        'private_companion_task_name_mode' => $private_companion_task_name_mode,
        'private_companion_task_name_template' => $private_companion_task_name_template,
        'private_data_companion_enabled' => $private_data_companion_enabled,
        'private_companion_depends_on_main' => $private_companion_depends_on_main,
        'private_data_project_gid' => $private_data_project_gid,
        'private_companion_upload_mode' => $private_companion_upload_mode,
        'private_companion_field_targets' => $private_companion_field_targets,
        'custom_due_date_configs'  => $custom_due_date_configs,
        'custom_assignee_labels'   => $custom_assignee_labels,
        'custom_assignee_configs'  => $custom_assignee_configs,
        'custom_recipient_labels'  => $custom_recipient_labels,
        'custom_recipient_configs' => $custom_recipient_configs,
        'visible_enum_options'    => $visible_enum_options,
        'option_visibility_rules' => $option_visibility_rules,
        'allow_uploads'           => $allow_uploads,
        'form_title'              => $form_title,
        'half_width_fields'       => $half_width_fields,
        'third_width_fields'      => $third_width_fields,
    ];

    update_option('afb_project_field_config', $field_config);

    wp_send_json_success([
        'message' => 'Field flow saved.',
        'project' => $gid,
        'saved_at' => current_time('mysql'),
    ]);
});
