<?php

declare(strict_types=1);

namespace PpPhp\Core\Designer\Defaults;

/**
 * Default design for the **Menu** common page — the generated app's landing page.
 *
 * This is the single source of truth for that default. The build renders it when
 * the project has not designed a Menu page, and the Designer seeds the canvas
 * from the very same tree, so "what you see is what ships" holds by construction
 * rather than by two implementations agreeing.
 *
 * Everything here is an ordinary component tree: once seeded, a developer can
 * drag, delete and re-arrange any of it from the Components panel. Nothing about
 * the landing page is special-cased in the renderer.
 *
 * Only component types `ComponentRenderer::handles()` can render are used, so
 * the tree is guaranteed to survive the trip into the generated app.
 */
final class MenuPageDefault
{
    /** Modules shown as cards before the rest collapse into the "all modules" row. */
    private const MAX_CARDS = 8;
    /** KPI tiles across the top. */
    private const MAX_KPIS = 4;

    /**
     * Build the default component tree.
     *
     * @param list<string> $tables  Table names the app exposes, in menu order.
     * @param array{appName?: string, auth?: bool, brandImage?: string} $opts
     * @return list<array<string, mixed>>
     */
    public static function components(array $tables, array $opts = []): array
    {
        // Accept either a plain list of names or the controller's
        // [['name' => …], …] shape, so callers do not each have to normalise.
        $tables = array_values(array_filter(array_map(
            static fn ($t) => is_array($t) ? (string) ($t['name'] ?? '') : (is_string($t) ? $t : ''),
            $tables
        ), static fn (string $t): bool => $t !== ''));
        $appName = trim((string) ($opts['appName'] ?? '')) ?: 'your workspace';
        $auth    = ($opts['auth'] ?? true) !== false;
        // The hero clock shows a brand medallion inside the dial. Use the app's
        // own logo when it has one, else the PPPHP monogram.
        $brandImage = trim((string) ($opts['brandImage'] ?? '')) ?: 'https://ppradhan.com/logo.svg';

        $out = [];

        // ── 1. Welcome hero ──────────────────────────────────────────────────
        // `greeting` is rendered by BOTH engines: PHP computes the time of day
        // and the signed-in user at request time, the Designer registry renders
        // the same markup on the canvas.
        $out[] = self::node('greeting', 'menu_hero', [
            'title'    => '',
            'subtitle' => $tables === []
                ? 'Your workspace is ready. Add a table to get started.'
                : 'Here is what is happening in ' . $appName . ' today.',
            'showName' => $auth,
            'showDate' => true,
            // The avatar replaces a separate shortcuts row — profile and
            // password live where the user's identity already is.
            'showUser' => $auth,
            // Hero clock — the shipped branded default: a frosted, transparent
            // dial with a radial face and the brand logo medallion (borderless)
            // at its centre. Baked in here so "Reset to Default" restores exactly
            // this look rather than a bare clock.
            'art'             => 'clock',
            'clkCenterType'   => 'image',
            'clkImage'        => $brandImage,
            'clkCenterBorder' => false,
            'clkCenterSize'   => 26,
            'clkTransparent'  => true,
            'clkFaceStyle'    => 'radial',
            'clkText'         => 'PP',
        ]);

        // ── 2. KPI strip — live record counts ────────────────────────────────
        if ($tables !== []) {
            $kpis = [];
            foreach (array_slice($tables, 0, self::MAX_KPIS) as $i => $t) {
                $kpis[] = self::node('kpi-stat', 'menu_kpi_' . $t, [
                    'label' => self::label($t),
                    'icon'  => self::icon($t, $i),
                    'table' => $t,
                    'agg'   => 'count',
                ]);
            }
            $out[] = self::node('kpi_grid', 'menu_kpis', [], $kpis);
        }

        // ── 3. Module cards — the actual "menu" ──────────────────────────────
        if ($tables !== []) {
            $cards = [];
            foreach (array_slice($tables, 0, self::MAX_CARDS) as $i => $t) {
                $label = self::label($t);
                // Purpose-built menu card: styleable header / description / footer.
                // Turn description + footer off (per card) and it becomes a single
                // click-through tile to the table's list page.
                $cards[] = self::node('menu-card', 'menu_card_' . $t, [
                    'table'           => $t,
                    'heading'         => $label,
                    'icon'            => self::icon($t, $i),
                    'showDescription' => true,
                    'description'     => 'Browse, search and manage ' . strtolower($label) . '.',
                    'showFooter'      => true,
                ]);
            }
            $out[] = self::node('heading', 'menu_modules_h', ['text' => 'Modules', 'level' => 'h2']);
            $out[] = self::node('kpi_grid', 'menu_modules', [], $cards);
        }

        // ── 4. Empty state ───────────────────────────────────────────────────
        if ($tables === []) {
            $out[] = self::node('alert', 'menu_empty', [
                'variant' => 'info',
                'text'    => 'No modules yet. Add tables and pages in the builder, then rebuild — they will appear here automatically.',
            ]);
        }

        return $out;
    }

    /**
     * The Designer stores common pages as a page object, not a bare list.
     *
     * @param list<string> $tables
     * @param array{appName?: string, auth?: bool, brandImage?: string} $opts
     * @return array<string, mixed>
     */
    public static function page(array $tables, array $opts = []): array
    {
        return [
            'id'         => 'common_menu',
            'name'       => 'menu',
            'type'       => 'menu',
            'scope'      => 'common',
            'title'      => 'Menu',
            'pristine'   => true,
            'components' => self::components($tables, $opts),
        ];
    }

    /** @param list<array<string,mixed>> $children */
    private static function node(string $type, string $id, array $props = [], array $children = []): array
    {
        $n = ['id' => $id, 'type' => $type, 'props' => $props];
        if ($children !== []) $n['children'] = $children;
        return $n;
    }

    /**
     * A stable, varied icon per module. Name-matched first (an "invoice" table
     * should look like invoices), otherwise cycled so a row of cards never
     * renders as the same glyph repeated.
     */
    private static function icon(string $table, int $i): string
    {
        $t = strtolower($table);
        $byName = [
            'user' => 'bi-people', 'customer' => 'bi-person-badge', 'client' => 'bi-person-badge',
            'order' => 'bi-bag-check', 'invoice' => 'bi-receipt', 'payment' => 'bi-credit-card',
            'product' => 'bi-box-seam', 'item' => 'bi-box-seam', 'stock' => 'bi-boxes',
            'task' => 'bi-check2-square', 'project' => 'bi-kanban', 'ticket' => 'bi-life-preserver',
            'event' => 'bi-calendar-event', 'log' => 'bi-journal-text', 'report' => 'bi-graph-up',
            'setting' => 'bi-sliders', 'category' => 'bi-tags', 'detail' => 'bi-card-list',
        ];
        foreach ($byName as $needle => $ico) {
            if (str_contains($t, $needle)) return $ico;
        }
        $cycle = ['bi-collection', 'bi-grid-1x2', 'bi-layers', 'bi-diagram-3', 'bi-folder2-open', 'bi-database'];
        return $cycle[$i % count($cycle)];
    }

    private static function label(string $table): string
    {
        return ucwords(str_replace(['-', '_'], ' ', $table));
    }
}
