<?php
/**
 * Site design vocabulary checks before global class writes.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Support;

use LolaCoreForBricks\Governance\ClassSimilarity;
use LolaCoreForBricks\Support\DesignConventionStore;
use LolaCoreForBricks\Support\ExternalFrameworkDetector;

/**
 * Guardrail consulted before creating global classes.
 */
final class SystemVocabulary
{
    public const CONVENTION_OPTION = 'lolacore_bricks_naming_convention';

    public function __construct(
        private readonly ?ClassSimilarity $classSimilarity = null
    ) {
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<int, array<string, mixed>>
     */
    public function notesBeforeCreate(string $name, array $settings = []): array
    {
        $notes = array_merge(
            $this->duplicateNotes($name, $settings),
            $this->conventionNotes($name)
        );

        return $notes;
    }

    /**
     * @param array<int, array<string, mixed>> $variables
     * @return array<int, array<string, mixed>>
     */
    public function notesBeforeVariableSet(array $variables): array
    {
        $notes    = [];
        $existing = $this->loadExistingVariables();

        foreach ($variables as $variable) {
            if (!is_array($variable)) {
                continue;
            }

            $name  = trim((string) ($variable['name'] ?? ''));
            $value = trim((string) ($variable['value'] ?? ''));
            if ($name === '' || $value === '') {
                continue;
            }

            foreach ($existing as $stored) {
                $storedName = trim((string) ($stored['name'] ?? ''));
                $storedValue = trim((string) ($stored['value'] ?? ''));
                if ($storedName === '' || strcasecmp($storedName, $name) === 0) {
                    continue;
                }

                if (strcasecmp($storedValue, $value) === 0) {
                    $notes[] = [
                        'type'     => 'duplicate_value',
                        'variable' => $storedName,
                        'value'    => $value,
                    ];
                }
            }
        }

        return $notes;
    }

    /**
     * Warn when a raw style value already exists as a site variable.
     *
     * @param array<string, mixed> $settings
     * @return array<int, array<string, mixed>>
     */
    public function notesBeforeStyle(array $settings): array
    {
        $notes    = [];
        $existing = $this->loadExistingVariables();

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

            foreach ($this->scalarValues($value) as $scalar) {
                if ($scalar === '' || str_contains($scalar, 'var(--')) {
                    continue;
                }

                foreach ($existing as $stored) {
                    $storedName  = ltrim(trim((string) ($stored['name'] ?? '')), '-');
                    $storedValue = trim((string) ($stored['value'] ?? ''));
                    if ($storedName === '' || $storedValue === '') {
                        continue;
                    }

                    if (strcasecmp($storedValue, $scalar) !== 0) {
                        continue;
                    }

                    $notes[] = [
                        'type'     => 'equivalent_variable',
                        'variable' => $storedName,
                        'value'    => $scalar,
                        'setting'  => $key,
                        'message'  => sprintf(
                            'The site already has --%s with that value. Want me to use it?',
                            $storedName
                        ),
                    ];
                }
            }
        }

        return $notes;
    }

    /**
     * @return string[]
     */
    private function scalarValues(mixed $value): array
    {
        if (is_scalar($value) || $value === null) {
            return [trim((string) $value)];
        }

        if (!is_array($value)) {
            return [];
        }

        $found = [];
        foreach ($value as $nested) {
            if (is_scalar($nested) || $nested === null) {
                $found[] = trim((string) $nested);
            }
        }

        return $found;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function loadExistingVariables(): array
    {
        $raw = get_option(BricksMetaKeys::OPTION_GLOBAL_VARIABLES, []);
        if (!is_array($raw)) {
            return [];
        }

        $variables = [];
        foreach ($raw as $variable) {
            if (is_array($variable) && !empty($variable['name'])) {
                $variables[] = $variable;
            }
        }

        return $variables;
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<int, array<string, mixed>>
     */
    private function duplicateNotes(string $name, array $settings): array
    {
        $candidate = [
            'id'       => '__candidate__',
            'name'     => $name,
            'settings' => $settings,
        ];

        $similarity = $this->classSimilarity ?? new ClassSimilarity();
        $notes      = [];

        foreach ($this->loadActiveClasses() as $existing) {
            $pair = $similarity->comparePair($candidate, $existing);
            if ($pair['match_percent'] < ClassSimilarity::DEFAULT_THRESHOLD) {
                continue;
            }

            $notes[] = [
                'type'          => 'duplicate_class',
                'class_id'      => (string) ($existing['id'] ?? ''),
                'class_name'    => (string) ($existing['name'] ?? ''),
                'match_percent' => $pair['match_percent'],
            ];
        }

        return $notes;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function conventionNotes(string $name): array
    {
        $convention = DesignConventionStore::get();
        $pattern    = trim((string) ($convention['naming_pattern'] ?? ''));

        foreach ($convention['protected_prefixes'] as $prefix) {
            if ($prefix !== '' && str_starts_with(strtolower($name), strtolower($prefix))) {
                return [[
                    'type'    => 'protected_prefix',
                    'prefix'  => $prefix,
                    'message' => 'That name uses a protected prefix on this site.',
                ]];
            }
        }

        if ($pattern === '') {
            $detected = (new ExternalFrameworkDetector())->detect();
            if ($detected !== null) {
                return [[
                    'type'              => 'detected_framework',
                    'framework'         => (string) $detected['framework'],
                    'confidence'        => (int) $detected['confidence'],
                    'suggested_pattern' => (string) $detected['suggested_pattern'],
                    'message'           => 'No convention is saved yet, but existing classes look like '
                        . (string) $detected['framework'] . '. Consider adopting that pattern.',
                ]];
            }

            return [[
                'type'    => 'no_convention',
                'message' => 'No naming convention is configured for this site yet.',
            ]];
        }

        if (@preg_match($pattern, $name) === 1) {
            return [];
        }

        return [[
            'type'          => 'convention_mismatch',
            'pattern'       => $pattern,
            'class_name'    => $name,
            'proposed_name' => self::proposeClassName($name, $convention),
        ]];
    }

    public static function proposeClassName(string $name, ?array $convention = null): string
    {
        $slug = strtolower((string) preg_replace('/([a-z])([A-Z])/', '$1-$2', $name));
        $slug = strtolower((string) preg_replace('/[^a-z0-9._-]+/', '-', $slug));
        $slug = trim((string) preg_replace('/-+/', '-', $slug), '-');

        $convention ??= DesignConventionStore::get();
        $examples   = is_array($convention['examples_valid'] ?? null) ? $convention['examples_valid'] : [];
        if ($examples !== [] && str_contains((string) $examples[0], '.')) {
            $parts = explode('-', $slug);
            if (count($parts) > 1) {
                $last  = array_pop($parts);
                $slug  = implode('-', $parts) . '.' . $last;
            }
        }

        return $slug;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function loadActiveClasses(): array
    {
        $raw = get_option(BricksMetaKeys::OPTION_GLOBAL_CLASSES, []);
        if (!is_array($raw)) {
            return [];
        }

        $classes = [];
        foreach ($raw as $class) {
            if (is_array($class) && !empty($class['id'])) {
                $classes[] = $class;
            }
        }

        return $classes;
    }
}
