import { useEffect, useState } from 'react';
import {
    DndContext, DragOverlay, PointerSensor, closestCenter,
    useSensor, useSensors, DragEndEvent, DragStartEvent, DragOverEvent,
} from '@dnd-kit/core';
import { arrayMove } from '@dnd-kit/sortable';
import type { PointerEvent as ReactPointerEvent } from 'react';
import { PencilSquareIcon, SwatchIcon, XMarkIcon } from '@heroicons/react/24/solid';
import { Button, Input, TextArea, TextField, toast } from '@heroui/react';
import { BoardColumnView } from '@/Components/Board/BoardColumnView';
import { DraggableLabelChip } from '@/Components/Board/DraggableLabelChip';
import { TaskCard, TaskCardData } from '@/Components/Board/TaskCard';
import { TaskModal } from '@/Components/Board/TaskModal';
import { useAll, useList } from '@/hooks/useResource';
import { useAuth } from '@/hooks/useAuth';
import { useConfirm } from '@/hooks/useConfirm';
import { SearchInput } from '@/Components/ui/SearchInput';
import { FilterSelect } from '@/Components/ui/FilterSelect';
import { api } from '@/lib/api';
import { useMutation, useQueryClient } from '@tanstack/react-query';

// Ignore drag activation on anything inside a [data-no-dnd] element (e.g. the
// card's edit/delete buttons), so clicking them never gets swallowed by dnd-kit.
class CardAwarePointerSensor extends PointerSensor {
    static activators = [
        {
            eventName: 'onPointerDown' as const,
            handler: ({ nativeEvent }: ReactPointerEvent) =>
                !(nativeEvent.target as HTMLElement).closest('[data-no-dnd]'),
        },
    ];
}

interface ColumnData {
    id: number;
    name: string;
    position: number;
    tasks: TaskCardData[];
}

interface UserOpt { id: number; name: string; }

interface BoardData {
    id: number;
    name: string;
    description: string | null;
    kegiatan?: { nama_kegiatan: string } | null;
    labels: { id: number; name: string; color: string }[];
    columns: ColumnData[];
}

const LABEL_COLORS = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#0ea5e9', '#6366f1', '#a855f7', '#ec4899'];

export function BoardView({ boardId, onBack, title }: { boardId: number; onBack?: () => void; title?: string }) {
    const key = `board-${boardId}`;
    const { data: board } = useAll<BoardData>(key, `/boards/${boardId}`);
    const qc = useQueryClient();

    const { user, isAdmin } = useAuth();
    const canMoveAny = isAdmin || user?.role === 'Koordinator';

    const [columns, setColumns] = useState<ColumnData[]>([]);
    const [activeTask, setActiveTask] = useState<TaskCardData | null>(null);
    const [activeLabel, setActiveLabel] = useState<{ id: number; name: string; color: string } | null>(null);
    const [labelOverTaskId, setLabelOverTaskId] = useState<number | null>(null);
    const [overColumnId, setOverColumnId] = useState<number | null>(null);
    const [openTaskId, setOpenTaskId] = useState<number | null>(null);
    const [addingLabel, setAddingLabel] = useState(false);
    const [newLabelName, setNewLabelName] = useState('');
    const [isDragging, setIsDragging] = useState(false);
    const [editingDescription, setEditingDescription] = useState(false);
    const [description, setDescription] = useState('');
    const [assigneeFilter, setAssigneeFilter] = useState('');
    const [search, setSearch] = useState('');

    const { data: usersPage } = useList<UserOpt>('users-all', '/users', { per_page: 1000 });
    const users = usersPage?.data ?? [];

    const sensors = useSensors(useSensor(CardAwarePointerSensor, { activationConstraint: { distance: 5 } }));

    // Server data is the source of truth, but we don't want an in-flight
    // drag to be clobbered by a refetch mid-gesture.
    useEffect(() => {
        if (board && !isDragging) setColumns(board.columns);
    }, [board, isDragging]);

    useEffect(() => {
        if (board && !editingDescription) setDescription(board.description ?? '');
    }, [board, editingDescription]);

    function findColumnOf(taskId: number, cols: ColumnData[] = columns): ColumnData | undefined {
        return cols.find((c) => c.tasks.some((t) => t.id === taskId));
    }

    const moveTask = useMutation({
        mutationFn: ({ taskId, columnId, position }: { taskId: number; columnId: number; position: number }) =>
            api.patch(`/tasks/${taskId}/move`, { board_column_id: columnId, position }),
        onError: () => toast.danger('Tugas ini hanya bisa dipindahkan oleh penanggung jawabnya'),
        // Keep local state authoritative until the refetch it triggers actually
        // lands - otherwise the stale `board` still in cache flashes back first.
        onSettled: () => { qc.invalidateQueries({ queryKey: [key] }).then(() => setIsDragging(false)); },
    });

    const addTask = useMutation({
        mutationFn: ({ columnId, title }: { columnId: number; title: string }) =>
            api.post(`/board-columns/${columnId}/tasks`, { title }),
        onSuccess: () => qc.invalidateQueries({ queryKey: [key] }),
    });

    const deleteTask = useMutation({
        mutationFn: (id: number) => api.delete(`/tasks/${id}`),
        onSuccess: () => qc.invalidateQueries({ queryKey: [key] }),
    });

    const updateBoard = useMutation({
        mutationFn: (payload: { description: string }) => api.put(`/boards/${boardId}`, payload),
        onSuccess: () => qc.invalidateQueries({ queryKey: [key] }),
    });

    const addLabel = useMutation({
        mutationFn: (payload: { name: string; color: string }) => api.post(`/boards/${boardId}/labels`, payload),
        onSuccess: () => qc.invalidateQueries({ queryKey: [key] }),
    });

    const deleteLabel = useMutation({
        mutationFn: (labelId: number) => api.delete(`/task-labels/${labelId}`),
        onSuccess: () => qc.invalidateQueries({ queryKey: [key] }),
    });
    const confirm = useConfirm();

    const assignLabel = useMutation({
        mutationFn: ({ taskId, labelIds }: { taskId: number; labelIds: number[] }) =>
            api.put(`/tasks/${taskId}/labels`, { label_ids: labelIds }),
        onSuccess: () => qc.invalidateQueries({ queryKey: [key] }),
    });

    function resolveTargetColumnId(overId: string): number | null {
        if (overId.startsWith('column-')) return Number(overId.replace('column-', ''));
        return findColumnOf(Number(overId))?.id ?? null;
    }

    function canMoveTask(task: TaskCardData | undefined): boolean {
        if (!task) return false;
        return canMoveAny || task.assignee?.id === user?.id;
    }

    function handleDragStart(event: DragStartEvent) {
        const activeId = String(event.active.id);

        if (activeId.startsWith('label-')) {
            const labelId = Number(activeId.replace('label-', ''));
            setActiveLabel(board?.labels.find((l) => l.id === labelId) ?? null);
            return;
        }

        const id = Number(activeId);
        const col = findColumnOf(id);
        const task = col?.tasks.find((t) => t.id === id);
        if (!canMoveTask(task)) return;
        setActiveTask(task ?? null);
        setIsDragging(true);
    }

    // Live-move the card as the pointer crosses a task or column boundary -
    // both across columns and while reordering within the same column - so
    // the board reflows in real time instead of snapping back on drop.
    function handleDragOver(event: DragOverEvent) {
        const { active, over } = event;

        if (String(active.id).startsWith('label-')) {
            const overId = over ? String(over.id) : '';
            setLabelOverTaskId(overId && !overId.startsWith('column-') ? Number(overId) : null);
            return;
        }

        if (!over || active.id === over.id) return;

        const taskId = Number(active.id);
        const overId = String(over.id);
        const targetColumnId = resolveTargetColumnId(overId);
        if (!targetColumnId) return;
        setOverColumnId(targetColumnId);

        setColumns((prev) => {
            const from = prev.find((c) => c.tasks.some((t) => t.id === taskId));
            const to = prev.find((c) => c.id === targetColumnId);
            if (!from || !to) return prev;

            const taskIndex = from.tasks.findIndex((t) => t.id === taskId);
            const overTaskIndex = overId.startsWith('column-') ? -1 : to.tasks.findIndex((t) => t.id === Number(overId));

            if (from.id === to.id) {
                // Hovering the empty column area below the last card (very likely
                // with few cards) resolves to the column, not a task, so there's
                // no task index to target - treat it as "move to the end".
                const targetIndex = overTaskIndex === -1 ? to.tasks.length - 1 : overTaskIndex;

                // No-op if the card would land back in its own slot - skip so we
                // don't re-trigger dragOver every pixel of pointer movement,
                // which starves the click/drag distinction and can make a drag
                // register as a click instead.
                if (targetIndex === taskIndex) return prev;
                // arrayMove swaps directly to the target index, which - unlike a
                // splice-then-insert - works in both directions, including
                // moving the first card past the last one.
                return prev.map((c) => (c.id === from.id ? { ...c, tasks: arrayMove(c.tasks, taskIndex, targetIndex) } : c));
            }

            const insertIndex = overTaskIndex === -1 ? to.tasks.length : overTaskIndex;
            const next = prev.map((c) => ({ ...c, tasks: [...c.tasks] }));
            const nextFrom = next.find((c) => c.id === from.id)!;
            const nextTo = next.find((c) => c.id === to.id)!;
            const [moved] = nextFrom.tasks.splice(taskIndex, 1);
            nextTo.tasks.splice(insertIndex, 0, moved);

            return next;
        });
    }

    function handleDragEnd(event: DragEndEvent) {
        const { active, over } = event;
        const activeId = String(active.id);

        if (activeId.startsWith('label-')) {
            setActiveLabel(null);
            setLabelOverTaskId(null);
            if (!over || String(over.id).startsWith('column-')) return;
            const labelId = Number(activeId.replace('label-', ''));
            const targetTask = columns.flatMap((c) => c.tasks).find((t) => t.id === Number(over.id));
            if (!targetTask) return;
            const currentIds = (targetTask.labels ?? []).map((l) => l.id);
            if (currentIds.includes(labelId)) return;
            assignLabel.mutate({ taskId: targetTask.id, labelIds: [...currentIds, labelId] });
            return;
        }

        setActiveTask(null);
        setOverColumnId(null);
        if (!over) { setIsDragging(false); return; }

        const taskId = Number(active.id);
        const targetColumnId = resolveTargetColumnId(String(over.id));
        if (!targetColumnId) { setIsDragging(false); return; }

        const targetColumn = columns.find((c) => c.id === targetColumnId);
        if (!targetColumn) { setIsDragging(false); return; }

        // targetColumn already reflects handleDragOver's live reorder, so the
        // task's current index there is the final position - no need to
        // recompute from `over.id`, which can point at a different slot than
        // where the card actually landed and would otherwise clobber it.
        let position = targetColumn.tasks.findIndex((t) => t.id === taskId);
        if (position === -1) position = targetColumn.tasks.length;

        // isDragging stays true - and local `columns` stays authoritative -
        // until moveTask's onSettled confirms the server matches it.
        moveTask.mutate({ taskId, columnId: targetColumnId, position });
    }

    if (!board) {
        return <p className="text-sm text-gray-400">Memuat papan...</p>;
    }

    return (
        <div>
            <div className="flex flex-wrap items-start justify-between mb-2 gap-4">
                <div className="min-w-0 flex-1">
                    {onBack && (
                        <button onClick={onBack} className="text-sm text-sky-600 hover:underline mb-1">
                            &larr; Kembali ke daftar papan
                        </button>
                    )}
                    <h1 className="text-lg sm:text-xl font-semibold text-gray-900">{title ?? board.name}</h1>
                    <p className="text-sm text-muted">{board.kegiatan?.nama_kegiatan ?? 'Papan mandiri'}</p>
                </div>
                <div className="shrink-0">
                    <div className="relative" title="Label = tag warna untuk mengelompokkan tugas (mis. Urgent, Bug, Desain)">
                        <Button variant="outline" onPress={() => setAddingLabel((v) => !v)}>
                            <SwatchIcon className="w-4 h-4" />
                            <span className="hidden sm:inline">Label Tugas</span>
                        </Button>
                        {addingLabel && (
                            <div className="absolute right-0 top-full mt-2 w-64 sm:w-72 max-w-[90vw] bg-white border border-gray-200 rounded-xl shadow-lg p-3 z-30">
                                <p className="text-xs text-gray-500 mb-2">
                                    Label adalah tag warna untuk mengelompokkan tugas, misalnya <span className="font-medium">Urgent</span>, <span className="font-medium">Bug</span>, atau <span className="font-medium">Desain</span>. Klik tag pada kartu tugas untuk menandainya.
                                </p>
                                <div className="flex flex-wrap gap-1.5 mb-2">
                                    {board.labels.map((l) => (
                                        <span key={l.id} className="inline-flex items-center gap-1 text-[11px] font-medium pl-2 pr-1 py-1 rounded-full" style={{ backgroundColor: `${l.color}22`, color: l.color }}>
                                            {l.name}
                                            <button
                                                type="button"
                                                onClick={async () => { if (await confirm({ title: `Hapus label "${l.name}"?` })) deleteLabel.mutate(l.id); }}
                                                className="hover:opacity-70"
                                                aria-label={`Hapus label ${l.name}`}
                                            >
                                                <XMarkIcon className="w-3 h-3" />
                                            </button>
                                        </span>
                                    ))}
                                    {board.labels.length === 0 && <span className="text-xs text-gray-400">Belum ada label.</span>}
                                </div>
                                <TextField value={newLabelName} onChange={setNewLabelName} aria-label="Nama label baru" className="mb-2">
                                    <Input placeholder="Nama label baru" fullWidth />
                                </TextField>
                                <div className="flex flex-wrap gap-1.5">
                                    {LABEL_COLORS.map((c) => (
                                        <button
                                            key={c}
                                            onClick={() => { if (newLabelName.trim()) { addLabel.mutate({ name: newLabelName.trim(), color: c }); setNewLabelName(''); setAddingLabel(false); } }}
                                            className="w-6 h-6 rounded-full"
                                            style={{ backgroundColor: c }}
                                        />
                                    ))}
                                </div>
                            </div>
                        )}
                    </div>
                </div>
            </div>

            <div className="mb-4">
                {editingDescription ? (
                    <div className="flex flex-col gap-1.5">
                        <TextField value={description} onChange={setDescription} aria-label="Deskripsi papan">
                            <TextArea autoFocus placeholder="Tujuan papan ini, cakupan tugas, dsb." rows={2} fullWidth />
                        </TextField>
                        <div className="flex gap-2">
                            <Button variant="primary" onPress={() => { updateBoard.mutate({ description }); setEditingDescription(false); }}>
                                Simpan
                            </Button>
                            <Button variant="outline" onPress={() => setEditingDescription(false)}>Batal</Button>
                        </div>
                    </div>
                ) : (
                    <button onClick={() => setEditingDescription(true)} className="group flex items-start gap-1.5 text-left">
                        <p className="text-sm text-gray-600">
                            {board.description || <span className="text-gray-400">Belum ada deskripsi. Klik untuk menambahkan.</span>}
                        </p>
                        <PencilSquareIcon className="w-3.5 h-3.5 text-gray-300 group-hover:text-gray-500 shrink-0 mt-0.5" />
                    </button>
                )}
            </div>

            <div className="flex flex-col sm:flex-row sm:flex-nowrap items-stretch sm:items-center gap-2 mb-3">
                <SearchInput value={search} onChange={setSearch} placeholder="Cari tugas..." />
                <FilterSelect
                    value={assigneeFilter}
                    onChange={setAssigneeFilter}
                    placeholder="Semua Penanggung Jawab"
                    items={users.map((u) => ({ id: String(u.id), label: u.name }))}
                />
            </div>

            <DndContext
                sensors={sensors}
                collisionDetection={closestCenter}
                onDragStart={handleDragStart}
                onDragOver={handleDragOver}
                onDragEnd={handleDragEnd}
                onDragCancel={() => { setActiveTask(null); setActiveLabel(null); setLabelOverTaskId(null); setOverColumnId(null); setIsDragging(false); }}
            >
                {board.labels.length > 0 && (
                    <div className="flex flex-wrap items-center gap-1.5 mb-4">
                        <span className="text-xs text-gray-400">Seret label ke kartu:</span>
                        {board.labels.map((l) => (
                            <DraggableLabelChip key={l.id} id={l.id} name={l.name} color={l.color} />
                        ))}
                    </div>
                )}

                <div className="flex gap-4 overflow-x-auto pb-4 items-start">
                    {columns.map((col, index) => (
                        <BoardColumnView
                            key={col.id}
                            id={col.id}
                            index={index}
                            name={col.name}
                            tasks={col.tasks}
                            isDropTarget={isDragging && overColumnId === col.id}
                            assigneeFilter={assigneeFilter}
                            search={search}
                            currentUserId={user?.id}
                            canMoveAny={canMoveAny}
                            onTaskClick={(id) => setOpenTaskId(id)}
                            onQuickAddTask={(title) => addTask.mutate({ columnId: col.id, title })}
                            onDeleteTask={(id) => deleteTask.mutate(id)}
                            onRemoveLabel={(taskId, labelId) => {
                                const targetTask = columns.flatMap((c) => c.tasks).find((t) => t.id === taskId);
                                if (!targetTask) return;
                                const nextIds = (targetTask.labels ?? []).map((l) => l.id).filter((id) => id !== labelId);
                                assignLabel.mutate({ taskId, labelIds: nextIds });
                            }}
                            labelOverTaskId={labelOverTaskId}
                        />
                    ))}
                </div>

                {/* ponytail: dropAnimation flies the overlay back to the dragged item's
                    pre-reorder rect before snapping to its new slot, since dnd-kit measures
                    that rect before our reordered state re-renders. Disabling it avoids the
                    fly-back flash; the card is already in the right spot underneath. */}
                <DragOverlay dropAnimation={null}>
                    {activeTask && (
                        <div className="rotate-2 shadow-xl">
                            <TaskCard task={activeTask} onClick={() => {}} />
                        </div>
                    )}
                    {activeLabel && (
                        <span
                            className="text-xs font-semibold px-2.5 py-1 rounded-full text-white shadow-xl"
                            style={{ backgroundColor: activeLabel.color }}
                        >
                            {activeLabel.name}
                        </span>
                    )}
                </DragOverlay>
            </DndContext>

            <TaskModal boardId={board.id} taskId={openTaskId} isOpen={openTaskId !== null} onOpenChange={(open) => !open && setOpenTaskId(null)} />
        </div>
    );
}
