<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\WPBakery;

use Numinix\SeekmodoWordPress\Blocks\AttributeSchema;

/**
 * Sprint 11 PR 3 — `[seekmodo_*]` shortcode registry.
 *
 * Four shortcodes mapped 1:1 to the four AttributeSchema elements:
 *
 *   `[seekmodo_search]`    -> `<seekmodo-search-box>`
 *   `[seekmodo_recommend]` -> `<seekmodo-recommendations>`
 *   `[seekmodo_chat]`      -> `<seekmodo-chat>`
 *   `[seekmodo_quiz]`      -> `<seekmodo-quiz>`
 *
 * Each shortcode accepts the same attributes as its matching
 * Gutenberg block (PR 1) and Elementor widget (PR 2), in
 * lower-snake-case form (WordPress shortcode convention) instead
 * of camelCase. Example:
 *
 *   [seekmodo_quiz quiz_id="gift-finder" auto_start="1"]
 *
 * Boolean attributes accept `"1"` / `"true"` / `"yes"` / `"on"`
 * via the same normaliser AttributeSchema uses internally.
 */
final class Shortcodes
{
    private const SHORTCODE_TAGS = [
        'search'    => 'seekmodo_search',
        'recommend' => 'seekmodo_recommend',
        'chat'      => 'seekmodo_chat',
        'quiz'      => 'seekmodo_quiz',
        'ask'       => 'seekmodo_ask',
    ];

    public static function register(): void
    {
        if (!function_exists('add_shortcode')) {
            return;
        }

        foreach (self::SHORTCODE_TAGS as $element => $tag) {
            add_shortcode($tag, static function ($atts) use ($element): string {
                return self::renderShortcode($element, is_array($atts) ? $atts : []);
            });
        }
    }

    /**
     * Test-friendly shortcode renderer. Public so unit tests can
     * exercise the snake_case -> camelCase mapping without
     * round-tripping through WP's shortcode parser.
     *
     * @param array<string, mixed> $atts Shortcode attributes (snake_case).
     */
    public static function renderShortcode(string $element, array $atts): string
    {
        $defaults = self::defaultsFor($element);
        $merged   = function_exists('shortcode_atts')
            ? shortcode_atts($defaults, $atts, self::SHORTCODE_TAGS[$element] ?? '')
            : array_merge($defaults, $atts);

        $values = [];
        foreach (AttributeSchema::attributesFor($element) as $attr) {
            $snake = self::camelToSnake($attr['key']);
            if (array_key_exists($snake, $merged)) {
                $values[$attr['key']] = $merged[$snake];
            }
        }

        return AttributeSchema::render($element, $values);
    }

    /**
     * Test helper — return the canonical shortcode tag for an
     * element slug.
     */
    public static function tagFor(string $element): ?string
    {
        return self::SHORTCODE_TAGS[$element] ?? null;
    }

    /**
     * Test helper — return the canonical shortcode tags as a list.
     *
     * @return list<string>
     */
    public static function allTags(): array
    {
        return array_values(self::SHORTCODE_TAGS);
    }

    /**
     * Convert a camelCase attribute key (used by AttributeSchema /
     * Gutenberg block.json / Elementor controls) to the snake_case
     * form WordPress shortcodes use.
     *
     * Public for unit tests.
     */
    public static function camelToSnake(string $camel): string
    {
        $snake = preg_replace('/([a-z0-9])([A-Z])/', '$1_$2', $camel) ?? $camel;

        return strtolower($snake);
    }

    /**
     * Build the default-value array used by `shortcode_atts()` for a
     * given element. Keys are snake_case to match WP shortcode
     * convention; values are the AttributeSchema defaults.
     *
     * @return array<string, string|int|float|bool|null>
     */
    private static function defaultsFor(string $element): array
    {
        $defaults = [];
        foreach (AttributeSchema::attributesFor($element) as $attr) {
            $snake = self::camelToSnake($attr['key']);
            $defaults[$snake] = $attr['default'];
        }

        return $defaults;
    }
}
