import { invoke, Channel } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import type { Group, WorkspaceView } from "./layoutTypes";

export interface WorkspaceStatus {
  workspace_id: string;
  path: string;
  surfaces: string[];
}

export async function openWorkspace(path: string): Promise<string> {
  const data = await invoke<{ workspace_id: string }>("open", { path });
  return data.workspace_id;
}

export async function newSurface(
  workspaceId: string,
  cols: number,
  rows: number,
  command?: string,
  args: string[] = []
): Promise<string> {
  const data = await invoke<{ surface_id: string }>("new_surface", {
    workspaceId,
    command: command ?? null,
    args,
    cols,
    rows,
  });
  return data.surface_id;
}

export async function sendInput(surfaceId: string, data: Uint8Array): Promise<void> {
  await invoke("input", { surfaceId, data: Array.from(data) });
}

export async function resizeSurface(surfaceId: string, cols: number, rows: number): Promise<void> {
  await invoke("resize", { surfaceId, cols, rows });
}

export interface AttachResult {
  snapshot: string;
  cols: number;
  rows: number;
  cursor_row?: number;
  cursor_col?: number;
  stopped?: boolean;
}

export async function attachSurface(
  surfaceId: string,
  onOutput: (bytes: Uint8Array) => void
): Promise<AttachResult> {
  const channel = new Channel<number[]>();
  channel.onmessage = (msg) => onOutput(new Uint8Array(msg));
  return await invoke<AttachResult>("attach", { surfaceId, onOutput: channel });
}

export async function detachSurface(surfaceId: string): Promise<void> {
  await invoke("detach", { surfaceId });
}

export async function getStatus(): Promise<WorkspaceStatus[]> {
  const data = await invoke<{ workspaces: WorkspaceStatus[] }>("status");
  return data.workspaces;
}

export interface EventRecord {
  id: number;
  surface_id: string;
  workspace_id: string;
  workspace_name: string;
  agent_label: string | null;
  kind: "done" | "wait" | "error" | "exit";
  ts: number;
  read: boolean;
}

export type MarkReadTarget =
  | { target: "all" }
  | { target: "ids"; value: number[] }
  | { target: "surface"; value: string };

export async function getEventLog(limit?: number): Promise<{ events: EventRecord[]; unread: number }> {
  return await invoke<{ events: EventRecord[]; unread: number }>("event_log", { limit: limit ?? null });
}

export async function markEventsRead(target: MarkReadTarget): Promise<void> {
  await invoke("mark_read", { target });
}

export async function clearEvents(): Promise<void> {
  await invoke("clear_events");
}

export type DaemonEvt =
  | { evt: "exit"; data: { surface_id: string; code: number } }
  | { evt: "surface_created"; data: { surface_id: string; workspace_id: string } }
  | { evt: "surface_closed"; data: { surface_id: string } }
  | { evt: "state"; data: { surface_id: string; state: import("./layoutTypes").SurfaceState } }
  | { evt: "layout_changed"; data: { workspace_id: string } }
  | { evt: "workspace_changed"; data: unknown }
  | { evt: "groups_changed"; data: unknown }
  | { evt: "config_changed"; data: { config: ConfigView } }
  | { evt: "event"; data: { record: EventRecord } }
  | { evt: "events_read"; data: { ids: number[] } }
  | { evt: "events_cleared"; data: unknown };

export function onDaemonEvent(handler: (evt: DaemonEvt) => void): Promise<() => void> {
  return listen<DaemonEvt>("spacesh:evt", (e) => handler(e.payload));
}

export async function focusSurface(surfaceId: string): Promise<void> {
  await invoke("focus", { surfaceId });
}

export function onDaemonRawEvent(name: string, handler: () => void): Promise<() => void> {
  return listen(name, () => handler());
}

// ---- M2 additions ----

export interface StatusResult {
  groups: Group[];
  workspaces: WorkspaceView[];
}

export async function getStatusFull(): Promise<StatusResult> {
  return await invoke<StatusResult>("status");
}

export async function splitSurface(surfaceId: string, dir: "right" | "down", command?: string, args: string[] = []): Promise<string> {
  const data = await invoke<{ surface_id: string }>("split_surface", { surfaceId, dir, command: command ?? null, args });
  return data.surface_id;
}

export async function setRatios(workspaceId: string, nodePath: number[], ratios: number[]): Promise<void> {
  await invoke("set_ratios", { workspaceId, nodePath, ratios });
}

export async function moveSurface(surfaceId: string, targetSurfaceId: string, edge: "left" | "right" | "top" | "bottom"): Promise<void> {
  await invoke("move_surface", { surfaceId, targetSurfaceId, edge });
}

export async function applyPreset(workspaceId: string, presetId: string, slots: { command?: string; args?: string[] }[]): Promise<string[]> {
  const data = await invoke<{ surface_ids: string[] }>("apply_preset", {
    workspaceId, presetId,
    slots: slots.map((s) => ({ command: s.command ?? null, args: s.args ?? [] })),
  });
  return data.surface_ids;
}

export async function restartSurface(surfaceId: string, resume = false): Promise<void> {
  await invoke("restart_surface", { surfaceId, resume });
}

export async function closeWorkspaceCmd(workspaceId: string): Promise<void> {
  await invoke("close_workspace", { workspaceId });
}

export async function setWorkspaceMeta(workspaceId: string, meta: { name?: string; groupId?: string | null; unread?: boolean; order?: number; pinned?: boolean }): Promise<void> {
  await invoke("set_workspace_meta", {
    workspaceId,
    name: meta.name ?? null,
    groupId: meta.groupId === undefined ? null : meta.groupId,
    unread: meta.unread ?? null,
    order: meta.order ?? null,
    pinned: meta.pinned ?? null,
  });
}

export async function createGroup(name: string, color: string): Promise<string> {
  const data = await invoke<{ group_id: string }>("create_group", { name, color });
  return data.group_id;
}

export async function setGroup(groupId: string, meta: { name?: string; color?: string; order?: number }): Promise<void> {
  await invoke("set_group", { groupId, name: meta.name ?? null, color: meta.color ?? null, order: meta.order ?? null });
}

export async function deleteGroup(groupId: string): Promise<void> {
  await invoke("delete_group", { groupId });
}

export async function closeSurfaceCmd(surfaceId: string): Promise<void> {
  await invoke("close_surface", { surfaceId });
}

export interface DaemonHealth { version: string; build?: string; pid: number; started_at_ms: number }

export async function getHealth(): Promise<DaemonHealth> {
  return await invoke<DaemonHealth>("health");
}

export interface UpdateInfo { current: string; latest: string; has_update: boolean; url: string }

export async function checkUpdate(): Promise<UpdateInfo> {
  return await invoke<UpdateInfo>("check_update");
}

export async function openExternal(url: string): Promise<void> {
  await invoke("open_external", { url });
}

export async function listFonts(): Promise<string[]> {
  return await invoke<string[]>("list_fonts");
}

/** Whether spaceshell.app has Full Disk Access (terminals inherit its TCC grants). */
export async function hasFullDiskAccess(): Promise<boolean> {
  try { return await invoke<boolean>("has_full_disk_access"); } catch { return true; }
}

/** Deep-link System Settings → Privacy & Security → Full Disk Access. */
export async function openFullDiskAccessSettings(): Promise<void> {
  try { await invoke("open_full_disk_access_settings"); } catch { /* settings pane unavailable */ }
}

/** Which of the given CLI candidates are actually installed on the daemon's spawn PATH. */
export async function whichAgents(candidates: string[]): Promise<string[]> {
  const data = await invoke<{ available: string[] }>("which_agents", { candidates });
  return data.available;
}

export async function setZoom(workspaceId: string, surfaceId: string | null): Promise<void> {
  await invoke("set_zoom", { workspaceId, surfaceId });
}

// ---- Settings ----

export interface ConfigView {
  default_shell: string;
  font_family: string;
  font_size: number;
  theme: "dark" | "light";
  accent: string;
  background: string;
  background_image: string;
  log_shell_commands: boolean;
}

export async function getConfig(): Promise<ConfigView> {
  return await invoke<ConfigView>("get_config");
}

export async function setConfig(patch: Partial<Pick<ConfigView, "default_shell" | "font_family" | "font_size" | "theme" | "accent" | "background" | "background_image" | "log_shell_commands">>): Promise<void> {
  await invoke("set_config", {
    defaultShell: patch.default_shell ?? null,
    fontFamily: patch.font_family ?? null,
    fontSize: patch.font_size ?? null,
    theme: patch.theme ?? null,
    accent: patch.accent ?? null,
    background: patch.background ?? null,
    backgroundImage: patch.background_image ?? null,
    logShellCommands: patch.log_shell_commands ?? null,
  });
}

/** Read a local image file into a `data:` URL for use as a CSS background. */
export async function readImageDataUrl(path: string): Promise<string> {
  return await invoke<string>("read_image_data_url", { path });
}

export async function shutdownDaemon(): Promise<void> {
  try { await invoke("shutdown_daemon"); } catch { /* connection drops as the daemon exits — expected */ }
}

export async function restartDaemon(): Promise<void> {
  await shutdownDaemon();
  // Let the old process exit; the next request triggers the bridge's
  // ensure_daemon respawn (or launchd KeepAlive) and reconnects.
  await new Promise((r) => setTimeout(r, 600));
  try { await getHealth(); } catch { /* reconnect loop will retry */ }
}
