<?php

declare(strict_types=1);

namespace App\Services;

use App\Filament\Support\DberpShortcutItem;
use App\Filament\Support\DberpShortcutRegistry;
use App\Models\DberpAdminMenuShortcut;
use App\Models\User;
use Filament\Navigation\NavigationItem;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\ValidationException;

/**
 * 侧栏「常用功能」用户固定项：仅存 shortcut_key，展示与权限依赖 {@see DberpShortcutRegistry}；无固定项时侧栏不展示业务入口。
 */
final class DberpAdminMenuShortcutService
{
    public function __construct(
        private readonly DberpShortcutRegistry $registry,
    ) {}

    public function shortcutsTableExists(): bool
    {
        return once(fn (): bool => Schema::hasTable('dberp_admin_menu_shortcuts'));
    }

    /**
     * 是否存在任意一条用户固定记录（有记录即视为「已配置常用功能」）。
     */
    public function usesCustomShortcuts(?Authenticatable $user): bool
    {
        return $this->hasPinnedShortcuts($user);
    }

    public function hasPinnedShortcuts(?Authenticatable $user): bool
    {
        if (! $this->shortcutsTableExists() || ! $user instanceof User) {
            return false;
        }

        return DberpAdminMenuShortcut::query()->where('admin_id', $user->getKey())->exists();
    }

    public function isPinned(User $user, string $shortcutKey): bool
    {
        if (! $this->shortcutsTableExists()) {
            return false;
        }

        return DberpAdminMenuShortcut::query()
            ->where('admin_id', $user->getKey())
            ->where('shortcut_key', $shortcutKey)
            ->exists();
    }

    /**
     * 侧栏单项是否可见：仅已固定项可能展示；无权限时隐藏但保留库记录。
     */
    public function isSidebarShortcutVisible(DberpShortcutItem $shortcut, ?Authenticatable $user): bool
    {
        if (! $user instanceof User) {
            return false;
        }

        if (! $this->isPinned($user, $shortcut->key)) {
            return false;
        }

        return $shortcut->isAccessible($user);
    }

    public function resolvePinnedDisplayLabel(DberpAdminMenuShortcut $row): string
    {
        $definition = $this->registry->definitionByKey($row->shortcut_key);
        if ($definition === null || ! $definition->hasPlacement(DberpShortcutRegistry::PLACEMENT_SIDEBAR_PINNABLE)) {
            return (string) __('dberp.shortcuts.preferences.unknown_shortcut_generic');
        }

        return $definition->localizedLabel();
    }

    /**
     * @return list<DberpAdminMenuShortcut>
     */
    public function pinnedRecordsOrdered(User $user): array
    {
        if (! $this->shortcutsTableExists()) {
            return [];
        }

        return DberpAdminMenuShortcut::query()
            ->where('admin_id', $user->getKey())
            ->orderBy('sort')
            ->orderBy('id')
            ->get()
            ->all();
    }

    /**
     * 侧栏「常用功能」下展示的已固定入口（须在 Filament 认证通过后注册，{@see RegisterDberpPinnedShortcutNavigationItems}）。
     * 仅包含注册表中仍存在且当前用户可访问的项；无效 key 或无权限的固定记录不落侧栏（库中记录保留，常用功能设置页仍可管理）。
     *
     * @return list<NavigationItem>
     */
    public function pinnedShortcutNavigationItems(User $user): array
    {
        if (! $this->shortcutsTableExists()) {
            return [];
        }

        $registry = $this->registry;
        $items = [];
        foreach ($this->pinnedRecordsOrdered($user) as $row) {
            $definition = $registry->definitionByKey($row->shortcut_key);
            if ($definition === null || ! $definition->isAccessible($user)) {
                continue;
            }

            $items[] = NavigationItem::make(fn (): string => $definition->localizedLabel())
                ->group(fn (): string => __('dberp.nav.common_functions'))
                ->url(fn (): string => $definition->resolveUrl())
                ->sort((int) $row->sort);
        }

        return $items;
    }

    /**
     * 当前用户可固定、且尚未固定的候选项（含 Filament 导航发现项，已按权限过滤）。
     *
     * @return list<DberpShortcutItem>
     */
    public function pinEligibleShortcuts(User $user): array
    {
        $eligible = [];
        foreach ($this->registry->forPlacement(DberpShortcutRegistry::PLACEMENT_SIDEBAR_PINNABLE, $user) as $item) {
            if ($this->isPinned($user, $item->key)) {
                continue;
            }
            $eligible[] = $item;
        }

        return $eligible;
    }

    public function pin(User $user, string $shortcutKey): void
    {
        if (! $this->shortcutsTableExists()) {
            throw ValidationException::withMessages([
                'shortcut_key' => __('dberp.shortcuts.preferences.table_not_ready'),
            ]);
        }

        $definition = $this->registry->definitionByKey($shortcutKey);
        if ($definition === null || ! $definition->hasPlacement(DberpShortcutRegistry::PLACEMENT_SIDEBAR_PINNABLE)) {
            throw ValidationException::withMessages([
                'shortcut_key' => __('dberp.shortcuts.preferences.invalid_key'),
            ]);
        }

        if (! $definition->isAccessible($user)) {
            throw ValidationException::withMessages([
                'shortcut_key' => __('dberp.shortcuts.preferences.no_permission_to_pin'),
            ]);
        }

        if ($this->isPinned($user, $shortcutKey)) {
            return;
        }

        $maxSort = (int) DberpAdminMenuShortcut::query()->where('admin_id', $user->getKey())->max('sort');

        DberpAdminMenuShortcut::query()->create([
            'admin_id' => $user->getKey(),
            'shortcut_key' => $shortcutKey,
            'sort' => $maxSort > 0 ? $maxSort + 10 : 10,
        ]);
    }

    public function unpin(User $user, int $recordId): void
    {
        if (! $this->shortcutsTableExists()) {
            return;
        }

        DberpAdminMenuShortcut::query()
            ->where('admin_id', $user->getKey())
            ->whereKey($recordId)
            ->delete();

        $this->resequencePinnedSort($user);
    }

    public function moveUp(User $user, int $recordId): void
    {
        if (! $this->shortcutsTableExists()) {
            return;
        }

        $row = DberpAdminMenuShortcut::query()
            ->where('admin_id', $user->getKey())
            ->whereKey($recordId)
            ->first();

        if ($row === null) {
            return;
        }

        $prev = DberpAdminMenuShortcut::query()
            ->where('admin_id', $user->getKey())
            ->where('sort', '<', (int) $row->sort)
            ->orderByDesc('sort')
            ->orderByDesc('id')
            ->first();

        if ($prev === null) {
            return;
        }

        $this->swapSort($row, $prev);
    }

    public function moveDown(User $user, int $recordId): void
    {
        if (! $this->shortcutsTableExists()) {
            return;
        }

        $row = DberpAdminMenuShortcut::query()
            ->where('admin_id', $user->getKey())
            ->whereKey($recordId)
            ->first();

        if ($row === null) {
            return;
        }

        $next = DberpAdminMenuShortcut::query()
            ->where('admin_id', $user->getKey())
            ->where('sort', '>', (int) $row->sort)
            ->orderBy('sort')
            ->orderBy('id')
            ->first();

        if ($next === null) {
            return;
        }

        $this->swapSort($row, $next);
    }

    /**
     * 清空当前用户已固定的常用功能（侧栏对应区域将无业务入口）。
     */
    public function clearPinnedShortcuts(User $user): void
    {
        if (! $this->shortcutsTableExists()) {
            return;
        }

        DberpAdminMenuShortcut::query()->where('admin_id', $user->getKey())->delete();
    }

    /**
     * @deprecated 使用 {@see clearPinnedShortcuts}
     */
    public function resetToRegistryDefaults(User $user): void
    {
        $this->clearPinnedShortcuts($user);
    }

    private function swapSort(DberpAdminMenuShortcut $a, DberpAdminMenuShortcut $b): void
    {
        DB::transaction(function () use ($a, $b): void {
            $tmp = (int) $a->sort;
            $a->sort = (int) $b->sort;
            $b->sort = $tmp;
            $a->save();
            $b->save();
        });
    }

    private function resequencePinnedSort(User $user): void
    {
        $rows = DberpAdminMenuShortcut::query()
            ->where('admin_id', $user->getKey())
            ->orderBy('sort')
            ->orderBy('id')
            ->get();

        $position = 10;
        foreach ($rows as $row) {
            if ((int) $row->sort !== $position) {
                $row->sort = $position;
                $row->save();
            }
            $position += 10;
        }
    }
}
