<?php

declare(strict_types=1);

namespace Atdt;

/**
 * Menu data + generated rendering (SPEC §10.3).
 *
 * Menus and items are data (§3.2): loaded from the DB, ACS-filtered per
 * caller, with the DOORS menu injecting its numbered entries from the doors
 * registry at render time. The generated two-column layout is the fallback
 * when a theme provides neither art nor a pipe template for a menu.
 */
final class MenuEngine
{
    /**
     * Load a menu + its visible, ACS-passing items for this caller.
     * @return array{menu: array<string, mixed>, items: list<array<string, mixed>>}|null
     */
    public static function load(Db $db, ?array $user, string $name, ?Settings $settings = null): ?array
    {
        $menu = $db->one('SELECT * FROM menus WHERE name = ?', [$name]);
        if ($menu === null) {
            return null;
        }
        // Hidden items ride along for DISPATCH (their hotkeys must fire —
        // the easter-egg contract); render() is what leaves them unlisted.
        $items = $db->all(
            'SELECT * FROM menu_items WHERE menu_id = ? ORDER BY sort, id',
            [(int)$menu['id']]
        );
        // EMAILSYSOP items vanish entirely while outgoing mail is off
        // (1.5.0), the way Slaughter hides when PvP is off.
        $mailReady = $settings !== null && Mailer::ready($settings);
        $items = array_values(array_filter(
            $items,
            fn(array $i) => Acs::check((string)($i['acs'] ?? ''), $user)
                && ((string)($i['action'] ?? '') !== 'EMAILSYSOP' || $mailReady)
        ));
        // No menu is magic (1.1.0): DOORS used to synthesize its door
        // rows at render time, uneditable and invisible to the designer.
        // Doors are ordinary DOOR-action items now -- seeded, added by
        // the console's scan, and edited like anything else.
        return ['menu' => $menu, 'items' => $items];
    }

    /** Find the item a hotkey addresses (case-insensitive). */
    public static function match(array $items, string $key): ?array
    {
        $key = strtoupper($key);
        foreach ($items as $item) {
            if (strtoupper((string)$item['hotkey']) === $key) {
                return $item;
            }
        }
        return null;
    }

    /**
     * The auto-generated menu body (§10.3): boxed header, two item columns.
     * The caller appends the status/prompt band.
     */
    public static function render(Ctx $c, array $menu, array $items): string
    {
        $o = Ansi::make()->cls();
        $vars = $c->mciVars();

        $inner = 76;
        $left = 2;
        $bn = mb_strimwidth((string)$vars['BN'], 0, 40);
        $head = '═══[ ';
        $tail = ' ]';
        $bar = $head . $bn . $tail;
        $fill = max(0, $inner - mb_strlen($bar));
        $o->at(1, $left)->fg(3)->txt('╔' . $head)->fg(15)->txt($bn)->fg(3)
            ->txt($tail . str_repeat('═', $fill) . '╗');
        $title = mb_strimwidth((string)$menu['title'], 0, $inner - 2);
        $pad = intdiv($inner - mb_strlen($title), 2);
        $o->at(2, $left)->fg(3)->txt('║')->fg(14)
            ->txt(str_repeat(' ', $pad) . $title . str_repeat(' ', $inner - $pad - mb_strlen($title)))
            ->fg(3)->txt('║');
        $o->at(3, $left)->fg(3)->txt('╚' . str_repeat('═', $inner) . '╝')->reset();

        $items = array_values(array_filter(
            $items,
            fn(array $i) => (int)($i['hidden'] ?? 0) === 0
        ));
        $perCol = max(1, (int)ceil(count($items) / 2));
        $perCol = min($perCol, 15);
        foreach ($items as $i => $item) {
            if ($i >= $perCol * 2) {
                break; // §20 humility: a menu this deep needs a template
            }
            $row = 5 + ($i % $perCol);
            $col = $i < $perCol ? 7 : 43;
            $o->at($row, $col)
                ->fg(8)->txt('[')->fg(14)->txt(strtoupper((string)$item['hotkey']))->fg(8)->txt(']')
                ->fg(7)->txt(' ' . mb_strimwidth((string)$item['label'], 0, 30));
        }
        return $o->reset()->str();
    }

    /** The status + prompt band every menu mode shares (rows 21–23). */
    /** The rule/status/prompt band. A short art backdrop pulls it up
     *  under the picture (1.5.5); the generated layout keeps row 21. */
    public static function promptBand(Ctx $c, int $top = 21): string
    {
        $top = max(3, min(21, $top));
        return Ansi::make()
            ->at($top, 2)->fg(8)->txt(str_repeat('─', 78))
            ->at($top + 1, 1)->txt("\x1b[K")->txt($c->center($c->tstr('menu.status')))
            ->at($top + 2, 1)->txt("\x1b[K")->at($top + 2, 7)->txt($c->tstr('menu.prompt'))
            ->str();
    }
}
