<?php

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound -- APD is the established namespace shared with the Pro add-on and retained for backward compatibility.
namespace APD\Admin;

use APD\Support\RequestData;

defined('ABSPATH') || exit;

trait CatalogPageTrait
{
    protected function pageShellStart(string $badge, string $title, string $description) : void
    {
        $settings = (new AdminSettings())->all();
        echo '<div class="wrap apd-admin-shell apd-admin-theme-' . esc_attr($settings['admin_theme']) . ' apd-catalog-page">';
        echo '<div class="apd-page-heading">';
        echo '<div>';
        echo '<span class="apd-badge">' . esc_html($badge) . '</span>';
        echo '<h1>' . esc_html($title) . '</h1>';
        echo '<p>' . esc_html($description) . '</p>';
        echo '</div>';
        echo '</div>';
    }

    protected function pageShellEnd() : void
    {
        echo '</div>';
    }

    protected function renderCatalogNotice() : void
    {
        $query = RequestData::query();
        $notice = sanitize_key((string) ($query['apd_notice'] ?? ''));
        $messages = [
            'saved' => __('Item saved.', 'asuka-product-designer'),
            'deleted' => __('Item deleted.', 'asuka-product-designer'),
            'duplicated' => __('Item duplicated.', 'asuka-product-designer'),
            'reordered' => __('Item order updated.', 'asuka-product-designer'),
            'imported' => __('Items imported.', 'asuka-product-designer'),
            'imported_media' => __('Media items imported.', 'asuka-product-designer'),
            'import_error' => __('Import failed. Please provide a valid JSON array or a JSON file.', 'asuka-product-designer'),
        ];
        if (! isset($messages[$notice])) {
            return;
        }
        $class = $notice === 'import_error' ? 'notice notice-error' : 'notice notice-success';
        echo '<div class="' . esc_attr($class) . ' is-dismissible"><p>' . esc_html($messages[$notice]) . '</p></div>';
    }

    protected function getCatalogPageUrl(string $pageSlug, array $args = []) : string
    {
        return add_query_arg(array_merge(['page' => $pageSlug], $args), admin_url('admin.php'));
    }

    protected function getCatalogEditorUrl(string $editorPageSlug, string $collection, string $action = 'new', ?int $index = null) : string
    {
        $args = [
            'page' => $editorPageSlug,
            'apd_collection' => $collection,
            'apd_action' => in_array($action, ['new', 'edit'], true) ? $action : 'new',
        ];
        if ($index !== null) {
            $args['apd_index'] = $index;
        }

        return add_query_arg($args, admin_url('admin.php'));
    }

    protected function getCatalogActionUrl(string $pageSlug, string $collection, string $action, int $index, array $extra = []) : string
    {
        $args = array_merge([
            'page' => $pageSlug,
            'apd_collection' => $collection,
            'apd_catalog_do' => $action,
            'apd_index' => $index,
        ], $extra);

        $nonceAction = 'apd_catalog_action_' . $action . '_' . $collection . '_' . $index;
        if (isset($extra['apd_direction'])) {
            $nonceAction .= '_' . sanitize_key((string) $extra['apd_direction']);
        }

        return wp_nonce_url(add_query_arg($args, admin_url('admin.php')), $nonceAction);
    }

    protected function renderCrudSection(string $pageSlug, string $key, string $title, string $description, array $items, array $columns, array $defaults, ?string $editorPageSlug = null) : void
    {
        $editorPageSlug = $editorPageSlug ?: $pageSlug;

        echo '<section class="apd-card apd-crud-card" data-apd-catalog-section="' . esc_attr($key) . '">';
        echo '<div class="apd-card-head apd-card-head-stack">';
        echo '<div><h2>' . esc_html($title) . '</h2><p>' . esc_html($description) . '</p></div>';
        echo '<div class="apd-inline-actions apd-inline-actions-wrap">';
        echo '<a class="button button-primary" href="' . esc_url($this->getCatalogEditorUrl($editorPageSlug, $key, 'new')) . '">' . esc_html__('Add item', 'asuka-product-designer') . '</a>';
        echo '<button type="button" class="button button-secondary" data-apd-import-toggle="' . esc_attr($key) . '">' . esc_html__('Bulk import', 'asuka-product-designer') . '</button>';
        if ($this->supportsMediaImport($key)) {
            echo '<button type="button" class="button button-secondary" data-apd-media-import-toggle="' . esc_attr($key) . '">' . esc_html__('Import from Media Library', 'asuka-product-designer') . '</button>';
        }

        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="apd-inline-form">';
        wp_nonce_field('apd_export_catalog_items');
        echo '<input type="hidden" name="action" value="apd_export_catalog_items">';
        echo '<input type="hidden" name="apd_collection" value="' . esc_attr($key) . '">';
        echo '<button type="submit" class="button button-secondary">' . esc_html__('Export JSON', 'asuka-product-designer') . '</button>';
        echo '</form>';
        echo '</div>';
        echo '</div>';

        $this->renderCrudToolbar($key, $items);
        $this->renderBulkImportPanel($pageSlug, $key);
        if ($this->supportsMediaImport($key)) {
            $this->renderMediaImportPanel($pageSlug, $key);
        }

        $this->renderCrudList($pageSlug, $key, $items, $editorPageSlug);
        echo '</section>';
    }

    protected function renderCrudStandaloneEditor(string $listPageSlug, string $editorPageSlug, string $key, string $title, string $description, array $items, array $columns, array $defaults) : void
    {
        $query = RequestData::query();
        $currentAction = sanitize_key((string) ($query['apd_action'] ?? 'new'));
        if (! in_array($currentAction, ['new', 'edit'], true)) {
            $currentAction = 'new';
        }
        $currentIndex = isset($query['apd_index']) ? max(0, (int) $query['apd_index']) : null;
        $editorItem = $defaults;
        $editorTitle = $currentAction === 'edit' ? __('Edit item', 'asuka-product-designer') : __('Add item', 'asuka-product-designer');
        $missingItem = false;

        if ($currentAction === 'edit') {
            if ($currentIndex !== null && isset($items[$currentIndex])) {
                $editorItem = wp_parse_args((array) $items[$currentIndex], $defaults);
            } else {
                $missingItem = true;
                $currentAction = 'new';
                $currentIndex = null;
                $editorTitle = __('Add item', 'asuka-product-designer');
            }
        }

        echo '<section class="apd-card apd-crud-card apd-crud-card-editor">';
        echo '<div class="apd-card-head apd-card-head-stack">';
        echo '<div><h2>' . esc_html($title) . '</h2><p>' . esc_html($description) . '</p></div>';
        echo '<div class="apd-inline-actions apd-inline-actions-wrap">';
        echo '<a class="button button-secondary" href="' . esc_url($this->getCatalogPageUrl($listPageSlug, ['apd_collection' => $key])) . '">' . esc_html__('Back to list', 'asuka-product-designer') . '</a>';
        echo '</div>';
        echo '</div>';
        if ($missingItem) {
            echo '<div class="notice notice-warning inline"><p>' . esc_html__('The requested item could not be found. A new item form is shown instead.', 'asuka-product-designer') . '</p></div>';
        }
        $this->renderCrudEditor($listPageSlug, $key, $editorTitle, $editorItem, $columns, $currentAction === 'edit' ? $currentIndex : null);
        echo '</section>';
    }

    protected function renderCrudToolbar(string $key, array $items) : void
    {
        $categories = [];
        foreach ($items as $item) {
            if (! is_array($item)) {
                continue;
            }
            $category = trim((string) ($item['category'] ?? $item['group'] ?? $item['source'] ?? ''));
            if ($category !== '') {
                $categories[$category] = $category;
            }
        }
        ksort($categories);

        echo '<div class="apd-catalog-toolbar">';
        echo '<label class="apd-catalog-search">';
        echo '<span class="screen-reader-text">' . esc_html__('Search items', 'asuka-product-designer') . '</span>';
        echo '<input type="search" placeholder="' . esc_attr__('Search this library…', 'asuka-product-designer') . '" data-apd-catalog-search="' . esc_attr($key) . '">';
        echo '</label>';
        echo '<label class="apd-catalog-filter">';
        echo '<span class="screen-reader-text">' . esc_html__('Filter by category', 'asuka-product-designer') . '</span>';
        echo '<select data-apd-catalog-category="' . esc_attr($key) . '">';
        echo '<option value="">' . esc_html__('All categories', 'asuka-product-designer') . '</option>';
        foreach ($categories as $category) {
            echo '<option value="' . esc_attr($category) . '">' . esc_html($category) . '</option>';
        }
        echo '</select>';
        echo '</label>';
        echo '<span class="apd-chip apd-chip-soft"><span data-apd-catalog-count="' . esc_attr($key) . '">' . esc_html((string) count($items)) . '</span> ' . esc_html(_n('item', 'items', count($items), 'asuka-product-designer')) . '</span>';
        echo '</div>';
    }


    protected function supportsMediaImport(string $key) : bool
    {
        return in_array($key, ['designs', 'shapes'], true);
    }

    protected function renderMediaImportPanel(string $pageSlug, string $key) : void
    {
        $query = RequestData::query();
        $isOpen = sanitize_key((string) ($query['apd_collection'] ?? '')) === $key && sanitize_key((string) ($query['apd_action'] ?? '')) === 'media-import';
        echo '<div class="apd-import-panel apd-media-import-panel' . ($isOpen ? ' is-open' : '') . '" data-apd-media-import-panel="' . esc_attr($key) . '">';
        echo '<div class="apd-import-panel__head">';
        echo '<h3>' . esc_html__('Import from Media Library', 'asuka-product-designer') . '</h3>';
        echo '<p>' . esc_html__('Select multiple existing media items at once and turn them into catalog entries.', 'asuka-product-designer') . '</p>';
        echo '</div>';
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="apd-import-form" data-apd-media-import-form="' . esc_attr($key) . '">';
        wp_nonce_field('apd_import_catalog_media');
        echo '<input type="hidden" name="action" value="apd_import_catalog_media">';
        echo '<input type="hidden" name="apd_page" value="' . esc_attr($pageSlug) . '">';
        echo '<input type="hidden" name="apd_collection" value="' . esc_attr($key) . '">';
        echo '<input type="hidden" name="apd_media_ids" value="" data-apd-media-import-ids="' . esc_attr($key) . '">';
        echo '<div class="apd-form-grid apd-form-grid-2">';
        echo '<label class="apd-field"><span>' . esc_html__('Import mode', 'asuka-product-designer') . '</span><select name="apd_import_mode"><option value="append">' . esc_html__('Append to current items', 'asuka-product-designer') . '</option><option value="replace">' . esc_html__('Replace this library', 'asuka-product-designer') . '</option></select></label>';
        echo '<div class="apd-field apd-field-full">';
        echo '<span>' . esc_html__('Selected media', 'asuka-product-designer') . '</span>';
        echo '<div class="apd-inline-actions apd-inline-actions-wrap">';
        echo '<button type="button" class="button button-secondary" data-apd-media-import-pick="' . esc_attr($key) . '">' . esc_html__('Choose media items', 'asuka-product-designer') . '</button>';
        echo '<button type="button" class="button button-link" data-apd-media-import-clear="' . esc_attr($key) . '">' . esc_html__('Clear selection', 'asuka-product-designer') . '</button>';
        echo '</div>';
        echo '<div class="apd-media-import-selection" data-apd-media-import-list="' . esc_attr($key) . '"><p class="description">' . esc_html__('No media selected yet.', 'asuka-product-designer') . '</p></div>';
        echo '<small>' . esc_html__('Designs will import their URL directly. Shapes will import the media URL as sourceUrl.', 'asuka-product-designer') . '</small>';
        echo '</div>';
        echo '</div>';
        echo '<div class="apd-inline-actions">';
        echo '<button type="submit" class="button button-primary">' . esc_html__('Import selected media', 'asuka-product-designer') . '</button>';
        echo '<button type="button" class="button button-secondary" data-apd-media-import-toggle="' . esc_attr($key) . '">' . esc_html__('Close', 'asuka-product-designer') . '</button>';
        echo '</div>';
        echo '</form>';
        echo '</div>';
    }

    protected function renderBulkImportPanel(string $pageSlug, string $key) : void
    {
        $query = RequestData::query();
        $isOpen = sanitize_key((string) ($query['apd_collection'] ?? '')) === $key && sanitize_key((string) ($query['apd_action'] ?? '')) === 'import';
        echo '<div class="apd-import-panel' . ($isOpen ? ' is-open' : '') . '" data-apd-import-panel="' . esc_attr($key) . '">';
        echo '<div class="apd-import-panel__head">';
        echo '<h3>' . esc_html__('Bulk import', 'asuka-product-designer') . '</h3>';
        echo '<p>' . esc_html__('Paste a JSON array, or upload a JSON file exported from another Asuka Designer library.', 'asuka-product-designer') . '</p>';
        echo '</div>';
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" enctype="multipart/form-data" class="apd-import-form">';
        wp_nonce_field('apd_import_catalog_items');
        echo '<input type="hidden" name="action" value="apd_import_catalog_items">';
        echo '<input type="hidden" name="apd_page" value="' . esc_attr($pageSlug) . '">';
        echo '<input type="hidden" name="apd_collection" value="' . esc_attr($key) . '">';
        echo '<div class="apd-form-grid apd-form-grid-2">';
        echo '<label class="apd-field apd-field-full"><span>' . esc_html__('JSON payload', 'asuka-product-designer') . '</span><textarea name="apd_import_json" rows="10" class="apd-codearea" placeholder="' . esc_attr('[{"id":"example","label":"Example"}]') . '"></textarea><small>' . esc_html__('You can paste either a raw array or a full exported object containing this collection.', 'asuka-product-designer') . '</small></label>';
        echo '<label class="apd-field"><span>' . esc_html__('JSON file', 'asuka-product-designer') . '</span><input type="file" name="apd_import_file" accept="application/json,.json"></label>';
        echo '<label class="apd-field"><span>' . esc_html__('Import mode', 'asuka-product-designer') . '</span><select name="apd_import_mode"><option value="append">' . esc_html__('Append to current items', 'asuka-product-designer') . '</option><option value="replace">' . esc_html__('Replace this library', 'asuka-product-designer') . '</option></select></label>';
        echo '</div>';
        echo '<div class="apd-inline-actions">';
        echo '<button type="submit" class="button button-primary">' . esc_html__('Import items', 'asuka-product-designer') . '</button>';
        echo '<button type="button" class="button button-secondary" data-apd-import-toggle="' . esc_attr($key) . '">' . esc_html__('Close', 'asuka-product-designer') . '</button>';
        echo '</div>';
        echo '</form>';
        echo '</div>';
    }

    protected function renderCrudEditor(string $pageSlug, string $key, string $title, array $item, array $columns, ?int $index = null) : void
    {
        echo '<div class="apd-crud-editor">';
        echo '<div class="apd-crud-editor__head">';
        echo '<div><h3>' . esc_html($title) . '</h3><p>' . esc_html__('Use this dedicated editor page to update one item, then return to the library list after saving.', 'asuka-product-designer') . '</p></div>';
        echo '<a class="button button-secondary" href="' . esc_url($this->getCatalogPageUrl($pageSlug)) . '">' . esc_html__('Close editor', 'asuka-product-designer') . '</a>';
        echo '</div>';
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="apd-crud-editor__form">';
        wp_nonce_field('apd_save_catalog_item');
        echo '<input type="hidden" name="action" value="apd_save_catalog_item">';
        echo '<input type="hidden" name="apd_page" value="' . esc_attr($pageSlug) . '">';
        echo '<input type="hidden" name="apd_collection" value="' . esc_attr($key) . '">';
        if ($index !== null) {
            echo '<input type="hidden" name="apd_index" value="' . esc_attr((string) $index) . '">';
        }
        echo '<div class="apd-form-grid apd-form-grid-2 apd-crud-fields">';
        foreach ($columns as $name => $field) {
            $value = $item[$name] ?? ($field['default'] ?? '');
            $this->renderCrudField($name, $field, $value);
        }
        echo '</div>';
        echo '<div class="apd-inline-actions">';
        submit_button(__('Save item', 'asuka-product-designer'), 'primary', 'submit', false);
        echo '<a class="button button-secondary" href="' . esc_url($this->getCatalogPageUrl($pageSlug)) . '">' . esc_html__('Cancel', 'asuka-product-designer') . '</a>';
        echo '</div>';
        echo '</form>';
        echo '</div>';
    }

    protected function renderCrudList(string $pageSlug, string $key, array $items, ?string $editorPageSlug = null) : void
    {
        $editorPageSlug = $editorPageSlug ?: $pageSlug;
        echo '<div class="apd-crud-list">';
        if (empty($items)) {
            echo '<p class="description">' . esc_html__('No items yet.', 'asuka-product-designer') . '</p>';
            echo '</div>';
            return;
        }

        echo '<table class="widefat striped apd-crud-table">';
        echo '<thead><tr>';
        echo '<th>' . esc_html__('Preview', 'asuka-product-designer') . '</th>';
        echo '<th>' . esc_html__('Label', 'asuka-product-designer') . '</th>';
        echo '<th>' . esc_html__('ID', 'asuka-product-designer') . '</th>';
        echo '<th>' . esc_html__('Category', 'asuka-product-designer') . '</th>';
        echo '<th>' . esc_html__('Status', 'asuka-product-designer') . '</th>';
        echo '<th>' . esc_html__('Actions', 'asuka-product-designer') . '</th>';
        echo '</tr></thead><tbody data-apd-catalog-list="' . esc_attr($key) . '">';

        foreach (array_values($items) as $index => $item) {
            $label = (string) ($item['label'] ?? $item['family'] ?? $item['id'] ?? __('Untitled item', 'asuka-product-designer'));
            $id = (string) ($item['id'] ?? '');
            $category = trim((string) ($item['category'] ?? $item['group'] ?? $item['source'] ?? ''));
            $enabled = ! isset($item['enabled']) || ! empty($item['enabled']);
            $search = strtolower(trim($label . ' ' . $id . ' ' . $category . ' ' . (string) ($item['description'] ?? '')));

            echo '<tr data-apd-search="' . esc_attr($search) . '" data-apd-category="' . esc_attr($category) . '">';
            echo '<td>' . wp_kses_post($this->buildPreviewHtml($item)) . '</td>';
            echo '<td><strong>' . esc_html($label) . '</strong></td>';
            echo '<td><code>' . esc_html($id) . '</code></td>';
            echo '<td>' . esc_html($category) . '</td>';
            echo '<td>' . ($enabled ? '<span class="apd-chip apd-chip-success">' . esc_html__('Visible', 'asuka-product-designer') . '</span>' : '<span class="apd-chip apd-chip-soft">' . esc_html__('Hidden', 'asuka-product-designer') . '</span>') . '</td>';
            echo '<td>';
            echo '<div class="apd-inline-actions apd-inline-actions-wrap apd-row-actions">';
            $this->renderCatalogEditorButton($editorPageSlug, $key, $index, __('Edit', 'asuka-product-designer'));
            if ($index === 0) {
                echo '<span class="button button-secondary" aria-disabled="true">' . esc_html__('↑', 'asuka-product-designer') . '</span>';
            } else {
                $this->renderCatalogActionButton('apd_move_catalog_item', $pageSlug, $key, $index, __('↑', 'asuka-product-designer'), 'apd_move_catalog_item', ['apd_direction' => 'up']);
            }
            if ($index >= count($items) - 1) {
                echo '<span class="button button-secondary" aria-disabled="true">' . esc_html__('↓', 'asuka-product-designer') . '</span>';
            } else {
                $this->renderCatalogActionButton('apd_move_catalog_item', $pageSlug, $key, $index, __('↓', 'asuka-product-designer'), 'apd_move_catalog_item', ['apd_direction' => 'down']);
            }
            $this->renderCatalogActionButton('apd_duplicate_catalog_item', $pageSlug, $key, $index, __('Duplicate', 'asuka-product-designer'), 'apd_duplicate_catalog_item');
            $this->renderCatalogActionButton('apd_delete_catalog_item', $pageSlug, $key, $index, __('Delete', 'asuka-product-designer'), 'apd_delete_catalog_item', [], __('Delete this item?', 'asuka-product-designer'));
            echo '</div>';
            echo '</td>';
            echo '</tr>';
        }

        echo '</tbody></table>';
        echo '</div>';
    }

    protected function renderCatalogEditorButton(string $editorPageSlug, string $collection, int $index, string $label) : void
    {
        $url = $this->getCatalogEditorUrl($editorPageSlug, $collection, 'edit', $index);
        echo '<a class="button button-secondary" href="' . esc_url($url) . '">' . esc_html($label) . '</a>';
    }

    protected function renderCatalogActionButton(string $adminAction, string $pageSlug, string $collection, int $index, string $label, string $nonceAction, array $extra = [], ?string $confirm = null) : void
    {
        $actionMap = [
            'apd_duplicate_catalog_item' => 'duplicate',
            'apd_delete_catalog_item' => 'delete',
            'apd_move_catalog_item' => 'move',
        ];
        $action = $actionMap[$adminAction] ?? '';
        if ($action === '') {
            return;
        }

        $url = $this->getCatalogActionUrl($pageSlug, $collection, $action, $index, $extra);
        $confirmAttr = '';
        if ($confirm) {
            $confirmAttr = ' onclick="return confirm(' . esc_attr(wp_json_encode($confirm)) . ');"';
        }

        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $confirmAttr is empty or contains only an esc_attr()-escaped confirmation handler assembled above.
        echo '<a class="button button-secondary" href="' . esc_url($url) . '"' . $confirmAttr . '>' . esc_html($label) . '</a>';
    }

    protected function renderCrudField(string $name, array $field, mixed $value) : void
    {
        $fieldName = sprintf('item[%s]', $name);
        if (is_array($value)) {
            $value = implode(', ', array_map(static fn($item) => is_scalar($item) ? (string) $item : '', $value));
        }
        $type = (string) ($field['type'] ?? 'text');
        $label = (string) ($field['label'] ?? $name);
        $placeholder = (string) ($field['placeholder'] ?? '');
        $help = (string) ($field['help'] ?? '');
        $full = ! empty($field['full']);
        $wrapperClass = trim((string) ($field['wrapperClass'] ?? ''));
        $class = trim('apd-field' . ($full ? ' apd-field-full' : '') . ($wrapperClass !== '' ? ' ' . $wrapperClass : ''));
        $mediaType = (string) ($field['media'] ?? '');
        $fieldAttributes = ' data-apd-field="' . esc_attr($name) . '"';
        foreach ((array) ($field['attributes'] ?? []) as $attrName => $attrValue) {
            if ($attrName === '' || $attrValue === null) {
                continue;
            }
            $fieldAttributes .= ' ' . esc_attr((string) $attrName) . '="' . esc_attr((string) $attrValue) . '"';
        }

        echo '<label class="' . esc_attr($class) . '">';
        echo '<span>' . esc_html($label) . '</span>';
        if ($type === 'textarea') {
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $fieldAttributes contains only internally defined names and esc_attr()-escaped values.
            echo '<textarea name="' . esc_attr($fieldName) . '" rows="' . esc_attr((string) ($field['rows'] ?? 4)) . '" placeholder="' . esc_attr($placeholder) . '" class="apd-codearea"' . $fieldAttributes . '>' . esc_textarea((string) $value) . '</textarea>';
        } elseif ($type === 'select') {
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $fieldAttributes contains only internally defined names and esc_attr()-escaped values.
            echo '<select name="' . esc_attr($fieldName) . '"' . $fieldAttributes . '>';
            foreach ((array) ($field['options'] ?? []) as $optionValue => $optionLabel) {
                echo '<option value="' . esc_attr((string) $optionValue) . '" ' . selected((string) $value, (string) $optionValue, false) . '>' . esc_html((string) $optionLabel) . '</option>';
            }
            echo '</select>';
        } elseif ($type === 'checkbox') {
            echo '<input type="hidden" name="' . esc_attr($fieldName) . '" value="0">';
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $fieldAttributes contains only internally defined names and esc_attr()-escaped values.
            echo '<label class="apd-checkbox"><input type="checkbox" name="' . esc_attr($fieldName) . '" value="1" ' . checked(! empty($value), true, false) . $fieldAttributes . '> <span>' . esc_html((string) ($field['checkbox_label'] ?? __('Enabled', 'asuka-product-designer'))) . '</span></label>';
        } else {
            if ($mediaType !== '') {
                echo '<div class="apd-media-field">';
            }
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $fieldAttributes contains only internally defined names and esc_attr()-escaped values.
            echo '<input type="' . esc_attr($type === 'number' ? 'number' : 'text') . '" name="' . esc_attr($fieldName) . '" value="' . esc_attr((string) $value) . '" placeholder="' . esc_attr($placeholder) . '"' . $fieldAttributes . '>';
            if ($mediaType !== '') {
                echo '<div class="apd-inline-actions apd-inline-actions-wrap apd-media-picker-row apd-media-actions">';
                echo '<button type="button" class="button button-secondary js-apd-media-pick" data-media-type="' . esc_attr($mediaType) . '" data-apd-media-open data-apd-media-type="' . esc_attr($mediaType) . '">' . esc_html__('Select or upload', 'asuka-product-designer') . '</button>';
                echo '<button type="button" class="button button-link js-apd-media-clear" data-apd-media-clear>' . esc_html__('Clear', 'asuka-product-designer') . '</button>';
                echo '</div>';
                echo '</div>';
            }
        }

        if ($help !== '') {
            echo '<small>' . esc_html($help) . '</small>';
        }
        echo '</label>';
    }

    protected function buildPreviewHtml(array $item) : string
    {
        $source = (string) ($item['url'] ?? $item['sourceUrl'] ?? $item['previewUrl'] ?? '');
        $svg = trim((string) ($item['svg'] ?? $item['svgPath'] ?? ''));
        $label = (string) ($item['label'] ?? $item['family'] ?? $item['id'] ?? '');
        if ($source !== '') {
            return '<div class="apd-list-preview"><img src="' . esc_url($source) . '" alt="' . esc_attr($label) . '" loading="lazy"></div>';
        }
        if ($svg !== '') {
            return '<div class="apd-list-preview apd-list-preview--svg">' . wp_kses($svg, [
                'svg' => ['xmlns' => true, 'viewBox' => true, 'width' => true, 'height' => true, 'fill' => true, 'stroke' => true, 'class' => true],
                'path' => ['d' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'transform' => true],
                'circle' => ['cx' => true, 'cy' => true, 'r' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true],
                'rect' => ['x' => true, 'y' => true, 'width' => true, 'height' => true, 'rx' => true, 'ry' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true],
                'g' => ['fill' => true, 'stroke' => true, 'transform' => true],
                'polygon' => ['points' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true],
                'line' => ['x1' => true, 'y1' => true, 'x2' => true, 'y2' => true, 'stroke' => true, 'stroke-width' => true],
            ]) . '</div>';
        }
        if (isset($item['value']) && (string) $item['value'] !== '') {
            return '<div class="apd-list-preview apd-list-preview--color"><span style="background:' . esc_attr((string) $item['value']) . ';"></span></div>';
        }
        return '<div class="apd-list-preview apd-list-preview--empty"><span>' . esc_html__('No preview', 'asuka-product-designer') . '</span></div>';
    }
}
