<?php

namespace GriffinForms\Includes\Ai;

class PatchPlanParser
{
    protected const ALLOWED_MODES = [
        'patch',
        'mutation',
        'patch_with_user_input',
        'no_op',
    ];

    protected const ALLOWED_OPERATIONS = [
        'update_form',
        'update_page',
        'update_row',
        'update_column',
        'update_field',
        'insert_page',
        'insert_row',
        'insert_column',
        'insert_field',
        'move_page',
        'move_row',
        'move_column',
        'move_field',
        'remove_page',
        'remove_row',
        'remove_column',
        'remove_field',
        'replace',
        'no_op',
    ];

    protected const ALLOWED_TARGET_SCOPES = [
        'form',
        'page',
        'row',
        'column',
        'field',
        'selected',
    ];

    protected const ALLOWED_POSITION_VALUES = [
        'before',
        'after',
        'first',
        'last',
        'inside',
        'replace',
    ];

    protected const ALLOWED_FIELD_TYPES = [
        'inputtext',
        'inputemail',
        'inputphone',
        'inputaddress',
        'textarea',
        'selectfield',
        'inputcheckbox',
        'inputradio',
        'inputnumber',
        'inputdate',
        'fileupload',
        'inputlikert',
        'inputrating',
        'signature',
        'inputrichtext',
        'imageselection',
        'payment',
    ];

    public function parse($decoded, array $structure = [], array $context = []): array
    {
        if (!is_array($decoded)) {
            return [null, [__('Patch plan must decode to an object.', 'griffinforms-form-builder')]];
        }

        $mode = sanitize_key((string) ($decoded['mode'] ?? ''));
        if (!in_array($mode, self::ALLOWED_MODES, true)) {
            return [null, [__('Patch plan mode is missing or unsupported.', 'griffinforms-form-builder')]];
        }

        $normalized = [
            'mode' => ($mode === 'mutation') ? 'patch' : $mode,
            'success_message' => $this->normalizeText((string) ($decoded['success_message'] ?? ''), 160),
        ];

        $request_context = is_array($context['request_context'] ?? null) ? (array) $context['request_context'] : [];
        $request_prompt = sanitize_text_field((string) ($request_context['prompt'] ?? ''));
        if ($request_prompt !== '') {
            $normalized['prompt'] = $request_prompt;
        }

        if ($mode === 'no_op') {
            $normalized['message'] = $this->normalizeText((string) ($decoded['message'] ?? ''), 400);
            return [$normalized, []];
        }

        if ($mode === 'patch_with_user_input') {
            $required_inputs = $this->normalizeRequiredInputs((array) ($decoded['required_inputs'] ?? []));
            if (empty($required_inputs)) {
                return [null, [__('patch_with_user_input requires at least one required input choice.', 'griffinforms-form-builder')]];
            }

            [$draft_patch, $draft_errors] = $this->parseDraftPatch((array) ($decoded['draft_patch'] ?? []), $structure, $context);
            if (!empty($draft_errors)) {
                return [null, $draft_errors];
            }

            $normalized['required_inputs'] = $required_inputs;
            $normalized['draft_patch'] = $draft_patch;
            return [$normalized, []];
        }

        $operations = [];
        $errors = [];
        foreach (array_values((array) ($decoded['operations'] ?? [])) as $index => $operation) {
            [$normalized_op, $op_errors] = $this->normalizeOperation((array) $operation, $index, $structure, $context);
            if (!empty($op_errors)) {
                $errors = array_merge($errors, $op_errors);
                continue;
            }

            if (is_array($normalized_op)) {
                $operations[] = $normalized_op;
            }
        }

        if (empty($operations)) {
            $errors[] = __('Patch plan requires at least one valid operation.', 'griffinforms-form-builder');
        }

        if (!empty($errors)) {
            return [null, array_values(array_unique($errors))];
        }

        $normalized['operations'] = $operations;
        return [$normalized, []];
    }

    protected function parseDraftPatch(array $draft_patch, array $structure, array $context): array
    {
        $draft_patch['mode'] = 'patch';
        return $this->parse($draft_patch, $structure, $context);
    }

    protected function normalizeOperation(array $operation, int $index, array $structure, array $context): array
    {
        $errors = [];
        $op = sanitize_key((string) ($operation['op'] ?? ''));
        if (!in_array($op, self::ALLOWED_OPERATIONS, true)) {
            return [null, [sprintf(__('Operation %d uses an unsupported op.', 'griffinforms-form-builder'), $index + 1)]];
        }

        if ($op === 'no_op') {
            return [[
                'op' => 'no_op',
                'reason' => $this->normalizeText((string) ($operation['reason'] ?? ''), 240),
            ], []];
        }

        $normalized = [
            'op' => $op,
            'reason' => $this->normalizeText((string) ($operation['reason'] ?? ''), 240),
        ];

        $target = $this->normalizeTarget((array) ($operation['target'] ?? []), $op, $structure, $context);
        if (!is_array($target)) {
            $errors[] = sprintf(__('Operation %d is missing a valid target.', 'griffinforms-form-builder'), $index + 1);
        } else {
            $normalized['target'] = $target;
        }

        $anchor = $this->normalizeTarget((array) ($operation['anchor'] ?? []), $op, $structure, $context, true);
        if (is_array($anchor)) {
            $normalized['anchor'] = $anchor;
        }

        $position = sanitize_key((string) ($operation['position'] ?? ''));
        if ($position !== '') {
            if (!in_array($position, self::ALLOWED_POSITION_VALUES, true)) {
                $errors[] = sprintf(__('Operation %d uses an unsupported position.', 'griffinforms-form-builder'), $index + 1);
            } else {
                $normalized['position'] = $position;
            }
        }

        $changes_input = (array) ($operation['changes'] ?? []);
        foreach (['field_settings', 'validations', 'options', 'field_options'] as $root_key) {
            if (!isset($changes_input[$root_key]) && isset($operation[$root_key]) && is_array($operation[$root_key])) {
                $changes_input[$root_key] = $operation[$root_key];
            }
        }
        $changes = $this->normalizeProperties($changes_input, $op);
        if (!empty($changes)) {
            $normalized['changes'] = $changes;
        }

        $page = $this->normalizePagePayload((array) ($operation['page'] ?? []));
        if (!empty($page)) {
            $normalized['page'] = $page;
        }

        $row = $this->normalizeRowPayload((array) ($operation['row'] ?? []));
        if (!empty($row)) {
            $normalized['row'] = $row;
        }

        $column = $this->normalizeColumnPayload((array) ($operation['column'] ?? []));
        if (!empty($column)) {
            $normalized['column'] = $column;
        }

        $field = $this->normalizeFieldPayload((array) ($operation['field'] ?? []));
        if (!empty($field)) {
            $normalized['field'] = $field;
        }

        $replace = $this->normalizeReplacePayload((array) ($operation['replace'] ?? []));
        if (!empty($replace)) {
            $normalized['replace'] = $replace;
        }

        if ($this->operationRequiresChanges($op) && empty($normalized['changes']) && empty($normalized['field']) && empty($normalized['page']) && empty($normalized['row']) && empty($normalized['column']) && empty($normalized['replace'])) {
            $errors[] = sprintf(__('Operation %d does not declare any changes to apply.', 'griffinforms-form-builder'), $index + 1);
        }

        if (str_starts_with($op, 'insert_') && empty($normalized['position'])) {
            $normalized['position'] = 'after';
        }

        if (!empty($errors)) {
            return [null, $errors];
        }

        return [$normalized, []];
    }

    protected function normalizeTarget(array $target, string $op, array $structure, array $context, bool $allow_empty = false): ?array
    {
        if (empty($target)) {
            if ($allow_empty) {
                return null;
            }

            $selected = is_array($context['selected_context'] ?? null) ? (array) $context['selected_context'] : [];
            $request_context = is_array($context['request_context'] ?? null) ? (array) $context['request_context'] : [];
            if (!empty($selected['item_type'])) {
                return [
                    'scope' => $this->mapSelectedTypeToScope((string) ($selected['item_type'] ?? '')),
                    'item_id' => absint($selected['item_id'] ?? 0),
                    'item_label' => sanitize_text_field((string) ($selected['item_label'] ?? '')),
                    'prompt' => sanitize_text_field((string) ($request_context['prompt'] ?? '')),
                    'source' => 'selected_context',
                ];
            }

            if ($op === 'update_form') {
                return [
                    'scope' => 'form',
                    'source' => 'implicit_form',
                ];
            }

            return null;
        }

        $scope = sanitize_key((string) ($target['scope'] ?? ''));
        if ($scope === '') {
            $scope = 'selected';
        }
        if ($scope === 'pagerow') {
            $scope = 'row';
        }
        if (!in_array($scope, self::ALLOWED_TARGET_SCOPES, true)) {
            return null;
        }

        if ($scope === 'selected') {
            $selected = is_array($context['selected_context'] ?? null) ? (array) $context['selected_context'] : [];
            $request_context = is_array($context['request_context'] ?? null) ? (array) $context['request_context'] : [];
            if (empty($selected['item_type'])) {
                return null;
            }

            return [
                'scope' => $this->mapSelectedTypeToScope((string) ($selected['item_type'] ?? '')),
                'item_id' => absint($selected['item_id'] ?? 0),
                'item_label' => sanitize_text_field((string) ($selected['item_label'] ?? '')),
                'prompt' => sanitize_text_field((string) ($request_context['prompt'] ?? '')),
                'source' => 'selected_context',
            ];
        }

        $normalized = [
            'scope' => $scope,
        ];

        $request_context = is_array($context['request_context'] ?? null) ? (array) $context['request_context'] : [];
        $selected = is_array($context['selected_context'] ?? null) ? (array) $context['selected_context'] : [];
        $request_prompt = sanitize_text_field((string) ($request_context['prompt'] ?? ''));
        if ($request_prompt !== '') {
            $normalized['prompt'] = $request_prompt;
        }

        if (!empty($selected['item_type']) && empty($normalized['item_label'])) {
            $normalized['item_label'] = sanitize_text_field((string) ($selected['item_label'] ?? ''));
        }

        $shadow_uid = sanitize_text_field((string) ($target['shadow_uid'] ?? ''));
        if ($shadow_uid !== '') {
            $normalized['shadow_uid'] = $shadow_uid;
        }

        $item_id = absint($target['item_id'] ?? 0);
        if ($item_id > 0) {
            $normalized['item_id'] = $item_id;
        }

        $semantic_hint = sanitize_key((string) ($target['semantic_hint'] ?? ''));
        if ($semantic_hint !== '') {
            $normalized['semantic_hint'] = $semantic_hint;
        }

        $item_label = $this->normalizeText((string) ($target['item_label'] ?? ''), 120);
        if ($item_label !== '') {
            $normalized['item_label'] = $item_label;
        }

        $match = is_array($target['match'] ?? null) ? (array) $target['match'] : [];
        $match_payload = [];
        foreach (['label', 'name', 'field_type', 'page_label', 'row_label', 'column_label'] as $key) {
            $value = $this->normalizeText((string) ($match[$key] ?? ''), 120);
            if ($value !== '') {
                $match_payload[$key] = $value;
            }
        }
        if (!empty($match_payload)) {
            $normalized['match'] = $match_payload;
        }

        $is_container_scope = in_array($scope, ['form', 'page', 'row', 'column', 'selected'], true);
        if (count($normalized) === 1 && !$is_container_scope && !$allow_empty) {
            return null;
        }

        return $normalized;
    }

    protected function mapSelectedTypeToScope(string $selected_type): string
    {
        return match (sanitize_key($selected_type)) {
            'formpage' => 'page',
            'pagerow' => 'row',
            'rowcolumn' => 'column',
            'field' => 'field',
            default => 'form',
        };
    }

    protected function normalizeProperties(array $changes, string $op): array
    {
        $normalized = [];
        foreach ($changes as $key => $value) {
            $property = sanitize_key((string) $key);
            if ($property === '') {
                continue;
            }

            if (is_bool($value)) {
                $normalized[$property] = $value;
                continue;
            }

            if (is_numeric($value) && in_array($property, ['width'], true)) {
                $width = absint($value);
                if ($width >= 3 && $width <= 12) {
                    $normalized[$property] = $width;
                }
                continue;
            }

            if (is_string($value)) {
                $normalized[$property] = $this->normalizeText($value, 400);
                continue;
            }

            if (is_array($value) && in_array($property, ['field_settings', 'validations', 'options', 'field_options'], true)) {
                $normalized[$property] = $value;
                continue;
            }
        }

        if (str_starts_with($op, 'update_')) {
            return $normalized;
        }

        return $normalized;
    }

    protected function normalizePagePayload(array $page): array
    {
        $payload = [];
        foreach (['name', 'heading', 'description', 'previous_label', 'next_label'] as $key) {
            $value = $this->normalizeText((string) ($page[$key] ?? ''), 240);
            if ($value !== '') {
                $payload[$key] = $value;
            }
        }

        return $payload;
    }

    protected function normalizeRowPayload(array $row): array
    {
        $payload = [];
        foreach (['name', 'heading', 'description'] as $key) {
            $value = $this->normalizeText((string) ($row[$key] ?? ''), 240);
            if ($value !== '') {
                $payload[$key] = $value;
            }
        }

        $column_count = absint($row['column_count'] ?? 0);
        if ($column_count > 0) {
            $payload['column_count'] = $column_count;
        }

        return $payload;
    }

    protected function normalizeColumnPayload(array $column): array
    {
        $payload = [];
        $name = $this->normalizeText((string) ($column['name'] ?? ''), 120);
        if ($name !== '') {
            $payload['name'] = $name;
        }

        $width = absint($column['width'] ?? 0);
        if ($width >= 3 && $width <= 12) {
            $payload['width'] = $width;
        }

        return $payload;
    }

    protected function normalizeFieldPayload(array $field): array
    {
        $payload = [];

        $field_type = sanitize_key((string) ($field['field_type'] ?? ''));
        if ($field_type !== '') {
            if (!in_array($field_type, self::ALLOWED_FIELD_TYPES, true)) {
                return [];
            }

            $payload['field_type'] = $field_type;
        }

        foreach (['name', 'label', 'description', 'placeholder'] as $key) {
            $value = $this->normalizeText((string) ($field[$key] ?? ''), 240);
            if ($value !== '') {
                $payload[$key] = $value;
            }
        }

        if (array_key_exists('required', $field)) {
            $payload['required'] = !empty($field['required']);
        }

        $options = $this->normalizeFieldOptions((array) ($field['options'] ?? []), $payload['field_type'] ?? '');
        if (!empty($options)) {
            $payload['options'] = $options;
        }

        foreach (['field_settings', 'validations'] as $key) {
            if (isset($field[$key]) && is_array($field[$key])) {
                $payload[$key] = $field[$key];
            }
        }

        if ($field_type === 'inputlikert') {
            // Support AI embedding rows/columns inside field_settings instead of at top level
            $nested_fs = is_array($field['field_settings'] ?? null) ? (array) $field['field_settings'] : [];
            $likert_rows = array_values((array) ($field['likert_rows'] ?? $nested_fs['rows'] ?? []));
            $likert_columns = array_values((array) ($field['likert_columns'] ?? $nested_fs['columns'] ?? []));
            if (!empty($likert_rows)) {
                $payload['likert_rows'] = $likert_rows;
            }
            if (!empty($likert_columns)) {
                $payload['likert_columns'] = $likert_columns;
            }
            $allow_na_source = array_key_exists('likert_allow_na', $field) ? $field : (array_key_exists('likert_allow_na', $nested_fs) ? $nested_fs : null);
            if ($allow_na_source !== null) {
                $payload['likert_allow_na'] = !empty($allow_na_source['likert_allow_na']);
            }
            $req_mode_source = array_key_exists('required_mode', $field) ? $field : (array_key_exists('required_mode', $nested_fs) ? $nested_fs : null);
            if ($req_mode_source !== null) {
                $payload['required_mode'] = sanitize_key((string) ($req_mode_source['required_mode'] ?? 'all'));
            }
            $col_width_source = array_key_exists('likert_column_width', $field) ? $field : (array_key_exists('likert_column_width', $nested_fs) ? $nested_fs : null);
            if ($col_width_source !== null) {
                $payload['likert_column_width'] = sanitize_text_field((string) ($col_width_source['likert_column_width'] ?? ''));
            }

            $errors = $this->validateLikertPayload($payload);
            if (!empty($errors)) {
                return [];
            }
        }

        return $payload;
    }

    protected function validateLikertPayload(array $field): array
    {
        $errors = [];
        if (sanitize_key((string) ($field['field_type'] ?? '')) !== 'inputlikert') {
            return $errors;
        }

        $rows = (array) ($field['likert_rows'] ?? []);
        if (empty($rows)) {
            $errors[] = __('Likert fields require explicit rows.', 'griffinforms-form-builder');
            return $errors;
        }

        foreach ($rows as $row) {
            $label = $this->normalizeText((string) (is_array($row) ? ($row['label'] ?? '') : ''), 120);
            if ($label === '' || preg_match('/^row\s*\d+$/i', $label)) {
                $errors[] = __('Likert row labels must be explicit and not generic Row 1 / Row 2 / Row 3.', 'griffinforms-form-builder');
                break;
            }
        }

        $columns = (array) ($field['likert_columns'] ?? []);
        if (count($columns) < 2) {
            $errors[] = __('Likert fields require at least two columns.', 'griffinforms-form-builder');
        }

        return $errors;
    }

    protected function normalizeFieldOptions(array $options, string $field_type): array
    {
        if (empty($options)) {
            return [];
        }

        if (in_array($field_type, ['inputcheckbox', 'inputradio'], true)) {
            $normalized = [];
            
            $first = reset($options);
            if (is_array($first) && !array_key_exists('options', $first) && isset($first['label'])) {
                $options = [['options' => $options]];
            }

            foreach (array_values($options) as $group) {
                $group = (array) $group;
                $group_options = [];
                foreach (array_values((array) ($group['options'] ?? [])) as $option) {
                    $label = $this->normalizeText((string) ($option['label'] ?? ''), 120);
                    $value = $this->normalizeText((string) ($option['value'] ?? ''), 120);
                    if ($label === '' || $value === '') {
                        continue;
                    }
                    $group_options[] = [
                        'label' => $label,
                        'value' => $value,
                    ];
                }
                if (!empty($group_options)) {
                    $normalized[] = ['options' => $group_options];
                }
            }

            return $normalized;
        }

        if ($field_type === 'payment') {
            $normalized = [];
            foreach (array_values($options) as $item) {
                if (!is_array($item)) {
                    continue;
                }
                $name = $this->normalizeText((string) ($item['name'] ?? ''), 120);
                if ($name === '') {
                    continue;
                }
                $normalized[] = [
                    'id'          => $this->normalizeText((string) ($item['id'] ?? ''), 80),
                    'name'        => $name,
                    'price'       => round((float) ($item['price'] ?? 0), 2),
                    'description' => $this->normalizeText((string) ($item['description'] ?? ''), 240),
                ];
            }
            return $normalized;
        }

        $normalized = [];
        foreach (array_values($options) as $option) {
            $label = $this->normalizeText((string) ($option['label'] ?? ''), 120);
            $value = $this->normalizeText((string) ($option['value'] ?? ''), 120);
            if ($label === '' || $value === '') {
                continue;
            }
            $normalized[] = [
                'label' => $label,
                'value' => $value,
            ];
        }

        return $normalized;
    }

    protected function normalizeReplacePayload(array $replace): array
    {
        $scope = sanitize_key((string) ($replace['scope'] ?? ''));
        if (!in_array($scope, ['form', 'page', 'row', 'column', 'field'], true)) {
            return [];
        }

        $payload = [
            'scope' => $scope,
        ];

        $strategy = sanitize_key((string) ($replace['strategy'] ?? ''));
        if ($strategy !== '') {
            $payload['strategy'] = $strategy;
        }

        return $payload;
    }

    protected function normalizeRequiredInputs(array $required_inputs): array
    {
        $normalized = [];
        if (empty($required_inputs)) {
            return $normalized;
        }

        // Detect if we have a flat list of strings: ["Choice 1", "Choice 2"]
        reset($required_inputs);
        if (is_numeric(key($required_inputs)) && is_string(current($required_inputs))) {
            $normalized[] = [
                'label' => __('Choose a preferred option or type your own:', 'griffinforms-form-builder'),
                'value' => __('Choose a preferred option or type your own:', 'griffinforms-form-builder'),
            ];
            foreach ($required_inputs as $choice_str) {
                $choice_str = sanitize_text_field((string) $choice_str);
                if ($choice_str !== '') {
                    $normalized[] = [
                        'label' => $choice_str,
                        'value' => $choice_str,
                    ];
                }
            }
            return array_slice($normalized, 0, 6);
        }

        foreach ($required_inputs as $key => $input) {
            if (is_string($input)) {
                if (is_string($key)) {
                    // Handle key-value hallucination: { "label": "Please provide a label" }
                    $normalized[] = [
                        'type' => 'text',
                        'key' => sanitize_key($key),
                        'label' => sanitize_text_field($input),
                        'suggested_prompt' => sprintf('Change %s to "', $key),
                    ];
                    continue;
                } else {
                    // Single string hallucinated in a key-value iteration, map to standard object
                    $normalized[] = [
                        'label' => sanitize_text_field($input),
                        'value' => sanitize_text_field($input),
                    ];
                    continue;
                }
            }

            if (!is_array($input)) {
                continue;
            }

            $label = $this->normalizeText((string) ($input['label'] ?? ''), 120);
            $value = $this->normalizeText((string) ($input['value'] ?? ''), 120);
            $suggested_prompt = $this->normalizeText((string) ($input['suggested_prompt'] ?? ''), 220);
            if ($label === '' || $value === '') {
                continue;
            }

            $item = [
                'label' => $label,
                'value' => $value,
            ];

            if ($suggested_prompt !== '') {
                $item['suggested_prompt'] = $suggested_prompt;
            }

            $normalized[] = $item;
        }

        return array_slice($normalized, 0, 6);
    }

    protected function operationRequiresChanges(string $op): bool
    {
        return in_array($op, [
            'update_form',
            'update_page',
            'update_row',
            'update_column',
            'update_field',
            'insert_page',
            'insert_row',
            'insert_column',
            'insert_field',
            'replace',
        ], true);
    }

    protected function normalizeText(string $value, int $limit = 255): string
    {
        $value = trim(wp_strip_all_tags($value));
        if ($value === '') {
            return '';
        }

        if (function_exists('mb_substr')) {
            return mb_substr($value, 0, $limit);
        }

        return substr($value, 0, $limit);
    }
}
