<?php

namespace App\Controllers\View;

use App\Controllers\BaseController;
use App\Models\NavItemModel;
use App\Models\RoleNavItemModel;
use App\Services\NavbarService;

class NavBuilderController extends BaseController
{
    protected NavItemModel $items;
    protected RoleNavItemModel $maps;
    protected NavbarService $service;

    public function __construct()
    {
        $this->items   = new NavItemModel();
        $this->maps    = new RoleNavItemModel();
        $this->service = new NavbarService();
    }

protected function ensureAdmin(): void
{
    $sessionRole = session()->get('role');           // could be a string or array in your app
    $roleNames   = is_array($sessionRole) ? $sessionRole : [$sessionRole];
    $roleNames   = array_values(array_filter(array_map('strval', $roleNames)));

    if (empty($roleNames)) {
        throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
    }

    $db = \Config\Database::connect();

    // Map role names -> ids
    $roleIdRows = $db->table('roles')->select('id')->whereIn('name', $roleNames)->get()->getResultArray();
    $roleIds    = array_map('intval', array_column($roleIdRows, 'id'));
    if (empty($roleIds)) {
        throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
    }

    // Is this route allowed for any of the user's roles?
    $allowed = $db->table('role_nav_items AS rni')
        ->select('1')
        ->join('nav_items AS ni', 'ni.id = rni.nav_item_id')
        ->where('ni.url', 'nav-builder')     // IMPORTANT: your current route path
        ->whereIn('rni.role_id', $roleIds)
        ->get(1)->getFirstRow();

    if (!$allowed) {
        // You can show a nicer "Access Denied" view if you prefer
        throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
    }
}

    public function index(){
    $this->ensureAdmin();

    helper(['url', 'form']);

    return view('nav_builder/index');
}

/** Case-insensitive, natural sort; ignores leading punctuation & articles (“a/an/the”) */
private function labelKey(string $s): string
{
    $s = trim(preg_replace('/\s+/', ' ', $s));
    $s = preg_replace('/^[^[:alnum:]]+/u', '', $s);      // strip leading punctuation
    $s = preg_replace('/^(?i)(the|an|a)\s+/', '', $s);   // drop leading articles
    return mb_strtolower($s, 'UTF-8');
}

private function sortTreeAlpha(array &$nodes): void
{
    usort($nodes, fn($a,$b) => strnatcasecmp(
        $this->labelKey($a['label'] ?? ''), $this->labelKey($b['label'] ?? '')
    ));
    foreach ($nodes as &$n) {
        if (!empty($n['children'])) {
            $this->sortTreeAlpha($n['children']);
        }
    }
    unset($n);
}


public function save()
{
    $this->ensureAdmin();

    $id = (int) $this->request->getPost('id');

    // Accept either "menu_parent_id" or "parent_id" from the form
    $menuParentRaw = $this->request->getPost('menu_parent_id');
    if ($menuParentRaw === null) {
        $menuParentRaw = $this->request->getPost('parent_id');
    }

    $menuParentId = ($menuParentRaw === '' || $menuParentRaw === null) ? null : (int) $menuParentRaw;
    if ($id && $menuParentId === $id) {
        // item cannot be its own parent
        $menuParentId = null;
    }

    // Validate parent exists (optional but helpful)
    if ($menuParentId !== null) {
        $parent = $this->items->select('id')->where('id', $menuParentId)->first();
        if (!$parent) {
            return redirect()->back()->with('error', 'Selected parent does not exist.')->withInput();
        }
    }

    $data = [
        'menu_parent_id' => $menuParentId,
        'label'          => trim((string) $this->request->getPost('label')),
        'url'            => trim((string) $this->request->getPost('url')) ?: null,
        'icon_class'     => trim((string) $this->request->getPost('icon_class')) ?: null,
        'target'         => trim((string) $this->request->getPost('target')) ?: null,
        'sort_order'     => (int) $this->request->getPost('sort_order'),
        'is_enabled'     => (int) ($this->request->getPost('is_enabled') ? 1 : 0),
    ];

    // Save (force insert to return ID)
    if ($id) {
        $this->items->update($id, $data);
    } else {
        $id = (int) $this->items->insert($data, true); // ensure insertID is returned
    }

    // Roles
    $roleIds = array_values(array_unique(array_filter(
        array_map('intval', (array) ($this->request->getPost('roles') ?? [])),
        fn ($v) => $v > 0
    )));

    $this->maps->where('nav_item_id', $id)->delete();
    foreach ($roleIds as $rid) {
        $this->maps->insert(['role_id' => $rid, 'nav_item_id' => $id]);
    }

    $this->service->clearCache();
    return redirect()->back()->with('success', 'Menu saved.');
}


    public function delete($id)
    {
        $this->ensureAdmin();
        $id = (int) $id;

        $this->items->delete($id);          // children handled by FK (SET NULL on parent)
        $this->service->clearCache();

        return redirect()->back()->with('success', 'Menu item deleted.');
    }

    public function reorder()
    {
        $this->ensureAdmin();

        $orders = $this->request->getPost('orders') ?? [];
        foreach ($orders as $id => $order) {
            $this->items->update((int) $id, ['sort_order' => (int) $order]);
        }
        $this->service->clearCache();
        return $this->response->setJSON(['ok' => true]);
    }

    protected function distinctRoles(): array
    {
        $db = \Config\Database::connect();
        return $db->table('roles')
            ->select('id, name')
            ->orderBy('name')
            ->get()
            ->getResultArray();
    }

    public function data()
    {
        $this->ensureAdmin();
        return $this->response->setJSON($this->buildNavPayload());
    }

    private function buildNavPayload(): array
    {
        $all = $this->items
            ->orderBy('menu_parent_id', 'ASC')
            ->orderBy('label', 'ASC')
            ->orderBy('id', 'ASC')
            ->findAll();

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

        $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, fn($a, $b) => strnatcasecmp(
            $this->labelKey($a['label'] ?? ''),
            $this->labelKey($b['label'] ?? '')
        ));

        $roleAssignments = [];
        $roleRows = $this->maps
            ->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', 'left')
            ->findAll();

        foreach ($roleRows 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;
            }
        }

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

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

        $roles = array_map(static function ($role) {
            return [
                'id'   => (int) ($role['id'] ?? 0),
                'name' => (string) ($role['name'] ?? ''),
            ];
        }, $this->distinctRoles());

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

    private function flattenTreeForResponse(array $nodes, array $roleAssignments, ?string $parentLabel = null, int $depth = 0, array &$rows = []): array
    {
        foreach ($nodes as $node) {
            $raw = $node;
            unset($raw['children']);

            $navId = (int) ($raw['id'] ?? 0);
            $roles = $roleAssignments[$navId] ?? ['ids' => [], 'names' => []];

            $rows[] = [
                'id'            => $navId,
                'label'         => (string) ($raw['label'] ?? ''),
                'url'           => $raw['url'] ?? null,
                'parent_label'  => $parentLabel ?? '—',
                'parent_id'     => isset($raw['menu_parent_id']) && (int) $raw['menu_parent_id'] !== 0
                    ? (int) $raw['menu_parent_id']
                    : null,
                'order'         => (int) ($raw['sort_order'] ?? 0),
                'enabled'       => (int) ($raw['is_enabled'] ?? 0) === 1,
                'target'        => $raw['target'] ?? null,
                'depth'         => $depth,
                'roles'         => [
                    'ids'   => array_values(array_unique($roles['ids'] ?? [])),
                    'names' => array_values(array_unique($roles['names'] ?? [])),
                ],
                'raw'           => $raw,
            ];

            if (!empty($node['children'])) {
                $this->flattenTreeForResponse(
                    $node['children'],
                    $roleAssignments,
                    (string) ($raw['label'] ?? ''),
                    $depth + 1,
                    $rows
                );
            }
        }

        return $rows;
    }
}
