<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Blocks;

/**
 * Sprint 11 — single source of truth for "which attributes does each
 * Seekmodo web component accept", consumed by:
 *
 *   - {@see Registry} (Gutenberg blocks, PR 1)
 *   - {@see \Numinix\SeekmodoWordPress\Elementor\Widgets} (PR 2)
 *   - {@see \Numinix\SeekmodoWordPress\WPBakery\VcMap} (PR 3)
 *
 * Keeping a single canonical map prevents the three page-builder
 * surfaces from drifting apart. Each entry mirrors the matching
 * component's `static get observedAttributes()` in
 * `seekmodo/packages/web-components/src/components/*.ts`. When the
 * SDK adds a new attribute, this map is the one place that needs
 * updating to flow it through to all three builders.
 *
 * The PHP type maps to the closest equivalent attribute primitive
 * each builder framework understands:
 *
 *   - `string`  -> Gutenberg `string`,  Elementor `TEXT`,    VC `textfield`
 *   - `number`  -> Gutenberg `number`,  Elementor `NUMBER`,  VC `textfield`
 *   - `boolean` -> Gutenberg `boolean`, Elementor `SWITCHER`, VC `checkbox`
 *
 * The `attribute` field on each entry is the SDK-side HTML attribute
 * name (kebab-case) and is what the renderer emits. The `key` is the
 * Gutenberg-friendly camelCase variant used for editor controls.
 */
final class AttributeSchema
{
    public const ELEMENTS = ['search', 'recommend', 'chat', 'quiz', 'ask'];

    /**
     * Map element-slug -> custom-element tag name. Used by the renderer
     * to know which `<seekmodo-...>` to emit.
     */
    public const TAG_NAMES = [
        'search'    => 'seekmodo-search-box',
        'recommend' => 'seekmodo-recommendations',
        'chat'      => 'seekmodo-chat',
        'quiz'      => 'seekmodo-quiz',
        'ask'       => 'seekmodo-ask',
    ];

    /**
     * @return array<int, array{
     *   key: string,
     *   attribute: string,
     *   type: string,
     *   default: string|int|float|bool|null,
     *   label: string,
     *   description: string,
     * }>
     */
    public static function attributesFor(string $element): array
    {
        switch ($element) {
            case 'search':
                return [
                    [
                        'key'         => 'placeholder',
                        'attribute'   => 'placeholder',
                        'type'        => 'string',
                        'default'     => 'Search products',
                        'label'       => 'Placeholder',
                        'description' => 'Text shown in the empty search input.',
                    ],
                    [
                        'key'         => 'value',
                        'attribute'   => 'value',
                        'type'        => 'string',
                        'default'     => '',
                        'label'       => 'Initial query',
                        'description' => 'Optional pre-filled search term. Most storefronts leave this empty.',
                    ],
                    [
                        'key'         => 'withTypeahead',
                        'attribute'   => 'with-typeahead',
                        'type'        => 'boolean',
                        'default'     => false,
                        'label'       => 'Show typeahead overlay',
                        'description' => 'Mounts an inline `<seekmodo-typeahead>` overlay below the input.',
                    ],
                    [
                        'key'         => 'submitOnEnter',
                        'attribute'   => 'submit-on-enter',
                        'type'        => 'boolean',
                        'default'     => true,
                        'label'       => 'Submit on Enter',
                        'description' => 'When enabled, pressing Enter dispatches the seekmodo:search event.',
                    ],
                ];
            case 'recommend':
                return [
                    [
                        'key'         => 'algorithm',
                        'attribute'   => 'algorithm',
                        'type'        => 'string',
                        'default'     => 'related',
                        'label'       => 'Algorithm',
                        'description' => 'One of: related, popular, frequently_bought_together, similar_to_viewed.',
                    ],
                    [
                        'key'         => 'placement',
                        'attribute'   => 'placement',
                        'type'        => 'string',
                        'default'     => 'pdp',
                        'label'       => 'Placement label',
                        'description' => 'Logical placement reported in analytics: pdp, cart, search-empty, etc.',
                    ],
                    [
                        'key'         => 'sourceDocId',
                        'attribute'   => 'source-doc-id',
                        'type'        => 'string',
                        'default'     => '',
                        'label'       => 'Source product ID',
                        'description' => 'Anchor product for the recommendation set. Optional for popular/trending.',
                    ],
                    [
                        'key'         => 'limit',
                        'attribute'   => 'limit',
                        'type'        => 'number',
                        'default'     => 6,
                        'label'       => 'Maximum items',
                        'description' => 'Number of cards to render. Defaults to 6.',
                    ],
                ];
            case 'chat':
                return [
                    [
                        'key'         => 'placeholder',
                        'attribute'   => 'placeholder',
                        'type'        => 'string',
                        'default'     => 'Ask anything…',
                        'label'       => 'Input placeholder',
                        'description' => 'Text shown in the empty chat input.',
                    ],
                    [
                        'key'         => 'intro',
                        'attribute'   => 'intro',
                        'type'        => 'string',
                        'default'     => '',
                        'label'       => 'Intro message',
                        'description' => 'Optional first assistant turn shown before the shopper types.',
                    ],
                    [
                        'key'         => 'maxTurns',
                        'attribute'   => 'max-turns',
                        'type'        => 'number',
                        'default'     => 12,
                        'label'       => 'Maximum turns',
                        'description' => 'Hard cap on transcript length before older turns scroll out.',
                    ],
                    [
                        'key'         => 'autoCitations',
                        'attribute'   => 'auto-citations',
                        'type'        => 'boolean',
                        'default'     => false,
                        'label'       => 'Show citations inline',
                        'description' => 'When enabled, citation chips render under each assistant turn.',
                    ],
                ];
            case 'ask':
                return [
                    [
                        'key'         => 'placeholder',
                        'attribute'   => 'placeholder',
                        'type'        => 'string',
                        'default'     => 'Ask about products or policies…',
                        'label'       => 'Input placeholder',
                        'description' => 'Text shown in the empty Ask AI input.',
                    ],
                    [
                        'key'         => 'buttonLabel',
                        'attribute'   => 'button-label',
                        'type'        => 'string',
                        'default'     => 'Ask AI',
                        'label'       => 'Submit button label',
                        'description' => 'Label on the submit button.',
                    ],
                ];
            case 'quiz':
                return [
                    [
                        'key'         => 'quizId',
                        'attribute'   => 'quiz-id',
                        'type'        => 'string',
                        'default'     => '',
                        'label'       => 'Quiz ID',
                        'description' => 'The numeric or slug ID of the configured quiz definition.',
                    ],
                    [
                        'key'         => 'quizSlug',
                        'attribute'   => 'quiz-slug',
                        'type'        => 'string',
                        'default'     => '',
                        'label'       => 'Quiz slug',
                        'description' => 'Optional slug-based selector if you do not have a numeric ID.',
                    ],
                    [
                        'key'         => 'definitionSrc',
                        'attribute'   => 'definition-src',
                        'type'        => 'string',
                        'default'     => '',
                        'label'       => 'Definition source selector',
                        'description' => 'CSS selector pointing to a `<script type="application/json">` block carrying the quiz definition.',
                    ],
                    [
                        'key'         => 'inlineDefinition',
                        'attribute'   => 'inline-definition',
                        'type'        => 'string',
                        'default'     => '',
                        'label'       => 'Inline definition (JSON)',
                        'description' => 'Optional inline JSON definition. Use the source-selector path for any non-trivial quiz.',
                    ],
                    [
                        'key'         => 'autoStart',
                        'attribute'   => 'auto-start',
                        'type'        => 'boolean',
                        'default'     => false,
                        'label'       => 'Auto-start',
                        'description' => 'When enabled, the quiz advances past the intro screen on first paint.',
                    ],
                ];
        }

        return [];
    }

    /**
     * Render the matching `<seekmodo-...>` element with attributes
     * piped through. Used by the dynamic block render callbacks
     * (PR 1) and by the WPBakery shortcodes (PR 3).
     *
     * @param string                                    $element  Element slug from {@see ELEMENTS}.
     * @param array<string, string|int|float|bool|null> $values   Attribute key (camelCase) -> value.
     */
    public static function render(string $element, array $values): string
    {
        $tag = self::TAG_NAMES[$element] ?? null;
        if ($tag === null) {
            return '';
        }

        $attrs = self::attributesFor($element);
        $bits  = [];
        foreach ($attrs as $attr) {
            $key       = $attr['key'];
            $type      = $attr['type'];
            $supplied  = array_key_exists($key, $values) ? $values[$key] : null;
            $value     = $supplied;

            if ($type === 'boolean') {
                $bool = self::coerceBool($supplied, (bool) $attr['default']);
                if ($bool) {
                    $bits[] = sprintf('%s', $attr['attribute']);
                }
                continue;
            }

            if ($value === null || $value === '') {
                if ($attr['default'] === '' || $attr['default'] === null) {
                    continue;
                }
                $value = $attr['default'];
            }

            if ($type === 'number') {
                $value = (string) $value;
            }

            $bits[] = sprintf('%s="%s"', $attr['attribute'], esc_attr((string) $value));
        }

        $attrString = $bits === [] ? '' : ' ' . implode(' ', $bits);

        return sprintf('<%1$s%2$s></%1$s>', $tag, $attrString);
    }

    private static function coerceBool($value, bool $default): bool
    {
        if (is_bool($value)) {
            return $value;
        }
        if ($value === null || $value === '') {
            return $default;
        }
        if (is_numeric($value)) {
            return (int) $value !== 0;
        }
        if (is_string($value)) {
            $lower = strtolower(trim($value));

            return in_array($lower, ['1', 'true', 'yes', 'on'], true);
        }

        return $default;
    }
}
