<?php

declare(strict_types=1);

namespace Maestro\Core\Services;

use Illuminate\Contracts\Auth\Authenticatable;
use Maestro\Core\Models\MaestroModule;
use Maestro\Core\Models\NavigationNode;

/**
 * Builds the SPA navigation JSON from DB rows (permission + disabled-module filtering).
 */
final class NavigationTreeBuilder
{
    /**
     * @return list<array<string, mixed>>
     */
    public function buildForUser(?Authenticatable $user): array
    {
        $disabledModuleIds = MaestroModule::query()
            ->where('is_enabled', false)
            ->pluck('id')
            ->all();

        $root = NavigationNode::query()
            ->whereNull('parent_id')
            ->where('key', 'root')
            ->with(['children' => fn ($q) => $q->orderBy('sort_order')])
            ->first();

        if ($root === null) {
            return [];
        }

        $items = [];
        foreach ($root->children as $child) {
            $node = $this->filterModelNode($child, $user, $disabledModuleIds);
            if ($node !== null) {
                $items[] = $node;
            }
        }

        $items = $this->stripEmptySectionHeaders($items);

        return [[
            'key' => 'root',
            'label' => $root->label,
            'items' => $items,
        ]];
    }

    /**
     * @param  list<int>  $disabledModuleIds
     * @return array<string, mixed>|null
     */
    private function filterModelNode(NavigationNode $node, ?Authenticatable $user, array $disabledModuleIds): ?array
    {
        if ($node->module_id !== null && in_array($node->module_id, $disabledModuleIds, true)) {
            return null;
        }

        $permission = $node->permission;
        if ($permission !== null && $permission !== '' && $user !== null && ! $user->can($permission)) {
            return null;
        }

        $children = $node->relationLoaded('children') ? $node->children : $node->children()->orderBy('sort_order')->get();
        $filteredChildren = [];
        foreach ($children as $child) {
            $n = $this->filterModelNode($child, $user, $disabledModuleIds);
            if ($n !== null) {
                $filteredChildren[] = $n;
            }
        }

        $isHeader = $node->is_header;
        $hadChildren = $children->isNotEmpty();
        $hasRoute = ! empty($node->route);

        if (! $isHeader && $hadChildren && $filteredChildren === []) {
            return null;
        }

        if (! $isHeader && ! $hasRoute && $filteredChildren === []) {
            return null;
        }

        return $this->toFrontendNode($node, $filteredChildren);
    }

    /**
     * @param  list<array<string, mixed>>  $filteredChildren
     * @return array<string, mixed>
     */
    private function toFrontendNode(NavigationNode $node, array $filteredChildren): array
    {
        $out = [
            'label' => $node->label,
            'items' => $filteredChildren,
        ];

        $out['key'] = $node->key;

        if ($node->icon !== null && $node->icon !== '') {
            $out['icon'] = $node->icon;
        }

        if ($node->route !== null && $node->route !== '') {
            $out['to'] = $node->route;
        }

        if ($node->is_header) {
            $out['header'] = true;
        }

        if ($node->pinned_bottom) {
            $out['pinnedBottom'] = true;
        }

        if ($node->description !== null && $node->description !== '') {
            $out['description'] = $node->description;
        }

        if ($node->permission !== null && $node->permission !== '') {
            $out['permission'] = $node->permission;
        }

        return $out;
    }

    /**
     * Drop section headers not followed by at least one visible item (TLP behaviour).
     *
     * @param  list<array<string, mixed>>  $items
     * @return list<array<string, mixed>>
     */
    private function stripEmptySectionHeaders(array $items): array
    {
        $newItems = [];
        $count = count($items);
        for ($i = 0; $i < $count; $i++) {
            $node = $items[$i];
            if (($node['header'] ?? false) !== true) {
                $newItems[] = $node;

                continue;
            }

            $hasItemAfter = false;
            for ($j = $i + 1; $j < $count; $j++) {
                if (($items[$j]['header'] ?? false) === true) {
                    break;
                }
                $hasItemAfter = true;

                break;
            }

            if ($hasItemAfter) {
                $newItems[] = $node;
            }
        }

        return $newItems;
    }
}
