<?php

declare(strict_types=1);

namespace PpPhp\Core\Events;

use PpPhp\Core\ModuleState;

/**
 * Build-time / pre-deploy sweep: run the same {@see EventLinter} the editor uses
 * across EVERY saved event in a project, so a typo or broken snippet is caught
 * before the app is built and shipped — not discovered at runtime on the server.
 *
 * PHP events get the full check (syntax + did-you-mean + arg-count + unused).
 * JS events get the parser-free part (the pp.* "did you mean" spell-check) — the
 * editor still does the full Acorn-based JS check live while authoring.
 */
final class EventScanner
{
    /**
     * @return array{
     *   events: array<int, array{label:string,scope:string,lang:string,enabled:bool,
     *     nav:array<string,string>,counts:array{error:int,warning:int,info:int},
     *     diagnostics:array<int,array<string,mixed>>}>,
     *   totals: array{error:int,warning:int,info:int,checked:int,withIssues:int}
     * }
     */
    public static function scan(string $slug): array
    {
        $state = ModuleState::load($slug, 'events') ?: [];
        $index = self::catalogueIndex();

        $events = [];
        $checked = 0;
        self::walk($state, [], $index, $events, $checked);

        // Most severe first, then most warnings, then alphabetical.
        usort($events, static function ($a, $b) {
            return ($b['counts']['error'] <=> $a['counts']['error'])
                ?: ($b['counts']['warning'] <=> $a['counts']['warning'])
                ?: strcmp($a['label'], $b['label']);
        });

        $totals = ['error' => 0, 'warning' => 0, 'info' => 0, 'checked' => $checked, 'withIssues' => count($events)];
        foreach ($events as $e) {
            $totals['error'] += $e['counts']['error'];
            $totals['warning'] += $e['counts']['warning'];
            $totals['info'] += $e['counts']['info'];
        }
        return ['events' => $events, 'totals' => $totals];
    }

    /** Recursively find event nodes ({code:string,…}) and lint them. */
    private static function walk($node, array $path, array $index, array &$out, int &$checked): void
    {
        if (!is_array($node)) {
            return;
        }
        if (array_key_exists('code', $node) && is_string($node['code'])) {
            self::lintNode($node, $path, $index, $out, $checked);
            return;
        }
        foreach ($node as $k => $v) {
            if (is_array($v)) {
                self::walk($v, array_merge($path, [(string) $k]), $index, $out, $checked);
            }
        }
    }

    private static function lintNode(array $node, array $path, array $index, array &$out, int &$checked): void
    {
        $code = (string) ($node['code'] ?? '');
        if (trim($code) === '') {
            return;
        }
        if (($node['enabled'] ?? true) === false) {
            return;   // a disabled event does not ship
        }

        [$lang, $args, $label, $nav] = self::resolve($path, $node, $index);
        $diagnostics = $lang === 'js' ? EventLinter::js($code) : EventLinter::php($code, $args);
        $checked++;
        if ($diagnostics === []) {
            return;
        }

        $counts = ['error' => 0, 'warning' => 0, 'info' => 0];
        foreach ($diagnostics as $d) {
            $sev = $d['severity'] ?? 'info';
            if (isset($counts[$sev])) {
                $counts[$sev]++;
            }
        }
        $out[] = [
            'label' => $label,
            'scope' => $path[0] ?? '',
            'lang' => $lang,
            'enabled' => ($node['enabled'] ?? true) !== false,
            'nav' => $nav,
            'counts' => $counts,
            'diagnostics' => $diagnostics,
        ];
    }

    /**
     * Resolve a state path to [lang, contextVarNames, humanLabel, navHints].
     *
     * @return array{0:string,1:array<int,string>,2:string,3:array<string,string>}
     */
    private static function resolve(array $path, array $node, array $index): array
    {
        $scope = $path[0] ?? '';

        if ($scope === 'global') {
            $group = $path[1] ?? '';
            if ($group === 'functions') {
                $l = ($path[2] ?? 'php') === 'js' ? 'js' : 'php';
                return [$l, [], 'Global · ' . strtoupper($l) . ' function', ['scope' => 'global', 'group' => 'functions', 'event' => (string) ($path[2] ?? '')]];
            }
            if ($group === 'schedule') {
                return ['php', [], 'Scheduled task · ' . (($node['name'] ?? '') ?: 'Untitled'), ['scope' => 'global', 'group' => 'schedule']];
            }
            if ($group === 'actions') {
                return ['php', [], 'Server action · ' . (($node['name'] ?? '') ?: 'Untitled'), ['scope' => 'global', 'group' => 'actions']];
            }
            $event = $path[2] ?? '';
            $def = $index['global'][$group][$event] ?? null;
            $lang = $def['lang'] ?? ($group === 'client' ? 'js' : 'php');
            $label = 'Global · ' . ($def['group'] ?? $group) . ' · ' . ($def['label'] ?? $event);
            return [$lang, $def['args'] ?? [], $label, ['scope' => 'global', 'group' => $group, 'event' => $event]];
        }

        if ($scope === 'tables') {
            $table = $path[1] ?? '';
            if (($path[2] ?? '') === 'fields') {
                $field = $path[3] ?? '';
                $fgroup = $path[4] ?? '';   // server | client
                $event = $path[5] ?? '';
                $def = $index['field'][$fgroup][$event] ?? null;
                $lang = $def['lang'] ?? ($fgroup === 'client' ? 'js' : 'php');
                $label = $table . ' · ' . $field . ' · ' . ($def['label'] ?? $event) . ' (' . $fgroup . ')';
                return [$lang, $def['args'] ?? [], $label, ['scope' => $table, 'table' => $table, 'field' => $field, 'fieldGroup' => $fgroup, 'event' => $event]];
            }
            $group = $path[2] ?? '';
            $event = $path[3] ?? '';
            $def = $index['table'][$group][$event] ?? null;
            $lang = $def['lang'] ?? ($group === 'client' ? 'js' : 'php');
            $label = $table . ' · ' . ($def['group'] ?? $group) . ' · ' . ($def['label'] ?? $event);
            return [$lang, $def['args'] ?? [], $label, ['scope' => $table, 'table' => $table, 'group' => $group, 'event' => $event]];
        }

        return ['php', [], implode(' · ', $path), []];
    }

    /** Build a lang/args/label lookup from the event catalogue. */
    private static function catalogueIndex(): array
    {
        $index = ['global' => [], 'table' => [], 'field' => []];

        $g = new GlobalEvent();
        foreach ($g->groups() as $gk => $gv) {
            foreach (($gv['events'] ?? []) as $ek => $ev) {
                $index['global'][$gk][$ek] = [
                    'lang' => $ev['lang'] ?? 'php',
                    'args' => array_keys((array) ($ev['args'] ?? [])),
                    'label' => $ev['label'] ?? $ek,
                    'group' => $gv['label'] ?? $gk,
                ];
            }
        }

        $t = new TableEvent('');
        foreach ($t->groups() as $gk => $gv) {
            $glang = $gk === 'client' ? 'js' : 'php';
            foreach (($gv['events'] ?? []) as $ek => $ev) {
                $index['table'][$gk][$ek] = [
                    'lang' => $ev['lang'] ?? $glang,
                    'args' => array_keys((array) ($ev['args'] ?? [])),
                    'label' => $ev['label'] ?? $ek,
                    'group' => $gv['label'] ?? $gk,
                ];
            }
        }
        foreach ($t->fieldGroups() as $fk => $fv) {
            $flang = $fv['lang'] ?? ($fk === 'client' ? 'js' : 'php');
            foreach (($fv['events'] ?? []) as $ek => $ev) {
                $index['field'][$fk][$ek] = [
                    'lang' => $flang,
                    'args' => array_keys((array) ($ev['args'] ?? [])),
                    'label' => $ev['label'] ?? $ek,
                ];
            }
        }
        return $index;
    }
}
