<?php

namespace BookWellAB;

defined('ABSPATH') || exit;

if (!class_exists('BookWellAB_Form_Fields')) {

    class BookWellAB_Form_Fields
    {
        private static $instance = null;

        /** @var string */
        private $bookwell_page = 'form-fields';

        /** @var string */
        public const SETTING_TYPE = 'form_fields';

        /** @var string */
        public const SETTING_NAME = 'fields';

        /** @var string[] */
        private const ALLOWED_FIELD_NAMES = [
            'first_name',
            'last_name',
            'phone',
            'email',
            'note',
            'terms_and_conditions',
        ];

        public static function get_instance()
        {
            if (self::$instance === null) {
                self::$instance = new self();
            }
            return self::$instance;
        }

        public function __construct()
        {
            add_action('wp_ajax_bookwellab_get_form_fields', [$this, 'ajax_get_form_fields']);
            add_action('wp_ajax_bookwellab_save_form_fields', [$this, 'ajax_save_form_fields']);

            global $BOOKWELLAB;
            $current_page = (isset($BOOKWELLAB) && $BOOKWELLAB instanceof BOOKWELL)
                ? $BOOKWELLAB->get_bookwell_admin_page()
                : '';

            if (is_admin() && $current_page === $this->bookwell_page) {
                add_filter('bookwellab_admin_script_vriable_data', [$this, 'filter_admin_script_data'], 10, 2);
                add_action('bookwellab_' . $this->bookwell_page . '_admin_html', [$this, 'render_admin_html'], 10);
                add_filter('bookwellab_page_method_data', [$this, 'filter_page_method_data'], 10, 2);
                add_filter('bookwellab_page_mounted_data', [$this, 'filter_page_mounted_data'], 10, 2);
                add_action('bookwellab_add_extra_admin_assets', [$this, 'enqueue_admin_assets']);
            }
        }

        /**
         * @return array<int, array<string, mixed>>
         */
        /**
         * Build form field rows from legacy Booking Form Labels settings (pre-2.0.6).
         *
         * @param array<string, mixed> $labels Raw labels settings from the database.
         * @param array<string, mixed> $general General settings (for terms visibility).
         * @return array<int, array<string, mixed>>|null Null when no legacy label keys exist.
         */
        public static function build_fields_from_legacy_labels(array $labels, array $general = [])
        {
            if (!self::has_legacy_form_field_labels($labels)) {
                return null;
            }

            $fields_by_name = [];
            foreach (self::get_default_fields() as $field) {
                $fields_by_name[$field['field_name']] = $field;
            }

            if (isset($labels['phone']) && (string) $labels['phone'] !== '') {
                $fields_by_name['phone']['label'] = (string) $labels['phone'];
            }
            if (isset($labels['first_name']) && (string) $labels['first_name'] !== '') {
                $fields_by_name['first_name']['label'] = (string) $labels['first_name'];
            }
            if (isset($labels['last_name']) && (string) $labels['last_name'] !== '') {
                $fields_by_name['last_name']['label'] = (string) $labels['last_name'];
            }
            if (isset($labels['email']) && (string) $labels['email'] !== '') {
                $fields_by_name['email']['label'] = (string) $labels['email'];
            }
            if (isset($labels['notes']) && (string) $labels['notes'] !== '') {
                $fields_by_name['note']['label'] = (string) $labels['notes'];
            }
            if (isset($labels['terms_agree_error']) && (string) $labels['terms_agree_error'] !== '') {
                $fields_by_name['terms_and_conditions']['error_message'] = (string) $labels['terms_agree_error'];
            }

            $terms_prefix = isset($labels['terms_agree_prefix']) ? (string) $labels['terms_agree_prefix'] : '';
            $terms_link = isset($labels['terms_agree_link']) ? (string) $labels['terms_agree_link'] : '';
            $terms_url = isset($labels['terms_conditions_url']) ? (string) $labels['terms_conditions_url'] : '';
            if ($terms_prefix !== '' || $terms_link !== '' || $terms_url !== '') {
                if ($terms_prefix === '') {
                    $terms_prefix = __('I agree with', 'bookwellnow-appointment-booking');
                }
                if ($terms_link === '') {
                    $terms_link = __('terms & conditions', 'bookwellnow-appointment-booking');
                }
                $terms_href = $terms_url !== '' ? esc_url($terms_url) : '#';
                $fields_by_name['terms_and_conditions']['label'] = sprintf(
                    '%s <a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
                    esc_html($terms_prefix),
                    $terms_href,
                    esc_html($terms_link)
                );
            }

            if (isset($general['show_terms_conditions'])) {
                $show_terms = filter_var($general['show_terms_conditions'], FILTER_VALIDATE_BOOLEAN);
                $fields_by_name['terms_and_conditions']['is_hide'] = !$show_terms;
            }

            $fields = array_values($fields_by_name);
            usort(
                $fields,
                static function ($a, $b) {
                    return (float) ($a['field_position'] ?? 0) <=> (float) ($b['field_position'] ?? 0);
                }
            );

            return $fields;
        }

        /**
         * @param array<string, mixed> $labels Raw labels settings from the database.
         * @return bool
         */
        public static function has_legacy_form_field_labels(array $labels)
        {
            foreach (BookWellAB_Settings::get_form_field_managed_label_keys() as $label_key) {
                if (array_key_exists($label_key, $labels)) {
                    return true;
                }
            }

            return false;
        }

        public static function get_default_fields()
        {
            bookwellab_load_textdomain();
            $terms_link = __('terms & conditions', 'bookwellnow-appointment-booking');
            $terms_prefix = __('I agree with', 'bookwellnow-appointment-booking');
            $terms_href = '#';
            $terms_label = sprintf(
                '%s <a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
                esc_html($terms_prefix),
                $terms_href,
                esc_html($terms_link)
            );

            return [
                [
                    'field_name' => 'first_name',
                    'field_type' => 'Text',
                    'is_required' => true,
                    'label' => __('First Name', 'bookwellnow-appointment-booking'),
                    'placeholder' => __('Enter your first name', 'bookwellnow-appointment-booking'),
                    'error_message' => __('Please enter your first name.', 'bookwellnow-appointment-booking'),
                    'is_hide' => false,
                    'field_position' => 1,
                    'field_width' => 'half',
                ],
                [
                    'field_name' => 'last_name',
                    'field_type' => 'Text',
                    'is_required' => true,
                    'label' => __('Last Name', 'bookwellnow-appointment-booking'),
                    'placeholder' => __('Enter your last name', 'bookwellnow-appointment-booking'),
                    'error_message' => __('Please enter your last name.', 'bookwellnow-appointment-booking'),
                    'is_hide' => false,
                    'field_position' => 2,
                    'field_width' => 'half',
                ],
                [
                    'field_name' => 'phone',
                    'field_type' => 'Phone',
                    'is_required' => true,
                    'label' => __('Phone', 'bookwellnow-appointment-booking'),
                    'placeholder' => '',
                    'error_message' => __('Please enter your phone number.', 'bookwellnow-appointment-booking'),
                    'is_hide' => false,
                    'field_position' => 3,
                    'field_width' => 'half',
                ],
                [
                    'field_name' => 'email',
                    'field_type' => 'Email',
                    'is_required' => true,
                    'label' => __('Email', 'bookwellnow-appointment-booking'),
                    'placeholder' => __('Enter your email address', 'bookwellnow-appointment-booking'),
                    'error_message' => __('Please enter your email address.', 'bookwellnow-appointment-booking'),
                    'is_hide' => false,
                    'field_position' => 4,
                    'field_width' => 'half',
                ],
                [
                    'field_name' => 'note',
                    'field_type' => 'Textarea',
                    'is_required' => false,
                    'label' => __('Notes', 'bookwellnow-appointment-booking'),
                    'placeholder' => __('Enter note details', 'bookwellnow-appointment-booking'),
                    'error_message' => __('Please enter a note.', 'bookwellnow-appointment-booking'),
                    'is_hide' => false,
                    'field_position' => 5,
                    'field_width' => 'full',
                ],
                [
                    'field_name' => 'terms_and_conditions',
                    'field_type' => 'Terms',
                    'is_required' => true,
                    'label' => $terms_label,
                    'placeholder' => '',
                    'error_message' => __('Please accept the terms & conditions to continue.', 'bookwellnow-appointment-booking'),
                    'is_hide' => false,
                    'field_position' => 6,
                    'field_width' => 'full',
                ],
            ];
        }

        /**
         * Normalize form field width values to full|half.
         *
         * @param mixed $width Raw width value.
         * @return string
         */
        public static function sanitize_field_width($width)
        {
            return (sanitize_key((string) $width) === 'half') ? 'half' : 'full';
        }

        /**
         * @return array<int, array<string, mixed>>
         */
        public static function get_form_fields()
        {
            $bwdb = new BookWellAB_Database();
            $bwdb->ensure_form_fields_table_and_data();

            $rows = $bwdb->get_form_field_rows();
            if (empty($rows)) {
                return self::get_default_fields();
            }

            $saved = [];
            foreach ($rows as $row) {
                $saved[] = $bwdb->map_form_field_row_to_array($row);
            }

            return self::merge_with_defaults($saved);
        }

        /**
         * Visible fields for the frontend booking form.
         *
         * @return array<int, array<string, mixed>>
         */
        public static function get_frontend_form_fields()
        {
            $fields = self::get_form_fields();

            $visible = [];
            foreach ($fields as $field) {
                if (!empty($field['is_hide']) && ($field['field_name'] ?? '') !== 'email') {
                    continue;
                }
                if (($field['field_name'] ?? '') === 'terms_and_conditions') {
                    $field['label'] = self::hydrate_terms_label((string) ($field['label'] ?? ''), '');
                }
                $visible[] = $field;
            }

            usort(
                $visible,
                static function ($a, $b) {
                    return (float) ($a['field_position'] ?? 0) <=> (float) ($b['field_position'] ?? 0);
                }
            );

            return apply_filters('bookwellab_frontend_form_fields', $visible);
        }

        /**
         * @param array<int, array<string, mixed>> $saved_fields Saved field rows.
         * @return array<int, array<string, mixed>>
         */
        private static function merge_with_defaults(array $saved_fields)
        {
            $defaults_by_name = [];
            foreach (self::get_default_fields() as $default_field) {
                $defaults_by_name[$default_field['field_name']] = $default_field;
            }

            $merged = [];
            foreach ($saved_fields as $field) {
                if (!is_array($field)) {
                    continue;
                }
                $field_name = isset($field['field_name']) ? sanitize_key((string) $field['field_name']) : '';
                if ($field_name === '') {
                    continue;
                }
                if (!in_array($field_name, self::ALLOWED_FIELD_NAMES, true)) {
                    /**
                     * Allow Pro/custom field rows to pass through merge (return null to skip).
                     *
                     * @param array<string, mixed>|null $custom Mapped custom field or null.
                     * @param array<string, mixed>      $field  Raw saved field row.
                     */
                    $custom = apply_filters('bookwellab_form_fields_custom_row', null, $field);
                    if (is_array($custom) && !empty($custom['field_name'])) {
                        $merged[] = $custom;
                    }
                    continue;
                }
                $base = isset($defaults_by_name[$field_name]) ? $defaults_by_name[$field_name] : [];
                $merged[] = array_merge($base, $field, [
                    'field_name' => $field_name,
                    'is_default' => 1,
                ]);
            }

            foreach (self::ALLOWED_FIELD_NAMES as $field_name) {
                $exists = false;
                foreach ($merged as $field) {
                    if (($field['field_name'] ?? '') === $field_name) {
                        $exists = true;
                        break;
                    }
                }
                if (!$exists && isset($defaults_by_name[$field_name])) {
                    $merged[] = array_merge($defaults_by_name[$field_name], ['is_default' => 1]);
                }
            }

            usort(
                $merged,
                static function ($a, $b) {
                    return (float) ($a['field_position'] ?? 0) <=> (float) ($b['field_position'] ?? 0);
                }
            );

            /**
             * Filters the full admin/frontend form fields list after merging defaults.
             *
             * @param array<int, array<string, mixed>> $merged       Merged fields.
             * @param array<int, array<string, mixed>> $saved_fields Raw saved fields.
             */
            return apply_filters('bookwellab_form_fields_list', $merged, $saved_fields);
        }

        /**
         * @param string $label Raw label HTML.
         * @param string $terms_url Terms page URL.
         * @return string
         */
        private static function hydrate_terms_label($label, $terms_url)
        {
            if ($label === '') {
                return $label;
            }

            $href = $terms_url !== '' ? esc_url($terms_url) : '#';
            $label = str_replace('%terms_url%', $href, $label);
            if (strpos($label, 'href=') === false && $terms_url !== '') {
                $label = preg_replace('/href=(["\'])#\\1/', 'href=$1' . esc_url($terms_url) . '$1', $label, 1);
            }

            return wp_kses(
                $label,
                [
                    'a' => [
                        'href' => true,
                        'target' => true,
                        'rel' => true,
                        'class' => true,
                    ],
                    'span' => ['class' => true],
                    'strong' => [],
                    'em' => [],
                ]
            );
        }

        /**
         * @param array<int, array<string, mixed>> $fields Raw submitted fields.
         * @return array<int, array<string, mixed>>
         */
        private function sanitize_fields_for_save(array $fields)
        {
            $type_map = [
                'phone' => 'Phone',
                'first_name' => 'Text',
                'last_name' => 'Text',
                'email' => 'Email',
                'note' => 'Textarea',
                'terms_and_conditions' => 'Terms',
            ];

            $sanitized = [];
            $position = 1;

            foreach ($fields as $field) {
                if (!is_array($field)) {
                    continue;
                }

                $field_name = isset($field['field_name']) ? sanitize_key((string) $field['field_name']) : '';
                if ($field_name === '') {
                    continue;
                }

                if (!in_array($field_name, self::ALLOWED_FIELD_NAMES, true)) {
                    /**
                     * Sanitize a custom form field before save (return null to skip).
                     *
                     * @param array<string, mixed>|null $custom Sanitized field or null.
                     * @param array<string, mixed>      $field  Raw submitted field.
                     * @param float                     $position Suggested position.
                     */
                    $custom = apply_filters('bookwellab_form_fields_sanitize_custom_field', null, $field, (float) $position);
                    if (is_array($custom) && !empty($custom['field_name'])) {
                        $sanitized[] = $custom;
                        ++$position;
                    }
                    continue;
                }

                $is_required = !empty($field['is_required']);
                if ($field_name === 'terms_and_conditions') {
                    $is_required = true;
                }

                $label = isset($field['label']) ? (string) $field['label'] : '';
                if ($field_name === 'terms_and_conditions') {
                    $label = wp_kses_post($label);
                } else {
                    $label = sanitize_text_field($label);
                }

                $sanitized[] = [
                    'field_name' => $field_name,
                    'field_type' => $type_map[$field_name],
                    'is_required' => $is_required,
                    'label' => $label,
                    'placeholder' => sanitize_text_field((string) ($field['placeholder'] ?? '')),
                    'error_message' => sanitize_text_field((string) ($field['error_message'] ?? '')),
                    'is_hide' => $field_name === 'email' ? false : !empty($field['is_hide']),
                    'field_position' => isset($field['field_position']) ? (float) $field['field_position'] : (float) $position,
                    'is_default' => 1,
                    'field_width' => self::sanitize_field_width($field['field_width'] ?? 'full'),
                ];

                ++$position;
            }

            usort(
                $sanitized,
                static function ($a, $b) {
                    return (float) ($a['field_position'] ?? 0) <=> (float) ($b['field_position'] ?? 0);
                }
            );

            $position = 1;
            foreach ($sanitized as &$field) {
                $field['field_position'] = (float) $position;
                ++$position;
            }
            unset($field);

            /**
             * Filters sanitized form fields before persistence.
             *
             * @param array<int, array<string, mixed>> $sanitized Sanitized fields.
             * @param array<int, array<string, mixed>> $fields    Raw submitted fields.
             */
            return apply_filters('bookwellab_form_fields_sanitize_for_save', $sanitized, $fields);
        }

        /**
         * @param array<int, array<string, mixed>> $fields Field rows.
         * @return array<int, array<string, mixed>>
         */
        private function prepare_fields_for_admin(array $fields)
        {
            $prepared = [];
            foreach ($fields as $field) {
                $field['is_edit'] = false;
                $field['is_required'] = !empty($field['is_required']);
                $field['is_hide'] = !empty($field['is_hide']);
                $field['field_width'] = self::sanitize_field_width($field['field_width'] ?? 'full');
                if (($field['field_name'] ?? '') === 'email') {
                    $field['is_hide'] = false;
                }
                $prepared[] = $field;
            }

            /**
             * Filters form fields prepared for the admin Vue list.
             *
             * @param array<int, array<string, mixed>> $prepared Prepared fields.
             */
            return apply_filters('bookwellab_form_fields_prepare_for_admin', $prepared);
        }

        public function enqueue_admin_assets()
        {
            global $BOOKWELLAB;
            $current_page = (isset($BOOKWELLAB) && $BOOKWELLAB instanceof BOOKWELL)
                ? $BOOKWELLAB->get_bookwell_admin_page()
                : '';
            if ($current_page !== $this->bookwell_page) {
                return;
            }

            wp_enqueue_style(
                'bookwellab-form-fields-admin',
                BOOKWELLAB_URL . 'assets/css/form-fields-admin.css',
                ['bookwellab-admin-style'],
                BOOKWELLAB_VERSION
            );
        }

        public function render_admin_html()
        {
            require BOOKWELLAB_HTML_PATH . 'form_fields_view.php';
        }

        /**
         * @param array<string, mixed> $bookwellab_admin_script_vriable_data Vue data.
         * @param string               $bookwellpage Current page key.
         * @return array<string, mixed>
         */
        public function filter_admin_script_data($bookwellab_admin_script_vriable_data, $bookwellpage)
        {
            if ($this->bookwell_page !== $bookwellpage) {
                return $bookwellab_admin_script_vriable_data;
            }

            $bookwellab_admin_script_vriable_data['form_fields_list'] = $this->prepare_fields_for_admin(self::get_form_fields());
            $bookwellab_admin_script_vriable_data['form_fields_saving'] = false;
            $bookwellab_admin_script_vriable_data['form_fields_ui'] = [
                'required' => __('Required', 'bookwellnow-appointment-booking'),
                'hide_field' => __('Hide field on frontend', 'bookwellnow-appointment-booking'),
                'label' => __('Label', 'bookwellnow-appointment-booking'),
                'placeholder' => __('Placeholder', 'bookwellnow-appointment-booking'),
                'error_message' => __('Error message', 'bookwellnow-appointment-booking'),
                'save' => __('Save', 'bookwellnow-appointment-booking'),
                'save_changes' => __('Save Changes', 'bookwellnow-appointment-booking'),
                'saved' => __('Form fields saved successfully.', 'bookwellnow-appointment-booking'),
                'terms_required_note' => __('Terms & conditions is always required when visible.', 'bookwellnow-appointment-booking'),
                'width_full' => __('Full Width', 'bookwellnow-appointment-booking'),
                'width_half' => __('Half Width', 'bookwellnow-appointment-booking'),
            ];

            return $bookwellab_admin_script_vriable_data;
        }

        /**
         * @param string $bookwellab_page_mounted_data Mounted JS snippet.
         * @param string $bookwellpage Current page key.
         * @return string
         */
        public function filter_page_mounted_data($bookwellab_page_mounted_data, $bookwellpage)
        {
            if ($this->bookwell_page !== $bookwellpage) {
                return $bookwellab_page_mounted_data;
            }

            $bookwellab_page_mounted_data .= '
                this.$nextTick(() => {
                    this.bwab_initFormFieldsSortable();
                });
            ';

            return $bookwellab_page_mounted_data;
        }

        /**
         * @param string $bookwellab_page_method_data Vue methods JS.
         * @param string $bookwellpage Current page key.
         * @return string
         */
        public function filter_page_method_data($bookwellab_page_method_data, $bookwellpage)
        {
            if ($this->bookwell_page !== $bookwellpage) {
                return $bookwellab_page_method_data;
            }

            $saved_msg = esc_js(__('Form fields saved successfully.', 'bookwellnow-appointment-booking'));
            $error_msg = esc_js(__('Unable to save form fields. Please try again.', 'bookwellnow-appointment-booking'));

            $bookwellab_page_method_data .= '
                bwab_initFormFieldsSortable() {
                    const vuedata = this;
                    const listEl = document.getElementById("bw-form-fields-sortable");
                    if (!listEl || typeof Sortable === "undefined") {
                        return;
                    }
                    if (vuedata.bw_form_fields_sortable) {
                        vuedata.bw_form_fields_sortable.destroy();
                        vuedata.bw_form_fields_sortable = null;
                    }
                    vuedata.bw_form_fields_sortable = Sortable.create(listEl, {
                        handle: ".bw-ff-drag-handle",
                        animation: 150,
                        draggable: ".bw-ff-field-card",
                        onEnd() {
                            const ordered = [];
                            listEl.querySelectorAll(".bw-ff-field-card").forEach((card, index) => {
                                const fieldName = card.getAttribute("data-field-name");
                                const field = vuedata.form_fields_list.find((item) => item.field_name === fieldName);
                                if (field) {
                                    field.field_position = index + 1;
                                    ordered.push(field);
                                }
                            });
                            if (ordered.length) {
                                vuedata.form_fields_list = ordered;
                            }
                        }
                    });
                },

                bwab_closeFormFieldSettings(fieldName) {
                    this.form_fields_list.forEach((field) => {
                        field.is_edit = field.field_name === fieldName ? false : field.is_edit;
                    });
                },

                bwab_toggleFormFieldSettings(fieldName) {
                    this.form_fields_list.forEach((field) => {
                        if (field.field_name === fieldName) {
                            field.is_edit = !field.is_edit;
                        } else {
                            field.is_edit = false;
                        }
                    });
                },

                bwab_saveFormFields() {
                    const vuedata = this;
                    vuedata.form_fields_saving = true;
                    const ajax_url = bookwellab_ajax_obj.ajax_url;
                    const pass_data = {
                        action: "bookwellab_save_form_fields",
                        _wpnonce: bookwellab_ajax_obj.bookwellab_nonce,
                        field_settings: JSON.stringify(vuedata.form_fields_list)
                    };
                    axios.post(ajax_url, Qs.stringify(pass_data))
                        .then(function(response) {
                            vuedata.form_fields_saving = false;
                            if (response.data && response.data.type === "success") {
                                vuedata.form_fields_list = response.data.list_data || vuedata.form_fields_list;
                                vuedata.$notify({
                                    title: "Success",
                                    message: "' . $saved_msg . '",
                                    type: "success",
                                    duration: 2500,
                                    customClass: "bwab-admin-notification"
                                });
                            } else {
                                vuedata.$notify({
                                    title: "Error",
                                    message: (response.data && response.data.message) ? response.data.message : "' . $error_msg . '",
                                    type: "error",
                                    duration: 3000,
                                    customClass: "bwab-admin-notification"
                                });
                            }
                        })
                        .catch(function() {
                            vuedata.form_fields_saving = false;
                            vuedata.$notify({
                                title: "Error",
                                message: "' . $error_msg . '",
                                type: "error",
                                duration: 3000,
                                customClass: "bwab-admin-notification"
                            });
                        });
                }
            ';

            return $bookwellab_page_method_data;
        }

        public function ajax_get_form_fields()
        {
            if (!check_ajax_referer('bw_wp_nonce', '_wpnonce', false)) {
                wp_send_json_error(['message' => __('Invalid security token.', 'bookwellnow-appointment-booking')]);
            }

            if (!current_user_can('bookwellab_manage_settings')) {
                wp_send_json_error(['message' => __('Permission denied.', 'bookwellnow-appointment-booking')]);
            }

            wp_send_json(
                [
                    'type' => 'success',
                    'list_data' => $this->prepare_fields_for_admin(self::get_form_fields()),
                ]
            );
        }

        public function ajax_save_form_fields()
        {
            if (!check_ajax_referer('bw_wp_nonce', '_wpnonce', false)) {
                wp_send_json_error(['message' => __('Invalid security token.', 'bookwellnow-appointment-booking')]);
            }

            if (!current_user_can('bookwellab_manage_settings')) {
                wp_send_json_error(['message' => __('Permission denied.', 'bookwellnow-appointment-booking')]);
            }

            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in sanitize_fields_for_save().
            $raw_json = isset($_POST['field_settings']) ? wp_unslash($_POST['field_settings']) : '';
            $decoded = json_decode((string) $raw_json, true);
            if (!is_array($decoded)) {
                wp_send_json_error(['message' => __('Invalid form field data.', 'bookwellnow-appointment-booking')]);
            }

            $sanitized = $this->sanitize_fields_for_save($decoded);
            $bwdb = new BookWellAB_Database();
            $bwdb->ensure_form_fields_table_and_data();
            $bwdb->save_form_field_rows_from_api_format($sanitized);

            /**
             * Fires after form field definitions are saved (Pro may delete removed custom fields).
             *
             * @param array<int, array<string, mixed>> $sanitized Fields that were saved.
             * @param array<int, array<string, mixed>> $decoded   Raw submitted fields.
             */
            do_action('bookwellab_form_fields_after_save', $sanitized, $decoded);

            $saved = [];
            foreach ($bwdb->get_form_field_rows() as $row) {
                $saved[] = $bwdb->map_form_field_row_to_array($row);
            }
            $saved = self::merge_with_defaults($saved);

            BookWellAB_Settings::clear_frontend_cache();

            wp_send_json(
                [
                    'type' => 'success',
                    'list_data' => $this->prepare_fields_for_admin($saved),
                    'message' => __('Form fields saved successfully.', 'bookwellnow-appointment-booking'),
                ]
            );
        }
    }
}
