"use client";

import * as React from "react";
import {
  DndContext,
  DragOverlay,
  PointerSensor,
  closestCorners,
  useSensor,
  useSensors,
  type DragEndEvent,
  type DragOverEvent,
  type DragStartEvent,
} from "@dnd-kit/core";
import { arrayMove } from "@dnd-kit/sortable";

import type { ViewConfig, ViewObject } from "@/lib/hooks/use-view-data";
import { useViewData } from "@/lib/hooks/use-view-data";
import { cn } from "@/lib/utils";

import { BoardCardPreview } from "./board-card";
import { BoardColumn } from "./board-column";

const STATUS_ORDER = ["open", "in_progress", "done", "closed"] as const;

const COLUMN_THEME: Record<
  string,
  { dot: string; borderTop: string; label: string }
> = {
  open: {
    dot: "bg-muted-foreground/55",
    borderTop: "border-t-[3px] border-t-muted-foreground/45",
    label: "Open",
  },
  in_progress: {
    dot: "bg-blue-500",
    borderTop: "border-t-[3px] border-t-blue-500",
    label: "In progress",
  },
  done: {
    dot: "bg-green-500",
    borderTop: "border-t-[3px] border-t-green-500",
    label: "Done",
  },
  closed: {
    dot: "bg-slate-500",
    borderTop: "border-t-[3px] border-t-slate-500",
    label: "Closed",
  },
};

function getColumnTheme(columnId: string) {
  return (
    COLUMN_THEME[columnId] ?? {
      dot: "bg-muted-foreground/50",
      borderTop: "border-t-[3px] border-t-muted-foreground/35",
      label: columnId.replace(/_/g, " "),
    }
  );
}

function getColumnKeys(
  groupBy: string | null,
  grouped: Record<string, ViewObject[]>,
): string[] {
  if (groupBy === "status" || groupBy === null) {
    return [...STATUS_ORDER];
  }
  return Object.keys(grouped).sort();
}

function buildColumnsFromGrouped(
  columnKeys: string[],
  grouped: Record<string, ViewObject[]>,
): Record<string, ViewObject[]> {
  const out: Record<string, ViewObject[]> = {};
  for (const k of columnKeys) {
    out[k] = grouped[k] ? [...grouped[k]] : [];
  }
  return out;
}

function findContainer(
  id: string,
  cols: Record<string, ViewObject[]>,
): string | undefined {
  if (id in cols) return id;
  for (const key of Object.keys(cols)) {
    if (cols[key].some((o) => o.id === id)) return key;
  }
  return undefined;
}

function patchObjectForColumn(
  object: ViewObject,
  columnId: string,
  groupField: string,
): ViewObject {
  const field = groupField === "status" ? "status" : groupField;
  if (field === "status") {
    return { ...object, status: columnId };
  }
  return { ...object, [field]: columnId } as ViewObject;
}

export interface BoardViewProps {
  config: ViewConfig;
  className?: string;
}

export function BoardView({ config, className }: BoardViewProps) {
  const effectiveConfig = React.useMemo(
    () => ({
      ...config,
      groupBy: config.groupBy ?? "status",
    }),
    [config],
  );

  const { grouped, isLoading, total } = useViewData(effectiveConfig);
  const groupField = effectiveConfig.groupBy ?? "status";

  const columnKeys = React.useMemo(
    () => getColumnKeys(effectiveConfig.groupBy, grouped),
    [effectiveConfig.groupBy, grouped],
  );

  const initialColumns = React.useMemo(
    () => buildColumnsFromGrouped(columnKeys, grouped),
    [columnKeys, grouped],
  );

  const [columns, setColumns] =
    React.useState<Record<string, ViewObject[]>>(initialColumns);
  const [activeId, setActiveId] = React.useState<string | null>(null);

  React.useEffect(() => {
    setColumns(initialColumns);
  }, [initialColumns]);

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: { distance: 8 },
    }),
  );

  const activeObject = React.useMemo(() => {
    if (!activeId) return null;
    for (const list of Object.values(columns)) {
      const found = list.find((o) => o.id === activeId);
      if (found) return found;
    }
    return null;
  }, [activeId, columns]);

  const handleDragStart = (event: DragStartEvent) => {
    setActiveId(String(event.active.id));
  };

  const handleDragCancel = () => {
    setActiveId(null);
  };

  const handleDragOver = (event: DragOverEvent) => {
    const { active, over } = event;
    if (!over) return;

    const activeIdStr = String(active.id);
    const overIdStr = String(over.id);

    if (activeIdStr === overIdStr) return;

    setColumns((prev) => {
      const activeContainer = findContainer(activeIdStr, prev);
      const overContainer = findContainer(overIdStr, prev);

      if (!activeContainer || !overContainer) return prev;
      if (activeContainer === overContainer) return prev;

      const activeItems = [...prev[activeContainer]];
      const overItems = [...prev[overContainer]];
      const activeIndex = activeItems.findIndex((i) => i.id === activeIdStr);
      if (activeIndex === -1) return prev;

      let newIndex: number;
      if (overIdStr in prev) {
        newIndex = overItems.length;
      } else {
        const overItemIndex = overItems.findIndex((i) => i.id === overIdStr);
        const isBelowOverItem =
          over.rect &&
          active.rect.current.translated &&
          active.rect.current.translated.top > over.rect.top + over.rect.height;

        const modifier = isBelowOverItem ? 1 : 0;
        newIndex =
          overItemIndex >= 0 ? overItemIndex + modifier : overItems.length;
      }

      const [removed] = activeItems.splice(activeIndex, 1);
      const patched = patchObjectForColumn(removed, overContainer, groupField);
      const nextOver = [...overItems];
      nextOver.splice(newIndex, 0, patched);

      return {
        ...prev,
        [activeContainer]: activeItems,
        [overContainer]: nextOver,
      };
    });
  };

  const handleDragEnd = (event: DragEndEvent) => {
    const { active, over } = event;
    setActiveId(null);

    if (!over) return;

    const activeIdStr = String(active.id);
    const overIdStr = String(over.id);

    setColumns((prev) => {
      const activeContainer = findContainer(activeIdStr, prev);
      const overContainer = findContainer(overIdStr, prev);

      if (!activeContainer || !overContainer) return prev;

      if (activeContainer !== overContainer) {
        const activeItems = [...prev[activeContainer]];
        const overItems = [...prev[overContainer]];
        const activeIndex = activeItems.findIndex((i) => i.id === activeIdStr);
        if (activeIndex === -1) return prev;

        const [removed] = activeItems.splice(activeIndex, 1);
        const patched = patchObjectForColumn(removed, overContainer, groupField);
        let newIndex = overItems.length;
        if (!(overIdStr in prev)) {
          const overItemIndex = overItems.findIndex((i) => i.id === overIdStr);
          if (overItemIndex >= 0) newIndex = overItemIndex;
        }
        const nextOver = [...overItems];
        nextOver.splice(newIndex, 0, patched);
        return {
          ...prev,
          [activeContainer]: activeItems,
          [overContainer]: nextOver,
        };
      }

      const list = [...prev[activeContainer]];
      const oldIndex = list.findIndex((i) => i.id === activeIdStr);

      if (oldIndex === -1) return prev;

      if (overIdStr in prev) {
        return prev;
      }

      const newIndex = list.findIndex((i) => i.id === overIdStr);
      if (newIndex === -1 || oldIndex === newIndex) return prev;

      return {
        ...prev,
        [activeContainer]: arrayMove(list, oldIndex, newIndex),
      };
    });
  };

  if (isLoading) {
    return (
      <div className={cn("flex flex-1 items-center justify-center p-8", className)}>
        <p className="text-sm text-muted-foreground">Loading board…</p>
      </div>
    );
  }

  return (
    <div className={cn("flex min-h-0 flex-1 flex-col gap-3", className)}>
      <div className="shrink-0 px-1 text-xs text-muted-foreground">
        {total} task{total === 1 ? "" : "s"}
      </div>

      <DndContext
        sensors={sensors}
        collisionDetection={closestCorners}
        onDragStart={handleDragStart}
        onDragOver={handleDragOver}
        onDragEnd={handleDragEnd}
        onDragCancel={handleDragCancel}
      >
        <div className="min-h-0 flex-1 overflow-x-auto overflow-y-hidden pb-2">
          <div className="flex h-full min-h-[min(420px,70vh)] gap-3 px-1 pb-1">
            {columnKeys.map((columnId) => {
              const theme = getColumnTheme(columnId);
              return (
                <BoardColumn
                  key={columnId}
                  columnId={columnId}
                  label={theme.label}
                  items={columns[columnId] ?? []}
                  dotClass={theme.dot}
                  borderTopClass={theme.borderTop}
                />
              );
            })}
          </div>
        </div>

        <DragOverlay dropAnimation={null}>
          {activeObject ? (
            <div className="w-[min(100%,290px)] min-w-[260px] max-w-[300px] cursor-grabbing">
              <BoardCardPreview object={activeObject} />
            </div>
          ) : null}
        </DragOverlay>
      </DndContext>
    </div>
  );
}
