<?php

declare(strict_types=1);

namespace Builder\Core\Foundation\Definition;

use Closure;

final class MenuItem
{
    private string|Closure $label;

    private ?string $icon = null;

    private ?string $route = null;

    /** @var ?Closure(): string */
    private ?Closure $routeResolver = null;

    private int $priority = 0;

    /** @var ?Closure(): (int|string|null) */
    private ?Closure $badge = null;

    /** @var list<self> */
    private array $children = [];

    private ?string $permission = null;

    private function __construct(private readonly string $key)
    {
        $this->label = $key;
    }

    public static function make(string $key): self
    {
        return new self($key);
    }

    public function label(string|Closure $label): self
    {
        $this->label = $label;

        return $this;
    }

    public function icon(string $icon): self
    {
        $this->icon = $icon;

        return $this;
    }

    public function route(string $route): self
    {
        $this->route = $route;
        $this->routeResolver = null;

        return $this;
    }

    /**
     * Set the route using a named route (resolved lazily so it works during service provider boot).
     */
    public function namedRoute(string $name, mixed ...$params): self
    {
        $this->routeResolver = fn (): string => route($name, ...$params);
        $this->route = null;

        return $this;
    }

    public function priority(int $priority): self
    {
        $this->priority = $priority;

        return $this;
    }

    /** @param Closure(): (int|string|null) $badge */
    public function badge(Closure $badge): self
    {
        $this->badge = $badge;

        return $this;
    }

    /** @param list<self> $children */
    public function children(array $children): self
    {
        $this->children = $children;

        return $this;
    }

    /**
     * Append a single child, idempotent on the child's key.
     *
     * A repeated call with the same key is a no-op rather than a duplicate —
     * this lets external modules (e.g. migrator) safely re-register their
     * navigation entry after a config refresh without populating the menu
     * twice.
     */
    public function appendChild(self $child): self
    {
        foreach ($this->children as $existing) {
            if ($existing->getKey() === $child->getKey()) {
                return $this;
            }
        }

        $this->children[] = $child;

        return $this;
    }

    public function permission(string $permission): self
    {
        $this->permission = $permission;

        return $this;
    }

    public function getKey(): string
    {
        return $this->key;
    }

    public function getLabel(): string
    {
        return $this->label instanceof Closure ? ($this->label)() : $this->label;
    }

    public function getIcon(): ?string
    {
        return $this->icon;
    }

    public function getRoute(): ?string
    {
        if ($this->routeResolver !== null) {
            return ($this->routeResolver)();
        }

        return $this->route;
    }

    public function getPriority(): int
    {
        return $this->priority;
    }

    public function getBadgeValue(): int|string|null
    {
        return $this->badge instanceof Closure ? ($this->badge)() : null;
    }

    /** @return list<self> */
    public function getChildren(): array
    {
        return $this->children;
    }

    public function getPermission(): ?string
    {
        return $this->permission;
    }

    /**
     * Returns a filtered version of this item's children based on the given permissions.
     *
     * @param  list<string>  $userPermissions
     * @return list<self>
     */
    public function filterChildren(array $userPermissions): array
    {
        return array_values(array_filter(
            $this->children,
            fn (self $child): bool => self::isAllowed($child, $userPermissions),
        ));
    }

    /** @param list<string> $userPermissions */
    private static function isAllowed(self $item, array $userPermissions): bool
    {
        $permission = $item->getPermission();

        if ($permission === null) {
            return true;
        }

        return in_array('*', $userPermissions, true) || in_array($permission, $userPermissions, true);
    }

    /**
     * Returns a copy of this item serialized with its children filtered by the given permissions.
     *
     * @param  list<string>  $userPermissions
     * @return array<string, mixed>
     */
    public function toArrayForUser(array $userPermissions): array
    {
        $children = array_filter(
            $this->children,
            fn (self $c): bool => self::isAllowed($c, $userPermissions),
        );

        return [
            'key' => $this->key,
            'label' => $this->getLabel(),
            'icon' => $this->icon,
            'route' => $this->getRoute(),
            'priority' => $this->priority,
            'badge' => $this->getBadgeValue(),
            'permission' => $this->permission,
            'children' => array_map(
                fn (self $c): array => $c->toArrayForUser($userPermissions),
                array_values($children),
            ),
        ];
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'key' => $this->key,
            'label' => $this->getLabel(),
            'icon' => $this->icon,
            'route' => $this->getRoute(),
            'priority' => $this->priority,
            'badge' => $this->getBadgeValue(),
            'permission' => $this->permission,
            'children' => array_map(fn (self $c): array => $c->toArray(), $this->children),
        ];
    }
}
