import type {
  Board,
  Card,
  CardHistoryResponse,
  CardMessage,
  Column,
  Metrics,
  MetricsFilter,
  Sticker,
  User,
} from "./types";
import { fetchJSON as registryFetchJSON, HTTPError } from "@fn_library/infra/fetch_json";

export { HTTPError };

const BASE = "/api";

function fetchJSON<T>(path: string, init?: RequestInit): Promise<T> {
  return registryFetchJSON<T>(path, init, BASE);
}

export function getBoard(): Promise<Board> {
  return fetchJSON("/board");
}

export function getFlags(): Promise<Record<string, boolean>> {
  return fetchJSON("/flags");
}

export function createColumn(name: string): Promise<Column> {
  return fetchJSON("/columns", { method: "POST", body: JSON.stringify({ name }) });
}

export interface UpdateColumnInput {
  name?: string;
  position?: number;
  location?: "board" | "sidebar";
  width?: number;
  wip_limit?: number;
  is_done?: boolean;
  max_time_minutes?: number;
}

export function updateColumn(id: string, patch: UpdateColumnInput): Promise<void> {
  return fetchJSON(`/columns/${id}`, {
    method: "PATCH",
    body: JSON.stringify(patch),
  });
}

export function deleteColumn(id: string): Promise<void> {
  return fetchJSON(`/columns/${id}`, { method: "DELETE" });
}

export function reorderColumns(ids: string[]): Promise<void> {
  return fetchJSON("/columns/reorder", { method: "POST", body: JSON.stringify({ ids }) });
}

export interface CreateCardInput {
  column_id: string;
  requester?: string;
  title: string;
  description?: string;
  assignee_id?: string | null;
  tags?: string[];
}

export function createCard(input: CreateCardInput): Promise<Card> {
  return fetchJSON("/cards", { method: "POST", body: JSON.stringify(input) });
}

export interface UpdateCardInput {
  requester?: string;
  title?: string;
  description?: string;
  color?: string;
  locked?: boolean;
  assignee_id?: string | null;
  tags?: string[];
  deadline?: string | null;
}

export function updateCard(id: string, patch: UpdateCardInput): Promise<void> {
  return fetchJSON(`/cards/${id}`, { method: "PATCH", body: JSON.stringify(patch) });
}

export function deleteCard(id: string): Promise<void> {
  return fetchJSON(`/cards/${id}`, { method: "DELETE" });
}

export function updateCardStickers(id: string, stickers: Sticker[]): Promise<void> {
  return fetchJSON(`/cards/${id}/stickers`, {
    method: "PUT",
    body: JSON.stringify({ stickers }),
  });
}

export function listTrash(): Promise<Card[]> {
  return fetchJSON("/trash");
}

export function restoreCard(id: string): Promise<void> {
  return fetchJSON(`/cards/${id}/restore`, { method: "POST" });
}

export function purgeCard(id: string): Promise<void> {
  return fetchJSON(`/cards/${id}/purge`, { method: "DELETE" });
}

export function moveCard(id: string, column_id: string, ordered_ids: string[]): Promise<void> {
  return fetchJSON(`/cards/${id}/move`, {
    method: "POST",
    body: JSON.stringify({ column_id, ordered_ids }),
  });
}

export function cardHistory(id: string): Promise<CardHistoryResponse> {
  return fetchJSON(`/cards/${id}/history`);
}

export function listCardMessages(id: string): Promise<CardMessage[]> {
  return fetchJSON(`/cards/${id}/messages`);
}

export function createCardMessage(id: string, body: string): Promise<CardMessage> {
  return fetchJSON(`/cards/${id}/messages`, {
    method: "POST",
    body: JSON.stringify({ body }),
  });
}

export function deleteCardMessage(cardId: string, messageId: string): Promise<void> {
  return fetchJSON(`/cards/${cardId}/messages/${messageId}`, { method: "DELETE" });
}

export function duplicateCard(id: string): Promise<Card> {
  return fetchJSON(`/cards/${id}/duplicate`, { method: "POST" });
}

export interface ChatMessage {
  role: "user" | "assistant";
  content: string;
}

export interface ChatToolCall {
  tool: string;
  ok: boolean;
  error?: string;
  input?: unknown;
}

// WebSocket streaming events emitted by /api/chat/ws.
export type ChatStreamEvent =
  | { type: "delta"; text: string }
  | { type: "tool_use"; tool_id: string; tool: string; input?: unknown }
  | { type: "tool_result"; tool_id: string; result?: string; is_error?: boolean }
  | { type: "result"; text?: string; is_error?: boolean }
  | { type: "done"; board_changed?: boolean }
  | { type: "error"; error: string };

// chatWSURL builds the absolute ws:// or wss:// URL of the streaming endpoint.
export function chatWSURL(): string {
  const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
  return `${proto}//${window.location.host}/api/chat/ws`;
}

// streamChat opens a WebSocket, sends the message history, and streams events
// to onEvent. Returns a Promise that resolves when the server closes the
// connection (after a "done" event) and rejects on transport errors.
export function streamChat(
  messages: ChatMessage[],
  onEvent: (ev: ChatStreamEvent) => void,
  signal?: AbortSignal
): Promise<void> {
  return new Promise((resolve, reject) => {
    const ws = new WebSocket(chatWSURL());
    let settled = false;
    const finish = (err?: Error) => {
      if (settled) return;
      settled = true;
      try {
        ws.close();
      } catch {
        /* ignore */
      }
      if (err) reject(err);
      else resolve();
    };

    if (signal) {
      const abort = () => finish(new Error("aborted"));
      if (signal.aborted) {
        abort();
        return;
      }
      signal.addEventListener("abort", abort, { once: true });
    }

    ws.onopen = () => {
      ws.send(JSON.stringify({ messages }));
    };
    ws.onmessage = (e) => {
      try {
        const ev = JSON.parse(typeof e.data === "string" ? e.data : "") as ChatStreamEvent;
        onEvent(ev);
        if (ev.type === "done" || ev.type === "error") {
          finish(ev.type === "error" ? new Error(ev.error) : undefined);
        }
      } catch (err) {
        finish(err as Error);
      }
    };
    ws.onerror = () => finish(new Error("websocket error"));
    ws.onclose = () => finish();
  });
}

export function login(username: string, password: string): Promise<User> {
  return fetchJSON("/auth/login", {
    method: "POST",
    body: JSON.stringify({ username, password }),
  });
}

export function register(username: string, password: string, display_name?: string): Promise<User> {
  return fetchJSON("/auth/register", {
    method: "POST",
    body: JSON.stringify({ username, password, display_name }),
  });
}

export function logout(): Promise<void> {
  return fetchJSON("/auth/logout", { method: "POST" });
}

export function getMe(): Promise<User> {
  return fetchJSON("/me");
}

export function updateMe(patch: { color?: string }): Promise<User> {
  return fetchJSON("/me", { method: "PATCH", body: JSON.stringify(patch) });
}

export function listUsers(): Promise<User[]> {
  return fetchJSON("/users");
}

export function listTags(): Promise<string[]> {
  return fetchJSON("/tags");
}

export function listRequesters(): Promise<string[]> {
  return fetchJSON("/requesters");
}

export function getMetrics(f: MetricsFilter): Promise<Metrics> {
  const qs = new URLSearchParams();
  if (f.from) qs.set("from", f.from);
  if (f.to) qs.set("to", f.to);
  if (f.assignee_id) qs.set("assignee_id", f.assignee_id);
  if (f.requester) qs.set("requester", f.requester);
  if (f.tags && f.tags.length > 0) qs.set("tags", f.tags.join(","));
  const q = qs.toString();
  return fetchJSON(`/metrics${q ? `?${q}` : ""}`);
}
