<?php

declare(strict_types=1);

namespace PpPhp\Core\Theme;

/**
 * MenuModel
 *
 * Master model for the project navigation menu. A menu is a tree of items;
 * each item is either a hyperlink (to an application page or an external URL)
 * or a submenu (a labelled group of children). This class normalizes/validates
 * the tree and can resolve an item to an href, keeping the menu independent of
 * the theme/navbar that renders it.
 */
final class MenuModel
{
    private const MAX_DEPTH = 3;

    /** Normalize a raw menu (from the client / storage) into a safe tree. */
    public static function normalize(array $menu): array
    {
        $items = is_array($menu['items'] ?? null) ? $menu['items'] : [];
        return [
            'items' => self::normalizeItems($items, 1),
            'spacing' => self::normalizeSpacing(is_array($menu['spacing'] ?? null) ? $menu['spacing'] : []),
        ];
    }

    /** @param array<int, mixed> $items */
    private static function normalizeItems(array $items, int $depth): array
    {
        $out = [];
        foreach ($items as $raw) {
            if (!is_array($raw)) {
                continue;
            }
            $rawType = $raw['type'] ?? 'hyperlink';
            $type = in_array($rawType, ['submenu', 'divider'], true) ? $rawType : 'hyperlink';
            $link = is_array($raw['link'] ?? null) ? $raw['link'] : [];
            $kind = in_array($link['kind'] ?? 'page', ['page', 'external', 'none'], true) ? $link['kind'] : 'page';

            $item = [
                'id' => (string) ($raw['id'] ?? self::uid()),
                'type' => $type,
                'label' => trim((string) ($raw['label'] ?? 'Item')) ?: 'Item',
                'icon' => trim((string) ($raw['icon'] ?? '')),
                'showIcon' => ($raw['showIcon'] ?? 'always') === 'collapsed' ? 'collapsed' : 'always',
                'link' => [
                    'kind' => $kind,
                    'page' => (string) ($link['page'] ?? ''),
                    'subpage' => (string) ($link['subpage'] ?? 'list'),
                    'url' => (string) ($link['url'] ?? ''),
                    'params' => (string) ($link['params'] ?? ''),
                    'target' => ($link['target'] ?? 'self') === 'blank' ? 'blank' : 'self',
                ],
                'css' => (string) ($raw['css'] ?? ''),
                'roles' => self::normalizeRoles($raw['roles'] ?? []),
                'dividerStyle' => in_array($raw['dividerStyle'] ?? 'line', ['line', 'dashed', 'dotted', 'gradient', 'label'], true) ? (string) $raw['dividerStyle'] : 'line',
                'children' => [],
            ];

            if ($depth < self::MAX_DEPTH && is_array($raw['children'] ?? null)) {
                $item['children'] = self::normalizeItems($raw['children'], $depth + 1);
            }
            $out[] = $item;
        }
        return $out;
    }

    /**
     * Normalize the per-item role visibility list. An empty list means the
     * item is visible to everyone; otherwise the item is shown only to users
     * whose role is in the list. Values are trimmed, de-duplicated, and capped.
     *
     * @param mixed $roles
     * @return array<int, string>
     */
    private static function normalizeRoles($roles): array
    {
        if (!is_array($roles)) {
            return [];
        }
        $out = [];
        foreach ($roles as $r) {
            $r = trim((string) $r);
            if ($r !== '' && !in_array($r, $out, true)) {
                $out[] = $r;
            }
            if (count($out) >= 24) {
                break;
            }
        }
        return $out;
    }

    /**
     * Normalize the sidebar spacing controls (item height / padding / gap /
     * divider gap), each clamped to a sane pixel range. Kept alongside the menu
     * so it round-trips through the Style save/load like the rest of the tree.
     *
     * @param array<string, mixed> $spacing
     * @return array{height:int, padX:int, gap:int, divGap:int}
     */
    private static function normalizeSpacing(array $spacing): array
    {
        $clamp = static fn ($v, int $lo, int $hi, int $def): int => is_numeric($v) ? max($lo, min($hi, (int) $v)) : $def;
        return [
            'height' => $clamp($spacing['height'] ?? null, 26, 64, 38),
            'padX' => $clamp($spacing['padX'] ?? null, 4, 28, 12),
            'gap' => $clamp($spacing['gap'] ?? null, 0, 16, 2),
            'divGap' => $clamp($spacing['divGap'] ?? null, 2, 28, 8),
        ];
    }

    /** Resolve an item's link to an href (best-effort, for preview/runtime). */
    public static function href(array $item, string $baseUrl = '', string $slug = ''): string
    {
        $link = $item['link'] ?? [];
        if (($link['kind'] ?? '') === 'external') {
            return (string) ($link['url'] ?? '#');
        }
        if (($link['kind'] ?? '') === 'page' && ($link['page'] ?? '') !== '') {
            $href = rtrim($baseUrl, '/') . '/' . ($slug !== '' ? $slug . '/' : '') . $link['page'] . '/' . ($link['subpage'] ?: 'list');
            if (!empty($link['params'])) {
                $href .= '?' . ltrim((string) $link['params'], '?&');
            }
            return $href;
        }
        return '#';
    }

    /** A sensible starter menu. */
    public static function defaultMenu(): array
    {
        $mk = static fn (string $label, string $icon, string $page): array => [
            'id' => self::uid(), 'type' => 'hyperlink', 'label' => $label, 'icon' => $icon,
            'showIcon' => 'always',
            'link' => ['kind' => 'page', 'page' => $page, 'subpage' => 'list', 'url' => '', 'params' => '', 'target' => 'self'],
            'css' => '', 'roles' => [], 'children' => [],
        ];
        return ['items' => [
            $mk('Dashboard', 'bi-speedometer2', ''),
            $mk('Customers', 'bi-people', 'customers'),
            $mk('Orders', 'bi-bag', 'orders'),
            $mk('Reports', 'bi-graph-up', 'reports'),
        ]];
    }

    private static function uid(): string
    {
        return 'mi_' . substr(bin2hex(random_bytes(4)), 0, 8);
    }
}
