<?php
/**
 * Form Builder — Custom booking form fields
 *
 * Manages reusable form templates with custom fields per platform category or product.
 * Supports Solo (admin-only) and Marketplace (admin + vendor) modes.
 *
 * Architecture:
 *   Solo mode:   Admin creates form templates at global/category/product scope.
 *                No vendor-level override exists. Admin IS the vendor.
 *   Marketplace: Admin creates global/category defaults. Vendors can create
 *                vendor-scope or product-scope overrides (if permitted).
 *
 *   Resolution order (first match wins):
 *     1. Product-level (scope=product, product_id matches)
 *     2. Vendor-level (scope=vendor, vendor_id + category) — Marketplace only
 *     3. Category-level (scope=category, category_slug matches)
 *     4. Global default (scope=global)
 *
 *   Form data is stored in booking meta_data JSON column.
 *
 *   Field types (13 total):
 *     Basic:    text, email, number, phone, date, textarea
 *     Choice:   select (dropdown), checkbox, radio
 *     Display:  heading (section separator), html (static content)
 *     Special:  hidden, consent (checkbox + terms link)
 *
 * @package WahraRentalsBookings
 * @since   2.3.0
 */

defined('ABSPATH') || exit;

/**
 * Class WAHRB_Form_Builder
 *
 * Singleton. Use WAHRB_Form_Builder::instance().
 */
class WAHRB_Form_Builder {

    /**
     * Singleton instance
     *
     * @var self|null
     */
    private static ?self $instance = null;

    /**
     * Database instance
     *
     * @var WAHRB_Database
     */
    private WAHRB_Database $db;

    /**
     * Supported field types (grouped by category)
     *
     * Basic:   text, email, number, phone, date, textarea
     * Choice:  select, checkbox, radio
     * Display: heading, html
     * Special: hidden, consent
     *
     * @var array
     */
    public const FIELD_TYPES = [
        // Basic input fields
        'text'     => 'Text',
        'email'    => 'Email',
        'number'   => 'Number',
        'phone'    => 'Phone',
        'date'     => 'Date',
        'textarea' => 'Textarea',
        // Choice fields
        'select'   => 'Dropdown',
        'checkbox' => 'Checkbox',
        'radio'    => 'Radio',
        // Display / layout fields
        'heading'  => 'Section Heading',
        'html'     => 'HTML Block',
        // Special fields
        'hidden'   => 'Hidden',
        'consent'  => 'Consent',
    ];

    /**
     * Field type categories (for UI grouping in admin/vendor form builder)
     *
     * @var array
     */
    public const FIELD_TYPE_GROUPS = [
        'basic'   => [
            'label'  => 'Basic Fields',
            'types'  => ['text', 'email', 'number', 'phone', 'date', 'textarea'],
        ],
        'choice'  => [
            'label'  => 'Choice Fields',
            'types'  => ['select', 'checkbox', 'radio'],
        ],
        'display' => [
            'label'  => 'Display Elements',
            'types'  => ['heading', 'html'],
        ],
        'special' => [
            'label'  => 'Special Fields',
            'types'  => ['hidden', 'consent'],
        ],
    ];

    /**
     * Display-only field types (not submitted, no value stored)
     *
     * @var array
     */
    public const DISPLAY_ONLY_TYPES = ['heading', 'html'];

    /**
     * Get singleton instance
     *
     * @return self
     */
    public static function instance(): self {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor
     */
    private function __construct() {
        $this->db = WAHRB_Database::instance();
    }

    // ──────────────────────────────────────────────────────────────
    // CRUD — Form Templates
    // ──────────────────────────────────────────────────────────────

    /**
     * Create a form template
     *
     * @param array $data Template data
     * @return int|false Template ID or false on failure
     */
    public function create_template(array $data) {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('form_templates');

        $fields = $this->validate_fields($data['fields'] ?? []);
        if (empty($fields)) {
            return false;
        }

        $insert = [
            'template_name'     => sanitize_text_field($data['template_name'] ?? ''),
            'description'       => sanitize_textarea_field($data['description'] ?? ''),
            'scope'             => in_array($data['scope'] ?? '', ['global', 'category', 'product', 'vendor'], true) ? $data['scope'] : 'global',
            'category_slug'     => sanitize_text_field($data['category_slug'] ?? ''),
            'product_id'        => absint($data['product_id'] ?? 0),
            'vendor_id'         => absint($data['vendor_id'] ?? 0),
            'fields_json'       => wp_json_encode($fields),
            'is_active'         => !empty($data['is_active']) ? 1 : 0,
            'sort_order'        => absint($data['sort_order'] ?? 0),
        ];

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->insert($table, $insert, [
            '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%d', '%d',
        ]);

        if (!$result && defined('WP_DEBUG') && WP_DEBUG) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            error_log('[WAHRB] Form Builder create_template failed: ' . $wpdb->last_error . ' | Table: ' . $table);
        }

        return $result ? (int) $wpdb->insert_id : false;
    }

    /**
     * Update a form template
     *
     * @param int   $template_id Template ID
     * @param array $data        Updated data
     * @return bool
     */
    public function update_template(int $template_id, array $data): bool {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('form_templates');

        $update = [];
        $formats = [];

        if (isset($data['template_name'])) {
            $update['template_name'] = sanitize_text_field($data['template_name']);
            $formats[] = '%s';
        }
        if (isset($data['description'])) {
            $update['description'] = sanitize_textarea_field($data['description']);
            $formats[] = '%s';
        }
        if (isset($data['scope'])) {
            $update['scope'] = in_array($data['scope'], ['global', 'category', 'product', 'vendor'], true) ? $data['scope'] : 'global';
            $formats[] = '%s';
        }
        if (isset($data['category_slug'])) {
            $update['category_slug'] = sanitize_text_field($data['category_slug']);
            $formats[] = '%s';
        }
        if (isset($data['product_id'])) {
            $update['product_id'] = absint($data['product_id']);
            $formats[] = '%d';
        }
        if (isset($data['vendor_id'])) {
            $update['vendor_id'] = absint($data['vendor_id']);
            $formats[] = '%d';
        }
        if (isset($data['fields'])) {
            $fields = $this->validate_fields($data['fields']);
            $update['fields_json'] = wp_json_encode($fields);
            $formats[] = '%s';
        }
        if (isset($data['is_active'])) {
            $update['is_active'] = !empty($data['is_active']) ? 1 : 0;
            $formats[] = '%d';
        }
        if (isset($data['sort_order'])) {
            $update['sort_order'] = absint($data['sort_order']);
            $formats[] = '%d';
        }

        if (empty($update)) {
            return false;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->update($table, $update, ['template_id' => $template_id], $formats, ['%d']);

        return $result !== false;
    }

    /**
     * Delete a form template
     *
     * @param int $template_id Template ID
     * @return bool
     */
    public function delete_template(int $template_id): bool {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('form_templates');

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->delete($table, ['template_id' => $template_id], ['%d']);

        return $result !== false;
    }

    /**
     * Get a form template by ID
     *
     * @param int $template_id Template ID
     * @return array|null
     */
    public function get_template(int $template_id): ?array {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('form_templates');

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE template_id = %d", $template_id), ARRAY_A);

        if (!$row) {
            return null;
        }

        $row['fields'] = json_decode($row['fields_json'] ?? '[]', true) ?: [];
        return $row;
    }

    /**
     * Get all form templates (optionally filtered)
     *
     * @param array $args Filter arguments
     * @return array
     */
    public function get_templates(array $args = []): array {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('form_templates');

        $where = ['1=1'];
        $values = [];

        if (isset($args['scope'])) {
            $where[] = 'scope = %s';
            $values[] = $args['scope'];
        }
        if (isset($args['category_slug']) && $args['category_slug'] !== '') {
            $where[] = 'category_slug = %s';
            $values[] = $args['category_slug'];
        }
        if (isset($args['vendor_id'])) {
            $where[] = 'vendor_id = %d';
            $values[] = $args['vendor_id'];
        }
        if (isset($args['product_id'])) {
            $where[] = 'product_id = %d';
            $values[] = $args['product_id'];
        }
        if (isset($args['is_active'])) {
            $where[] = 'is_active = %d';
            $values[] = $args['is_active'] ? 1 : 0;
        }

        $where_sql = implode(' AND ', $where);
        $sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY sort_order ASC, created_at DESC";

        if (!empty($values)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
            $results = $wpdb->get_results($wpdb->prepare($sql, ...$values), ARRAY_A);
        } else {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
            $results = $wpdb->get_results($sql, ARRAY_A);
        }

        foreach ($results as &$row) {
            $row['fields'] = json_decode($row['fields_json'] ?? '[]', true) ?: [];
        }

        return $results ?: [];
    }

    // ──────────────────────────────────────────────────────────────
    // Resolution — Get form for a product/booking
    // ──────────────────────────────────────────────────────────────

    /**
     * Resolve which form template applies to a product.
     *
     * Resolution order (first match wins):
     *   0. Direct product meta assignment (_wahrb_form_template_id)
     *      - If -1 → explicitly "no form", return null
     *      - If > 0 → fetch that specific template by ID
     *      - If 0 or absent → fall through to cascade
     *   1. Product-level form (scope=product, product_id matches)
     *   2. Vendor-level form (scope=vendor, vendor_id + category) — MARKETPLACE ONLY
     *   3. Category-level form (scope=category, category_slug matches)
     *   4. Global default form (scope=global)
     *
     * In Solo mode, step 2 is skipped entirely (no vendor overrides).
     *
     * @param int $product_id Product ID
     * @return array|null Template data or null if no form assigned
     */
    public function get_form_for_product(int $product_id): ?array {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('form_templates');

        // 0. Direct product meta assignment
        $direct_template_id = (int) get_post_meta($product_id, '_wahrb_form_template_id', true);
        if ($direct_template_id === -1) {
            // Explicitly disabled — no form for this product
            return null;
        }
        if ($direct_template_id > 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $template = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d AND is_active = 1 LIMIT 1", $direct_template_id), ARRAY_A);
            if ($template) {
                $template['fields'] = json_decode($template['fields_json'] ?? '[]', true) ?: [];
                return $template;
            }
            // If the assigned template doesn't exist or is inactive, fall through to cascade
        }

        // Get product's category and vendor
        $category_slug = get_post_meta($product_id, '_wahrb_platform_category', true) ?: '';
        $vendor_id = (int) get_post_field('post_author', $product_id);
        $is_marketplace = wahrb_is_dokan_active();

        // 1. Product-level override
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $template = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE scope = 'product' AND product_id = %d AND is_active = 1 ORDER BY sort_order ASC LIMIT 1", $product_id), ARRAY_A);

        // 2. Vendor-level override for category (Marketplace mode ONLY)
        if (!$template && $is_marketplace && $vendor_id && $category_slug) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $template = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE scope = 'vendor' AND vendor_id = %d AND category_slug = %s AND is_active = 1 ORDER BY sort_order ASC LIMIT 1", $vendor_id, $category_slug), ARRAY_A);
        }

        // 3. Category-level default
        if (!$template && $category_slug) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $template = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE scope = 'category' AND category_slug = %s AND is_active = 1 ORDER BY sort_order ASC LIMIT 1", $category_slug), ARRAY_A);
        }

        // 4. Global default
        if (!$template) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $template = $wpdb->get_row("SELECT * FROM {$table} WHERE scope = 'global' AND is_active = 1 ORDER BY sort_order ASC LIMIT 1", ARRAY_A);
        }

        if (!$template) {
            return null;
        }

        $template['fields'] = json_decode($template['fields_json'] ?? '[]', true) ?: [];
        return $template;
    }

    // ──────────────────────────────────────────────────────────────
    // Validation — Field data
    // ──────────────────────────────────────────────────────────────

    /**
     * Validate and sanitize field definitions
     *
     * @param array $fields Raw field data
     * @return array Sanitized fields
     */
    public function validate_fields(array $fields): array {
        $validated = [];

        foreach ($fields as $index => $field) {
            $type = sanitize_text_field($field['type'] ?? '');
            if (!array_key_exists($type, self::FIELD_TYPES)) {
                continue;
            }

            $sanitized = [
                'field_key'   => sanitize_key($field['field_key'] ?? 'field_' . $index),
                'label'       => sanitize_text_field($field['label'] ?? ''),
                'type'        => $type,
                'width'       => in_array($field['width'] ?? 'full', ['full', 'half', 'third'], true) ? $field['width'] : 'full',
                'required'    => !empty($field['required']),
                'placeholder' => sanitize_text_field($field['placeholder'] ?? ''),
                'default'     => sanitize_text_field($field['default'] ?? ''),
                'options'     => [],
                'sort_order'  => absint($field['sort_order'] ?? $index),
            ];

            // ── Choice fields: select, checkbox, radio ──
            if (in_array($type, ['select', 'checkbox', 'radio'], true) && !empty($field['options'])) {
                if (is_array($field['options'])) {
                    $sanitized['options'] = array_map('sanitize_text_field', $field['options']);
                } elseif (is_string($field['options'])) {
                    // Comma-separated fallback
                    $sanitized['options'] = array_map('trim', explode(',', sanitize_text_field($field['options'])));
                }
            }

            // ── Number constraints ──
            if ($type === 'number') {
                if (isset($field['min'])) {
                    $sanitized['min'] = (float) $field['min'];
                }
                if (isset($field['max'])) {
                    $sanitized['max'] = (float) $field['max'];
                }
            }

            // ── Heading — section title, no data stored ──
            if ($type === 'heading') {
                // Heading only needs a label, no required/placeholder
                $sanitized['required']    = false;
                $sanitized['placeholder'] = '';
                $sanitized['default']     = '';
            }

            // ── HTML block — static content ──
            if ($type === 'html') {
                $sanitized['required'] = false;
                $sanitized['content']  = wp_kses_post($field['content'] ?? '');
            }

            // ── Hidden — preset value, not user-editable ──
            if ($type === 'hidden') {
                $sanitized['required'] = false;
                // Default value is what gets submitted
            }

            // ── Consent — requires consent_text and optional consent_url ──
            if ($type === 'consent') {
                $sanitized['consent_text'] = sanitize_text_field($field['consent_text'] ?? '');
                $sanitized['consent_url']  = esc_url_raw($field['consent_url'] ?? '');
                if (empty($sanitized['consent_text'])) {
                    $sanitized['consent_text'] = $sanitized['label'];
                }
            }

            // Display-only fields (heading, html) don't need a label for data, but do need it for display
            if (in_array($type, self::DISPLAY_ONLY_TYPES, true)) {
                // Label can be empty for HTML blocks
                $validated[] = $sanitized;
                continue;
            }

            // Data fields must have a label
            if (!empty($sanitized['label'])) {
                $validated[] = $sanitized;
            }
        }

        return $validated;
    }

    /**
     * Validate submitted form data against a template
     *
     * @param array $template  Form template (with 'fields' decoded)
     * @param array $form_data Submitted key=>value data
     * @return array ['valid' => bool, 'errors' => [...], 'sanitized' => [...]]
     */
    public function validate_submission(array $template, array $form_data): array {
        $errors = [];
        $sanitized = [];

        foreach ($template['fields'] as $field) {
            $key = $field['field_key'];

            // Display-only fields: no submitted data expected
            if (in_array($field['type'], self::DISPLAY_ONLY_TYPES, true)) {
                continue;
            }

            $value = $form_data[$key] ?? '';

            // Hidden fields: use preset default value
            if ($field['type'] === 'hidden') {
                $sanitized[$key] = sanitize_text_field($field['default'] ?? '');
                continue;
            }

            // Required check
            if (!empty($field['required']) && (is_string($value) ? trim($value) === '' : empty($value))) {
                /* translators: %s: field label */
                $errors[$key] = sprintf(__('%s is required.', 'wahra-rentals-bookings'), $field['label']);
                continue;
            }

            // Type-specific validation
            switch ($field['type']) {
                case 'email':
                    if ($value !== '' && !is_email($value)) {
                        /* translators: %s: field label */
                        $errors[$key] = sprintf(__('%s must be a valid email.', 'wahra-rentals-bookings'), $field['label']);
                        continue 2;
                    }
                    $sanitized[$key] = sanitize_email($value);
                    break;

                case 'number':
                    $num = floatval($value);
                    if (isset($field['min']) && $num < $field['min']) {
                        /* translators: 1: field label, 2: minimum value */
                        $errors[$key] = sprintf(__('%1$s must be at least %2$s.', 'wahra-rentals-bookings'), $field['label'], $field['min']);
                        continue 2;
                    }
                    if (isset($field['max']) && $num > $field['max']) {
                        /* translators: 1: field label, 2: maximum value */
                        $errors[$key] = sprintf(__('%1$s must be at most %2$s.', 'wahra-rentals-bookings'), $field['label'], $field['max']);
                        continue 2;
                    }
                    $sanitized[$key] = $num;
                    break;

                case 'select':
                    if ($value !== '' && !empty($field['options']) && !in_array($value, $field['options'], true)) {
                        /* translators: %s: field label */
                        $errors[$key] = sprintf(__('%s has an invalid selection.', 'wahra-rentals-bookings'), $field['label']);
                        continue 2;
                    }
                    $sanitized[$key] = sanitize_text_field($value);
                    break;

                case 'radio':
                    // Radio must be one of the defined options
                    if ($value !== '' && !empty($field['options']) && !in_array($value, $field['options'], true)) {
                        /* translators: %s: field label */
                        $errors[$key] = sprintf(__('%s has an invalid selection.', 'wahra-rentals-bookings'), $field['label']);
                        continue 2;
                    }
                    $sanitized[$key] = sanitize_text_field($value);
                    break;

                case 'checkbox':
                    $sanitized[$key] = !empty($value) ? 'yes' : 'no';
                    break;

                case 'consent':
                    // Consent behaves like checkbox — must be accepted if required
                    $sanitized[$key] = !empty($value) ? 'accepted' : 'declined';
                    if (!empty($field['required']) && empty($value)) {
                        /* translators: %s: consent label */
                        $errors[$key] = sprintf(__('You must accept: %s', 'wahra-rentals-bookings'), $field['label']);
                        continue 2;
                    }
                    break;

                case 'textarea':
                    $sanitized[$key] = sanitize_textarea_field($value);
                    break;

                case 'date':
                    if ($value !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
                        /* translators: %s: field label */
                        $errors[$key] = sprintf(__('%s must be a valid date (YYYY-MM-DD).', 'wahra-rentals-bookings'), $field['label']);
                        continue 2;
                    }
                    $sanitized[$key] = sanitize_text_field($value);
                    break;

                default: // text, phone
                    $sanitized[$key] = sanitize_text_field($value);
                    break;
            }
        }

        return [
            'valid'     => empty($errors),
            'errors'    => $errors,
            'sanitized' => $sanitized,
        ];
    }

    /**
     * Store form submission data on a booking
     *
     * @param WAHRB_Booking $booking   Booking object
     * @param array         $template  Form template
     * @param array         $form_data Sanitized form data
     * @return bool
     */
    public function save_form_data(WAHRB_Booking $booking, array $template, array $form_data): bool {
        $stored = [
            'template_id'   => (int) ($template['template_id'] ?? 0),
            'template_name' => $template['template_name'] ?? '',
            'submitted_at'  => current_time('mysql'),
            'fields'        => [],
        ];

        foreach ($template['fields'] as $field) {
            $key = $field['field_key'];
            $stored['fields'][] = [
                'key'   => $key,
                'label' => $field['label'],
                'type'  => $field['type'],
                'value' => $form_data[$key] ?? '',
            ];
        }

        $booking->update_meta('form_data', $stored);
        return true;
    }

    /**
     * Get form submission data from a booking
     *
     * @param WAHRB_Booking $booking Booking object
     * @return array|null
     */
    public function get_form_data(WAHRB_Booking $booking): ?array {
        $data = $booking->get_meta('form_data', null);
        if (!is_array($data) || empty($data['fields'])) {
            return null;
        }
        return $data;
    }

    // ──────────────────────────────────────────────────────────────
    // Rendering — HTML output for booking form
    // ──────────────────────────────────────────────────────────────

    /**
     * Render custom form fields as HTML (for product-calendar.php template)
     *
     * @param int $product_id Product ID
     * @return string HTML markup
     */
    public function render_fields_html(int $product_id): string {
        $template = $this->get_form_for_product($product_id);
        if (!$template || empty($template['fields'])) {
            return '';
        }

        $html = '<div class="wahrb-custom-fields" data-template-id="' . esc_attr($template['template_id']) . '">';
        $html .= '<div class="wahrb-section-header">';
        $html .= '<span class="wahrb-section-icon">📝</span>';
        $html .= '<h4>' . esc_html__('Additional information', 'wahra-rentals-bookings') . '</h4>';
        $html .= '</div>';

        foreach ($template['fields'] as $field) {
            $html .= $this->render_single_field($field);
        }

        $html .= '</div>';

        return $html;
    }

    /**
     * Render a single form field
     *
     * @param array $field Field definition
     * @return string HTML
     */
    private function render_single_field(array $field): string {
        $key         = esc_attr($field['field_key']);
        $label       = esc_html($field['label']);
        $required    = !empty($field['required']);
        $placeholder = esc_attr($field['placeholder'] ?? '');
        $default     = esc_attr($field['default'] ?? '');
        $req_html    = $required ? '<span class="required">*</span>' : '';
        $req_attr    = $required ? 'required' : '';

        // ── Display-only: heading ──
        if ($field['type'] === 'heading') {
            $html = '<div class="wahrb-input-group full wahrb-custom-field-wrap wahrb-field-heading">';
            $html .= '<h4 class="wahrb-section-title">' . $label . '</h4>';
            $html .= '</div>';
            return $html;
        }

        // ── Display-only: html ──
        if ($field['type'] === 'html') {
            $html = '<div class="wahrb-input-group full wahrb-custom-field-wrap wahrb-field-html">';
            $html .= '<div class="wahrb-html-content">' . wp_kses_post($field['content'] ?? '') . '</div>';
            $html .= '</div>';
            return $html;
        }

        // ── Hidden field ──
        if ($field['type'] === 'hidden') {
            return '<input type="hidden" name="wahrb_cf_' . $key . '" value="' . $default . '">';
        }

        // ── Data fields ──
        $html = '<div class="wahrb-input-group full wahrb-custom-field-wrap" data-field-key="' . $key . '">';
        $html .= '<label>' . $label . ' ' . $req_html . '</label>';

        switch ($field['type']) {
            case 'textarea':
                $html .= '<textarea name="wahrb_cf_' . $key . '" rows="3" placeholder="' . $placeholder . '" ' . $req_attr . '>' . esc_textarea($field['default'] ?? '') . '</textarea>';
                break;

            case 'select':
                $html .= '<select name="wahrb_cf_' . $key . '" ' . $req_attr . '>';
                $html .= '<option value="">' . esc_html__('Select...', 'wahra-rentals-bookings') . '</option>';
                foreach (($field['options'] ?? []) as $option) {
                    $selected = ($option === ($field['default'] ?? '')) ? ' selected' : '';
                    $html .= '<option value="' . esc_attr($option) . '"' . $selected . '>' . esc_html($option) . '</option>';
                }
                $html .= '</select>';
                break;

            case 'radio':
                $html .= '<div class="wahrb-radio-group">';
                foreach (($field['options'] ?? []) as $option) {
                    $checked = ($option === ($field['default'] ?? '')) ? ' checked' : '';
                    $html .= '<label class="wahrb-radio-label">';
                    $html .= '<input type="radio" name="wahrb_cf_' . $key . '" value="' . esc_attr($option) . '"' . $checked . '> ';
                    $html .= esc_html($option);
                    $html .= '</label>';
                }
                $html .= '</div>';
                break;

            case 'checkbox':
                $checked = !empty($field['default']) ? ' checked' : '';
                $html .= '<label class="wahrb-checkbox-label">';
                $html .= '<input type="checkbox" name="wahrb_cf_' . $key . '" value="yes"' . $checked . '> ';
                $html .= esc_html($field['placeholder'] ?: $field['label']);
                $html .= '</label>';
                break;

            case 'consent':
                $consent_text = esc_html($field['consent_text'] ?? $field['label']);
                $consent_url  = esc_url($field['consent_url'] ?? '');
                $html .= '<label class="wahrb-consent-label">';
                $html .= '<input type="checkbox" name="wahrb_cf_' . $key . '" value="accepted" ' . $req_attr . '> ';
                if ($consent_url) {
                    $html .= '<a href="' . $consent_url . '" target="_blank" rel="noopener">' . $consent_text . '</a>';
                } else {
                    $html .= $consent_text;
                }
                $html .= '</label>';
                break;

            case 'number':
                $min = isset($field['min']) ? ' min="' . esc_attr($field['min']) . '"' : '';
                $max = isset($field['max']) ? ' max="' . esc_attr($field['max']) . '"' : '';
                $html .= '<div class="wahrb-input-wrapper">';
                $html .= '<span class="wahrb-input-icon">🔢</span>';
                $html .= '<input type="number" name="wahrb_cf_' . $key . '" value="' . $default . '" placeholder="' . $placeholder . '"' . $min . $max . ' ' . $req_attr . '>';
                $html .= '</div>';
                break;

            case 'email':
                $html .= '<div class="wahrb-input-wrapper">';
                $html .= '<span class="wahrb-input-icon">&#9993;</span>';
                $html .= '<input type="email" name="wahrb_cf_' . $key . '" value="' . $default . '" placeholder="' . $placeholder . '" ' . $req_attr . '>';
                $html .= '</div>';
                break;

            case 'phone':
                $html .= '<div class="wahrb-input-wrapper">';
                $html .= '<span class="wahrb-input-icon">&#128241;</span>';
                $html .= '<input type="tel" name="wahrb_cf_' . $key . '" value="' . $default . '" placeholder="' . $placeholder . '" ' . $req_attr . '>';
                $html .= '</div>';
                break;

            case 'date':
                $html .= '<div class="wahrb-input-wrapper">';
                $html .= '<span class="wahrb-input-icon">&#128197;</span>';
                $html .= '<input type="date" name="wahrb_cf_' . $key . '" value="' . $default . '" ' . $req_attr . '>';
                $html .= '</div>';
                break;

            default: // text
                $html .= '<div class="wahrb-input-wrapper">';
                $html .= '<span class="wahrb-input-icon">&#9998;</span>';
                $html .= '<input type="text" name="wahrb_cf_' . $key . '" value="' . $default . '" placeholder="' . $placeholder . '" ' . $req_attr . '>';
                $html .= '</div>';
                break;
        }

        $html .= '</div>';
        return $html;
    }

    /**
     * Get supported field types (for admin UI / API)
     *
     * Returns flat list with type info for each field type.
     *
     * @return array
     */
    public static function get_field_types(): array {
        return array_map(function ($key, $label) {
            $group = 'basic';
            foreach (self::FIELD_TYPE_GROUPS as $g => $def) {
                if (in_array($key, $def['types'], true)) {
                    $group = $g;
                    break;
                }
            }
            return [
                'value'        => $key,
                'label'        => __($label, 'wahra-rentals-bookings'), // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
                'group'        => $group,
                'has_options'  => in_array($key, ['select', 'checkbox', 'radio'], true),
                'display_only' => in_array($key, self::DISPLAY_ONLY_TYPES, true),
            ];
        }, array_keys(self::FIELD_TYPES), self::FIELD_TYPES);
    }

    /**
     * Get grouped field types (for drag-and-drop builder UI)
     *
     * @return array
     */
    public static function get_field_types_grouped(): array {
        $grouped = [];
        foreach (self::FIELD_TYPE_GROUPS as $group_key => $group) {
            $items = [];
            foreach ($group['types'] as $type) {
                if (isset(self::FIELD_TYPES[$type])) {
                    $items[] = [
                        'value' => $type,
                        'label' => __( self::FIELD_TYPES[$type], 'wahra-rentals-bookings'), // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
                    ];
                }
            }
            $grouped[] = [
                'key'   => $group_key,
                'label' => __($group['label'], 'wahra-rentals-bookings'), // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
                'types' => $items,
            ];
        }
        return $grouped;
    }

    /**
     * Get allowed scopes for the current operating mode
     *
     * Solo mode:       global, category, product (no vendor-level override)
     * Marketplace mode: global, category, product, vendor
     *
     * @param string $role 'admin' or 'vendor'
     * @return array Allowed scope values
     */
    public static function get_allowed_scopes(string $role = 'admin'): array {
        $is_marketplace = wahrb_is_dokan_active();

        if ($role === 'vendor') {
            // Vendors can only create vendor-scope or product-scope overrides
            return $is_marketplace ? ['vendor', 'product'] : ['product'];
        }

        // Admin scopes
        if ($is_marketplace) {
            return ['global', 'category', 'product', 'vendor'];
        }

        // Solo mode: no vendor scope
        return ['global', 'category', 'product'];
    }
}
