<?php

namespace GriffinForms\Includes\Pipelines\Accounts;

use GriffinForms\Config;
use GriffinForms\Includes\Integrations\FormSettingsManager;

class SetupResolver
{
    private $field_map = [];
    private $file_reference_cache = [];

    public function hasConfiguredSetups(int $form_id): bool
    {
        return !empty($this->getNormalizedSetups($form_id));
    }

    public function getNormalizedSetups(int $form_id): array
    {
        if ($form_id <= 0) {
            return [];
        }

        $stored = FormSettingsManager::getInstance()->getIntegrationSettings($form_id, Contract::INTEGRATION_ID);
        if (!is_array($stored)) {
            return [];
        }

        $settings = isset($stored['settings']) && is_array($stored['settings']) ? $stored['settings'] : [];
        $rules = isset($settings['rules']) && is_array($settings['rules']) ? $settings['rules'] : [];
        $normalized_setups = [];

        foreach ($rules as $index => $rule) {
            if (!is_array($rule)) {
                continue;
            }

            $normalized_setups[] = [
                'id' => isset($rule['id']) ? sanitize_key((string) $rule['id']) : 'wa_rule_' . ($index + 1),
                'enabled' => !isset($rule['enabled']) || !empty($rule['enabled']),
                'email_field_id' => absint($rule['email_field_id'] ?? 0),
                'password_mode' => sanitize_text_field((string) ($rule['password_mode'] ?? 'generate_password')),
                'password_field_id' => absint($rule['password_field_id'] ?? 0),
                'first_name_mode' => sanitize_text_field((string) ($rule['first_name_mode'] ?? 'none')),
                'first_name_field_id' => absint($rule['first_name_field_id'] ?? 0),
                'last_name_mode' => sanitize_text_field((string) ($rule['last_name_mode'] ?? 'none')),
                'last_name_field_id' => absint($rule['last_name_field_id'] ?? 0),
                'username_mode' => sanitize_text_field((string) ($rule['username_mode'] ?? 'generate_only')),
                'username_field_id' => absint($rule['username_field_id'] ?? 0),
                'profile_image_mode' => sanitize_text_field((string) ($rule['profile_image_mode'] ?? 'none')),
                'profile_image_field_id' => absint($rule['profile_image_field_id'] ?? 0),
                'send_confirmation_email' => !empty($rule['send_confirmation_email']),
                'confirmation_message_id' => absint($rule['confirmation_message_id'] ?? 0),
                'role' => sanitize_key((string) ($rule['role'] ?? 'subscriber')),
                'creation_mode' => sanitize_text_field((string) ($rule['creation_mode'] ?? 'immediate_create')),
                'existing_account_behavior' => sanitize_text_field((string) ($rule['existing_account_behavior'] ?? 'error')),
            ];
        }

        return $normalized_setups;
    }

    public function buildInitialAccountsState(array $context): array
    {
        $submission_id = isset($context['submission_id']) ? (int) $context['submission_id'] : 0;
        $form_id = isset($context['form_id']) ? (int) $context['form_id'] : 0;
        $field_value_map = isset($context['account_meta']['field_value_map']) && is_array($context['account_meta']['field_value_map'])
            ? $context['account_meta']['field_value_map']
            : [];
        $field_sequence_map = isset($context['account_meta']['field_sequence_map']) && is_array($context['account_meta']['field_sequence_map'])
            ? $context['account_meta']['field_sequence_map']
            : $field_value_map;

        $setups = $this->getNormalizedSetups($form_id);
        if (empty($setups)) {
            return [];
        }

        $this->field_map = $this->loadFieldMap($setups);
        $this->file_reference_cache = [];

        $resolved_setups = [];
        foreach ($setups as $setup_index => $setup) {
            if (empty($setup['enabled'])) {
                continue;
            }

            $resolved_setup = $this->buildResolvedSetup($setup, $setup_index, $submission_id, $field_sequence_map);
            $resolved_setups[] = $resolved_setup;
        }

        return [
            'version' => Contract::STATE_VERSION,
            'contract_phase' => 'phase_3',
            'integration_id' => Contract::INTEGRATION_ID,
            'form_id' => $form_id,
            'submission_id' => $submission_id,
            'setups' => $resolved_setups,
            'updated_at' => gmdate('c'),
        ];
    }

    private function buildResolvedSetup(array $setup, int $setup_index, int $submission_id, array $field_sequence_map): array
    {
        $email_field_id = (int) ($setup['email_field_id'] ?? 0);
        $email_field = $this->getFieldDefinition($email_field_id);
        $email_field_is_valid = $this->fieldMatchesType($email_field_id, ['inputemail']);
        $email_field_is_iterable = $this->fieldIsIterable($email_field_id);
        $email_values = $this->getFieldSequenceValues($field_sequence_map, $email_field_id);
        $targets = [];
        $seen_emails = [];
        $reserved_usernames = [];

        if (!$email_field_is_valid) {
            $targets[] = $this->buildBlockedTarget(
                $setup,
                $submission_id,
                0,
                '',
                'invalid_email_field',
                'The mapped email field is missing or no longer valid.',
                ['email_field_id' => $email_field_id]
            );
        } else {
            $candidate_values = $this->getTargetEmailCandidates($email_values, $email_field_is_iterable);
            if (empty($candidate_values)) {
                $targets[] = $this->buildBlockedTarget(
                    $setup,
                    $submission_id,
                    0,
                    '',
                    'missing_email_value',
                    'No email value was resolved for this setup.',
                    ['email_field_id' => $email_field_id]
                );
            } else {
                foreach ($candidate_values as $target_index => $email_value) {
                    $targets[] = $this->buildValidatedTargetAction(
                        $setup,
                        $submission_id,
                        $target_index,
                        $email_value,
                        $email_field_is_iterable,
                        $field_sequence_map,
                        $seen_emails,
                        $reserved_usernames
                    );
                }
            }
        }

        return [
            'setup_id' => (string) $setup['id'],
            'setup_index' => $setup_index,
            'email_field_id' => $email_field_id,
            'password_field_id' => absint($setup['password_field_id'] ?? 0),
            'email_field_is_iterable' => $email_field_is_iterable,
            'email_field_label' => $email_field['label'] ?? '',
            'creation_mode' => (string) $setup['creation_mode'],
            'existing_account_behavior' => (string) $setup['existing_account_behavior'],
            'target_count' => count($targets),
            'targets' => $targets,
        ];
    }

    private function buildValidatedTargetAction(
        array $setup,
        int $submission_id,
        int $target_index,
        string $email_value,
        bool $email_field_is_iterable,
        array $field_sequence_map,
        array &$seen_emails,
        array &$reserved_usernames
    ): array {
        $email = sanitize_email($email_value);
        if ($email === '') {
            return $this->buildBlockedTarget(
                $setup,
                $submission_id,
                $target_index,
                $email_value,
                'invalid_email_value',
                'Resolved email value is empty or invalid.'
            );
        }

        $normalized_email = strtolower($email);
        if (isset($seen_emails[$normalized_email])) {
            return $this->buildBlockedTarget(
                $setup,
                $submission_id,
                $target_index,
                $email,
                'duplicate_target_email',
                'This setup resolved the same email value more than once in the submission.'
            );
        }

        $seen_emails[$normalized_email] = true;

        $runtime_issues = [];
        $resolved_data = [
            'username' => '',
            'username_source' => '',
            'first_name' => '',
            'last_name' => '',
            'profile_image' => [],
        ];

        $password_validation = $this->validatePasswordStrategy(
            $setup,
            $submission_id,
            $target_index,
            $email,
            $email_field_is_iterable,
            $field_sequence_map,
            $reserved_usernames
        );
        if ($password_validation['result'] !== 'pass') {
            return $password_validation['target'];
        }
        if ($password_validation['issue'] !== '') {
            $runtime_issues[] = $password_validation['issue'];
        }

        $mapping_validation = $this->resolveOptionalMappings(
            $setup,
            $submission_id,
            $target_index,
            $email,
            $email_field_is_iterable,
            $field_sequence_map
        );
        if ($mapping_validation['result'] !== 'pass') {
            return $mapping_validation['target'];
        }

        $resolved_data['first_name'] = $mapping_validation['resolved']['first_name'] ?? '';
        $resolved_data['last_name'] = $mapping_validation['resolved']['last_name'] ?? '';
        $resolved_data['profile_image'] = $mapping_validation['resolved']['profile_image'] ?? [];
        if (!empty($mapping_validation['issues'])) {
            $runtime_issues = array_merge($runtime_issues, $mapping_validation['issues']);
        }

        $username_validation = $this->resolveUsername(
            $setup,
            $submission_id,
            $target_index,
            $email,
            $email_field_is_iterable,
            $field_sequence_map,
            $reserved_usernames
        );
        if ($username_validation['result'] !== 'pass') {
            return $username_validation['target'];
        }

        $resolved_data['username'] = $username_validation['username'];
        $resolved_data['username_source'] = $username_validation['username_source'];
        if (!empty($username_validation['issues'])) {
            $runtime_issues = array_merge($runtime_issues, $username_validation['issues']);
        }

        return $this->buildTargetAction(
            $setup,
            $submission_id,
            $target_index,
            $email,
            $email_field_is_iterable,
            $resolved_data,
            $runtime_issues
        );
    }

    private function validatePasswordStrategy(
        array $setup,
        int $submission_id,
        int $target_index,
        string $email,
        bool $email_field_is_iterable,
        array $field_sequence_map,
        array &$reserved_usernames
    ): array {
        $password_mode = (string) ($setup['password_mode'] ?? 'generate_password');
        if ($password_mode !== 'mapped_password') {
            return [
                'result' => 'pass',
                'issue' => '',
                'target' => [],
            ];
        }

        if ($email_field_is_iterable) {
            return [
                'result' => 'fail',
                'issue' => '',
                'target' => $this->buildBlockedTarget(
                    $setup,
                    $submission_id,
                    $target_index,
                    $email,
                    'iterable_email_requires_generated_password',
                    'Iterable email fields can only use Email Password Setup Link.'
                ),
            ];
        }

        $password_field_id = absint($setup['password_field_id'] ?? 0);
        if (!$this->fieldMatchesType($password_field_id, ['inputpassword'])) {
            return [
                'result' => 'fail',
                'issue' => '',
                'target' => $this->buildBlockedTarget(
                    $setup,
                    $submission_id,
                    $target_index,
                    $email,
                    'invalid_password_field',
                    'The mapped password field is missing or no longer valid.'
                ),
            ];
        }

        $password_value = $this->resolveFieldValueForTarget(
            $field_sequence_map,
            $password_field_id,
            $target_index,
            $email_field_is_iterable
        );

        if ($password_value !== '') {
            return [
                'result' => 'pass',
                'issue' => '',
                'target' => [],
            ];
        }

        $creation_mode = (string) ($setup['creation_mode'] ?? 'immediate_create');
        if ($creation_mode === 'immediate_create') {
            return [
                'result' => 'fail',
                'issue' => '',
                'target' => $this->buildBlockedTarget(
                    $setup,
                    $submission_id,
                    $target_index,
                    $email,
                    'missing_password_value',
                    'The mapped password field is empty for this submission target.'
                ),
            ];
        }

        return [
            'result' => 'pass',
            'issue' => 'missing_mapped_password_value',
            'target' => [],
        ];
    }

    private function resolveOptionalMappings(
        array $setup,
        int $submission_id,
        int $target_index,
        string $email,
        bool $email_field_is_iterable,
        array $field_sequence_map
    ): array {
        $resolved = [
            'first_name' => '',
            'last_name' => '',
            'profile_image' => [],
        ];
        $issues = [];

        foreach ($this->getOptionalMappingSpecs() as $mapping_key => $mapping_spec) {
            $mode = (string) ($setup[$mapping_spec['mode_key']] ?? 'none');
            if ($mode !== 'map_field') {
                continue;
            }

            $field_id = absint($setup[$mapping_spec['field_key']] ?? 0);
            if (!$this->fieldMatchesType($field_id, $mapping_spec['allowed_types'])) {
                return [
                    'result' => 'fail',
                    'issues' => [],
                    'resolved' => [],
                    'target' => $this->buildBlockedTarget(
                        $setup,
                        $submission_id,
                        $target_index,
                        $email,
                        'invalid_' . $mapping_key . '_field',
                        $mapping_spec['invalid_message']
                    ),
                ];
            }

            if ($email_field_is_iterable && !empty($mapping_spec['disallow_when_email_iterable'])) {
                return [
                    'result' => 'fail',
                    'issues' => [],
                    'resolved' => [],
                    'target' => $this->buildBlockedTarget(
                        $setup,
                        $submission_id,
                        $target_index,
                        $email,
                        $mapping_key . '_not_available_for_iterable_email',
                        $mapping_spec['iterable_disallowed_message']
                    ),
                ];
            }

            if ($email_field_is_iterable && !$this->fieldIsIterable($field_id)) {
                return [
                    'result' => 'fail',
                    'issues' => [],
                    'resolved' => [],
                    'target' => $this->buildBlockedTarget(
                        $setup,
                        $submission_id,
                        $target_index,
                        $email,
                        'iterable_email_requires_iterable_' . $mapping_key . '_field',
                        $mapping_spec['iterable_required_message']
                    ),
                ];
            }

            $value = $this->resolveFieldValueForTarget($field_sequence_map, $field_id, $target_index, $email_field_is_iterable);
            if ($email_field_is_iterable && $mapping_key !== 'profile_image' && trim($value) === '') {
                return [
                    'result' => 'fail',
                    'issues' => [],
                    'resolved' => [],
                    'target' => $this->buildBlockedTarget(
                        $setup,
                        $submission_id,
                        $target_index,
                        $email,
                        'missing_iterable_' . $mapping_key . '_value',
                        sprintf(
                            'Email entry #%d is missing a matching %s value.',
                            $target_index + 1,
                            $mapping_spec['label']
                        )
                    ),
                ];
            }

            if ($mapping_key === 'profile_image') {
                $resolved_reference = $this->resolveProfileImageReference($submission_id, $value);
                if (!empty($value) && empty($resolved_reference)) {
                    return [
                        'result' => 'fail',
                        'issues' => [],
                        'resolved' => [],
                        'target' => $this->buildBlockedTarget(
                            $setup,
                            $submission_id,
                            $target_index,
                            $email,
                            'profile_image_file_missing',
                            'The mapped profile image value could not be resolved to an attached submission file.'
                        ),
                    ];
                }

                $resolved['profile_image'] = $resolved_reference;
                continue;
            }

            $resolved[$mapping_key] = $value;
        }

        return [
            'result' => 'pass',
            'issues' => $issues,
            'resolved' => $resolved,
            'target' => [],
        ];
    }

    private function resolveUsername(
        array $setup,
        int $submission_id,
        int $target_index,
        string $email,
        bool $email_field_is_iterable,
        array $field_sequence_map,
        array &$reserved_usernames
    ): array {
        $username_mode = (string) ($setup['username_mode'] ?? 'generate_only');
        $issues = [];

        if ($username_mode === 'mapped_or_generate') {
            $username_field_id = absint($setup['username_field_id'] ?? 0);
            if (!$this->fieldMatchesType($username_field_id, ['inputtext', 'inputemail', 'inputurl', 'textarea'])) {
                return [
                    'result' => 'fail',
                    'issues' => [],
                    'username' => '',
                    'username_source' => '',
                    'target' => $this->buildBlockedTarget(
                        $setup,
                        $submission_id,
                        $target_index,
                        $email,
                        'invalid_username_field',
                        'The mapped username field is missing or no longer valid.'
                    ),
                ];
            }

            if ($email_field_is_iterable && !$this->fieldIsIterable($username_field_id)) {
                return [
                    'result' => 'fail',
                    'issues' => [],
                    'username' => '',
                    'username_source' => '',
                    'target' => $this->buildBlockedTarget(
                        $setup,
                        $submission_id,
                        $target_index,
                        $email,
                        'iterable_email_requires_iterable_username_field',
                        'Iterable email targets require the mapped username field to be iterable too.'
                    ),
                ];
            }

            $mapped_username = $this->resolveFieldValueForTarget($field_sequence_map, $username_field_id, $target_index, $email_field_is_iterable);
            $sanitized_mapped_username = sanitize_user($mapped_username, true);

            if ($email_field_is_iterable && trim($mapped_username) === '') {
                return [
                    'result' => 'fail',
                    'issues' => [],
                    'username' => '',
                    'username_source' => '',
                    'target' => $this->buildBlockedTarget(
                        $setup,
                        $submission_id,
                        $target_index,
                        $email,
                        'missing_iterable_username_value',
                        sprintf(
                            'Email entry #%d is missing a matching username value.',
                            $target_index + 1
                        )
                    ),
                ];
            }

            if ($sanitized_mapped_username !== '' && !username_exists($sanitized_mapped_username) && !isset($reserved_usernames[$sanitized_mapped_username])) {
                $reserved_usernames[$sanitized_mapped_username] = true;
                return [
                    'result' => 'pass',
                    'issues' => [],
                    'username' => $sanitized_mapped_username,
                    'username_source' => 'mapped_field',
                    'target' => [],
                ];
            }

            if ($sanitized_mapped_username !== '') {
                $issues[] = 'mapped_username_collision_fell_back_to_generated_username';
            }
        }

        $generated_username = $this->generateUniqueUsernameFromEmail($email, $reserved_usernames);
        if ($generated_username === '') {
            return [
                'result' => 'fail',
                'issues' => [],
                'username' => '',
                'username_source' => '',
                'target' => $this->buildBlockedTarget(
                    $setup,
                    $submission_id,
                    $target_index,
                    $email,
                    'username_generation_failed',
                    'A unique username could not be generated for this target.'
                ),
            ];
        }

        return [
            'result' => 'pass',
            'issues' => $issues,
            'username' => $generated_username,
            'username_source' => $username_mode === 'mapped_or_generate' ? 'generated_fallback' : 'generated',
            'target' => [],
        ];
    }

    private function buildTargetAction(
        array $setup,
        int $submission_id,
        int $target_index,
        string $email,
        bool $email_field_is_iterable,
        array $resolved_data,
        array $runtime_issues
    ): array {
        $creation_mode = (string) ($setup['creation_mode'] ?? 'immediate_create');
        $password_mode = (string) ($setup['password_mode'] ?? 'generate_password');
        $status = $creation_mode === 'immediate_create'
            ? Contract::STATUS_READY_FOR_QUEUE
            : ($creation_mode === 'pending_admin_activation'
                ? Contract::STATUS_PENDING_ADMIN_ACTIVATION
                : Contract::STATUS_MANUAL_REVIEW_ONLY);

        if ($password_mode === 'mapped_password' && in_array('missing_mapped_password_value', $runtime_issues, true)) {
            $status = $creation_mode === 'pending_admin_activation'
                ? Contract::STATUS_PENDING_ADMIN_ACTIVATION
                : Contract::STATUS_MANUAL_REVIEW_ONLY;
        }

        $status_reason = '';
        $status_message = '';
        if ($status === Contract::STATUS_PENDING_ADMIN_ACTIVATION) {
            $status_reason = in_array('missing_mapped_password_value', $runtime_issues, true)
                ? 'missing_password_value'
                : 'awaiting_admin_activation';
            $status_message = in_array('missing_mapped_password_value', $runtime_issues, true)
                ? 'The mapped password field was empty for this target. Admin activation is required before account creation can continue.'
                : 'This target is waiting for admin activation before the account is created.';
        } elseif ($status === Contract::STATUS_MANUAL_REVIEW_ONLY) {
            $status_reason = in_array('missing_mapped_password_value', $runtime_issues, true)
                ? 'missing_password_value'
                : 'manual_review_required';
            $status_message = in_array('missing_mapped_password_value', $runtime_issues, true)
                ? 'The mapped password field was empty for this target. Manual review is required before account creation can continue.'
                : 'This target is set to manual review only and will not create an account automatically.';
        }

        return [
            'action_id' => $this->buildActionId((string) $setup['id'], $submission_id, $target_index, $email),
            'target_index' => $target_index,
            'target_email' => $email,
            'status' => $status,
            'status_reason' => $status_reason,
            'status_message' => $status_message,
            'job_type' => $status === Contract::STATUS_READY_FOR_QUEUE ? Contract::JOB_TYPE_PROCESS : '',
            'job_id' => 0,
            'user_id' => 0,
            'creation_mode' => $creation_mode,
            'existing_account_behavior' => (string) ($setup['existing_account_behavior'] ?? 'error'),
            'password_mode' => $password_mode,
            'send_confirmation_email' => !empty($setup['send_confirmation_email']),
            'confirmation_message_id' => absint($setup['confirmation_message_id'] ?? 0),
            'password_setup_email_status' => $this->getInitialPasswordSetupEmailStatus($creation_mode, $password_mode),
            'password_setup_email_reason' => $this->getInitialPasswordSetupEmailReason($creation_mode, $password_mode),
            'password_setup_email_sent_at' => '',
            'confirmation_email_status' => $this->getInitialConfirmationEmailStatus($creation_mode, !empty($setup['send_confirmation_email'])),
            'confirmation_email_reason' => $this->getInitialConfirmationEmailReason($creation_mode, !empty($setup['send_confirmation_email'])),
            'confirmation_email_sent_at' => '',
            'role' => sanitize_key((string) ($setup['role'] ?? 'subscriber')),
            'email_field_is_iterable' => $email_field_is_iterable,
            'resolved_username' => $resolved_data['username'] ?? '',
            'resolved_username_source' => $resolved_data['username_source'] ?? '',
            'resolved_first_name' => $resolved_data['first_name'] ?? '',
            'resolved_last_name' => $resolved_data['last_name'] ?? '',
            'resolved_profile_image' => $resolved_data['profile_image'] ?? [],
            'runtime_issues' => array_values(array_unique(array_filter($runtime_issues))),
            'created_at' => gmdate('c'),
            'updated_at' => gmdate('c'),
        ];
    }

    private function buildBlockedTarget(
        array $setup,
        int $submission_id,
        int $target_index,
        string $email_value,
        string $reason_code,
        string $reason_message,
        array $extra = []
    ): array {
        return array_merge([
            'action_id' => $this->buildActionId((string) $setup['id'], $submission_id, $target_index, $email_value),
            'target_index' => $target_index,
            'target_email' => sanitize_email($email_value),
            'status' => Contract::STATUS_BLOCKED,
            'status_reason' => sanitize_key($reason_code),
            'status_message' => sanitize_text_field($reason_message),
            'job_type' => '',
            'job_id' => 0,
            'user_id' => 0,
            'creation_mode' => (string) ($setup['creation_mode'] ?? 'immediate_create'),
            'existing_account_behavior' => (string) ($setup['existing_account_behavior'] ?? 'error'),
            'password_mode' => (string) ($setup['password_mode'] ?? 'generate_password'),
            'send_confirmation_email' => !empty($setup['send_confirmation_email']),
            'confirmation_message_id' => absint($setup['confirmation_message_id'] ?? 0),
            'password_setup_email_status' => (string) ($setup['password_mode'] ?? 'generate_password') === 'mapped_password'
                ? 'not_requested'
                : 'blocked',
            'password_setup_email_reason' => (string) ($setup['password_mode'] ?? 'generate_password') === 'mapped_password'
                ? ''
                : sanitize_key($reason_code),
            'password_setup_email_sent_at' => '',
            'confirmation_email_status' => !empty($setup['send_confirmation_email']) ? 'blocked' : 'not_requested',
            'confirmation_email_reason' => !empty($setup['send_confirmation_email']) ? sanitize_key($reason_code) : '',
            'confirmation_email_sent_at' => '',
            'role' => sanitize_key((string) ($setup['role'] ?? 'subscriber')),
            'email_field_is_iterable' => false,
            'resolved_username' => '',
            'resolved_username_source' => '',
            'resolved_first_name' => '',
            'resolved_last_name' => '',
            'resolved_profile_image' => [],
            'runtime_issues' => [sanitize_key($reason_code)],
            'created_at' => gmdate('c'),
            'updated_at' => gmdate('c'),
        ], $extra);
    }

    private function loadFieldMap(array $setups): array
    {
        $field_ids = [];
        foreach ($setups as $setup) {
            if (!is_array($setup)) {
                continue;
            }

            foreach ([
                'email_field_id',
                'password_field_id',
                'first_name_field_id',
                'last_name_field_id',
                'username_field_id',
                'profile_image_field_id',
            ] as $field_key) {
                $field_id = absint($setup[$field_key] ?? 0);
                if ($field_id > 0) {
                    $field_ids[$field_id] = true;
                }
            }
        }

        if (empty($field_ids)) {
            return [];
        }

        $field_map = [];
        foreach (array_keys($field_ids) as $field_id) {
            $field = new \GriffinForms\Items\Field();
            if (!$field->loadItem($field_id)) {
                continue;
            }

            $field_map[$field_id] = [
                'id' => $field_id,
                'field_type' => (string) $field->getProp('field_type'),
                'label' => (string) $field->getProp('label'),
                'iterations' => $field->getProp('iterations'),
                'is_required' => (int) $field->getProp('is_required'),
                'validations' => $field->getProp('validations'),
                'field_settings' => $field->getProp('field_settings'),
            ];
        }

        return $field_map;
    }

    private function getFieldDefinition(int $field_id): array
    {
        return ($field_id > 0 && isset($this->field_map[$field_id]) && is_array($this->field_map[$field_id]))
            ? $this->field_map[$field_id]
            : [];
    }

    private function fieldMatchesType(int $field_id, array $allowed_types): bool
    {
        if ($field_id <= 0 || empty($allowed_types)) {
            return false;
        }

        $field = $this->getFieldDefinition($field_id);
        return !empty($field['field_type']) && in_array((string) $field['field_type'], $allowed_types, true);
    }

    private function fieldIsIterable(int $field_id): bool
    {
        if ($field_id <= 0) {
            return false;
        }

        $field = $this->getFieldDefinition($field_id);
        $iterations = $field['iterations'] ?? '';

        if (is_array($iterations)) {
            if (array_key_exists('allow', $iterations)) {
                return !empty($iterations['allow']);
            }

            if (array_key_exists('max', $iterations) && is_numeric($iterations['max'])) {
                return (int) $iterations['max'] > 1;
            }

            return !empty($iterations);
        }

        if (is_numeric($iterations)) {
            return (int) $iterations > 1;
        }

        if (is_bool($iterations)) {
            return $iterations;
        }

        $iterations = strtolower(trim((string) $iterations));
        return in_array($iterations, ['1', 'true', 'yes', 'on', 'multiple', 'iterable'], true);
    }

    private function getTargetEmailCandidates(array $email_values, bool $email_field_is_iterable): array
    {
        if (empty($email_values)) {
            return [];
        }

        if ($email_field_is_iterable) {
            return array_values($email_values);
        }

        foreach ($email_values as $value) {
            if (trim((string) $value) !== '') {
                return [trim((string) $value)];
            }
        }

        return [''];
    }

    private function getFieldSequenceValues(array $field_sequence_map, int $field_id): array
    {
        if ($field_id <= 0) {
            return [];
        }

        $values = $field_sequence_map[$field_id] ?? $field_sequence_map[(string) $field_id] ?? [];
        if (!is_array($values)) {
            return [];
        }

        return array_values(array_map(static function ($value) {
            return is_scalar($value) ? trim((string) $value) : '';
        }, $values));
    }

    private function resolveFieldValueForTarget(array $field_sequence_map, int $field_id, int $target_index, bool $email_field_is_iterable): string
    {
        $values = $this->getFieldSequenceValues($field_sequence_map, $field_id);
        if (empty($values)) {
            return '';
        }

        if ($email_field_is_iterable) {
            return isset($values[$target_index]) ? trim((string) $values[$target_index]) : '';
        }

        foreach ($values as $value) {
            $value = trim((string) $value);
            if ($value !== '') {
                return $value;
            }
        }

        return '';
    }

    private function resolveProfileImageReference(int $submission_id, string $raw_value): array
    {
        $raw_value = trim($raw_value);
        if ($submission_id <= 0 || $raw_value === '') {
            return [];
        }

        if (isset($this->file_reference_cache[$submission_id][$raw_value])) {
            return $this->file_reference_cache[$submission_id][$raw_value];
        }

        global $wpdb;
        $table = Config::getInstance()->getTable('file');
        $select_fields = 'id, token, url, path, mime';
        if ($this->tableHasColumn($table, 'attachment_id')) {
            $select_fields .= ', attachment_id';
        }

        $file_row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT {$select_fields} FROM %i WHERE submission_id = %d AND token = %s LIMIT 1",
                $table,
                $submission_id,
                $raw_value
            ),
            ARRAY_A
        );

        $resolved_reference = [];
        if (is_array($file_row)) {
            $resolved_reference = [
                'file_id' => absint($file_row['id'] ?? 0),
                'token' => sanitize_text_field((string) ($file_row['token'] ?? $raw_value)),
                'url' => esc_url_raw((string) ($file_row['url'] ?? '')),
                'path' => sanitize_text_field((string) ($file_row['path'] ?? '')),
                'mime' => sanitize_text_field((string) ($file_row['mime'] ?? '')),
                'attachment_id' => absint($file_row['attachment_id'] ?? 0),
            ];
        }

        if (!isset($this->file_reference_cache[$submission_id])) {
            $this->file_reference_cache[$submission_id] = [];
        }

        $this->file_reference_cache[$submission_id][$raw_value] = $resolved_reference;
        return $resolved_reference;
    }

    private function tableHasColumn(string $table, string $column): bool
    {
        static $cache = [];

        $cache_key = $table . '::' . $column;
        if (isset($cache[$cache_key])) {
            return $cache[$cache_key];
        }

        global $wpdb;
        $result = $wpdb->get_var(
            $wpdb->prepare(
                'SHOW COLUMNS FROM %i LIKE %s',
                $table,
                $column
            )
        );

        $cache[$cache_key] = !empty($result);
        return $cache[$cache_key];
    }

    private function generateUniqueUsernameFromEmail(string $email, array &$reserved_usernames): string
    {
        $base = '';
        if ($email !== '' && strpos($email, '@') !== false) {
            $base = (string) strstr($email, '@', true);
        }

        $base = sanitize_user($base, true);
        if ($base === '') {
            $base = 'user';
        }

        $candidate = $base;
        $suffix = 1;
        while (username_exists($candidate) || isset($reserved_usernames[$candidate])) {
            $suffix++;
            $candidate = sanitize_user($base . $suffix, true);
            if ($candidate === '') {
                $candidate = 'user' . $suffix;
            }

            if ($suffix > 1000) {
                return '';
            }
        }

        $reserved_usernames[$candidate] = true;
        return $candidate;
    }

    private function getOptionalMappingSpecs(): array
    {
        return [
            'first_name' => [
                'mode_key' => 'first_name_mode',
                'field_key' => 'first_name_field_id',
                'label' => 'first name',
                'allowed_types' => ['inputtext', 'inputemail', 'inputurl', 'textarea'],
                'invalid_message' => 'The mapped first name field is missing or no longer valid.',
                'iterable_required_message' => 'Iterable email targets require the mapped first name field to be iterable too.',
            ],
            'last_name' => [
                'mode_key' => 'last_name_mode',
                'field_key' => 'last_name_field_id',
                'label' => 'last name',
                'allowed_types' => ['inputtext', 'inputemail', 'inputurl', 'textarea'],
                'invalid_message' => 'The mapped last name field is missing or no longer valid.',
                'iterable_required_message' => 'Iterable email targets require the mapped last name field to be iterable too.',
            ],
            'profile_image' => [
                'mode_key' => 'profile_image_mode',
                'field_key' => 'profile_image_field_id',
                'label' => 'profile image',
                'allowed_types' => ['fileupload', 'file_upload'],
                'disallow_when_email_iterable' => true,
                'invalid_message' => 'The mapped profile image field is missing or no longer valid.',
                'iterable_disallowed_message' => 'Profile image mapping is not available when the selected email field is iterable.',
                'iterable_required_message' => 'Profile image mapping is not available when the selected email field is iterable.',
            ],
        ];
    }

    private function buildActionId(string $setup_id, int $submission_id, int $target_index, string $email_value): string
    {
        $hash = substr(sha1($setup_id . '|' . $submission_id . '|' . $target_index . '|' . strtolower($email_value)), 0, 12);
        return sanitize_key('wa_action_' . $hash);
    }

    private function getInitialConfirmationEmailStatus(string $creation_mode, bool $send_confirmation_email): string
    {
        if (!$send_confirmation_email) {
            return 'not_requested';
        }

        if ($creation_mode === 'immediate_create') {
            return 'pending_runtime';
        }

        return 'deferred';
    }

    private function getInitialPasswordSetupEmailStatus(string $creation_mode, string $password_mode): string
    {
        if ($password_mode === 'mapped_password') {
            return 'not_requested';
        }

        if ($creation_mode === 'immediate_create') {
            return 'pending_runtime';
        }

        return 'deferred';
    }

    private function getInitialConfirmationEmailReason(string $creation_mode, bool $send_confirmation_email): string
    {
        if (!$send_confirmation_email) {
            return '';
        }

        if ($creation_mode === 'pending_admin_activation') {
            return 'awaiting_admin_activation';
        }

        if ($creation_mode === 'manual_review_only') {
            return 'awaiting_manual_review';
        }

        return 'awaiting_account_creation';
    }

    private function getInitialPasswordSetupEmailReason(string $creation_mode, string $password_mode): string
    {
        if ($password_mode === 'mapped_password') {
            return '';
        }

        if ($creation_mode === 'pending_admin_activation') {
            return 'awaiting_admin_activation';
        }

        if ($creation_mode === 'manual_review_only') {
            return 'awaiting_manual_review';
        }

        return 'awaiting_account_creation';
    }
}
