<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\OAuth;

/**
 * Tiny self-contained i18n layer for the OAuth authorize/consent screen. Deliberately NOT a shop
 * language pack (no .po/.mo, no CONF_LOGO-style plumbing) — this screen is reached by an external
 * connector, sometimes before any admin session exists, so it can't rely on the backend GetText
 * machinery. Only two locales are supported: 'de' and 'en'. Every key MUST exist in both.
 */
final class ConsentLang
{
    /** @var array<string, array<string, string>> */
    private const TEXT = [
        'de' => [
            'consentPageTitle'   => 'MCP-Verbindung autorisieren',
            'consentHeading'     => 'MCP-Verbindung autorisieren',
            'consentIntro'       => 'Die Anwendung „%s“ möchte im Namen dieses Shops auf die '
                . 'folgenden Funktionen zugreifen.',
            'scopesLabel'        => 'Angeforderte Berechtigungen',
            'approve'            => 'Genehmigen',
            'deny'               => 'Ablehnen',
            'signedInAs'         => 'Angemeldet als Shop-Administrator.',
            'loginPromptTitle'   => 'Shop-Admin-Anmeldung erforderlich',
            'loginPromptHeading' => 'Anmeldung erforderlich',
            'loginPromptBody'    => 'Um diese MCP-Verbindung zu autorisieren, müssen Sie als '
                . 'Shop-Administrator im JTL-Backend angemeldet sein.',
            'loginPromptButton'  => 'Zum Backend-Login',
            'loginPromptFooter'  => 'Nach der Anmeldung diese Seite erneut öffnen:',
        ],
        'en' => [
            'consentPageTitle'   => 'Authorize MCP connection',
            'consentHeading'     => 'Authorize MCP connection',
            'consentIntro'       => 'The application “%s” wants to access the following '
                . 'functions on behalf of this shop.',
            'scopesLabel'        => 'Requested permissions',
            'approve'            => 'Approve',
            'deny'               => 'Deny',
            'signedInAs'         => 'Signed in as shop administrator.',
            'loginPromptTitle'   => 'Shop admin sign-in required',
            'loginPromptHeading' => 'Sign-in required',
            'loginPromptBody'    => 'To authorize this MCP connection you must be signed in as a '
                . 'shop administrator in the JTL backend.',
            'loginPromptButton'  => 'Go to backend login',
            'loginPromptFooter'  => 'After signing in, reopen this page:',
        ],
    ];

    /**
     * Known OAuth scopes, in both locales: a short badge label plus the sentence shown on hover.
     *
     * The label answers "what would this let the assistant do in my shop", not "what is the scope
     * called" — the merchant approving this screen has never seen `db:read`. The description names
     * the actual capability and, where it matters, its limit (read-only, sandboxed, undoable), so
     * the decision can be made from this card alone.
     *
     * Kept independent of {@see ScopeCatalog}/ToolFactory (owned by another work-stream) — any
     * scope not listed here falls back to a generic prettification, with no description and so no
     * tooltip, and never renders blank or crashes.
     *
     * @var array<string, array<string, array{label: string, desc: string}>>
     */
    private const SCOPES = [
        'shop:read' => [
            'de' => [
                'label' => 'Shop-Überblick',
                'desc'  => 'Liest Stammdaten des Shops: Name, Version, Systemzustand sowie die '
                    . 'vorhandenen Zahlungs- und Versandarten.',
            ],
            'en' => [
                'label' => 'Shop overview',
                'desc'  => 'Reads the shop\'s own basics: name, version, health, and the payment '
                    . 'and shipping methods it offers.',
            ],
        ],
        'settings:read' => [
            'de' => [
                'label' => 'Einstellungen lesen',
                'desc'  => 'Sieht Shop-Einstellungen und deren Bedeutung ein. Es wird nichts geändert.',
            ],
            'en' => [
                'label' => 'Read settings',
                'desc'  => 'Looks up shop settings and what each one does. Nothing is changed.',
            ],
        ],
        'settings:write' => [
            'de' => [
                'label' => 'Einstellungen ändern',
                'desc'  => 'Ändert Shop-Einstellungen. Jede Änderung wird protokolliert und kann '
                    . 'im Backend rückgängig gemacht werden.',
            ],
            'en' => [
                'label' => 'Change settings',
                'desc'  => 'Changes shop settings. Every change is logged and can be undone from '
                    . 'the backend.',
            ],
        ],
        'analytics:read' => [
            'de' => [
                'label' => 'Umsätze & Statistiken',
                'desc'  => 'Liest Bestellungen, Umsatzzahlen, Warenkörbe, Besucher, Topseller und '
                    . 'Suchbegriffe.',
            ],
            'en' => [
                'label' => 'Sales & statistics',
                'desc'  => 'Reads orders, revenue figures, carts, visitors, best sellers and '
                    . 'search terms.',
            ],
        ],
        'db:read' => [
            'de' => [
                'label' => 'Datenbank nur lesen',
                'desc'  => 'Führt einzelne SELECT-Abfragen auf der Shop-Datenbank aus und zeigt '
                    . 'Tabellenstrukturen. Schreiben ist technisch ausgeschlossen; Passwort- und '
                    . 'Token-Tabellen sind gesperrt.',
            ],
            'en' => [
                'label' => 'Read the database',
                'desc'  => 'Runs single SELECT queries against the shop database and lists table '
                    . 'structures. Writing is technically impossible; password and token tables '
                    . 'are off limits.',
            ],
        ],
        'logs:read' => [
            'de' => [
                'label' => 'Systemprotokoll',
                'desc'  => 'Liest das Systemprotokoll des Shops, um Fehler einzugrenzen.',
            ],
            'en' => [
                'label' => 'System log',
                'desc'  => 'Reads the shop\'s system log to track down errors.',
            ],
        ],
        'actions:read' => [
            'de' => [
                'label' => 'Backend-Aktionen ansehen',
                'desc'  => 'Sieht, welche Backend-Aktionen es gibt, prüft eine Aktion vor der '
                    . 'Ausführung und liest die Liste der letzten Änderungen.',
            ],
            'en' => [
                'label' => 'Inspect backend actions',
                'desc'  => 'Sees which backend actions exist, checks one before it runs, and '
                    . 'reads the list of recent changes.',
            ],
        ],
        'actions:write' => [
            'de' => [
                'label' => 'Backend-Aktionen ausführen',
                'desc'  => 'Führt Backend-Aktionen mit den Rechten des genehmigenden Administrators '
                    . 'aus und macht sie wieder rückgängig. Im Modus „Eingeschränkt“ sind '
                    . 'destruktive Aktionen gesperrt.',
            ],
            'en' => [
                'label' => 'Run backend actions',
                'desc'  => 'Runs backend actions with the approving administrator\'s rights, and '
                    . 'undoes them again. In “limited” mode destructive actions are blocked.',
            ],
        ],
        'ai:read' => [
            'de' => [
                'label' => 'KI-Einrichtung ansehen',
                'desc'  => 'Sieht, welcher KI-Anbieter und welches Modell im Shop eingerichtet '
                    . 'sind, testet die Verbindung und schätzt Kosten. API-Schlüssel werden nie '
                    . 'ausgegeben.',
            ],
            'en' => [
                'label' => 'View AI setup',
                'desc'  => 'Sees which AI provider and model the shop uses, tests the connection '
                    . 'and estimates cost. API keys are never revealed.',
            ],
        ],
        'ai:write' => [
            'de' => [
                'label' => 'KI-Einrichtung ändern',
                'desc'  => 'Setzt Anbieter, Modell, API-Adresse und Kostengrenzen der KI-Anbindung '
                    . 'und erstellt Einrichtungslinks — liest oder überträgt den API-Schlüssel nie.',
            ],
            'en' => [
                'label' => 'Change AI setup',
                'desc'  => 'Sets the AI connection\'s provider, model, API address and cost limits '
                    . 'and creates setup links — can never read or transmit the API key.',
            ],
        ],
        'snippets:read' => [
            'de' => [
                'label' => 'Bausteine ansehen',
                'desc'  => 'Liest die gespeicherten Automatisierungs-Bausteine (Snippets) und prüft '
                    . 'einen davon auf Fehler.',
            ],
            'en' => [
                'label' => 'View building blocks',
                'desc'  => 'Reads the stored automation building blocks (snippets) and checks one '
                    . 'for errors.',
            ],
        ],
        'snippets:write' => [
            'de' => [
                'label' => 'Bausteine verwalten',
                'desc'  => 'Legt Automatisierungs-Bausteine an, ändert und löscht sie. Sie laufen '
                    . 'nur in einer abgeschotteten Sandbox.',
            ],
            'en' => [
                'label' => 'Manage building blocks',
                'desc'  => 'Creates, changes and deletes automation building blocks. They only '
                    . 'ever run inside a locked-down sandbox.',
            ],
        ],
        'workflows:read' => [
            'de' => [
                'label' => 'Automationen ansehen',
                'desc'  => 'Liest die eingerichteten Automationen, ihre Zeitpläne und was sie tun.',
            ],
            'en' => [
                'label' => 'View automations',
                'desc'  => 'Reads the automations that exist, their schedules and what they do.',
            ],
        ],
        'workflows:write' => [
            'de' => [
                'label' => 'Automationen verwalten',
                'desc'  => 'Legt Automationen an, ändert, plant und löscht sie.',
            ],
            'en' => [
                'label' => 'Manage automations',
                'desc'  => 'Creates, changes, schedules and deletes automations.',
            ],
        ],
    ];

    /**
     * Pick 'de' or 'en'. Preference order: an authenticated admin's own backend language (most
     * reliable — mirrors how the shop core itself picks admin locale, see
     * includes/src/Smarty/BackendSmarty.php:70 and includes/src/Plugin/PluginLoader.php:35, both
     * `$_SESSION['AdminAccount']->language ?? $getText->getLanguage()`), then the browser's
     * Accept-Language header (used before any session exists, e.g. the login-prompt screen), then
     * the shop's own default admin locale ('de' — admin/locale ships de-DE alongside en-GB and the
     * backend login screen defaults to German when nothing else is known).
     */
    public static function resolve(?string $adminSessionLanguageTag, ?string $acceptLanguageHeader): string
    {
        if ($adminSessionLanguageTag !== null && self::tagIsEnglish($adminSessionLanguageTag)) {
            return 'en';
        }
        if ($adminSessionLanguageTag !== null) {
            return 'de';
        }

        if ($acceptLanguageHeader !== null) {
            foreach (\explode(',', $acceptLanguageHeader) as $part) {
                $tag = \strtolower(\trim(\explode(';', $part)[0]));
                if ($tag === '') {
                    continue;
                }
                if (\str_starts_with($tag, 'en')) {
                    return 'en';
                }
                if (\str_starts_with($tag, 'de')) {
                    return 'de';
                }
            }
        }

        return 'de';
    }

    public static function t(string $lang, string $key): string
    {
        return self::TEXT[$lang][$key] ?? self::TEXT['de'][$key] ?? $key;
    }

    public static function scopeLabel(string $lang, string $scope): string
    {
        $known = self::scopeEntry($lang, $scope);
        if ($known !== null) {
            return $known['label'];
        }

        // Generic fallback for any scope not in the dictionary yet, e.g. "orders:read" ->
        // "orders read" -> "Orders read". Never crashes, never renders blank.
        $pretty = \str_replace([':', '_', '-'], ' ', $scope);
        $pretty = \trim(\preg_replace('/\s+/', ' ', $pretty) ?? $pretty);

        return $pretty === '' ? $scope : \ucfirst($pretty);
    }

    /**
     * The sentence shown when the merchant hovers (or focuses) a permission badge. Empty for a
     * scope this class does not know yet — the badge then simply has no tooltip, which is honest;
     * inventing a description for an unknown scope would be worse than showing none.
     */
    public static function scopeDescription(string $lang, string $scope): string
    {
        return self::scopeEntry($lang, $scope)['desc'] ?? '';
    }

    /** True for the scopes that can change something, which the badge marks visually. */
    public static function scopeIsWrite(string $scope): bool
    {
        return \str_ends_with($scope, ':write');
    }

    /** @return array{label: string, desc: string}|null */
    private static function scopeEntry(string $lang, string $scope): ?array
    {
        return self::SCOPES[$scope][$lang] ?? self::SCOPES[$scope]['de'] ?? null;
    }

    private static function tagIsEnglish(string $tag): bool
    {
        return \str_starts_with(\strtolower($tag), 'en');
    }
}
