<?php
/**
 * Describes Bricks element control contracts for progressive disclosure.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Support;

/**
 * Wraps Bricks element registration without exposing Bricks calls to handlers.
 */
final class ControlIntrospector
{
    /**
     * @param array<string, array<string, array<string, mixed>>>|null $catalog
     */
    public function __construct(
        private readonly ?array $catalog = null
    ) {
    }

    private const UNIVERSAL_GROUPS = [
        'layout',
        'typography',
        'background',
        'border',
        'effects',
        'css',
    ];

    /**
     * @return array<string, mixed>
     */
    public function describe(string $elementName, ?string $group = null): array
    {
        $elementName = sanitize_key($elementName);
        if ($elementName === '') {
            return [
                'success' => false,
                'message' => 'I need an element name such as section, div, or heading.',
            ];
        }

        if (!class_exists(\Bricks\Elements::class)) {
            return [
                'success' => false,
                'message' => 'Bricks element registry is not available on this site.',
            ];
        }

        $element = \Bricks\Elements::get_element(['name' => $elementName]);
        if ($element === [] || !is_array($element)) {
            return [
                'success' => false,
                'message' => 'I could not find a Bricks element with that name.',
            ];
        }

        $controls = is_array($element['controls'] ?? null) ? $element['controls'] : [];
        $own      = $this->summarizeControls($controls, $group);
        $label    = (string) ($element['label'] ?? $elementName);

        return [
            'success'            => true,
            'element_name'       => $elementName,
            'label'              => $label,
            'own_controls'       => $own['controls'],
            'own_control_count'  => $own['count'],
            'universal_reference'=> [
                'summary' => 'All elements inherit 84 universal style controls from base.php (layout, spacing, typography, background, border, effects).',
                'groups'  => self::UNIVERSAL_GROUPS,
                'note'    => $group === null
                    ? 'Pass group to receive a focused slice of universal controls in a later call.'
                    : 'Universal controls are summarized here; request another group for more detail.',
            ],
            'settings_key_grammar' => 'Modern keys use colon suffixes (_padding:tablet_portrait:hover). Legacy underscore suffixes before Bricks 1.3.5 are still tolerated.',
            'truncated'          => $own['truncated'],
            'total'              => $own['count'],
        ];
    }

    /**
     * @param array<string, mixed> $controls
     * @return array{controls: array<int, array<string, mixed>>, count: int, truncated: bool}
     */
    private function summarizeControls(array $controls, ?string $group): array
    {
        $items = [];
        foreach ($controls as $controlId => $control) {
            if (!is_array($control)) {
                continue;
            }

            $controlGroup = (string) ($control['group'] ?? 'general');
            if ($group !== null && $group !== '' && $controlGroup !== $group) {
                continue;
            }

            $items[] = [
                'id'    => (string) $controlId,
                'label' => (string) ($control['label'] ?? $controlId),
                'type'  => (string) ($control['type'] ?? 'unknown'),
                'group' => $controlGroup,
            ];
        }

        $page = PaginatedList::slice($items, 80, 0);

        return [
            'controls'  => $page['items'],
            'count'     => $page['total'],
            'truncated' => $page['truncated'],
        ];
    }

    /**
     * @return string[]
     */
    public function knownKeys(string $elementName): array
    {
        $own = array_keys($this->controlsFor($elementName));

        return array_values(array_unique(array_merge(UniversalControls::keys(), $own)));
    }

    /**
     * Hostile validation: unknown keys abort. Closest valid keys come first.
     *
     * @param array<string, mixed> $settings
     * @return array{success: false, message: string, error: string, unknown_key: string, valid_keys: string[]}|null
     */
    public function validateSettings(string $elementName, array $settings, bool $universalOnly = false): ?array
    {
        $known = $universalOnly ? UniversalControls::keys() : $this->knownKeys($elementName);
        $controls = $this->controlsFor($elementName);

        foreach ($settings as $key => $value) {
            if (!is_string($key) || $key === '') {
                continue;
            }

            $parsed = SettingsKeyGrammar::parse($key);
            $base   = $parsed['base'];

            if (!in_array($base, $known, true)) {
                $valid = $this->suggestKeys($base, $known);

                return [
                    'success'     => false,
                    'message'     => sprintf(
                        'I do not recognize the setting %s on this element. Closest matches are listed in valid_keys.',
                        $base
                    ),
                    'error'       => 'unknown_setting',
                    'unknown_key' => $base,
                    'valid_keys'  => $valid,
                ];
            }

            $control = $controls[$base] ?? [];
            $options = is_array($control['options'] ?? null) ? $control['options'] : [];
            if ($options !== [] && (is_string($value) || is_int($value))) {
                $allowed = array_map('strval', array_keys($options));
                if (!in_array((string) $value, $allowed, true)) {
                    return [
                        'success'     => false,
                        'message'     => sprintf(
                            'I cannot use that value for %s. Allowed values are listed in valid_keys.',
                            $base
                        ),
                        'error'       => 'invalid_value',
                        'unknown_key' => $base,
                        'valid_keys'  => $allowed,
                    ];
                }
            }
        }

        return null;
    }

    /**
     * @param string[] $known
     * @return string[]
     */
    public function suggestKeys(string $unknown, array $known): array
    {
        $scored = [];
        foreach ($known as $key) {
            $scored[$key] = levenshtein(strtolower($unknown), strtolower($key));
        }

        asort($scored, SORT_NUMERIC);

        return array_slice(array_keys($scored), 0, 8);
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    private function controlsFor(string $elementName): array
    {
        $elementName = sanitize_key($elementName);
        if ($this->catalog !== null) {
            $entry = $this->catalog[$elementName] ?? [];

            return is_array($entry) ? $entry : [];
        }

        if ($elementName === '' || !class_exists(\Bricks\Elements::class)) {
            return [];
        }

        $element = \Bricks\Elements::get_element(['name' => $elementName]);
        if (!is_array($element) || $element === []) {
            return [];
        }

        $controls = $element['controls'] ?? [];

        return is_array($controls) ? $controls : [];
    }
}
