import { useCallback, useEffect, useReducer, useRef, useState } from "preact/hooks";
import { api, APIError } from "./api";
import {
  addCard,
  addList,
  boardReducer,
  cloneBoard,
  initialBoardState,
  removeCard,
  removeList,
  updateCard,
  updateList
} from "./board-state";
import { movePosition } from "./drag";
import type { CardPatch, ListPatch } from "./types";
import { BoardCanvas } from "./components/BoardCanvas";
import { CardDetailModal } from "./components/CardDetailModal";
import { Header } from "./components/Header";
import { Toasts } from "./components/Toast";

let toastID = 0;

function friendlyError(error: unknown): string {
  if (error instanceof APIError) return error.message;
  if (error instanceof Error) return error.message;
  return "Could not save that change. Please try again.";
}

export function App() {
  const [state, dispatch] = useReducer(boardReducer, initialBoardState);
  const [managingLists, setManagingLists] = useState(false);
  const [selectedCardID, setSelectedCardID] = useState<number | null>(null);
  const boardRef = useRef(state.board);
  boardRef.current = state.board;

  const selectedList = state.board?.lists.find((list) => list.cards.some((card) => card.id === selectedCardID));
  const selectedCard = selectedList?.cards.find((card) => card.id === selectedCardID);

  useEffect(() => {
    if (selectedCardID !== null && !selectedCard) setSelectedCardID(null);
  }, [selectedCardID, selectedCard]);

  const toast = useCallback((message: string, tone: "success" | "error" | "info" = "info") => {
    const id = ++toastID;
    dispatch({ type: "toast", toast: { id, message, tone } });
    window.setTimeout(() => dispatch({ type: "dismiss", id }), 4200);
  }, []);

  const load = useCallback(async () => {
    dispatch({ type: "loading" });
    try {
      dispatch({ type: "loaded", board: await api.board() });
    } catch (error) {
      dispatch({ type: "error", error: friendlyError(error) });
    }
  }, []);

  useEffect(() => { void load(); }, [load]);

  const mutate = useCallback(async (
    makeOptimistic: (current: NonNullable<typeof boardRef.current>) => NonNullable<typeof boardRef.current>,
    request: () => Promise<void>,
    success?: string
  ) => {
    const before = boardRef.current;
    if (!before) return;
    dispatch({ type: "replace", board: makeOptimistic(cloneBoard(before)) });
    try {
      await request();
      if (success) toast(success, "success");
    } catch (error) {
      dispatch({ type: "replace", board: before });
      toast(friendlyError(error), "error");
      throw error;
    }
  }, [toast]);

  const createList = async (name: string) => {
    const current = boardRef.current;
    if (!current) return;
    const position = Math.max(0, ...current.lists.map((list) => list.position)) + 1024;
    try {
      const { list } = await api.createList(name, position);
      const board = boardRef.current;
      if (board) dispatch({ type: "replace", board: addList(board, list) });
      toast("List added", "success");
    } catch (error) {
      toast(friendlyError(error), "error");
      throw error;
    }
  };

  const editList = async (id: number, patch: ListPatch) => {
    const old = boardRef.current?.lists.find((list) => list.id === id);
    if (!old) return;
    await mutate(
      (board) => updateList(board, { ...old, ...patch }),
      async () => {
        const { list } = await api.patchList(id, patch);
        const current = boardRef.current;
        if (current) dispatch({ type: "replace", board: updateList(current, list) });
      }
    );
  };

  const deleteList = async (id: number) => {
    await mutate((board) => removeList(board, id), () => api.deleteList(id), "List deleted");
  };

  const createCard = async (listID: number, title: string) => {
    const current = boardRef.current;
    const list = current?.lists.find((item) => item.id === listID);
    if (!list) return;
    const position = Math.max(0, ...list.cards.map((card) => card.position)) + 1024;
    try {
      const { card } = await api.createCard(listID, title, "", position);
      const board = boardRef.current;
      if (board) dispatch({ type: "replace", board: addCard(board, card) });
      toast("Card added", "success");
    } catch (error) {
      toast(friendlyError(error), "error");
      throw error;
    }
  };

  const editCard = async (id: number, patch: CardPatch) => {
    const old = boardRef.current?.lists.flatMap((list) => list.cards).find((card) => card.id === id);
    if (!old) return;
    const optimistic = { ...old, ...patch, list_id: patch.list_id ?? old.list_id };
    await mutate(
      (board) => updateCard(board, optimistic),
      async () => {
        const { card } = await api.patchCard(id, patch);
        const current = boardRef.current;
        if (current) dispatch({ type: "replace", board: updateCard(current, card) });
      }
    );
  };

  const deleteCard = async (id: number) => {
    await mutate((board) => removeCard(board, id), () => api.deleteCard(id), "Card deleted");
  };

  const moveCard = async (cardID: number, targetListID: number, dropIndex: number) => {
    const current = boardRef.current;
    const card = current?.lists.flatMap((list) => list.cards).find((item) => item.id === cardID);
    if (!current || !card) return;
    const position = movePosition(current.lists, cardID, targetListID, dropIndex);
    await editCard(cardID, { list_id: targetListID, position });
  };

  return (
    <main class="app-shell">
      <Header
        onReload={load}
        loading={state.loading}
        managingLists={managingLists}
        onToggleManagingLists={() => setManagingLists((active) => !active)}
      />
      {state.loading && !state.board ? <section class="loading-card" aria-live="polite">Loading your Haven board…</section> : null}
      {state.error ? (
        <section class="fatal-state">
          <h1>Havenllo could not reach its board</h1>
          <p>{state.error}</p>
          <button type="button" class="button button-primary" onClick={() => void load()}>Try again</button>
        </section>
      ) : null}
      {state.board ? (
        <BoardCanvas
          lists={state.board?.lists ?? []}
          onCreateList={createList}
          onEditList={editList}
          onDeleteList={deleteList}
          onCreateCard={createCard}
          onMoveCard={moveCard}
          onOpenCard={setSelectedCardID}
          managingLists={managingLists}
        />
      ) : null}
      {selectedCard && selectedList ? (
        <CardDetailModal
          card={selectedCard}
          listName={selectedList.name}
          lists={state.board?.lists ?? []}
          onClose={() => setSelectedCardID(null)}
          onEdit={(patch) => editCard(selectedCard.id, patch)}
          onDelete={() => deleteCard(selectedCard.id)}
        />
      ) : null}
      <Toasts toasts={state.toasts} onDismiss={(id) => dispatch({ type: "dismiss", id })} />
    </main>
  );
}
