<?php

declare(strict_types=1);

require_once dirname(__DIR__) . '/runtime/autoload.php';

use Activetwist\Jsonm\JsonmProfile;
use Activetwist\Jsonm\Targets\MyronCssTarget;
use Activetwist\Jsonm\Util\PathAccess;
use Activetwist\Jsonm\Validator;

/**
 * MyronCMS-internal facade. The universal validator and the per-target
 * transpiler live in the `activetwist/jsonm-php` Composer package
 * (under `Activetwist\Jsonm`). This class preserves the static API every
 * existing MyronCMS call site expects, while internally delegating
 * validation, semantic-color resolution, path access, and CSS emission
 * to the library.
 *
 * Export (CSS → JSONM round-trip) remains here because it reads from
 * MyronCMS-specific runtime CSS surfaces and is not part of the AT-portable
 * library.
 */
final class StyleDefinitionRuntime
{
    public static function projectRoot(): string
    {
        return dirname(__DIR__, 4);
    }

    private static function myronCmsProfile(): JsonmProfile
    {
        $root = self::projectRoot();
        return new JsonmProfile(
            allowedCorpusIds: ['activetwist-design-corpus/v1'],
            cssVarPrefix: 'myron',
            baselinePath: $root . '/app/jsonm/baselines/apollo-v0.1.0.jsonm',
            tokenMapPath: $root . '/app/jsonm/maps/token-map.json',
            archetypesDir: $root . '/app/jsonm/archetypes',
            runtimeTokensPath: $root . '/app/assets/runtime/css/runtime-token-overrides.css',
            runtimeFontsPath: $root . '/app/assets/runtime/css/runtime-fonts.css',
            projectRoot: $root,
            schemaPath: $root . '/app/jsonm/schema/style-definition.schema.json',
        );
    }

    // ─── Loaders ────────────────────────────────────────────────────────

    public static function loadJson(string $path): array
    {
        if (!is_file($path)) {
            throw new RuntimeException("File not found: {$path}");
        }

        $decoded = json_decode((string) file_get_contents($path), true);
        if (!is_array($decoded)) {
            throw new RuntimeException("Invalid JSON: {$path}");
        }

        return $decoded;
    }

    public static function loadDefinition(?string $path = null): array
    {
        $target = $path ?? self::myronCmsProfile()->baselinePath;
        return self::loadJson((string) $target);
    }

    public static function loadTokenMap(?string $path = null): array
    {
        $target = $path ?? self::myronCmsProfile()->tokenMapPath;
        $map = self::loadJson((string) $target);
        if (!isset($map['mappings']) || !is_array($map['mappings'])) {
            throw new RuntimeException('Token map is missing `mappings`.');
        }

        return $map;
    }

    // ─── Validation facade ──────────────────────────────────────────────

    public static function validateDefinition(array $definition, array $tokenMap): array
    {
        return Validator::validate($definition, $tokenMap, self::myronCmsProfile());
    }

    public static function migrationWarnings(array $definition): array
    {
        return Validator::migrationWarnings($definition);
    }

    public static function resolveDefaultMode(array $definition, ?string $requestedMode = null): string
    {
        return Validator::resolveDefaultMode($definition, $requestedMode);
    }

    public static function resolveSemanticColors(array $definition, ?string $requestedMode = null): array
    {
        return Validator::resolveSemanticColors($definition, $requestedMode);
    }

    // ─── Compile (delegates to MyronCssTarget) ──────────────────────────

    public static function compileDefinition(array $definition, array $tokenMap): string
    {
        return (new MyronCssTarget())->transpile($definition, $tokenMap, self::myronCmsProfile());
    }

    public static function compileFontRolesDefinition(array $definition): string
    {
        $tokenMap = self::loadTokenMap();
        $errors = Validator::validate($definition, $tokenMap, self::myronCmsProfile());
        if ($errors !== []) {
            throw new RuntimeException("Definition is invalid:\n- " . implode("\n- ", $errors));
        }
        return (new MyronCssTarget())->transpileFontRoles($definition, self::myronCmsProfile());
    }

    // ─── Export (MyronCMS-internal — reads from runtime CSS surfaces) ───

    public static function activeDefinition(): array
    {
        $root = self::projectRoot();
        $baselinePath = $root . '/app/jsonm/baselines/apollo-v0.1.0.jsonm';
        $pointerPath = $root . '/app/jsonm/current-definition.json';
        if (!is_file($pointerPath)) {
            return self::loadJson($baselinePath);
        }

        $pointer = self::loadJson($pointerPath);
        if ((string) ($pointer['source'] ?? '') === 'baseline') {
            return self::loadJson($baselinePath);
        }

        $filename = basename((string) ($pointer['filename'] ?? ''));
        if ($filename !== '' && preg_match('/^[a-zA-Z0-9._-]+\.jsonm$/', $filename) === 1) {
            $definitionPath = $root . '/app/jsonm/definitions/' . $filename;
            if (is_file($definitionPath)) {
                return self::loadJson($definitionPath);
            }
        }

        return self::loadJson($baselinePath);
    }

    public static function definitionGoogleFontsCss2Url(array $definition): string
    {
        return trim((string) ($definition['fontRoles']['googleFontsCss2Url'] ?? ''));
    }

    public static function activeGoogleFontsCss2Url(): string
    {
        return self::definitionGoogleFontsCss2Url(self::activeDefinition());
    }

    public static function exportCurrentDefinition(
        array $tokenMap,
        ?string $id = null,
        ?string $name = null,
        ?string $googleFontsCss2Url = null
    ): array
    {
        $root = self::projectRoot();
        $files = [
            $root . '/app/assets/runtime/css/tokens.css',
            $root . '/app/assets/runtime/css/runtime-fonts.css',
            $root . '/app/assets/runtime/css/runtime-token-overrides.css',
        ];

        $variables = [];
        foreach ($files as $file) {
            if (!is_file($file)) {
                continue;
            }

            $content = (string) file_get_contents($file);
            $variables = array_merge($variables, self::parseCssVariables($content));
        }
        $definitionFontUrl = $googleFontsCss2Url ?? self::activeGoogleFontsCss2Url();

        $export = [
            'schemaVersion' => '1',
            'format' => 'JSONM',
            'kind' => 'baseline',
            'id' => $id ?? 'apollo-v0.1.0-active-export',
            'name' => $name ?? 'Apollo v0.1.0 Active Export',
            'description' => 'Exported from current app CSS surfaces.',
            'corpus' => [
                'id' => 'activetwist-design-corpus/v1',
                'version' => 'apollo-v0.1.0',
                'canonical' => true,
                'sourceFiles' => [
                    'app/assets/runtime/css/tokens.css',
                    'app/assets/runtime/css/runtime-fonts.css',
                    'app/assets/runtime/css/runtime-token-overrides.css',
                ],
            ],
            'settings' => [
                'wcagPolicy' => 'AA',
                'appearanceModes' => ['light'],
                'defaultMode' => 'light',
                'systemFollow' => false,
            ],
            'tokens' => [],
            'fontRoles' => [
                'googleFontsCss2Url' => $definitionFontUrl,
                'roles' => [],
            ],
        ];

        foreach ($tokenMap['mappings'] as $mapping) {
            $cssVar = (string) $mapping['cssVar'];
            $kind = (string) ($mapping['kind'] ?? 'value');
            if (!array_key_exists($cssVar, $variables)) {
                $jsonPath = (string) ($mapping['jsonPath'] ?? '');
                if (str_starts_with($jsonPath, 'tokens.layout.widthModes.')) {
                    PathAccess::set($export, $jsonPath, self::defaultLayoutWidthModeValue($jsonPath));
                    continue;
                }
                throw new RuntimeException("Missing CSS variable for export: {$cssVar}");
            }
            if ($kind === 'containerRef') {
                PathAccess::set($export, (string) $mapping['jsonPath'], self::exportContainerRef($variables[$cssVar], $variables));
                continue;
            }
            $value = self::exportMappedValue($variables[$cssVar], $kind, $variables[$cssVar]);

            if ($kind === 'fontFamilyPrimary') {
                [$family, $fallback] = self::splitFontStack($variables[$cssVar]);
                PathAccess::set($export, (string) $mapping['jsonPath'], $family);
                $fallbackPath = preg_replace('/\.family$/', '.fallback', (string) $mapping['jsonPath']);
                PathAccess::set($export, $fallbackPath, $fallback);
                continue;
            }

            PathAccess::set($export, (string) $mapping['jsonPath'], $value);
        }

        return $export;
    }

    public static function parseCssVariables(string $content): array
    {
        preg_match_all('/(--[a-z0-9_-]+)\s*:\s*([^;]+);/i', $content, $matches, PREG_SET_ORDER);
        $values = [];
        foreach ($matches as $match) {
            $values[trim((string) $match[1])] = trim((string) $match[2]);
        }
        return $values;
    }

    public static function extractImportUrl(string $content): string
    {
        if (preg_match('/@import\s+url\("([^"]+)"\);/', $content, $match)) {
            return trim((string) $match[1]);
        }
        $runtimeFontsPath = self::projectRoot() . '/app/assets/runtime/css/runtime-fonts.css';
        if (
            is_file($runtimeFontsPath)
            && hash_equals(
                hash('sha256', (string) file_get_contents($runtimeFontsPath)),
                hash('sha256', $content)
            )
        ) {
            return self::activeGoogleFontsCss2Url();
        }
        return '';
    }

    private static function exportMappedValue(string $cssValue, string $kind, string $original): mixed
    {
        return match ($kind) {
            'integer' => (int) trim($cssValue),
            'float' => (float) trim($cssValue),
            'spaceAlias' => self::extractAliasRef($cssValue, '--myron-space-'),
            'radiusAlias' => self::extractAliasRef($cssValue, '--myron-radius-'),
            default => trim($original),
        };
    }

    private static function splitFontStack(string $value): array
    {
        $inQuote = false;
        $buffer = '';
        $family = '';
        $length = strlen($value);

        for ($i = 0; $i < $length; $i++) {
            $char = $value[$i];
            if ($char === '"') {
                $inQuote = !$inQuote;
            }
            if ($char === ',' && !$inQuote) {
                $family = trim($buffer);
                $fallback = trim(substr($value, $i + 1));
                return [trim($family, '"'), $fallback];
            }
            $buffer .= $char;
        }

        $family = trim($buffer, " \t\n\r\0\x0B\"");
        return [$family, ''];
    }

    private static function extractAliasRef(string $value, string $prefix): string
    {
        if (preg_match('/^var\(' . preg_quote($prefix, '/') . '([a-zA-Z0-9_]+)\)$/', trim($value), $match)) {
            return (string) $match[1];
        }
        throw new RuntimeException("Alias value does not match expected pattern for {$prefix}: {$value}");
    }

    private static function defaultLayoutWidthModeValue(string $jsonPath): string
    {
        return str_ends_with($jsonPath, '.mode') ? 'contained' : 'lg';
    }

    private static function exportContainerRef(string $cssValue, array $variables): string
    {
        $trimmed = trim($cssValue);
        foreach (['sm', 'md', 'lg'] as $key) {
            if (($variables['--myron-layout-container-' . $key] ?? null) === $trimmed) {
                return $key;
            }
        }

        return 'lg';
    }

    // ─── Utilities ──────────────────────────────────────────────────────

    public static function writeFile(string $path, string $content): void
    {
        $dir = dirname($path);
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }
        file_put_contents($path, $content);
    }

    public static function jsonEncodePretty(array $data): string
    {
        $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        if (!is_string($json)) {
            throw new RuntimeException('Failed to encode JSON.');
        }
        return $json . "\n";
    }
}
