<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\adminmenu\Controller;

/**
 * The admin UI's class vocabulary — one place, so every tab looks like the same product.
 *
 * The templates used to carry the shop admin's Bootstrap classes while the KI tab rendered the
 * plugin's own compiled Tailwind, which is exactly why the plugin read as two products glued
 * together (docs/UI_REVIEW.md, ruling 20). Every tab now composes from the tokens below.
 *
 * Two constraints these strings encode, both burned before:
 *   - never `bg-white`: the shop admin ships `.bg-white{background-color:#fff!important}`, which
 *     beats every `dark:` variant;
 *   - the forms plugin is built with `strategy: 'class'`, so an input without `form-input` /
 *     `form-select` is an invisible box (ledger U22).
 *
 * This file is scanned by adminmenu/tailwind.config.js, so a class named here exists at runtime
 * even when no template spells it out literally. `make css` after every change.
 */
final class Ui
{
    /** Surfaces. */
    public const CARD = 'mb-4 rounded-lg border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-800/60';

    public const CARD_HEAD = 'flex flex-wrap items-center justify-between gap-2 border-b border-gray-200'
        . ' px-4 py-2.5 text-sm font-semibold text-gray-900 dark:border-gray-700 dark:text-gray-100';

    public const CARD_BODY = 'p-4';

    public const CARD_FOOT = 'border-t border-gray-200 px-4 py-3 dark:border-gray-700';

    /** Tables. A table is always inside TABLE_WRAP so a wide row scrolls itself, not the page. */
    public const TABLE_WRAP = 'overflow-x-auto';

    /**
     * The log tables (request log, audit log) are capped at 200 rows, which is still several
     * screens of card — the tab below it becomes unreachable without scrolling past all of it. The
     * long lists scroll inside their own box instead; TH_STICKY keeps the column names visible
     * while they do, which is the only reason a 400px window is usable at all.
     */
    public const TABLE_SCROLL = 'max-h-[400px] overflow-y-auto';

    public const TH_STICKY = self::TH . ' sticky top-0 z-10 bg-gray-50 dark:bg-gray-800';

    public const TABLE = 'w-full border-collapse text-left text-sm';

    public const TH = 'whitespace-nowrap border-b border-gray-200 px-2 py-1.5 text-xs font-semibold'
        . ' uppercase tracking-wide text-gray-500 dark:border-gray-700 dark:text-gray-400';

    /** `align-top` is not cosmetic: a tall status cell next to centred ones reads as a broken row. */
    public const TD = 'border-b border-gray-100 px-2 py-1.5 align-top text-gray-700'
        . ' dark:border-gray-800 dark:text-gray-300';

    /** Text. */
    public const MUTED = 'text-sm text-gray-500 dark:text-gray-400';

    public const SMALL = 'text-xs text-gray-500 dark:text-gray-400';

    /**
     * Bare `<code>` inherits Bootstrap's `color:#e83e8c`, so a column of endpoints or token IDs
     * renders magenta and reads as "everything failed" (ledger U10). Red stays for real errors.
     */
    public const CODE = 'font-mono text-xs text-gray-700 dark:text-gray-300';

    /** Controls. */
    public const BTN = 'inline-flex items-center rounded-md border border-gray-400 px-2.5 py-1 text-xs'
        . ' font-medium text-gray-600 hover:bg-gray-500/10 dark:border-gray-500 dark:text-gray-300';

    public const BTN_DANGER = 'inline-flex items-center rounded-md border border-red-400 px-2.5 py-1 text-xs'
        . ' font-medium text-red-700 hover:bg-red-500/10 dark:border-red-500 dark:text-red-300';

    public const INPUT = 'form-input w-full rounded-md border-gray-300 bg-transparent px-3 py-2 text-sm'
        . ' text-gray-900 dark:border-gray-600 dark:text-gray-100';

    /** A status dot; combine with one of {@see self::DOT_TONES}. */
    public const DOT = 'inline-block h-2.5 w-2.5 shrink-0 rounded-full';

    /** `whitespace-nowrap`: a chip that breaks mid-label ("public (PKCE)") reads as two chips. */
    private const CHIP = 'inline-flex items-center whitespace-nowrap rounded px-1.5 py-0.5 text-xs font-medium';

    private const ALERT = 'mb-4 rounded-lg border px-4 py-3 text-sm';

    /**
     * Everything a template needs, under `$ui`. Tone keys are shared by chips and alerts so a view
     * model can name a tone (`success`) instead of a Bootstrap class (`badge-success`).
     *
     * @return array<string, string|array<string, string>>
     */
    public static function vars(): array
    {
        return [
            'card'      => self::CARD,
            'cardHead'  => self::CARD_HEAD,
            'cardBody'  => self::CARD_BODY,
            'cardFoot'  => self::CARD_FOOT,
            'tableWrap' => self::TABLE_WRAP,
            'tableScroll' => self::TABLE_SCROLL,
            'table'     => self::TABLE,
            'th'        => self::TH,
            'thSticky'  => self::TH_STICKY,
            'td'        => self::TD,
            'muted'     => self::MUTED,
            'small'     => self::SMALL,
            'code'      => self::CODE,
            'btn'       => self::BTN,
            'btnDanger' => self::BTN_DANGER,
            'input'     => self::INPUT,
            'dot'       => self::DOT,
            'chip'      => self::chips(),
            'alert'     => self::alerts(),
            'dotTone'   => self::dotTones(),
        ];
    }

    /**
     * @return array<string, string>
     */
    public static function chips(): array
    {
        return [
            // Scopes: white-on-pale-blue `badge-info` measured ~2.5:1 in light mode (ledger U9).
            'info'    => self::CHIP . ' bg-sky-500/10 text-sky-700 dark:bg-sky-400/10 dark:text-sky-300',
            'success' => self::CHIP
                . ' bg-emerald-500/10 text-emerald-700 dark:bg-emerald-400/10 dark:text-emerald-300',
            'warning' => self::CHIP . ' bg-amber-500/10 text-amber-700 dark:bg-amber-400/10 dark:text-amber-300',
            'danger'  => self::CHIP . ' bg-red-500/10 text-red-700 dark:bg-red-400/10 dark:text-red-300',
            'neutral' => self::CHIP . ' bg-gray-500/10 text-gray-700 dark:bg-gray-400/10 dark:text-gray-300',
            // Tool names, endpoints — a chip that stays monospace without turning into an error.
            'mono'    => self::CHIP
                . ' bg-gray-500/10 font-mono text-gray-700 dark:bg-gray-400/10 dark:text-gray-300',
        ];
    }

    /**
     * @return array<string, string>
     */
    public static function alerts(): array
    {
        return [
            'info'    => self::ALERT . ' border-sky-500/30 bg-sky-500/10 text-sky-800 dark:text-sky-300',
            'success' => self::ALERT
                . ' border-emerald-500/30 bg-emerald-500/10 text-emerald-800 dark:text-emerald-300',
            'warning' => self::ALERT . ' border-amber-500/30 bg-amber-500/10 text-amber-800 dark:text-amber-300',
            'danger'  => self::ALERT . ' border-red-500/30 bg-red-500/10 text-red-800 dark:text-red-300',
            'neutral' => self::ALERT . ' border-gray-300 bg-gray-500/5 text-gray-700'
                . ' dark:border-gray-600 dark:text-gray-300',
        ];
    }

    /**
     * @return array<string, string>
     */
    public static function dotTones(): array
    {
        return [
            'green'  => 'bg-emerald-500',
            'orange' => 'bg-amber-500',
            'red'    => 'bg-red-500',
            'gray'   => 'bg-gray-400',
        ];
    }
}
