<?php

namespace App\Services\Navigation;

use App\Models\NavItem;
use App\Models\Role;
use App\Models\RoleNavItem;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class NavBuilderService
{
    public function __construct(private NavbarService $navbarService)
    {
    }

    public function getAdminPayload(): array
    {
        $all = NavItem::query()
            ->orderBy('menu_parent_id', 'asc')
            ->orderBy('label', 'asc')
            ->orderBy('id', 'asc')
            ->get()
            ->toArray();

        $byId = [];
        foreach ($all as $row) {
            $row['children'] = [];
            $byId[$row['id']] = $row;
        }

        $tree = [];
        foreach ($byId as $id => &$node) {
            $pid = $node['menu_parent_id'] ?: null;
            if ($pid && isset($byId[$pid])) {
                $byId[$pid]['children'][] = &$node;
            } else {
                $tree[] = &$node;
            }
        }
        unset($node);

        $this->sortTreeAlpha($tree);

        $flatAlpha = $all;
        usort($flatAlpha, function ($a, $b) {
            $result = strnatcasecmp($this->labelKey($a['label'] ?? ''), $this->labelKey($b['label'] ?? ''));

            return $result !== 0 ? $result : ((int) ($a['id'] ?? 0) <=> (int) ($b['id'] ?? 0));
        });

        $roleAssignments = $this->buildRoleAssignments();
        $flattened = $this->flattenTreeForResponse($tree, $roleAssignments);

        $parentOptions = array_map(static function ($row) {
            return [
                'id' => (int) ($row['id'] ?? 0),
                'label' => (string) ($row['label'] ?? ''),
            ];
        }, $flatAlpha);

        $roles = Role::query()->orderBy('name')->get(['id', 'name'])->toArray();

        return [
            'items' => $flattened,
            'roles' => $roles,
            'parentOptions' => $parentOptions,
        ];
    }

    public function save(array $payload): array
    {
        $id = (int) ($payload['id'] ?? 0);
        $menuParentId = $payload['menu_parent_id'] ?? $payload['parent_id'] ?? null;
        $menuParentId = ($menuParentId === '' || $menuParentId === null) ? null : (int) $menuParentId;
        if ($id && $menuParentId === $id) {
            $menuParentId = null;
        }

        if ($menuParentId !== null && !NavItem::query()->whereKey($menuParentId)->exists()) {
            return ['ok' => false, 'message' => 'Selected parent does not exist.'];
        }

        $data = [
            'menu_parent_id' => $menuParentId,
            'label' => trim((string) ($payload['label'] ?? '')),
            'url' => trim((string) ($payload['url'] ?? '')) ?: null,
            'icon_class' => trim((string) ($payload['icon_class'] ?? '')) ?: null,
            'target' => trim((string) ($payload['target'] ?? '')) ?: null,
            'sort_order' => (int) ($payload['sort_order'] ?? 0),
            'is_enabled' => !empty($payload['is_enabled']) ? 1 : 0,
        ];

        $roleIds = array_values(array_unique(array_filter(
            array_map('intval', (array) ($payload['roles'] ?? [])),
            fn ($v) => $v > 0
        )));

        $navItemId = null;

        DB::transaction(function () use ($id, $data, $roleIds, &$navItemId): void {
            if ($id > 0) {
                NavItem::query()->whereKey($id)->update($data);
                $navItemId = $id;
            } else {
                $navItemId = (int) NavItem::query()->create($data)->id;
            }

            RoleNavItem::query()->where('nav_item_id', $navItemId)->delete();
            foreach ($roleIds as $rid) {
                RoleNavItem::query()->create([
                    'role_id' => $rid,
                    'nav_item_id' => $navItemId,
                ]);
            }

            $this->normalizeSortOrdersAlphabetically();
        });

        $this->navbarService->clearCache();

        return ['ok' => true, 'id' => $navItemId];
    }

    public function delete(int $id): bool
    {
        $deleted = false;

        DB::transaction(function () use ($id, &$deleted): void {
            $deleted = (bool) NavItem::query()->whereKey($id)->delete();

            if ($deleted) {
                $this->normalizeSortOrdersAlphabetically();
            }
        });

        $this->navbarService->clearCache();

        return $deleted;
    }

    public function reorder(array $orders): void
    {
        DB::transaction(function (): void {
            $this->normalizeSortOrdersAlphabetically();
        });

        $this->navbarService->clearCache();
    }

    private function normalizeSortOrdersAlphabetically(): void
    {
        $rows = NavItem::query()
            ->select(['id', 'menu_parent_id', 'label'])
            ->orderBy('menu_parent_id', 'asc')
            ->orderBy('label', 'asc')
            ->orderBy('id', 'asc')
            ->get()
            ->groupBy(fn ($row) => $row->menu_parent_id === null ? 'root' : (string) $row->menu_parent_id);

        foreach ($rows as $siblings) {
            $ordered = $siblings->values()->all();
            usort($ordered, function ($a, $b) {
                $result = strnatcasecmp($this->labelKey((string) ($a->label ?? '')), $this->labelKey((string) ($b->label ?? '')));

                return $result !== 0 ? $result : ((int) $a->id <=> (int) $b->id);
            });

            foreach ($ordered as $index => $item) {
                NavItem::query()
                    ->whereKey((int) $item->id)
                    ->update(['sort_order' => ($index + 1) * 10]);
            }
        }
    }

    private function buildRoleAssignments(): array
    {
        $rows = RoleNavItem::query()
            ->select('role_nav_items.nav_item_id', 'roles.id as role_id', 'roles.name as role_name')
            ->join('roles', 'roles.id', '=', 'role_nav_items.role_id')
            ->get()
            ->toArray();

        $roleAssignments = [];
        foreach ($rows as $row) {
            $navId = (int) ($row['nav_item_id'] ?? 0);
            if ($navId <= 0) {
                continue;
            }

            $roleId = (int) ($row['role_id'] ?? 0);
            $roleName = $row['role_name'] ?? ($roleId ? ('#' . $roleId) : null);

            if ($roleId > 0) {
                $roleAssignments[$navId]['ids'][] = $roleId;
            }
            if ($roleName !== null) {
                $roleAssignments[$navId]['names'][] = $roleName;
            }
        }

        return $roleAssignments;
    }

    private function flattenTreeForResponse(array $tree, array $roleAssignments, int $depth = 0): array
    {
        $output = [];
        foreach ($tree as $node) {
            $id = (int) ($node['id'] ?? 0);
            $roles = $roleAssignments[$id] ?? ['ids' => [], 'names' => []];

            $output[] = [
                'id' => $id,
                'label' => (string) ($node['label'] ?? ''),
                'url' => $node['url'] ?? null,
                'icon_class' => $node['icon_class'] ?? null,
                'target' => $node['target'] ?? null,
                'menu_parent_id' => $node['menu_parent_id'] ?? null,
                'sort_order' => (int) ($node['sort_order'] ?? 0),
                'is_enabled' => (int) ($node['is_enabled'] ?? 0),
                'depth' => $depth,
                'roles' => [
                    'ids' => array_values(array_unique($roles['ids'] ?? [])),
                    'names' => array_values(array_unique($roles['names'] ?? [])),
                ],
            ];

            if (!empty($node['children'])) {
                $output = array_merge($output, $this->flattenTreeForResponse($node['children'], $roleAssignments, $depth + 1));
            }
        }

        return $output;
    }

    private function labelKey(string $s): string
    {
        $s = trim(preg_replace('/\s+/', ' ', $s));
        $s = preg_replace('/^[^[:alnum:]]+/u', '', $s);
        $s = preg_replace('/^(?i)(the|an|a)\s+/', '', $s);
        return mb_strtolower($s, 'UTF-8');
    }

    private function sortTreeAlpha(array &$nodes): void
    {
        usort($nodes, function ($a, $b) {
            $result = strnatcasecmp($this->labelKey($a['label'] ?? ''), $this->labelKey($b['label'] ?? ''));

            return $result !== 0 ? $result : ((int) ($a['id'] ?? 0) <=> (int) ($b['id'] ?? 0));
        });
        foreach ($nodes as &$node) {
            if (!empty($node['children'])) {
                $this->sortTreeAlpha($node['children']);
            }
        }
        unset($node);
    }
}
