"use client";

import { useState } from "react";
import { useSortable } from "@dnd-kit/sortable";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useDndContext, useDroppable } from "@dnd-kit/core";
import { CSS } from "@dnd-kit/utilities";
import { GripVertical, MoreVertical, Trash2 } from "lucide-react";

import { cn } from "@/lib/utils";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { GroupCard } from "@/components/board/group-card";
import { AddGroupPopover } from "@/components/board/add-group-popover";
import { ConfirmDeleteDialog } from "@/components/confirm-delete-dialog";
import { useBoard } from "@/components/board/board-context";
import { useBoardView } from "@/components/board/board-view-provider";
import { useHoldOnComplete } from "@/components/hold-on-complete";
import type { CategoryDTO } from "@/types/board";

export function CategoryLane({ category }: { category: CategoryDTO }) {
  const { removeCategory } = useBoard();
  const { view } = useBoardView();
  const { holds } = useHoldOnComplete();
  const [confirmOpen, setConfirmOpen] = useState(false);
  const isEmpty = category.groups.length === 0;

  // Compact hides any group with no unchecked to-dos left -- which also
  // covers a brand-new group with no to-dos at all, since it has zero
  // unchecked ones too. Two things override that and force a group to stay
  // visible a while longer (see useHoldOnComplete):
  //  - a to-do still mid-hold counts as "unchecked" here too, so checking
  //    off a group's very last open to-do doesn't yank the whole card (and
  //    its undo checkbox) out from under the fade-out.
  //  - a group itself can be held right after creation, so a freshly-made
  //    (necessarily empty) group gets a moment on screen instead of never
  //    appearing at all.
  const visibleGroups =
    view === "compact"
      ? category.groups.filter(
          (g) => holds.has(g.id) || g.todos.some((t) => !t.completed || holds.has(t.id))
        )
      : category.groups;

  // The lane itself is sortable (so lanes can be reordered), which
  // registers its *entire* rect -- header, cards, empty space, all of it --
  // as a droppable. Dragging a group card over empty lane space then had
  // two overlapping droppables to resolve against: this lane's own
  // "category" droppable and the narrower "category-dropzone" below. With
  // `closestCenter`, the much larger lane rect often won, so drop handling
  // never saw a "group" or "category-dropzone" target and silently did
  // nothing. Disabling this lane's droppable side while a *group* (not a
  // lane) is being dragged removes it from collision detection entirely,
  // leaving only the group cards and the dropzone as valid targets.
  const { active, over } = useDndContext();
  const isDraggingGroup = active?.data.current?.type === "group";
  // While a group is being dragged, light up this lane the moment the
  // pointer is over anything inside it -- a card or the empty dropzone
  // below them -- so an (especially empty or short) lane still gives clear
  // "drop here" feedback even when there's no card directly under the
  // cursor to highlight.
  const overData = over?.data.current as { categoryId?: string } | undefined;
  const isDropTargetLane = isDraggingGroup && overData?.categoryId === category.id;

  const {
    setNodeRef,
    setActivatorNodeRef,
    attributes,
    listeners,
    transform,
    transition,
    isDragging,
  } = useSortable({
    id: category.id,
    data: { type: "category" },
    disabled: { draggable: false, droppable: isDraggingGroup },
  });

  const { setNodeRef: setDropzoneRef } = useDroppable({
    id: `category-dropzone-${category.id}`,
    data: { type: "category-dropzone", categoryId: category.id },
  });

  const groupIds = visibleGroups.map((g) => g.id);

  return (
    <>
      <div
        ref={setNodeRef}
        style={{ transform: CSS.Transform.toString(transform), transition }}
        className={cn(
          "flex h-full w-72 shrink-0 flex-col rounded-xl bg-muted/40",
          isDragging && "opacity-50"
        )}
      >
        <div className="flex items-center gap-1 p-3">
          <button
            ref={setActivatorNodeRef}
            {...attributes}
            {...listeners}
            className="cursor-grab touch-none text-muted-foreground hover:text-foreground active:cursor-grabbing"
            aria-label={`Drag to reorder ${category.name}`}
          >
            <GripVertical className="size-4" />
          </button>
          <h2 className="min-w-0 flex-1 truncate font-semibold">{category.name}</h2>
          <DropdownMenu>
            <DropdownMenuTrigger
              render={
                <button
                  className="flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
                  aria-label={`More options for ${category.name}`}
                >
                  <MoreVertical className="size-4" />
                </button>
              }
            />
            <DropdownMenuContent align="end">
              <DropdownMenuItem
                variant="destructive"
                disabled={!isEmpty}
                onClick={() => setConfirmOpen(true)}
              >
                <Trash2 className="size-4" />
                Delete category
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </div>

        <div
          ref={setDropzoneRef}
          className={cn(
            // pt-2 (rather than relying on the header's own bottom padding)
            // leaves enough clearance that a card's hover lift
            // (-translate-y-0.5 in GroupCard) doesn't tuck its top stroke
            // under the lane header above it.
            "flex-1 space-y-2 overflow-y-auto rounded-lg px-3 pt-2 pb-2 transition-colors",
            isDropTargetLane &&
              "bg-primary/10 outline-2 outline-dashed outline-primary/50 -outline-offset-2"
          )}
        >
          <SortableContext items={groupIds} strategy={verticalListSortingStrategy}>
            {visibleGroups.map((group) => {
              // Only a group being shown *solely* because of its own hold
              // (i.e. still empty) actually animates out -- once it has
              // real open work it's staying for good, so it renders at
              // rest even if its hold hasn't technically expired yet.
              const hasOpenTodo = group.todos.some((t) => !t.completed || holds.has(t.id));
              const holdPhase = view === "compact" && !hasOpenTodo ? holds.get(group.id) : undefined;

              return (
                <div
                  key={group.id}
                  className="grid transition-[grid-template-rows] duration-200 ease-in"
                  style={{ gridTemplateRows: holdPhase === "collapsing" ? "0fr" : "1fr" }}
                >
                  <div className="overflow-hidden">
                    <div
                      className="transition-opacity duration-1000 ease-in"
                      style={{
                        opacity: holdPhase === "fading" || holdPhase === "collapsing" ? 0 : 1,
                      }}
                    >
                      <GroupCard group={group} />
                    </div>
                  </div>
                </div>
              );
            })}
          </SortableContext>
        </div>

        <div className="p-2">
          <AddGroupPopover categoryId={category.id} />
        </div>
      </div>

      <ConfirmDeleteDialog
        open={confirmOpen}
        onOpenChange={setConfirmOpen}
        title="Delete category?"
        description={`Delete "${category.name}"? This can't be undone.`}
        onConfirm={() => removeCategory(category.id)}
      />
    </>
  );
}
