import {
  CollisionDetection,
  DndContext,
  DragEndEvent,
  DragOverEvent,
  DragOverlay,
  DragStartEvent,
  KeyboardSensor,
  PointerSensor,
  closestCenter,
  closestCorners,
  pointerWithin,
  rectIntersection,
  useSensor,
  useSensors,
} from "@dnd-kit/core";
import {
  SortableContext,
  arrayMove,
  horizontalListSortingStrategy,
  sortableKeyboardCoordinates,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import {
  ActionIcon,
  AppShell,
  Avatar,
  Badge,
  Box,
  Button,
  Checkbox,
  Group,
  Loader,
  Menu,
  MultiSelect,
  Paper,
  Select,
  Stack,
  Tabs,
  Text,
  TextInput,
  Title,
  Tooltip,
} from "@mantine/core";
import { DatePickerInput } from "@mantine/dates";
import "@mantine/dates/styles.css";
import { modals } from "@mantine/modals";
import { notifications } from "@mantine/notifications";
import {
  IconArrowBackUp,
  IconCalendar,
  IconChartBar,
  IconChevronDown,
  IconChevronRight,
  IconLayoutKanban,
  IconLogout,
  IconMenu2,
  IconMessageChatbot,
  IconMoodSmile,
  IconPlus,
  IconRefresh,
  IconSearch,
  IconTrash,
  IconTrashX,
  IconX,
} from "@tabler/icons-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import * as api from "./api";
import { useAuth } from "./auth";
import { CardForm } from "./components/CardForm";
import { ChatPanel } from "./components/ChatPanel";
import { CalendarView } from "./components/CalendarView";
import { Dashboard } from "./components/Dashboard";
import { HistoryModal } from "./components/HistoryModal";
import { KanbanCard } from "./components/KanbanCard";
import { KanbanColumn } from "./components/KanbanColumn";
import { StickerPicker } from "./components/StickerPicker";
import { colorBg, colorBorder } from "./components/colors";
import type { Board, Card, CardColor, Column, ColumnLocation, User } from "./types";

const COL_PREFIX = "column-";

// Custom collision detection: prefiere otras columnas como destino al arrastrar
// columnas; al arrastrar cards prefiere cards/columnas via closestCorners.
function makeCollisionDetection(activeType: string | undefined): CollisionDetection {
  if (activeType === "column") {
    return (args) => {
      // Solo considerar drops sobre otras columnas (ids con COL_PREFIX).
      const filtered = args.droppableContainers.filter((c) =>
        String(c.id).startsWith(COL_PREFIX)
      );
      const inter = rectIntersection({ ...args, droppableContainers: filtered });
      if (inter.length > 0) return inter;
      return closestCenter({ ...args, droppableContainers: filtered });
    };
  }
  return (args) => {
    const pw = pointerWithin(args);
    if (pw.length > 0) return pw;
    return closestCorners(args);
  };
}

export function App() {
  const auth = useAuth();
  const [board, setBoard] = useState<Board | null>(null);
  const [users, setUsers] = useState<User[]>([]);
  const [activeCard, setActiveCard] = useState<Card | null>(null);
  const [activeColumnId, setActiveColumnId] = useState<string | null>(null);
  const [activeType, setActiveType] = useState<string | undefined>(undefined);
  const [addingCol, setAddingCol] = useState(false);
  const [colName, setColName] = useState("");
  const [now, setNow] = useState(Date.now());
  const [chatOpen, setChatOpen] = useState(false);
  const [activeTab, setActiveTab] = useState<string>("board");
  const [trash, setTrash] = useState<Card[]>([]);
  const [trashOpen, setTrashOpen] = useState(false);
  const [tagOptions, setTagOptions] = useState<string[]>([]);
  const [requesterOptions, setRequesterOptions] = useState<string[]>([]);
  const [searchTerm, setSearchTerm] = useState("");
  const [filterAssigneeId, setFilterAssigneeId] = useState<string | null>(null);
  const [filterRequester, setFilterRequester] = useState<string | null>(null);
  const [filterTags, setFilterTags] = useState<string[]>([]);
  const [filterUnassigned, setFilterUnassigned] = useState(false);
  const [filterDateFrom, setFilterDateFrom] = useState<Date | null>(null);
  const [filterDateTo, setFilterDateTo] = useState<Date | null>(null);
  const [stickerPickerOpen, setStickerPickerOpen] = useState(false);
  const [activeSticker, setActiveSticker] = useState<string | null>(null);
  const [navOpen, setNavOpen] = useState(false);
  const [navWidth, setNavWidth] = useState<number>(() => {
    const stored = localStorage.getItem("kanban_nav_width");
    const n = stored ? parseInt(stored, 10) : NaN;
    return Number.isFinite(n) && n >= 180 && n <= 600 ? n : 240;
  });
  const navWidthRef = useRef(navWidth);
  useEffect(() => {
    navWidthRef.current = navWidth;
    localStorage.setItem("kanban_nav_width", String(navWidth));
  }, [navWidth]);

  const onNavResizeMouseDown = (e: React.MouseEvent) => {
    e.preventDefault();
    const startX = e.clientX;
    const startWidth = navWidthRef.current;
    document.body.style.cursor = "col-resize";
    document.body.style.userSelect = "none";
    const onMove = (ev: MouseEvent) => {
      const dx = ev.clientX - startX;
      const next = Math.min(600, Math.max(180, startWidth + dx));
      setNavWidth(next);
    };
    const onUp = () => {
      document.body.style.cursor = "";
      document.body.style.userSelect = "";
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
  };

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
  );

  const reload = useCallback(async () => {
    try {
      const b = await api.getBoard();
      setBoard(b);
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
    }
  }, []);

  useEffect(() => {
    reload();
  }, [reload]);

  const reloadUsers = useCallback(async () => {
    try {
      const us = await api.listUsers();
      setUsers(us);
    } catch (e) {
      console.warn("listUsers failed", e);
    }
  }, []);

  const reloadTrash = useCallback(async () => {
    try {
      const t = await api.listTrash();
      setTrash(t);
    } catch (e) {
      console.warn("listTrash failed", e);
    }
  }, []);

  const reloadTags = useCallback(async () => {
    try {
      const t = await api.listTags();
      setTagOptions(t);
    } catch (e) {
      console.warn("listTags failed", e);
    }
  }, []);

  const reloadRequesters = useCallback(async () => {
    try {
      const r = await api.listRequesters();
      setRequesterOptions(r);
    } catch (e) {
      console.warn("listRequesters failed", e);
    }
  }, []);

  useEffect(() => {
    reloadUsers();
  }, [reloadUsers]);

  useEffect(() => {
    reloadTrash();
  }, [reloadTrash]);

  useEffect(() => {
    reloadTags();
    reloadRequesters();
  }, [reloadTags, reloadRequesters]);

  useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, []);

  useEffect(() => {
    if (!activeSticker) return;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") setActiveSticker(null);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [activeSticker]);

  const usersById = useMemo(() => {
    const m = new Map<string, User>();
    for (const u of users) m.set(u.id, u);
    return m;
  }, [users]);

  const sortedColumns = useMemo(() => {
    if (!board) return [];
    return [...board.columns].sort((a, b) => a.position - b.position);
  }, [board]);

  const boardColumns = useMemo(() => sortedColumns.filter((c) => c.location !== "sidebar"), [sortedColumns]);
  const sidebarColumns = useMemo(() => sortedColumns.filter((c) => c.location === "sidebar"), [sortedColumns]);

  const boardSortableIds = useMemo(() => boardColumns.map((c) => `${COL_PREFIX}${c.id}`), [boardColumns]);
  const sidebarSortableIds = useMemo(() => sidebarColumns.map((c) => `${COL_PREFIX}${c.id}`), [sidebarColumns]);

  const cardMatches = useCallback(
    (c: Card): boolean => {
      const term = searchTerm.trim().toLowerCase();
      if (term) {
        const hay = [
          c.title,
          c.description,
          c.requester,
          ...(c.tags || []),
        ]
          .filter(Boolean)
          .join(" ")
          .toLowerCase();
        if (!hay.includes(term)) return false;
      }
      if (filterAssigneeId && c.assignee_id !== filterAssigneeId) return false;
      if (filterUnassigned && c.assignee_id) return false;
      if (filterRequester && c.requester !== filterRequester) return false;
      if (filterTags.length > 0) {
        const cardTags = new Set(c.tags || []);
        for (const t of filterTags) if (!cardTags.has(t)) return false;
      }
      if (filterDateFrom || filterDateTo) {
        const fromMs = filterDateFrom ? new Date(filterDateFrom).setHours(0, 0, 0, 0) : -Infinity;
        const toMs = filterDateTo ? new Date(filterDateTo).setHours(23, 59, 59, 999) : Infinity;
        const created = c.created_at ? new Date(c.created_at).getTime() : NaN;
        const moved = c.entered_at ? new Date(c.entered_at).getTime() : NaN;
        const inRange = (t: number) => !isNaN(t) && t >= fromMs && t <= toMs;
        if (!inRange(created) && !inRange(moved)) return false;
      }
      return true;
    },
    [searchTerm, filterAssigneeId, filterUnassigned, filterRequester, filterTags, filterDateFrom, filterDateTo]
  );

  const cardsByColumn = useMemo(() => {
    const map = new Map<string, Card[]>();
    if (!board) return map;
    for (const col of board.columns) map.set(col.id, []);
    for (const c of [...board.cards].sort((a, b) => a.position - b.position)) {
      if (!cardMatches(c)) continue;
      const arr = map.get(c.column_id);
      if (arr) arr.push(c);
    }
    return map;
  }, [board, cardMatches]);

  const filtersActive =
    !!searchTerm.trim() ||
    !!filterAssigneeId ||
    filterUnassigned ||
    !!filterRequester ||
    filterTags.length > 0 ||
    !!filterDateFrom ||
    !!filterDateTo;

  const findCard = (id: string): Card | undefined => board?.cards.find((c) => c.id === id);
  const findColumn = (id: string): Column | undefined => board?.columns.find((c) => c.id === id);
  const findColumnIdOfCard = (id: string): string | undefined => findCard(id)?.column_id;

  const isColumnId = (id: string) => id.startsWith(COL_PREFIX);
  const stripColumnPrefix = (id: string) => id.slice(COL_PREFIX.length);

  const resolveColumnId = (overId: string): string | undefined => {
    if (!board) return undefined;
    if (isColumnId(overId)) return stripColumnPrefix(overId);
    return findColumnIdOfCard(overId);
  };

  // --- DnD handlers ---

  const onDragStart = (e: DragStartEvent) => {
    const id = e.active.id as string;
    const type = e.active.data.current?.type as string | undefined;
    setActiveType(type);
    if (type === "column") {
      setActiveColumnId(stripColumnPrefix(id));
      return;
    }
    const c = findCard(id);
    if (c) setActiveCard(c);
  };

  const onDragOver = (e: DragOverEvent) => {
    if (!board) return;
    if (e.active.data.current?.type !== "card") return;

    const activeId = e.active.id as string;
    const overId = e.over?.id as string | undefined;
    if (!overId) return;

    const fromCol = findColumnIdOfCard(activeId);
    const toCol = resolveColumnId(overId);
    if (!fromCol || !toCol || fromCol === toCol) return;

    setBoard((prev) => {
      if (!prev) return prev;
      const cards = prev.cards.map((c) => (c.id === activeId ? { ...c, column_id: toCol } : c));
      return { ...prev, cards };
    });
  };

  const onDragEnd = async (e: DragEndEvent) => {
    const type = e.active.data.current?.type as string | undefined;
    const activeId = e.active.id as string;
    const overId = e.over?.id as string | undefined;
    setActiveCard(null);
    setActiveColumnId(null);
    setActiveType(undefined);

    if (!board || !overId) return;

    if (type === "column") {
      if (!isColumnId(overId)) return;
      const activeColId = stripColumnPrefix(activeId);
      const overColId = stripColumnPrefix(overId);
      if (activeColId === overColId) return;

      const activeCol = findColumn(activeColId);
      const overCol = findColumn(overColId);
      if (!activeCol || !overCol) return;

      // Determine destination location: same as the column it was dropped on.
      const destLocation: ColumnLocation = overCol.location;
      const destSiblings = sortedColumns.filter((c) => c.location === destLocation);
      const destIds = destSiblings.map((c) => c.id);
      const oldIdx = destIds.indexOf(activeColId);
      const newIdx = destIds.indexOf(overColId);

      let reordered: string[];
      if (oldIdx === -1) {
        // Coming from another location: append at overCol position.
        const insertAt = newIdx === -1 ? destIds.length : newIdx;
        reordered = [...destIds.slice(0, insertAt), activeColId, ...destIds.slice(insertAt)];
      } else {
        if (oldIdx === newIdx) return;
        reordered = arrayMove(destIds, oldIdx, newIdx);
      }

      // Optimistic update.
      setBoard((prev) => {
        if (!prev) return prev;
        const posMap = new Map(reordered.map((id, i) => [id, i]));
        const columns = prev.columns.map((c) => {
          if (c.id === activeColId) return { ...c, location: destLocation, position: posMap.get(c.id) ?? c.position };
          if (posMap.has(c.id)) return { ...c, position: posMap.get(c.id)! };
          return c;
        });
        return { ...prev, columns };
      });

      try {
        if (activeCol.location !== destLocation) {
          await api.updateColumn(activeColId, { location: destLocation });
        }
        await api.reorderColumns(reordered);
      } catch (err) {
        notifications.show({ color: "red", message: (err as Error).message });
      }
      reload();
      return;
    }

    // Card drag
    const destCol = resolveColumnId(overId);
    if (!destCol) return;
    const destCards = board.cards
      .filter((c) => c.column_id === destCol)
      .sort((a, b) => a.position - b.position);
    const oldIdx = destCards.findIndex((c) => c.id === activeId);

    let orderedIds: string[];
    if (isColumnId(overId) || oldIdx === -1) {
      orderedIds = [...destCards.filter((c) => c.id !== activeId).map((c) => c.id), activeId];
    } else {
      const newIdx = destCards.findIndex((c) => c.id === overId);
      orderedIds = arrayMove(destCards.map((c) => c.id), oldIdx, newIdx);
    }

    setBoard((prev) => {
      if (!prev) return prev;
      const orderMap = new Map(orderedIds.map((id, i) => [id, i]));
      const cards = prev.cards.map((c) => {
        if (c.column_id === destCol && orderMap.has(c.id)) return { ...c, position: orderMap.get(c.id)! };
        return c;
      });
      return { ...prev, cards };
    });

    try {
      await api.moveCard(activeId, destCol, orderedIds);
    } catch (err) {
      notifications.show({ color: "red", message: (err as Error).message });
    }
    reload();
  };

  // --- mutations ---

  const handleAddColumn = async () => {
    const n = colName.trim();
    if (!n) return;
    try {
      await api.createColumn(n);
      setColName("");
      setAddingCol(false);
      reload();
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
    }
  };

  const handleRenameColumn = useCallback(async (id: string, name: string) => {
    try {
      await api.updateColumn(id, { name });
      reload();
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
    }
  }, [reload]);

  const handleResizeColumn = useCallback(async (id: string, width: number) => {
    try {
      await api.updateColumn(id, { width });
      reload();
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
    }
  }, [reload]);

  const handleMoveColumnLocation = useCallback(async (id: string, location: ColumnLocation) => {
    try {
      await api.updateColumn(id, { location });
      reload();
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
    }
  }, [reload]);

  const handleDeleteColumn = useCallback((id: string) => {
    modals.openConfirmModal({
      title: "Eliminar columna",
      children: <Text size="sm">Se borraran todas sus tarjetas. Continuar?</Text>,
      labels: { confirm: "Eliminar", cancel: "Cancelar" },
      confirmProps: { color: "red" },
      onConfirm: async () => {
        try {
          await api.deleteColumn(id);
          reload();
        } catch (e) {
          notifications.show({ color: "red", message: (e as Error).message });
        }
      },
    });
  }, [reload]);

  const openCreateCard = useCallback((columnId: string) => {
    const id = modals.open({
      title: "Nueva tarjeta",
      size: "md",
      children: (
        <CardForm
          users={users}
          requesterOptions={requesterOptions}
          tagOptions={tagOptions}
          initial={{ requester: auth.user?.display_name || auth.user?.username || "" }}
          submitLabel="Crear"
          onCancel={() => modals.close(id)}
          onSubmit={async (v) => {
            try {
              await api.createCard({
                column_id: columnId,
                requester: v.requester,
                title: v.title,
                description: v.description,
                assignee_id: v.assignee_id,
                tags: v.tags,
              });
              modals.close(id);
              reload();
              reloadTags();
              reloadRequesters();
            } catch (e) {
              notifications.show({ color: "red", message: (e as Error).message });
            }
          }}
        />
      ),
    });
  }, [reload, users, auth.user, requesterOptions, tagOptions]);

  const openEditCard = useCallback((card: Card) => {
    const id = modals.open({
      title: "Editar tarjeta",
      size: "md",
      children: (
        <CardForm
          users={users}
          requesterOptions={requesterOptions}
          tagOptions={tagOptions}
          initial={{
            requester: card.requester,
            title: card.title,
            description: card.description,
            assignee_id: card.assignee_id,
            tags: card.tags || [],
          }}
          submitLabel="Guardar"
          onCancel={() => modals.close(id)}
          onSubmit={async (v) => {
            try {
              await api.updateCard(card.id, {
                requester: v.requester,
                title: v.title,
                description: v.description,
                assignee_id: v.assignee_id,
                tags: v.tags,
              });
              modals.close(id);
              reload();
              reloadTags();
              reloadRequesters();
            } catch (e) {
              notifications.show({ color: "red", message: (e as Error).message });
            }
          }}
        />
      ),
    });
  }, [reload, users, requesterOptions, tagOptions]);

  const handleAssignCard = useCallback(async (id: string, assignee_id: string | null) => {
    setBoard((prev) => {
      if (!prev) return prev;
      return { ...prev, cards: prev.cards.map((c) => (c.id === id ? { ...c, assignee_id } : c)) };
    });
    try {
      await api.updateCard(id, { assignee_id });
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
      reload();
    }
  }, [reload]);

  const handleDeleteCard = useCallback(async (id: string) => {
    try {
      await api.deleteCard(id);
      reload();
      reloadTrash();
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
    }
  }, [reload, reloadTrash]);

  const handleRestoreCard = useCallback(async (id: string) => {
    try {
      await api.restoreCard(id);
      reload();
      reloadTrash();
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
    }
  }, [reload, reloadTrash]);

  const handlePurgeCard = useCallback(async (id: string) => {
    modals.openConfirmModal({
      title: "Borrar permanentemente",
      children: <Text size="sm">Esta accion no se puede deshacer.</Text>,
      labels: { confirm: "Borrar", cancel: "Cancelar" },
      confirmProps: { color: "red" },
      onConfirm: async () => {
        try {
          await api.purgeCard(id);
          reloadTrash();
        } catch (e) {
          notifications.show({ color: "red", message: (e as Error).message });
        }
      },
    });
  }, [reloadTrash]);

  const handleChangeCardColor = useCallback(async (id: string, color: CardColor) => {
    setBoard((prev) => {
      if (!prev) return prev;
      return { ...prev, cards: prev.cards.map((c) => (c.id === id ? { ...c, color } : c)) };
    });
    try {
      await api.updateCard(id, { color });
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
      reload();
    }
  }, [reload]);

  const persistStickers = useCallback(async (id: string, stickers: Card["stickers"]) => {
    setBoard((prev) => {
      if (!prev) return prev;
      return { ...prev, cards: prev.cards.map((c) => (c.id === id ? { ...c, stickers } : c)) };
    });
    try {
      await api.updateCardStickers(id, stickers);
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
      reload();
    }
  }, [reload]);

  const handleAddSticker = useCallback((cardId: string, x: number, y: number) => {
    if (!activeSticker) return;
    setBoard((prev) => {
      if (!prev) return prev;
      const cards = prev.cards.map((c) => {
        if (c.id !== cardId) return c;
        const stickers = [...(c.stickers || []), { emoji: activeSticker, x, y }];
        api.updateCardStickers(cardId, stickers).catch((e) => {
          notifications.show({ color: "red", message: (e as Error).message });
          reload();
        });
        return { ...c, stickers };
      });
      return { ...prev, cards };
    });
  }, [activeSticker, reload]);

  const handleRemoveSticker = useCallback((cardId: string, index: number) => {
    setBoard((prev) => {
      if (!prev) return prev;
      const cards = prev.cards.map((c) => {
        if (c.id !== cardId) return c;
        const stickers = (c.stickers || []).filter((_, i) => i !== index);
        api.updateCardStickers(cardId, stickers).catch((e) => {
          notifications.show({ color: "red", message: (e as Error).message });
          reload();
        });
        return { ...c, stickers };
      });
      return { ...prev, cards };
    });
  }, [reload]);

  const handleMoveSticker = useCallback((cardId: string, index: number, x: number, y: number) => {
    setBoard((prev) => {
      if (!prev) return prev;
      const cards = prev.cards.map((c) => {
        if (c.id !== cardId) return c;
        const stickers = (c.stickers || []).map((s, i) => (i === index ? { ...s, x, y } : s));
        return { ...c, stickers };
      });
      return { ...prev, cards };
    });
  }, []);

  const handleCommitSticker = useCallback((cardId: string) => {
    setBoard((prev) => {
      if (!prev) return prev;
      const card = prev.cards.find((c) => c.id === cardId);
      if (card) persistStickers(cardId, card.stickers || []);
      return prev;
    });
  }, [persistStickers]);

  const handleShowHistory = useCallback((card: Card) => {
    modals.open({
      title: card.title,
      size: "md",
      children: <HistoryModal card={card} />,
    });
  }, []);

  const handleToggleCardLock = useCallback(async (id: string, locked: boolean) => {
    setBoard((prev) => {
      if (!prev) return prev;
      return { ...prev, cards: prev.cards.map((c) => (c.id === id ? { ...c, locked } : c)) };
    });
    try {
      await api.updateCard(id, { locked });
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
      reload();
    }
  }, [reload]);

  const handleSetWIPLimit = useCallback(async (id: string, wip_limit: number) => {
    setBoard((prev) => {
      if (!prev) return prev;
      return { ...prev, columns: prev.columns.map((c) => (c.id === id ? { ...c, wip_limit } : c)) };
    });
    try {
      await api.updateColumn(id, { wip_limit });
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
      reload();
    }
  }, [reload]);

  const handleToggleDone = useCallback(async (id: string, is_done: boolean) => {
    setBoard((prev) => {
      if (!prev) return prev;
      return { ...prev, columns: prev.columns.map((c) => (c.id === id ? { ...c, is_done } : c)) };
    });
    try {
      await api.updateColumn(id, { is_done });
      reload();
    } catch (e) {
      notifications.show({ color: "red", message: (e as Error).message });
      reload();
    }
  }, [reload]);

  const headerConfig = useMemo(() => ({ height: 50 }), []);
  const navbarConfig = useMemo(
    () => ({
      width: navWidth,
      breakpoint: "md" as const,
      collapsed: { mobile: !navOpen, desktop: !navOpen },
    }),
    [navWidth, navOpen]
  );
  const asideConfig = useMemo(
    () => ({
      width: 380,
      breakpoint: "md" as const,
      collapsed: { mobile: !chatOpen, desktop: !chatOpen },
    }),
    [chatOpen]
  );
  const appShellStyles = useMemo(
    () => ({ main: { paddingInlineStart: 0, paddingInlineEnd: 0 } }),
    []
  );

  if (!board) {
    return (
      <Group justify="center" p="xl">
        <Loader />
      </Group>
    );
  }

  const dragOverlayCard = activeCard;
  const dragOverlayColumn = activeColumnId ? findColumn(activeColumnId) : null;

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={makeCollisionDetection(activeType)}
      onDragStart={onDragStart}
      onDragOver={onDragOver}
      onDragEnd={onDragEnd}
    >
      <AppShell
        header={headerConfig}
        navbar={navbarConfig}
        aside={asideConfig}
        padding={0}
        styles={appShellStyles}
      >
        <AppShell.Header>
          <Group h="100%" px="md" justify="space-between">
            <Group gap={6}>
              <ActionIcon
                variant={navOpen ? "filled" : "subtle"}
                onClick={() => setNavOpen((v) => !v)}
                aria-label="Toggle sidebar"
              >
                <IconMenu2 size={16} />
              </ActionIcon>
              <IconLayoutKanban size={22} />
              <Title order={4}>Kanban</Title>
              <Tabs value={activeTab} onChange={(v) => v && setActiveTab(v)} variant="pills" ml="md">
                <Tabs.List>
                  <Tabs.Tab value="board" leftSection={<IconLayoutKanban size={14} />}>
                    Tablero
                  </Tabs.Tab>
                  <Tabs.Tab value="dashboard" leftSection={<IconChartBar size={14} />}>
                    Dashboard
                  </Tabs.Tab>
                  <Tabs.Tab value="calendar" leftSection={<IconCalendar size={14} />}>
                    Calendario
                  </Tabs.Tab>
                </Tabs.List>
              </Tabs>
            </Group>
            <Group gap={4}>
              <ActionIcon variant="subtle" onClick={reload} aria-label="Refresh">
                <IconRefresh size={16} />
              </ActionIcon>
              <ActionIcon
                variant={chatOpen ? "filled" : "subtle"}
                onClick={() => setChatOpen((v) => !v)}
                aria-label="Toggle chat"
              >
                <IconMessageChatbot size={16} />
              </ActionIcon>
              {auth.user && (
                <Menu position="bottom-end" shadow="md" withArrow>
                  <Menu.Target>
                    <ActionIcon variant="subtle" aria-label="Usuario">
                      <Avatar size={26} radius="xl" color="blue">
                        {(auth.user.display_name || auth.user.username).slice(0, 2).toUpperCase()}
                      </Avatar>
                    </ActionIcon>
                  </Menu.Target>
                  <Menu.Dropdown>
                    <Menu.Label>{auth.user.display_name || auth.user.username}</Menu.Label>
                    <Menu.Item
                      leftSection={<IconLogout size={14} />}
                      color="red"
                      onClick={() => auth.logout()}
                    >
                      Cerrar sesion
                    </Menu.Item>
                  </Menu.Dropdown>
                </Menu>
              )}
            </Group>
          </Group>
        </AppShell.Header>

        <AppShell.Navbar p="xs">
          {/* Drag handle to resize navbar — absolute relative to navbar (which is position:fixed in v9) */}
          <Box
            onMouseDown={onNavResizeMouseDown}
            style={{
              position: "absolute",
              top: 0,
              right: -3,
              width: 6,
              height: "100%",
              cursor: "col-resize",
              zIndex: 10,
            }}
            aria-label="Resize sidebar"
          />
          <Stack gap="xs" h="100%">
            <Text size="xs" c="dimmed" fw={600} tt="uppercase">
              Columnas parqueadas
            </Text>
            <Box style={{ flex: 1, overflowY: "auto" }}>
              <SortableContext items={sidebarSortableIds} strategy={verticalListSortingStrategy}>
                <Stack gap="xs">
                  {sidebarColumns.length === 0 && (
                    <Text size="xs" c="dimmed">
                      Vacio. Mueve columnas aqui con el icono "archivar" en su cabecera.
                    </Text>
                  )}
                  {sidebarColumns.map((col) => (
                    <KanbanColumn
                      key={col.id}
                      column={col}
                      cards={cardsByColumn.get(col.id) ?? []}
                      now={now}
                      collapsed
                      onAddCard={openCreateCard}
                      onRenameColumn={handleRenameColumn}
                      onResizeColumn={handleResizeColumn}
                      onMoveColumnLocation={handleMoveColumnLocation}
                      onDeleteColumn={handleDeleteColumn}
                      onSetWIPLimit={handleSetWIPLimit}
                      onToggleDone={handleToggleDone}
                      onEditCard={openEditCard}
                      onDeleteCard={handleDeleteCard}
                      onChangeCardColor={handleChangeCardColor}
                      onShowHistory={handleShowHistory}
                      onToggleCardLock={handleToggleCardLock}
                      onAssignCard={handleAssignCard}
                      activeSticker={activeSticker}
                      onAddSticker={handleAddSticker}
                      onRemoveSticker={handleRemoveSticker}
                      onMoveSticker={handleMoveSticker}
                      onCommitSticker={handleCommitSticker}
                      users={users}
                      usersById={usersById}
                    />
                  ))}
                </Stack>
              </SortableContext>
            </Box>
            <Box style={{ borderTop: "1px solid var(--mantine-color-dark-5)", paddingTop: 8 }}>
              <Button
                variant="subtle"
                color="gray"
                size="xs"
                fullWidth
                justify="space-between"
                leftSection={<IconTrash size={14} />}
                rightSection={
                  <Group gap={4}>
                    <Badge size="xs" variant="light" color={trash.length > 0 ? "red" : "gray"}>
                      {trash.length}
                    </Badge>
                    {trashOpen ? <IconChevronDown size={12} /> : <IconChevronRight size={12} />}
                  </Group>
                }
                onClick={() => setTrashOpen((v) => !v)}
              >
                Papelera
              </Button>
              {trashOpen && (
                <Stack gap={4} mt={4} style={{ maxHeight: 220, overflowY: "auto" }}>
                  {trash.length === 0 && (
                    <Text size="xs" c="dimmed" px="xs">
                      Vacia.
                    </Text>
                  )}
                  {trash.map((c) => (
                    <Paper key={c.id} p={6} withBorder radius="sm" bg="dark.7">
                      <Group justify="space-between" gap={4} wrap="nowrap">
                        <Text size="xs" truncate style={{ flex: 1 }} title={c.title}>
                          {c.title}
                        </Text>
                        <Tooltip label="Restaurar" withArrow>
                          <ActionIcon size="xs" variant="subtle" color="green" onClick={() => handleRestoreCard(c.id)}>
                            <IconArrowBackUp size={12} />
                          </ActionIcon>
                        </Tooltip>
                        <Tooltip label="Borrar permanentemente" withArrow>
                          <ActionIcon size="xs" variant="subtle" color="red" onClick={() => handlePurgeCard(c.id)}>
                            <IconTrashX size={12} />
                          </ActionIcon>
                        </Tooltip>
                      </Group>
                    </Paper>
                  ))}
                </Stack>
              )}
            </Box>
          </Stack>
        </AppShell.Navbar>

        <AppShell.Aside>
          <ChatPanel onBoardChange={reload} />
        </AppShell.Aside>

        <AppShell.Main>
          {activeTab === "dashboard" ? (
            <Box style={{ height: "calc(100vh - 50px)", overflow: "auto" }}>
              <Dashboard users={users} />
            </Box>
          ) : activeTab === "calendar" ? (
            <Box style={{ height: "calc(100vh - 50px)", overflow: "auto" }}>
              <CalendarView users={users} />
            </Box>
          ) : (
          <Box style={{ height: "calc(100vh - 50px)", overflow: "hidden", display: "flex", flexDirection: "column" }}>
            <Group gap="xs" p="xs" wrap="wrap" align="end" style={{ borderBottom: "1px solid var(--mantine-color-dark-4)" }}>
              <TextInput
                leftSection={<IconSearch size={14} />}
                placeholder="Buscar (titulo, descripcion, solicitante, tag)"
                value={searchTerm}
                onChange={(e) => setSearchTerm(e.currentTarget.value)}
                rightSection={
                  searchTerm ? (
                    <ActionIcon size="sm" variant="subtle" color="gray" onClick={() => setSearchTerm("")} aria-label="Limpiar">
                      <IconX size={14} />
                    </ActionIcon>
                  ) : null
                }
                style={{ minWidth: 280, flex: 1 }}
                size="xs"
              />
              <Select
                placeholder="Asignado"
                value={filterAssigneeId}
                onChange={setFilterAssigneeId}
                data={users.map((u) => ({ value: u.id, label: u.display_name || u.username }))}
                clearable
                searchable
                size="xs"
                style={{ minWidth: 160 }}
                disabled={filterUnassigned}
              />
              <Checkbox
                size="xs"
                label="Sin asignar"
                checked={filterUnassigned}
                onChange={(e) => {
                  const v = e.currentTarget.checked;
                  setFilterUnassigned(v);
                  if (v) setFilterAssigneeId(null);
                }}
              />
              <Select
                placeholder="Solicitante"
                value={filterRequester}
                onChange={setFilterRequester}
                data={requesterOptions}
                clearable
                searchable
                size="xs"
                style={{ minWidth: 160 }}
              />
              <MultiSelect
                placeholder="Tags"
                value={filterTags}
                onChange={setFilterTags}
                data={tagOptions}
                clearable
                searchable
                size="xs"
                style={{ minWidth: 200 }}
              />
              <DatePickerInput
                placeholder="Desde"
                value={filterDateFrom}
                onChange={(v) => setFilterDateFrom(v ? new Date(v as unknown as string) : null)}
                clearable
                size="xs"
                style={{ minWidth: 130 }}
                valueFormat="DD/MM/YY"
              />
              <DatePickerInput
                placeholder="Hasta"
                value={filterDateTo}
                onChange={(v) => setFilterDateTo(v ? new Date(v as unknown as string) : null)}
                clearable
                size="xs"
                style={{ minWidth: 130 }}
                valueFormat="DD/MM/YY"
              />
              <Group gap={4}>
                <Button size="xs" variant="default" onClick={() => {
                  const t = new Date();
                  setFilterDateFrom(t);
                  setFilterDateTo(t);
                }}>Hoy</Button>
                <Button size="xs" variant="default" onClick={() => {
                  const t = new Date();
                  const f = new Date();
                  f.setDate(f.getDate() - 7);
                  setFilterDateFrom(f);
                  setFilterDateTo(t);
                }}>7d</Button>
                <Button size="xs" variant="default" onClick={() => {
                  const t = new Date();
                  const f = new Date();
                  f.setDate(f.getDate() - 30);
                  setFilterDateFrom(f);
                  setFilterDateTo(t);
                }}>30d</Button>
              </Group>
              <StickerPicker
                opened={stickerPickerOpen}
                onClose={() => setStickerPickerOpen(false)}
                onSelect={(emoji) => setActiveSticker(emoji)}
                target={
                  <Button
                    size="xs"
                    variant={activeSticker ? "filled" : "default"}
                    color={activeSticker ? "yellow" : undefined}
                    leftSection={<IconMoodSmile size={14} />}
                    onClick={() => setStickerPickerOpen((v) => !v)}
                  >
                    {activeSticker ? `Modo sticker: ${activeSticker}` : "Stickers"}
                  </Button>
                }
              />
              {activeSticker && (
                <Button
                  size="xs"
                  variant="subtle"
                  color="gray"
                  leftSection={<IconX size={12} />}
                  onClick={() => setActiveSticker(null)}
                >
                  ESC
                </Button>
              )}
              {filtersActive && (
                <Button
                  size="xs"
                  variant="subtle"
                  color="gray"
                  leftSection={<IconX size={12} />}
                  onClick={() => {
                    setSearchTerm("");
                    setFilterAssigneeId(null);
                    setFilterUnassigned(false);
                    setFilterRequester(null);
                    setFilterTags([]);
                    setFilterDateFrom(null);
                    setFilterDateTo(null);
                  }}
                >
                  Limpiar
                </Button>
              )}
            </Group>
            <SortableContext items={boardSortableIds} strategy={horizontalListSortingStrategy}>
              <Group
                align="stretch"
                wrap="nowrap"
                gap="md"
                p="md"
                style={{ flex: 1, overflowX: "auto", overflowY: "hidden" }}
              >
                {boardColumns.map((col) => (
                  <KanbanColumn
                    key={col.id}
                    column={col}
                    cards={cardsByColumn.get(col.id) ?? []}
                    now={now}
                    onAddCard={openCreateCard}
                    onRenameColumn={handleRenameColumn}
                    onResizeColumn={handleResizeColumn}
                    onMoveColumnLocation={handleMoveColumnLocation}
                    onDeleteColumn={handleDeleteColumn}
                    onSetWIPLimit={handleSetWIPLimit}
                    onToggleDone={handleToggleDone}
                    onEditCard={openEditCard}
                    onDeleteCard={handleDeleteCard}
                    onChangeCardColor={handleChangeCardColor}
                    onShowHistory={handleShowHistory}
                    onToggleCardLock={handleToggleCardLock}
                    onAssignCard={handleAssignCard}
                    activeSticker={activeSticker}
                    onAddSticker={handleAddSticker}
                    onRemoveSticker={handleRemoveSticker}
                    onMoveSticker={handleMoveSticker}
                    onCommitSticker={handleCommitSticker}
                    users={users}
                    usersById={usersById}
                  />
                ))}

                <Box style={{ minWidth: 280, maxWidth: 320 }}>
                  {addingCol ? (
                    <Stack gap={4}>
                      <TextInput
                        size="xs"
                        placeholder="Nombre de columna..."
                        value={colName}
                        onChange={(e) => setColName(e.currentTarget.value)}
                        autoFocus
                        onKeyDown={(e) => {
                          if (e.key === "Enter") handleAddColumn();
                          if (e.key === "Escape") {
                            setAddingCol(false);
                            setColName("");
                          }
                        }}
                      />
                      <Group gap={4}>
                        <Button size="xs" onClick={handleAddColumn}>
                          Anadir
                        </Button>
                        <ActionIcon variant="subtle" color="gray" onClick={() => setAddingCol(false)}>
                          <IconX size={14} />
                        </ActionIcon>
                      </Group>
                    </Stack>
                  ) : (
                    <Button
                      variant="light"
                      color="gray"
                      leftSection={<IconPlus size={14} />}
                      onClick={() => setAddingCol(true)}
                    >
                      Anadir columna
                    </Button>
                  )}
                </Box>
              </Group>
            </SortableContext>
          </Box>
          )}
        </AppShell.Main>

      </AppShell>

      <DragOverlay>
        {dragOverlayCard ? (
          <KanbanCard
            card={dragOverlayCard}
            now={now}
            onDelete={() => {}}
            onEdit={() => {}}
            onChangeColor={() => {}}
            onShowHistory={() => {}}
            onToggleLock={() => {}}
            onAssign={() => {}}
            users={users}
            assignee={dragOverlayCard.assignee_id ? usersById.get(dragOverlayCard.assignee_id) : undefined}
            isOverlay
          />
        ) : dragOverlayColumn ? (
          <Box
            style={{
              width: dragOverlayColumn.location === "sidebar" ? 220 : dragOverlayColumn.width,
              padding: 8,
              background: colorBg(""),
              border: `1px solid ${colorBorder("")}`,
              borderRadius: 8,
              opacity: 0.9,
            }}
          >
            <Text fw={600} size="sm">
              {dragOverlayColumn.name}
            </Text>
          </Box>
        ) : null}
      </DragOverlay>
    </DndContext>
  );
}
