@php
    $projectStatusLabels = [
        'planned' => __('Planned'),
        'active' => __('Active'),
        'on_hold' => __('On hold'),
        'completed' => __('Completed'),
        'cancelled' => __('Cancelled'),
    ];

    $projectHealthLabels = [
        'normal' => __('Normal'),
        'warning' => __('Warning'),
        'risk' => __('Risk'),
    ];

    $memberRoleLabels = [
        'owner' => __('Owner'),
        'manager' => __('Manager'),
        'contributor' => __('Contributor'),
    ];

    $taskStatusLabels = [
        'todo' => __('To do'),
        'in_progress' => __('In progress'),
        'review' => __('Review'),
        'done' => __('Done'),
    ];
@endphp

<x-app-layout>
    <x-slot name="header">
        <div class="flex items-center justify-between gap-4">
            <div>
                <h2 class="font-semibold text-xl text-gray-800 leading-tight">{{ $project->name }}</h2>
                <p class="text-sm text-gray-500">{{ $project->code }} · {{ $project->company?->name ?? 'Without company' }}</p>
            </div>
            <div class="flex items-center gap-2">
                <a href="{{ route('tasks.create', ['project_id' => $project->id]) }}" class="inline-flex items-center rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">New task</a>
                <a href="{{ route('projects.edit', $project) }}" class="inline-flex items-center rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500">Edit</a>
            </div>
        </div>
    </x-slot>

    <div class="pb-12">
        <div class="w-full px-6 space-y-5">
            <section class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
                <div class="bg-white border border-gray-200 rounded-xl p-4">
                    <p class="text-xs text-gray-500 uppercase">Project status</p>
                    <p class="mt-2 text-lg font-semibold text-gray-900">{{ $projectStatusLabels[$project->status] ?? $project->status }}</p>
                    <p class="mt-1 text-xs text-gray-500">State: {{ $projectHealthLabels[$project->health] ?? $project->health }}</p>
                </div>
                <div class="bg-white border border-gray-200 rounded-xl p-4">
                    <p class="text-xs text-gray-500 uppercase">Progress</p>
                    <p class="mt-2 text-lg font-semibold text-gray-900">{{ $project->progress }}%</p>
                    <div class="mt-2 h-2 rounded-full bg-gray-100">
                        <div class="h-2 rounded-full bg-indigo-500" style="width: {{ $project->progress }}%;"></div>
                    </div>
                </div>
                <div class="bg-white border border-gray-200 rounded-xl p-4">
                    <p class="text-xs text-gray-500 uppercase">Budget / Actual</p>
                    <p class="mt-2 text-lg font-semibold text-gray-900">${{ number_format((float) $project->budget, 0, '.', ' ') }}</p>
                    <p class="mt-1 text-xs text-gray-500">${{ number_format((float) $project->spent, 0, '.', ' ') }}</p>
                </div>
                <div class="bg-white border border-gray-200 rounded-xl p-4">
                    <p class="text-xs text-gray-500 uppercase">Team</p>
                    <p class="mt-2 text-lg font-semibold text-gray-900">{{ $project->members->count() }}</p>
                    <p class="mt-1 text-xs text-gray-500">{{ $project->manager?->name ?? 'Without a leader' }}</p>
                </div>
            </section>

            <section class="bg-white border border-gray-200 rounded-xl p-4">
                <div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
                    <div>
                        <p class="text-gray-500">Owner</p>
                        <p class="font-medium text-gray-900">{{ $project->owner?->name ?? '—' }}</p>
                    </div>
                    <div>
                        <p class="text-gray-500">Supervisor</p>
                        <p class="font-medium text-gray-900">{{ $project->manager?->name ?? '—' }}</p>
                    </div>
                    <div>
                        <p class="text-gray-500">Deadlines</p>
                        <p class="font-medium text-gray-900">{{ $project->starts_at?->format('d.m.Y') ?? '—' }} → {{ $project->due_at?->format('d.m.Y') ?? '—' }}</p>
                    </div>
                </div>

                @if ($project->description)
                    <div class="mt-4 text-sm">
                        <p class="text-gray-500">Description</p>
                        <p class="mt-1 whitespace-pre-line text-gray-900">{{ $project->description }}</p>
                    </div>
                @endif

                @if ($project->notes)
                    <div class="mt-3 text-sm">
                        <p class="text-gray-500">Notes</p>
                        <p class="mt-1 whitespace-pre-line text-gray-900">{{ $project->notes }}</p>
                    </div>
                @endif

                @if ($project->members->isNotEmpty())
                    <div class="mt-4">
                        <p class="text-sm text-gray-500 mb-2">Participants</p>
                        <div class="flex flex-wrap gap-2">
                            @foreach ($project->members as $member)
                                <span class="inline-flex rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-700">{{ $member->name }} · {{ $memberRoleLabels[$member->pivot->role] ?? $member->pivot->role }}</span>
                            @endforeach
                        </div>
                    </div>
                @endif
            </section>

            <section
                id="project-board"
                data-project-id="{{ $project->id }}"
                data-csrf="{{ csrf_token() }}"
                data-stage-update-url="{{ route('projects.update-stages', $project) }}"
                data-stage-create-url="{{ route('projects.stages.store', $project) }}"
                data-stage-delete-url-template="{{ route('projects.stages.destroy', [$project, '__STAGE__']) }}"
                data-can-manage-stages="{{ ($canManageTasks ?? false) ? '1' : '0' }}"
            >
                <div class="flex items-center justify-between gap-3 mb-3">
                    <h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Project Kanban</h3>
                    <div class="flex items-center gap-2">
                        @if ($canManageTasks ?? false)
                            <input
                                type="text"
                                class="h-9 w-44 rounded-md border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
                                placeholder="New stage"
                                data-new-stage-name
                            >
                            <input
                                type="color"
                                class="h-9 w-10 rounded-md border border-gray-300 bg-white px-1 py-1"
                                value="#6366F1"
                                data-new-stage-color
                                title="Color"
                            >
                            <button
                                type="button"
                                class="inline-flex h-9 items-center rounded-md border border-indigo-200 bg-indigo-50 px-3 text-sm font-medium text-indigo-700 hover:bg-indigo-100"
                                data-add-stage
                            >
                                <x-menu-icon name="fa-solid fa-plus" class="h-3.5 w-3.5 mr-1.5" />
                                Add stage
                            </button>
                        @endif
                        <p class="text-xs text-gray-500">Drag and drop + real-time updates without rebooting</p>
                    </div>
                </div>

                <div class="relative" data-board-scroll-wrap>
                    <div class="overflow-x-auto pb-2" data-board-scroll>
                        <div class="flex min-w-max items-stretch gap-4" data-board-grid>
                            <div class="w-[320px] shrink-0 self-start h-[calc(100vh-24rem)] min-h-[28rem] bg-white border border-gray-200 rounded-xl p-3 flex flex-col" data-stage-column="unassigned" data-stage-static="1">
                                <div class="mb-3">
                                    <p class="font-semibold text-gray-900">No stage</p>
                                    <p class="text-xs text-gray-500"><span data-stage-count>{{ $unassignedTasks->count() }}</span> tasks · <span data-stage-hours>{{ number_format((float) $unassignedTasks->sum('tracked_hours'), 1, '.', ' ') }}</span>h</p>
                                </div>

                                <div class="space-y-2 flex-1 min-h-0 overflow-y-auto pr-1" data-stage-list>
                                    @forelse ($unassignedTasks as $task)
                                        <article class="h-auto rounded-lg border border-gray-100 p-3 bg-gray-50 cursor-move {{ $task->status === 'done' ? 'opacity-70' : '' }}" draggable="true" data-task-card data-task-id="{{ $task->id }}" data-stage-id="" data-hours="{{ (float) $task->tracked_hours }}" data-update-url="{{ route('projects.tasks.update-stage', [$project, $task]) }}">
                                            <div class="flex items-start justify-between gap-2">
                                                <a href="{{ route('tasks.show', $task) }}" class="text-sm font-medium text-gray-900 hover:text-indigo-600" data-task-title>{{ $task->title }}</a>
                                                <span class="inline-flex rounded-full px-2 py-0.5 text-[11px] font-medium bg-gray-200 text-gray-700" data-task-status>{{ $taskStatusLabels[$task->status] ?? $task->status }}</span>
                                            </div>
                                            <p class="mt-1 text-xs text-gray-500" data-assignee-name>{{ $task->assignee?->name ?? 'Without performer' }}</p>
                                            <p class="text-xs text-gray-500" data-due-date>{{ $task->due_at?->format('d.m H:i') ?? 'No deadline' }}</p>

                                            <div class="mt-2 flex items-center justify-between text-xs text-gray-500">
                                                <span>Fact: <span data-tracked-hours>{{ number_format((float) $task->tracked_hours, 1, '.', ' ') }}</span>h</span>
                                                <a href="{{ route('tasks.show', $task) }}" data-edit-link class="text-indigo-600 hover:text-indigo-500">Open</a>
                                            </div>

                                            <form method="POST" action="{{ route('projects.tasks.update-stage', [$project, $task]) }}" class="mt-2" data-stage-form>
                                                @csrf
                                                @method('PATCH')
                                                <select name="project_stage_id" class="w-full rounded-md border-gray-300 text-xs shadow-sm focus:border-indigo-500 focus:ring-indigo-500" data-stage-select>
                                                    <option value="" @selected(! $task->project_stage_id)>No stage</option>
                                                    @foreach ($project->stages as $stageOption)
                                                        <option value="{{ $stageOption->id }}" @selected($task->project_stage_id === $stageOption->id)>{{ $stageOption->name }}</option>
                                                    @endforeach
                                                </select>
                                            </form>
                                        </article>
                                    @empty
                                        <p class="text-xs text-gray-500" data-empty>No tasks</p>
                                    @endforelse
                                </div>
                            </div>

                            @foreach ($board as $column)
                                @php
                                    $stageColor = preg_match('/^#[0-9A-Fa-f]{6}$/', (string) $column['stage']->color)
                                        ? strtoupper((string) $column['stage']->color)
                                        : '#94A3B8';
                                @endphp
                                <div
                                    class="w-[320px] shrink-0 self-start h-[calc(100vh-24rem)] min-h-[28rem] rounded-xl border border-gray-200 border-t-4 bg-white p-3 flex flex-col"
                                    data-stage-column="{{ $column['stage']->id }}"
                                    data-stage-column-id="{{ $column['stage']->id }}"
                                    data-stage-color="{{ $stageColor }}"
                                    style="border-top-color: {{ $stageColor }}; background-image: linear-gradient(to bottom, {{ $stageColor }}1F, #FFFFFF 42%);"
                                >
                                    <div class="mb-3 flex items-start justify-between gap-2">
                                        <div class="min-w-0">
                                            <p class="truncate font-semibold text-gray-900" data-stage-label-text>{{ $column['stage']->name }}</p>
                                            <p class="text-xs text-gray-500"><span data-stage-count>{{ $column['tasks']->count() }}</span> tasks · <span data-stage-hours>{{ number_format((float) $column['hours'], 1, '.', ' ') }}</span>h</p>
                                        </div>
                                        @if ($canManageTasks ?? false)
                                            <div class="flex items-center gap-1">
                                                <label class="inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border border-gray-200 text-gray-500 hover:bg-gray-50" title="Stage color">
                                                    <x-menu-icon name="fa-solid fa-palette" class="h-3.5 w-3.5" />
                                                    <input type="color" value="{{ $stageColor }}" class="sr-only" data-stage-color-picker>
                                                </label>
                                                <button
                                                    type="button"
                                                    class="inline-flex h-7 w-7 cursor-grab items-center justify-center rounded-md border border-gray-200 text-gray-500 hover:bg-gray-50 active:cursor-grabbing"
                                                    title="Move Stage"
                                                    data-stage-move-handle
                                                    draggable="true"
                                                >
                                                    <x-menu-icon name="fa-solid fa-grip-vertical" class="h-3.5 w-3.5" />
                                                </button>
                                                <button
                                                    type="button"
                                                    class="inline-flex h-7 w-7 items-center justify-center rounded-md border border-gray-200 text-gray-500 hover:border-red-200 hover:bg-red-50 hover:text-red-600"
                                                    title="Delete Stage"
                                                    data-stage-delete
                                                >
                                                    <x-menu-icon name="fa-solid fa-trash" class="h-3.5 w-3.5" />
                                                </button>
                                            </div>
                                        @endif
                                    </div>

                                    <div class="space-y-2 flex-1 min-h-0 overflow-y-auto pr-1" data-stage-list>
                                        @forelse ($column['tasks'] as $task)
                                            <article class="h-auto rounded-lg border border-gray-100 p-3 bg-gray-50 cursor-move {{ $task->status === 'done' ? 'opacity-70' : '' }}" draggable="true" data-task-card data-task-id="{{ $task->id }}" data-stage-id="{{ $task->project_stage_id }}" data-hours="{{ (float) $task->tracked_hours }}" data-update-url="{{ route('projects.tasks.update-stage', [$project, $task]) }}">
                                                <div class="flex items-start justify-between gap-2">
                                                    <a href="{{ route('tasks.show', $task) }}" class="text-sm font-medium text-gray-900 hover:text-indigo-600" data-task-title>{{ $task->title }}</a>
                                                    <span class="inline-flex rounded-full px-2 py-0.5 text-[11px] font-medium bg-gray-200 text-gray-700" data-task-status>{{ $taskStatusLabels[$task->status] ?? $task->status }}</span>
                                                </div>
                                                <p class="mt-1 text-xs text-gray-500" data-assignee-name>{{ $task->assignee?->name ?? 'Without performer' }}</p>
                                                <p class="text-xs text-gray-500" data-due-date>{{ $task->due_at?->format('d.m H:i') ?? 'No deadline' }}</p>

                                                <div class="mt-2 flex items-center justify-between text-xs text-gray-500">
                                                    <span>Fact: <span data-tracked-hours>{{ number_format((float) $task->tracked_hours, 1, '.', ' ') }}</span>h</span>
                                                    <a href="{{ route('tasks.show', $task) }}" data-edit-link class="text-indigo-600 hover:text-indigo-500">Open</a>
                                                </div>

                                                <form method="POST" action="{{ route('projects.tasks.update-stage', [$project, $task]) }}" class="mt-2" data-stage-form>
                                                    @csrf
                                                    @method('PATCH')
                                                    <select name="project_stage_id" class="w-full rounded-md border-gray-300 text-xs shadow-sm focus:border-indigo-500 focus:ring-indigo-500" data-stage-select>
                                                        <option value="" @selected(! $task->project_stage_id)>No stage</option>
                                                        @foreach ($project->stages as $stageOption)
                                                            <option value="{{ $stageOption->id }}" @selected($task->project_stage_id === $stageOption->id)>{{ $stageOption->name }}</option>
                                                        @endforeach
                                                    </select>
                                                </form>
                                            </article>
                                        @empty
                                            <p class="text-xs text-gray-500" data-empty>No tasks</p>
                                        @endforelse
                                    </div>
                                </div>
                            @endforeach
                        </div>
                    </div>
                    <div
                        class="pointer-events-none absolute inset-y-0 right-0 w-16 bg-gradient-to-l from-gray-100 via-gray-100/90 to-transparent opacity-0 transition-opacity duration-200"
                        data-board-scroll-fade-right
                    ></div>
                </div>
            </section>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const board = document.getElementById('project-board');
            if (!board) {
                return;
            }

            const csrf = board.dataset.csrf;
            const projectId = board.dataset.projectId;
            const stageUpdateUrl = board.dataset.stageUpdateUrl || '';
            const stageCreateUrl = board.dataset.stageCreateUrl || '';
            const stageDeleteUrlTemplate = board.dataset.stageDeleteUrlTemplate || '';
            const canManageStages = board.dataset.canManageStages === '1' && stageUpdateUrl !== '';
            const boardGrid = board.querySelector('[data-board-grid]');
            const boardScroll = board.querySelector('[data-board-scroll]');
            const fadeRight = board.querySelector('[data-board-scroll-fade-right]');
            const addStageButton = board.querySelector('[data-add-stage]');
            const newStageNameInput = board.querySelector('[data-new-stage-name]');
            const newStageColorInput = board.querySelector('[data-new-stage-color]');
            if (!boardGrid) {
                return;
            }

            const getStageColumns = () => Array.from(board.querySelectorAll('[data-stage-column]'));
            const getSortableStageColumns = () => Array.from(board.querySelectorAll('[data-stage-column-id]'));
            let draggingCard = null;
            let draggingStageColumn = null;
            let stageSettingsSaveTimer = null;
            let stageCreateInProgress = false;
            const statusLabels = @json($taskStatusLabels);

            const formatHours = (value) => {
                const numeric = Number(value || 0);
                return new Intl.NumberFormat(document.documentElement.lang || 'ru-RU', {
                    minimumFractionDigits: 1,
                    maximumFractionDigits: 1,
                }).format(numeric);
            };

            const formatDue = (isoDate) => {
                if (!isoDate) {
                    return 'No deadline';
                }

                const date = new Date(isoDate);
                if (Number.isNaN(date.getTime())) {
                    return 'No deadline';
                }

                return new Intl.DateTimeFormat('ru-RU', {
                    day: '2-digit',
                    month: '2-digit',
                    hour: '2-digit',
                    minute: '2-digit',
                }).format(date);
            };

            const normalizeStage = (stageId) => {
                if (stageId === null || stageId === undefined || String(stageId) === '') {
                    return 'unassigned';
                }
                return String(stageId);
            };

            const normalizeStageColor = (rawColor) => {
                const color = String(rawColor || '').trim().toUpperCase();
                if (!/^#[0-9A-F]{6}$/.test(color)) {
                    return '#94A3B8';
                }

                return color;
            };

            const escapeHtml = (value) => String(value || '')
                .replaceAll('&', '&amp;')
                .replaceAll('<', '&lt;')
                .replaceAll('>', '&gt;')
                .replaceAll('"', '&quot;')
                .replaceAll("'", '&#039;');

            const applyColumnColor = (column, rawColor) => {
                const color = normalizeStageColor(rawColor);
                column.dataset.stageColor = color;
                column.style.borderTopColor = color;
                column.style.backgroundImage = `linear-gradient(to bottom, ${color}1F, #FFFFFF 42%)`;

                const picker = column.querySelector('[data-stage-color-picker]');
                if (picker) {
                    picker.value = color;
                }
            };

            const collectStageSettings = () => {
                return getSortableStageColumns().map((column) => ({
                    id: Number(column.dataset.stageColumnId),
                    color: normalizeStageColor(column.dataset.stageColor || '#94A3B8'),
                }));
            };

            const persistStageSettings = async () => {
                if (!canManageStages) {
                    return;
                }

                try {
                    await fetch(stageUpdateUrl, {
                        method: 'PATCH',
                        headers: {
                            'Content-Type': 'application/json',
                            'Accept': 'application/json',
                            'X-CSRF-TOKEN': csrf,
                            'X-Requested-With': 'XMLHttpRequest',
                        },
                        body: JSON.stringify({
                            stages: collectStageSettings(),
                        }),
                    });
                } catch (error) {
                    // Keep UI interactive when the network request fails.
                }
            };

            const queueStageSettingsSave = () => {
                if (stageSettingsSaveTimer) {
                    window.clearTimeout(stageSettingsSaveTimer);
                }

                stageSettingsSaveTimer = window.setTimeout(() => {
                    persistStageSettings();
                }, 180);
            };

            const stageDeleteUrl = (stageId) => {
                return stageDeleteUrlTemplate.replace('__STAGE__', encodeURIComponent(String(stageId)));
            };

            const updateRightFade = () => {
                if (!boardScroll || !fadeRight) {
                    return;
                }

                const hasOverflow = boardScroll.scrollWidth > boardScroll.clientWidth + 1;
                const reachedEnd = boardScroll.scrollLeft + boardScroll.clientWidth >= boardScroll.scrollWidth - 2;
                fadeRight.classList.toggle('opacity-0', !hasOverflow || reachedEnd);
            };

            const updateStageStats = (column) => {
                const list = column.querySelector('[data-stage-list]');
                const cards = list ? Array.from(list.querySelectorAll('[data-task-card]')) : [];
                const count = cards.length;
                const tracked = cards.reduce((sum, card) => sum + Number(card.dataset.hours || 0), 0);

                const countNode = column.querySelector('[data-stage-count]');
                const hoursNode = column.querySelector('[data-stage-hours]');

                if (countNode) {
                    countNode.textContent = String(count);
                }

                if (hoursNode) {
                    hoursNode.textContent = formatHours(tracked);
                }

                const emptyNode = list ? list.querySelector('[data-empty]') : null;
                if (emptyNode) {
                    emptyNode.classList.toggle('hidden', count > 0);
                }

                if (count === 0 && list && !emptyNode) {
                    const placeholder = document.createElement('p');
                    placeholder.dataset.empty = '1';
                    placeholder.className = 'text-xs text-gray-500';
                    placeholder.textContent = 'No tasks';
                    list.appendChild(placeholder);
                }
            };

            const updateAllStats = () => {
                getStageColumns().forEach(updateStageStats);
            };

            const syncStageSelectOptions = () => {
                const stageOptions = getSortableStageColumns().map((column) => ({
                    id: String(column.dataset.stageColumnId || ''),
                    label: (column.querySelector('[data-stage-label-text]')?.textContent || '').trim(),
                }));

                board.querySelectorAll('[data-stage-select]').forEach((select) => {
                    const currentValue = String(select.value || '');
                    select.innerHTML = '';

                    const emptyOption = document.createElement('option');
                    emptyOption.value = '';
                    emptyOption.textContent = 'No stage';
                    select.appendChild(emptyOption);

                    stageOptions.forEach((stage) => {
                        const option = document.createElement('option');
                        option.value = stage.id;
                        option.textContent = stage.label || stage.id;
                        select.appendChild(option);
                    });

                    const hasCurrent = stageOptions.some((stage) => stage.id === currentValue);
                    select.value = hasCurrent ? currentValue : '';
                });
            };

            const placeTaskIntoStage = (card, stageKey) => {
                const normalized = normalizeStage(stageKey);
                const targetColumn = board.querySelector(`[data-stage-column="${normalized}"]`);
                const targetList = targetColumn ? targetColumn.querySelector('[data-stage-list]') : null;

                if (!targetList) {
                    return;
                }

                const existingEmpty = targetList.querySelector('[data-empty]');
                if (existingEmpty) {
                    existingEmpty.remove();
                }

                targetList.prepend(card);
                card.dataset.stageId = normalized === 'unassigned' ? '' : normalized;

                const select = card.querySelector('[data-stage-select]');
                if (select) {
                    select.value = normalized === 'unassigned' ? '' : normalized;
                }

                updateAllStats();
            };

            const syncTaskFromPayload = (payload) => {
                const card = board.querySelector(`[data-task-card][data-task-id="${payload.id}"]`);
                if (!card) {
                    return;
                }

                const stageKey = normalizeStage(payload.project_stage_id);
                card.dataset.hours = String(payload.tracked_hours || 0);

                const titleNode = card.querySelector('[data-task-title]');
                const assigneeNode = card.querySelector('[data-assignee-name]');
                const dueNode = card.querySelector('[data-due-date]');
                const statusNode = card.querySelector('[data-task-status]');
                const trackedNode = card.querySelector('[data-tracked-hours]');
                const editNode = card.querySelector('[data-edit-link]');

                if (titleNode && payload.title) {
                    titleNode.textContent = payload.title;
                    if (payload.url) {
                        titleNode.href = payload.url;
                    }
                }

                if (assigneeNode) {
                    assigneeNode.textContent = payload.assignee_name || 'Without performer';
                }

                if (dueNode) {
                    dueNode.textContent = formatDue(payload.due_at);
                }

                if (statusNode) {
                    statusNode.textContent = statusLabels[payload.status] || payload.status || 'For execution';
                }

                if (trackedNode) {
                    trackedNode.textContent = formatHours(payload.tracked_hours || 0);
                }

                if (editNode && payload.edit_url) {
                    editNode.href = payload.edit_url;
                }

                card.classList.toggle('opacity-70', payload.status === 'done');
                placeTaskIntoStage(card, stageKey);
            };

            const updateTaskStage = async (card, stageKey, previousStageKey = normalizeStage(card.dataset.stageId)) => {
                const url = card.dataset.updateUrl;

                try {
                    const response = await fetch(url, {
                        method: 'PATCH',
                        headers: {
                            'Content-Type': 'application/json',
                            'Accept': 'application/json',
                            'X-CSRF-TOKEN': csrf,
                        },
                        body: JSON.stringify({
                            project_stage_id: stageKey === 'unassigned' ? null : Number(stageKey),
                        }),
                    });

                    if (!response.ok) {
                        throw new Error('Task stage update failed');
                    }

                    const data = await response.json();
                    if (!data.task) {
                        throw new Error('Missing task payload');
                    }

                    syncTaskFromPayload(data.task);
                } catch (error) {
                    placeTaskIntoStage(card, previousStageKey);
                }
            };

            const bindTaskCard = (card) => {
                if (card.dataset.stageCardBound === '1') {
                    return;
                }

                card.dataset.stageCardBound = '1';

                card.addEventListener('dragstart', () => {
                    draggingCard = card;
                    card.classList.add('opacity-60');
                });

                card.addEventListener('dragend', () => {
                    card.classList.remove('opacity-60');
                });

                const select = card.querySelector('[data-stage-select]');
                if (select) {
                    select.addEventListener('change', () => {
                        const target = normalizeStage(select.value);
                        const previous = normalizeStage(card.dataset.stageId);
                        placeTaskIntoStage(card, target);
                        updateTaskStage(card, target, previous);
                    });
                }
            };

            const bindStageDropEvents = (column) => {
                if (column.dataset.stageDropBound === '1') {
                    return;
                }
                column.dataset.stageDropBound = '1';

                column.addEventListener('dragover', (event) => {
                    if (draggingStageColumn) {
                        return;
                    }

                    event.preventDefault();
                    column.classList.add('ring-2', 'ring-indigo-200');
                });

                column.addEventListener('dragleave', () => {
                    column.classList.remove('ring-2', 'ring-indigo-200');
                });

                column.addEventListener('drop', (event) => {
                    event.preventDefault();
                    column.classList.remove('ring-2', 'ring-indigo-200');

                    if (draggingStageColumn) {
                        return;
                    }

                    if (!draggingCard) {
                        return;
                    }

                    const target = normalizeStage(column.dataset.stageColumn);
                    const current = normalizeStage(draggingCard.dataset.stageId);
                    if (target === current) {
                        return;
                    }

                    placeTaskIntoStage(draggingCard, target);
                    updateTaskStage(draggingCard, target, current);
                });
            };

            const buildStageColumn = (stage) => {
                const color = normalizeStageColor(stage.color || '#94A3B8');
                const stageId = Number(stage.id);
                const stageName = escapeHtml(stage.name || `Stage ${stageId}`);

                const column = document.createElement('div');
                column.className = 'w-[320px] shrink-0 self-start h-[calc(100vh-24rem)] min-h-[28rem] rounded-xl border border-gray-200 border-t-4 bg-white p-3 flex flex-col';
                column.dataset.stageColumn = String(stageId);
                column.dataset.stageColumnId = String(stageId);
                column.dataset.stageColor = color;
                column.style.borderTopColor = color;
                column.style.backgroundImage = `linear-gradient(to bottom, ${color}1F, #FFFFFF 42%)`;

                const controlsHtml = canManageStages
                    ? `
                        <div class="flex items-center gap-1">
                            <label class="inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border border-gray-200 text-gray-500 hover:bg-gray-50" title="Stage color">
                                <i class="fa-solid fa-palette h-3.5 w-3.5" aria-hidden="true"></i>
                                <input type="color" value="${color}" class="sr-only" data-stage-color-picker>
                            </label>
                            <button
                                type="button"
                                class="inline-flex h-7 w-7 cursor-grab items-center justify-center rounded-md border border-gray-200 text-gray-500 hover:bg-gray-50 active:cursor-grabbing"
                                title="Move Stage"
                                data-stage-move-handle
                                draggable="true"
                            >
                                <i class="fa-solid fa-grip-vertical h-3.5 w-3.5" aria-hidden="true"></i>
                            </button>
                            <button
                                type="button"
                                class="inline-flex h-7 w-7 items-center justify-center rounded-md border border-gray-200 text-gray-500 hover:border-red-200 hover:bg-red-50 hover:text-red-600"
                                title="Delete Stage"
                                data-stage-delete
                            >
                                <i class="fa-solid fa-trash h-3.5 w-3.5" aria-hidden="true"></i>
                            </button>
                        </div>
                    `
                    : '';

                column.innerHTML = `
                    <div class="mb-3 flex items-start justify-between gap-2">
                        <div class="min-w-0">
                            <p class="truncate font-semibold text-gray-900" data-stage-label-text>${stageName}</p>
                            <p class="text-xs text-gray-500"><span data-stage-count>0</span> tasks · <span data-stage-hours>0.0</span>h</p>
                        </div>
                        ${controlsHtml}
                    </div>
                    <div class="space-y-2 flex-1 min-h-0 overflow-y-auto pr-1" data-stage-list>
                        <p class="text-xs text-gray-500" data-empty>No tasks</p>
                    </div>
                `;

                return column;
            };

            const deleteStage = async (column) => {
                if (!canManageStages) {
                    return;
                }

                const stageId = Number(column.dataset.stageColumnId || 0);
                if (!stageId) {
                    return;
                }

                const cardCount = column.querySelectorAll('[data-task-card]').length;
                const confirmed = window.confirm(
                    cardCount > 0
                        ? 'Delete stage and move tasks to "No stage"?'
                        : 'Delete stage?'
                );
                if (!confirmed) {
                    return;
                }

                const deleteButton = column.querySelector('[data-stage-delete]');
                if (deleteButton instanceof HTMLButtonElement) {
                    deleteButton.disabled = true;
                }

                try {
                    const response = await fetch(stageDeleteUrl(stageId), {
                        method: 'DELETE',
                        headers: {
                            'Accept': 'application/json',
                            'X-CSRF-TOKEN': csrf,
                            'X-Requested-With': 'XMLHttpRequest',
                        },
                    });

                    if (!response.ok) {
                        throw new Error('Stage delete failed');
                    }

                    const cards = Array.from(column.querySelectorAll('[data-task-card]'));
                    cards.forEach((card) => {
                        placeTaskIntoStage(card, 'unassigned');
                    });

                    column.remove();
                    syncStageSelectOptions();
                    updateAllStats();
                    updateRightFade();
                    queueStageSettingsSave();
                } catch (error) {
                    if (deleteButton instanceof HTMLButtonElement) {
                        deleteButton.disabled = false;
                    }
                }
            };

            const bindStageManageControls = (column) => {
                if (!column.dataset.stageColumnId) {
                    return;
                }

                applyColumnColor(column, column.dataset.stageColor || '#94A3B8');

                if (!canManageStages || column.dataset.stageManageBound === '1') {
                    return;
                }

                column.dataset.stageManageBound = '1';

                const picker = column.querySelector('[data-stage-color-picker]');
                if (picker) {
                    picker.addEventListener('input', () => {
                        applyColumnColor(column, picker.value);
                        queueStageSettingsSave();
                    });
                }

                const deleteButton = column.querySelector('[data-stage-delete]');
                if (deleteButton) {
                    deleteButton.addEventListener('click', () => {
                        deleteStage(column);
                    });
                }
            };

            const createStage = async () => {
                if (!canManageStages || !stageCreateUrl || stageCreateInProgress) {
                    return;
                }

                const stageName = String(newStageNameInput?.value || '').trim();
                if (stageName === '') {
                    newStageNameInput?.focus();
                    return;
                }

                const stageColor = normalizeStageColor(newStageColorInput?.value || '#6366F1');
                stageCreateInProgress = true;
                if (addStageButton instanceof HTMLButtonElement) {
                    addStageButton.disabled = true;
                }

                try {
                    const response = await fetch(stageCreateUrl, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                            'Accept': 'application/json',
                            'X-CSRF-TOKEN': csrf,
                            'X-Requested-With': 'XMLHttpRequest',
                        },
                        body: JSON.stringify({
                            name: stageName,
                            color: stageColor,
                        }),
                    });

                    if (!response.ok) {
                        throw new Error('Stage create failed');
                    }

                    const data = await response.json();
                    if (!data.stage || !data.stage.id) {
                        throw new Error('Missing stage payload');
                    }

                    const column = buildStageColumn(data.stage);
                    boardGrid.appendChild(column);
                    bindStageDropEvents(column);
                    bindStageManageControls(column);

                    syncStageSelectOptions();
                    updateAllStats();
                    updateRightFade();
                    queueStageSettingsSave();

                    if (newStageNameInput instanceof HTMLInputElement) {
                        newStageNameInput.value = '';
                        newStageNameInput.focus();
                    }
                } catch (error) {
                    // Keep board interactive when create request fails.
                } finally {
                    stageCreateInProgress = false;
                    if (addStageButton instanceof HTMLButtonElement) {
                        addStageButton.disabled = false;
                    }
                }
            };

            board.querySelectorAll('[data-task-card]').forEach(bindTaskCard);
            getStageColumns().forEach(bindStageDropEvents);
            getSortableStageColumns().forEach(bindStageManageControls);

            if (canManageStages) {
                boardGrid.addEventListener('dragstart', (event) => {
                    const target = event.target instanceof Element
                        ? event.target.closest('[data-stage-move-handle]')
                        : null;
                    if (!target) {
                        return;
                    }

                    const column = target.closest('[data-stage-column-id]');
                    if (!column) {
                        return;
                    }

                    draggingStageColumn = column;
                    draggingCard = null;
                    column.classList.add('opacity-60');

                    if (event.dataTransfer) {
                        event.dataTransfer.effectAllowed = 'move';
                        event.dataTransfer.setData('text/plain', 'project-stage-column');
                    }
                });

                boardGrid.addEventListener('dragover', (event) => {
                    if (!draggingStageColumn) {
                        return;
                    }

                    const target = event.target instanceof Element
                        ? event.target.closest('[data-stage-column-id]')
                        : null;
                    if (!target || target === draggingStageColumn) {
                        return;
                    }

                    event.preventDefault();

                    const rect = target.getBoundingClientRect();
                    const shouldInsertAfter = event.clientX > rect.left + rect.width / 2;
                    if (shouldInsertAfter) {
                        boardGrid.insertBefore(draggingStageColumn, target.nextElementSibling);
                    } else {
                        boardGrid.insertBefore(draggingStageColumn, target);
                    }
                });

                boardGrid.addEventListener('drop', (event) => {
                    if (!draggingStageColumn) {
                        return;
                    }

                    event.preventDefault();
                });

                boardGrid.addEventListener('dragend', () => {
                    if (!draggingStageColumn) {
                        return;
                    }

                    draggingStageColumn.classList.remove('opacity-60');
                    draggingStageColumn = null;
                    queueStageSettingsSave();
                    updateRightFade();
                });

                if (addStageButton) {
                    addStageButton.addEventListener('click', () => {
                        createStage();
                    });
                }

                if (newStageNameInput) {
                    newStageNameInput.addEventListener('keydown', (event) => {
                        if (event.key !== 'Enter') {
                            return;
                        }

                        event.preventDefault();
                        createStage();
                    });
                }
            }

            if (window.Echo && projectId) {
                window.Echo.private(`projects.${projectId}`)
                    .listen('.project.task-stage-changed', (payload) => {
                        syncTaskFromPayload(payload);
                    });
            }

            if (boardScroll) {
                boardScroll.addEventListener('scroll', updateRightFade, { passive: true });
                window.addEventListener('resize', updateRightFade);
            }

            syncStageSelectOptions();
            updateAllStats();
            updateRightFade();
        });
    </script>
</x-app-layout>
