<?php

declare(strict_types=1);

namespace PpPhp\Core\Events;

/**
 * Builds editor completion metadata from an event definition.
 *
 * The returned rows are deliberately editor-agnostic so the same contract can
 * be consumed by the current textarea editor and a future Monaco/CodeMirror
 * implementation.
 */
final class EventIntellisense
{
    /**
     * @param array<string, mixed> $definition
     * @param array<int, string> $fields
     * @param array<string, mixed> $context
     * @return array<int, array<string, mixed>>
     */
    public static function suggestions(
        array $definition,
        string $language,
        array $fields = [],
        array $context = []
    ): array {
        $language = strtolower($language) === EventCatalogue::LANG_JS
            ? EventCatalogue::LANG_JS
            : EventCatalogue::LANG_PHP;

        $items = [];
        $arguments = is_array($definition['args'] ?? null) ? $definition['args'] : [];

        foreach ($arguments as $name => $description) {
            $name = (string) $name;
            $description = (string) $description;
            $items[] = self::item(
                $name,
                $name,
                'variable',
                $description,
                'A variable this event hands you to work with.',
                'This event'
            );

            if ($fields !== [] && self::isRecordArgument($name, $description)) {
                foreach ($fields as $field) {
                    $field = (string) $field;
                    if ($field === '') {
                        continue;
                    }

                    $accessor = self::fieldAccessor($language, $name, $field);
                    $items[] = self::item(
                        $accessor,
                        $accessor,
                        'field',
                        $field,
                        'Field from ' . (string) ($context['table'] ?? 'the selected table'),
                        'Fields'
                    );
                }
            }
        }

        // JavaScript form/control helpers accept a database field name rather
        // than a record accessor.  Keep these exact schema names in a dedicated
        // context so the editor can show them only while the caret is inside a
        // field argument (for example Runner.getControl('…')).  Global events
        // intentionally receive no rows because they do not have one table
        // whose schema can be treated as authoritative.
        if ($language === EventCatalogue::LANG_JS && $fields !== []) {
            $table = (string) ($context['table'] ?? 'the selected table');
            foreach ($fields as $field) {
                $field = (string) $field;
                if ($field === '') {
                    continue;
                }

                $safeField = str_replace(['\\', "'"], ['\\\\', "\\'"], $field);
                $items[] = self::item(
                    $field,
                    $field,
                    'field',
                    $field . ' · ' . $table,
                    'Exact field name from the selected table. Use it in Runner.getControl(), pp.val(), pp.form.* and other field-aware helpers.',
                    'Table fields',
                    "Runner.getControl('" . $safeField . "')",
                    ['fieldArgument']
                );
            }
        }

        // The full API surface + ready-to-use snippets for this language.
        if ($language === EventCatalogue::LANG_JS) {
            $items = array_merge($items, EventApiReference::js(), EventApiReference::jsSnippets());
        } else {
            $items = array_merge($items, EventApiReference::php(), EventApiReference::phpSnippets());
        }

        $unique = [];
        foreach ($items as $item) {
            $contexts = is_array($item['contexts'] ?? null)
                ? array_map('strval', $item['contexts'])
                : [];
            sort($contexts);
            $key = implode(',', $contexts)
                . '|'
                . strtolower((string) ($item['insertText'] ?? $item['label'] ?? ''));
            if ($key !== '') {
                $unique[$key] = $item;
            }
        }

        return array_values($unique);
    }

    /** @return array<string, mixed> */
    private static function item(
        string $label,
        string $insertText,
        string $kind,
        string $detail,
        string $documentation,
        string $category = '',
        string $example = '',
        array $contexts = []
    ): array {
        return [
            'label' => $label,
            'insertText' => $insertText,
            'kind' => $kind,
            'detail' => $detail,
            'documentation' => $documentation,
            'category' => $category,
            'example' => $example,
            'contexts' => array_values(array_map('strval', $contexts)),
        ];
    }

    private static function isRecordArgument(string $name, string $description): bool
    {
        $name = ltrim($name, '$');
        $recordArguments = [
            'data', 'record', 'records', 'values', 'rawValues', 'sqlValues', 'old',
            'defaults', 'source', 'keys', 'userdata', 'rows', 'results',
        ];

        return in_array($name, $recordArguments, true);
    }

    private static function fieldAccessor(string $language, string $argument, string $field): string
    {
        $argument = ltrim($argument, '$');
        $safeField = str_replace(['\\', "'"], ['\\\\', "\\'"], $field);
        $plural = in_array($argument, ['records', 'rows', 'results'], true);

        if ($language === EventCatalogue::LANG_JS) {
            $base = $plural ? $argument . '[0]' : $argument;
            return preg_match('/^[A-Za-z_$][A-Za-z0-9_$]*$/', $field)
                ? $base . '.' . $field
                : $base . "['" . $safeField . "']";
        }

        return '$' . $argument . ($plural ? '[0]' : '') . "['" . $safeField . "']";
    }

}
