<?php

namespace FluentForm\App\Modules\Ai;

defined('ABSPATH') or die;

use Exception;
use FluentForm\App\Helpers\Helper;
use FluentForm\App\Models\Form;
use FluentForm\App\Models\FormMeta;
use FluentForm\App\Modules\Acl\Acl;
use FluentForm\App\Modules\Form\FormFieldsParser;
use FluentForm\App\Modules\Payments\PaymentHelper;
use FluentForm\App\Services\FluentConversational\Classes\Converter\Converter;
use FluentForm\App\Services\Form\FormService;
use FluentForm\Framework\Helpers\ArrayHelper as Arr;
use FluentForm\Framework\Support\Sanitizer;

class AiFormBuilder extends FormService
{
    private $allDefaultFields = [];

    public function __construct()
    {
        parent::__construct();
        add_action('wp_ajax_fluentform_ai_create_form', [$this, 'buildForm'], 11, 0);
    }

    public function buildForm()
    {
        try {
            Acl::verify('fluentform_forms_manager');
            $form = $this->generateForm($this->app->request->all());
            $form = $this->prepareAndSaveForm($form);
            wp_send_json_success([
                'formId'       => $form->id,
                'redirect_url' => admin_url(
                    'admin.php?page=fluent_forms&form_id=' . $form->id . '&route=editor'
                ),
                'message'      => __('Successfully created a form.', 'fluentform'),
            ], 200);
        } catch (Exception $e) {
            wp_send_json_error([
                'message' => $e->getMessage(),
            ], 422);
        }
    }

    /**
     * @param array $form
     * @return Form|\FluentForm\Framework\Database\Query\Builder
     * @throws Exception
     */
    protected function prepareAndSaveForm($form)
    {
        $allFields = $this->getDefaultFields();
        $fluentFormFields = [];
        $fields = Arr::get($form, 'fields', []);
        $isConversational = Arr::isTrue($form, 'is_conversational');
        $customCss = Arr::get($form, 'custom_css', '');
        $hasStep = false;
        $lastFieldIndex = count($fields) - 1;

        $disableFields = array_keys($this->getDisabledComponents());
        foreach ($fields as $index => $field) {
            if (count($field) == 1) {
                $field = reset($field);
            }
            if ($element = $this->resolveInput($field)) {
                if (in_array($element, $disableFields)) {
                    continue;
                }
                if (!$hasStep && 'form_step' === $element) {
                    if (0 === $index || $lastFieldIndex === $index) {
                        continue;
                    }
                    $hasStep = true;
                }
                $fluentFormFields[] = $this->processField($element, $field, $allFields);
            }
        }
        $fluentFormFields = array_filter($fluentFormFields);
        if (!$fluentFormFields) {
            throw new Exception(esc_html__('Empty form. Please try again!', 'fluentform'));
        }
        $title = Arr::get($form, 'title', '');
        return $this->saveForm($fluentFormFields, $title, $hasStep, $isConversational, $customCss);
    }

    /**
     * @param array $args
     * @return array response form fields
     * @throws Exception
     */
    protected function generateForm($args)
    {
        $aiModel = Arr::get($args, 'ai_model', 'default');
        $isUsingChatGpt = Helper::hasPro() && 'chat_gpt' == $aiModel && class_exists('FluentFormPro\classes\Chat\ChatFormBuilder');

        if ($isUsingChatGpt) {
            (new \FluentFormPro\classes\Chat\ChatFormBuilder())->buildForm();
        }

        $paymentSetting = PaymentHelper::getPaymentSettings();
        $queryArgs = [
            'user_prompt'    => $this->getUserPrompt($args),
            'site_url'       => site_url(),
            'site_title'     => get_bloginfo('name'),
            'site_locale'    => determine_locale(),
            'has_pro'        => Helper::hasPro(),
            'has_payment'    => $paymentSetting['status'] == 'yes',
            'request_id'     => uniqid('ff_ai_'),
            'save_usage'     => apply_filters('fluentform/ai_save_usage', true),
        ];

        $result = (new FluentFormAIAPI())->makeRequest($queryArgs);

        if (is_wp_error($result)) {
            throw new Exception(esc_html($result->get_error_message()));
        }
       
        $response = trim(Arr::get($result, 'response', ''), '"');
        if (false !== preg_match('/```json(.*?)```/s', $response, $matches)) {
            $response = trim($matches[1]);
        }

        $decoded = json_decode($response, true);
        if (json_last_error() !== JSON_ERROR_NONE || empty($decoded) || empty($decoded['fields'])) {
            throw new Exception(esc_html__('Invalid response: Please try again!', 'fluentform'));
        }
        return $this->applyPromptHints($decoded, $args);
    }
    
    protected function getDefaultFields()
    {
        if ($this->allDefaultFields) {
            return $this->allDefaultFields;
        }
        /**
         * @var \FluentForm\App\Services\FormBuilder\Components
         */
        $components = $this->app->make('components');
        $this->app->doAction('fluentform/editor_init', $components);
        $editorComponents = $components->toArray();
        $general = Arr::get($editorComponents, 'general', []);
        $advanced = Arr::get($editorComponents, 'advanced', []);
        $container = Arr::get($editorComponents, 'container', []);

        // Apply filter to get additional components
        // The second parameter (true) is passed to prevent field loss when form ID is falsy loss some field
        $editorComponents = apply_filters('fluentform/editor_components', [], true);
        if ($generalExtra = Arr::get($editorComponents, 'general')) {
            $generalExtra = array_column($generalExtra, null, 'element');
            $general = array_merge($general, $generalExtra);
        }

        if ($advancedExtra = Arr::get($editorComponents, 'advanced')) {
            $advancedExtra = array_column($advancedExtra, null, 'element');
            $advanced = array_merge($advanced, $advancedExtra);
        }

        $payments = Arr::get($editorComponents, 'payments', []);
        $payments = array_column($payments, null, 'element');
        $this->allDefaultFields = array_merge($general, $payments, $advanced, ['container' => $container]);
        return $this->allDefaultFields;
    }
    
    protected function processField($element, $field, $allFields)
    {
        if ('container' == $element) {
            return $this->resolveContainerFields($field, $allFields);
        }

        $matchedField = Arr::get($allFields, $element);
        if (!$matchedField) {
            return [];
        }
        $formatField = $matchedField;
        if ($settings = Arr::get($field, 'settings')) {
            // Replace 'label' with 'admin_field_label' if 'label' is shorter
            if (isset($settings['label']) && $adminFieldLabel = Arr::get($settings, 'admin_field_label')) {
                if (strlen($settings['label']) < strlen($adminFieldLabel)) {
                    $settings['label'] = $adminFieldLabel;
                }
            }
            $formatField['settings'] = wp_parse_args($settings, $matchedField['settings']);
        }
        if ($attributes = Arr::get($field, 'attributes')) {
            $formatField['attributes'] = wp_parse_args($attributes, $matchedField['attributes']);
        }

        $formatField['uniqElKey'] = "el_" . uniqid();

        if ('form_step' === $element) {
            return $formatField;
        }

        if ($fieldName = Arr::get($field, 'attributes.name')) {
            $formatField['attributes']['name'] = $fieldName;
        }

        if ($options = $this->getOptions(Arr::get($field, 'options'))) {
            if (isset($formatField['settings']['advanced_options'])) {
                $formatField['settings']['advanced_options'] = $options;
            }
            if ('ratings' == $element) {
                $formatField['options'] = array_column($options, 'label', 'value');
            }
        }

        if ('rangeslider' == $element) {
            if ($min = Arr::get($field, 'min')) {
                $formatField['attributes']['min'] = intval($min);
            }
            if ($max = intval(Arr::get($field, 'max', 10))) {
                $formatField['attributes']['max'] = $max;
            }
        }

        if (in_array($element, ['input_name', 'address']) && $fields = Arr::get($field, 'fields')) {
            foreach ($formatField['fields'] as $name => &$field) {
                if ($targetAttributes = Arr::get($fields, "$name.attributes")) {
                    $field['attributes'] = wp_parse_args($targetAttributes, $field['attributes']);
                }
                if ($targetSettings = Arr::get($fields, "$name.settings")) {
                    $field['settings'] = wp_parse_args($targetSettings, $field['settings']);
                }
            }
        }
        
        return $formatField;
    }
    
    protected function resolveInput($field)
    {
        if (!is_array($field)) {
            return false;
        }
        $element = Arr::get($field, 'element');
        $allElements = array_keys($this->getDefaultFields());
        if (in_array($element, $allElements)) {
            return $element;
        }

        $type = Arr::get($field, 'type');
        if (!$type) {
            return false;
        }

        $searchTags = fluentformLoadFile('Services/FormBuilder/ElementSearchTags.php');
        $form = ['type' => ''];
        $form = json_decode(json_encode($form));
        $searchTags = apply_filters('fluentform/editor_element_search_tags', $searchTags, $form);
        foreach ($searchTags as $inputKey => $tags) {
            if (array_search($type, $tags) !== false) {
                return $inputKey;
            } else {
                foreach ($tags as $tag) {
                    if (strpos($tag, $type) !== false) {
                        return $inputKey;
                    }
                }
            }
        }
        return false;
    }
    
    protected function getOptions($options = [])
    {
        $formattedOptions = [];
        if (empty($options) || !is_array($options)) {
            return $options;
        }
        foreach ($options as $key => $option) {
            if (is_string($option) || is_numeric($option)) {
                $value = $label = $option;
            } elseif (is_array($option)) {
                $label = Arr::get($option, 'label');
                $value = Arr::get($option, 'value');
            } else {
                continue;
            }
            if (!$value || !$label) {
                $value = $value ?? $label;
                $label = $label ?? $value;
            }
            if (!$value || !$label) {
                continue;
            }
            $formattedOptions[] = [
                'label' => $label,
                'value' => $value,
            ];
        }
        
        return $formattedOptions;
    }
    
    protected function getBlankFormConfig()
    {
        $attributes = ['type' => 'form', 'predefined' => 'blank_form'];
        $customForm = Form::resolvePredefinedForm($attributes);
        $customForm['form_fields'] = json_decode($customForm['form_fields'], true);
        $customForm['form_fields']['submitButton'] = $customForm['form']['submitButton'];
        $customForm['form_fields'] = json_encode($customForm['form_fields']);
        return $customForm;
    }
    
    protected function saveForm($formattedInputs, $title, $isStepForm = false, $isConversational = false, $customCss = '')
    {
        $customForm = $this->prepareCustomForm($formattedInputs, $isStepForm);
        $data = Form::prepare($customForm);

        $form = $this->model->create($data);
        $form->title = $title ?: $form->title . ' (ChatGPT#' . $form->id . ')';

        $formData = (object)$form->toArray();
        if (FormFieldsParser::hasPaymentFields($formData)) {
            $form->has_payment = 1;
        }

        if ($isConversational) {
            $formMeta = FormMeta::prepare(['type' => 'form', 'predefined' => 'conversational'], $customForm);
            $form->fill([
                'form_fields' => Converter::convertExistingForm($form),
            ])->save();
        } else {
            $form->save();
            $formMeta = FormMeta::prepare(['type' => 'form', 'predefined' => 'blank_form'], $customForm);
        }

        FormMeta::store($form, $formMeta);

        if ($customCss = fluentformSanitizeCSS($customCss)) {
            Helper::setFormMeta($form->id, '_custom_form_css', $customCss);
        }
        
        do_action('fluentform/inserted_new_form', $form->id, $data);
        return $form;
    }

    protected function prepareCustomForm($formattedInputs, $isStepForm)
    {
        $formattedInputs = fluentFormSanitizer($formattedInputs);
        $customForm = $this->getBlankFormConfig();
        $fields = json_decode($customForm['form_fields'], true);

        $fields['form_fields']['fields'] = $formattedInputs;
        $fields['form_fields']['submitButton'] = Arr::get($customForm, 'form.submitButton');

        if ($isStepForm) {
            $fields['form_fields']['stepsWrapper'] = $this->getStepWrapper();
        }

        $customForm['form_fields'] = json_encode($fields['form_fields']);

        return $customForm;
    }

    protected function resolveContainerFields($field, $allFields)
    {
        $columns = Arr::get($field, 'columns');
        $columnsCount = count($columns);
        if (!$columnsCount || $columnsCount > 6) {
            return [];
        }
        $matchedField = Arr::get($allFields, 'container.container_' . $columnsCount . '_col');
        if (!$matchedField) {
            return [];
        }
        $columnWidth = round(100 / $columnsCount, 2);
        foreach ($columns as &$column) {
            $formatedFields = [];
            $fields = Arr::get($column, 'fields', []);
            $columnWidth = Arr::get($column, 'width', $columnWidth);
            foreach ($fields as $colField) {
                $element = Arr::get($colField, 'element');
                if ($columnField = $this->processField($element, $colField, $allFields)) {
                    $formatedFields[] = $columnField;
                }
            }
            if ($formatedFields) {
                $column['fields'] = $formatedFields;
                $column['width'] = $columnWidth;
            }
        }
        $matchedField['columns'] = $columns;
        return $matchedField;
    }

    /**
     * @return array
     */
    protected function getStepWrapper()
    {
        return [
            'stepStart' => [
                'element'        => 'step_start',
                'attributes'     => [
                    'id'    => '',
                    'class' => '',
                ],
                'settings'       => [
                    'progress_indicator'           => 'progress-bar',
                    'step_titles'                  => [],
                    'disable_auto_focus'           => 'no',
                    'enable_auto_slider'           => 'no',
                    'enable_step_data_persistency' => 'no',
                    'enable_step_page_resume'      => 'no',
                ],
                'editor_options' => [
                    'title' => 'Start Paging'
                ],
            ],
            'stepEnd'   => [
                'element'        => 'step_end',
                'attributes'     => [
                    'id'    => '',
                    'class' => '',
                ],
                'settings'       => [
                    'prev_btn' => [
                        'type'    => 'default',
                        'text'    => 'Previous',
                        'img_url' => ''
                    ]
                ],
                'editor_options' => [
                    'title' => 'End Paging'
                ],
            ]
        ];
    }
    
    private function getUserPrompt($args)
    {
        $startingQuery = "Create a form for ";
        $query = Sanitizer::sanitizeTextField(Arr::get($args, 'query'));
        if (empty($query)) {
            throw new Exception(esc_html__('Query is empty!', 'fluentform'));
        }
        
        // Validate query length to prevent abuse (max 2000 characters)
        if (strlen($query) > 2000) {
            throw new Exception(esc_html__('Query is too long. Please limit your prompt to 2000 characters.', 'fluentform'));
        }
        
        $additionalQuery = Sanitizer::sanitizeTextField(Arr::get($args, 'additional_query'));
        
        // Validate additional query length (max 1000 characters)
        if ($additionalQuery && strlen($additionalQuery) > 1000) {
            throw new Exception(esc_html__('Additional query is too long. Please limit to 1000 characters.', 'fluentform'));
        }
        
        if ($additionalQuery) {
            $query .= "\n including questions for information like  " . $additionalQuery . ".";
        }
        return $startingQuery . $query . $this->getPromptContractInstructions();
    }

    private function getPromptContractInstructions()
    {
        return "\n\nReturn strict JSON only. The user's instructions may be written in any language. "
            . "Preserve labels and help text in the user's original language, but always return machine-readable field settings. "
            . "For every field, explicitly set whether it is required in settings.validation_rules.required.value when the prompt marks it as required or optional. "
            . "If the prompt lists allowed upload extensions, map them into settings.validation_rules.allowed_file_types.value. "
            . "If the prompt asks for a multi-step form with sections, include form_step elements between sections.";
    }

    private function applyPromptHints(array $form, array $args)
    {
        $query = (string) Arr::get($args, 'query', '');
        $additionalQuery = (string) Arr::get($args, 'additional_query', '');
        $hints = $this->extractPromptFieldHints(trim($query . "\n" . $additionalQuery));

        if (!$hints) {
            return $form;
        }

        $hintIndex = 0;
        $form['fields'] = $this->applyHintsToFields(Arr::get($form, 'fields', []), $hints, $hintIndex);

        return $form;
    }

    private function applyHintsToFields(array $fields, array $hints, &$hintIndex)
    {
        foreach ($fields as &$field) {
            if (!is_array($field)) {
                continue;
            }

            if (count($field) === 1) {
                $field = reset($field);
            }

            $element = Arr::get($field, 'element');

            if ('container' === $element) {
                $columns = Arr::get($field, 'columns', []);
                foreach ($columns as &$column) {
                    $column['fields'] = $this->applyHintsToFields(Arr::get($column, 'fields', []), $hints, $hintIndex);
                }
                $field['columns'] = $columns;
                continue;
            }

            if (in_array($element, ['form_step', 'section_break'])) {
                continue;
            }

            $hint = Arr::get($hints, $hintIndex);
            $hintIndex++;

            if (!$hint) {
                continue;
            }

            $field = $this->mergeFieldHint($field, $hint);
        }

        return $fields;
    }

    private function mergeFieldHint(array $field, array $hint)
    {
        if (array_key_exists('required', $hint)) {
            Arr::set($field, 'settings.validation_rules.required.value', (bool) $hint['required']);
        }

        if (!empty($hint['help_message'])) {
            Arr::set($field, 'settings.help_message', $hint['help_message']);
        }

        if (!empty($hint['allowed_file_types']) && in_array(Arr::get($field, 'element'), ['input_file', 'input_image'])) {
            Arr::set($field, 'settings.validation_rules.allowed_file_types.value', array_values(array_unique($hint['allowed_file_types'])));
        }

        return $field;
    }

    private function extractPromptFieldHints($prompt)
    {
        if (!$prompt) {
            return [];
        }

        $lines = preg_split('/\R/u', $prompt);
        $hints = [];

        foreach ($lines as $line) {
            $line = trim(wp_strip_all_tags($line));
            $line = preg_replace('/^[\-\*\d\.\)\s]+/u', '', $line);

            if (!$line || !$this->looksLikeFieldLine($line)) {
                continue;
            }

            $attributesText = '';
            if (preg_match('/\(([^()]*)\)/u', $line, $matches)) {
                $attributesText = trim($matches[1]);
            }

            $hint = [];
            $normalizedLine = function_exists('mb_strtolower') ? mb_strtolower($line, 'UTF-8') : strtolower($line);

            if ($this->containsAnyPhrase($normalizedLine, $this->getRequiredKeywords())) {
                $hint['required'] = true;
            } elseif ($this->containsAnyPhrase($normalizedLine, $this->getOptionalKeywords())) {
                $hint['required'] = false;
            }

            $helpMessage = $this->extractHelpMessage($attributesText);
            if ($helpMessage) {
                $hint['help_message'] = $helpMessage;
            }

            if ($this->containsAnyPhrase($normalizedLine, $this->getFileUploadKeywords())) {
                $allowedTypes = $this->extractAllowedFileTypes($line);
                if ($allowedTypes) {
                    $hint['allowed_file_types'] = $allowedTypes;
                }
            }

            $hints[] = $hint;
        }

        return $hints;
    }

    private function looksLikeFieldLine($line)
    {
        if (preg_match('/^(create|erstelle|crée|crear|criar|crea|maak|utw[oó]rz|oluştur|创建|作成)\b/ui', $line)) {
            return false;
        }

        if (preg_match('/^(section|abschnitt|secci[oó]n|sectione|seção|sectie|sekcja|b[oö]l[uü]m|章节|セクション)\b/ui', $line)) {
            return false;
        }

        if (false === strpos($line, '(')) {
            return false;
        }

        preg_match('/\(([^()]*)\)/u', $line, $matches);
        $attributesText = isset($matches[1]) ? $matches[1] : '';

        return $this->containsAnyPhrase(
            function_exists('mb_strtolower') ? mb_strtolower($attributesText, 'UTF-8') : strtolower($attributesText),
            $this->getFieldDescriptorKeywords()
        );
    }

    private function extractHelpMessage($attributesText)
    {
        if (!$attributesText) {
            return '';
        }

        if (preg_match('/(?:note|hint|hinweis|remarque|nota|nota bene|opmerking|uwaga|not|说明|備考)\s*:\s*["“]?(.+?)["”]?$/ui', $attributesText, $matches)) {
            return trim($matches[1], " \t\n\r\0\x0B\"'“”");
        }

        return '';
    }

    private function extractAllowedFileTypes($line)
    {
        preg_match_all('/\b(jpe?g|gif|png|tiff?|bmp|webp|svg|pdf|docx?|xlsx?|xls|csv|zip|rar|txt)\b/ui', $line, $matches);

        if (empty($matches[1])) {
            return [];
        }

        $types = array_map(function ($type) {
            $type = strtolower($type);

            if ('jpeg' === $type) {
                return 'jpg';
            }

            if ('tif' === $type) {
                return 'tiff';
            }

            return $type;
        }, $matches[1]);

        return array_values(array_unique($types));
    }

    private function containsAnyPhrase($text, array $phrases)
    {
        foreach ($phrases as $phrase) {
            if (false !== strpos($text, $phrase)) {
                return true;
            }
        }

        return false;
    }

    private function getRequiredKeywords()
    {
        return [
            'required',
            'mandatory',
            'pflichtfeld',
            'erforderlich',
            'obligatoire',
            'requis',
            'obligatorio',
            'obrigatório',
            'obbligatorio',
            'verplicht',
            'wymagane',
            'zorunlu',
            '必填',
            '必須'
        ];
    }

    private function getOptionalKeywords()
    {
        return [
            'optional',
            'facultatif',
            'facoltativo',
            'opcjonalne',
            'isteğe bağlı',
            'opcional',
            'opcionales',
            '选填',
            '任意'
        ];
    }

    private function getFileUploadKeywords()
    {
        return [
            'file upload',
            'upload',
            'datei',
            'datei upload',
            'foto upload',
            'image upload',
            'upload de fichier',
            'carga de archivo',
            'subida de archivo',
            'carregamento de arquivo',
            'caricamento file',
            'bestandsupload',
            'yükleme',
            '上传'
        ];
    }

    private function getFieldDescriptorKeywords()
    {
        return [
            'text',
            'textarea',
            'email',
            'date',
            'file',
            'upload',
            'phone',
            'number',
            'radio',
            'checkbox',
            'select',
            'dropdown',
            'image',
            'foto',
            'bild',
            'datei',
            'telefon',
            'mobil',
            'correo',
            'texte',
            'texto',
            'testo',
            'fecha',
            'fichier',
            'archivo',
            'caricamento',
            'ficheiro',
            'bestand',
            'yükleme',
            '上传'
        ];
    }
}
