import { contextBridge, ipcRenderer } from "electron";
import { electronAPI } from "@electron-toolkit/preload";
import type { RuntimeConfigResult } from "../shared/runtime-config";
import {
  isNavigationGesture,
  NAVIGATION_GESTURE_CHANNEL,
  type NavigationGesture,
} from "../shared/navigation-gestures";

// Synchronously fetch app metadata from main at preload time so the renderer
// can pass it into CoreProvider during the initial render — the alternative
// (async ipc.invoke) would race the ApiClient construction in initCore and
// the first few HTTP requests would go out without X-Client-Version/OS.
function fetchAppInfo(): { version: string; os: "macos" | "windows" | "linux" | "unknown" } {
  try {
    const info = ipcRenderer.sendSync("app:get-info") as
      | { version: string; os: "macos" | "windows" | "linux" | "unknown" }
      | undefined;
    if (info && typeof info.version === "string" && typeof info.os === "string") return info;
  } catch {
    // fall through
  }
  // Fallback: derive OS from process.platform; version unknown.
  const p = process.platform;
  const os: "macos" | "windows" | "linux" | "unknown" =
    p === "darwin" ? "macos" : p === "win32" ? "windows" : p === "linux" ? "linux" : "unknown";
  return { version: "unknown", os };
}

function fetchRuntimeConfig(): RuntimeConfigResult {
  try {
    const result = ipcRenderer.sendSync("runtime-config:get") as RuntimeConfigResult | undefined;
    if (result && typeof result === "object" && "ok" in result) return result;
  } catch (err) {
    return {
      ok: false,
      error: {
        message: err instanceof Error ? err.message : String(err),
      },
    };
  }
  return { ok: false, error: { message: "Runtime config unavailable" } };
}

const appInfo = fetchAppInfo();
const runtimeConfig = fetchRuntimeConfig();

// Read the OS-preferred locale that main injected via additionalArguments.
// Zero IPC, zero blocking — process.argv is populated before preload runs.
function fetchSystemLocale(): string {
  const arg = process.argv.find((a) => a.startsWith("--multica-locale="));
  return arg?.split("=")[1] ?? "en";
}

const systemLocale = fetchSystemLocale();

const desktopAPI = {
  /** App version + normalized OS. Read once at preload time so the renderer
   *  can use it synchronously when initializing the API client. */
  appInfo,
  /** OS-preferred locale (BCP 47), passed from main via additionalArguments.
   *  Used by the renderer's LocaleAdapter as the system-preference signal. */
  systemLocale,
  /** Subscribe to OS language changes detected after boot. The renderer
   *  decides whether to act (no-op when the user has an explicit Settings
   *  choice). Returns an unsubscribe function. */
  onSystemLocaleChanged: (callback: (locale: string) => void) => {
    const handler = (_event: Electron.IpcRendererEvent, locale: string) =>
      callback(locale);
    ipcRenderer.on("locale:system-changed", handler);
    return () => {
      ipcRenderer.removeListener("locale:system-changed", handler);
    };
  },
  /** Validated runtime endpoint config, or a blocking config error. */
  runtimeConfig,
  /** Listen for auth token delivered via deep link */
  onAuthToken: (callback: (token: string) => void) => {
    const handler = (_event: Electron.IpcRendererEvent, token: string) =>
      callback(token);
    ipcRenderer.on("auth:token", handler);
    return () => {
      ipcRenderer.removeListener("auth:token", handler);
    };
  },
  /** Listen for invitation IDs delivered via deep link */
  onInviteOpen: (callback: (invitationId: string) => void) => {
    const handler = (_event: Electron.IpcRendererEvent, invitationId: string) =>
      callback(invitationId);
    ipcRenderer.on("invite:open", handler);
    return () => {
      ipcRenderer.removeListener("invite:open", handler);
    };
  },
  /** Open a URL in the default browser */
  openExternal: (url: string) => ipcRenderer.invoke("shell:openExternal", url),
  /** Download a file by URL through Electron's native download system.
   *  Shows a save dialog and saves to disk. Unlike openExternal, this
   *  avoids browser rendering of HTML files on Linux.
   *  On non-desktop platforms this property is undefined. */
  downloadURL: (url: string) => ipcRenderer.invoke("file:download-url", url),
  /** Toggle immersive mode — hide macOS traffic lights for full-screen modals */
  setImmersiveMode: (immersive: boolean) =>
    ipcRenderer.invoke("window:setImmersive", immersive),
  /**
   * Show a native OS notification for a new inbox item. Fired from the
   * renderer only when the app is unfocused — in-focus feedback is the
   * inbox sidebar's unread styling. `slug`, `itemId`, and `issueKey` are
   * all round-tripped on click: slug pins routing to the source workspace
   * (the user may switch workspaces before clicking the banner), itemId
   * lets the renderer mark the row read, issueKey maps to the inbox URL
   * param.
   */
  showNotification: (payload: {
    slug: string;
    itemId: string;
    issueKey: string;
    title: string;
    body: string;
  }) => ipcRenderer.send("notification:show", payload),
  /**
   * Update the OS dock / taskbar unread badge. Pass 0 to clear. Values
   * above 99 render as "99+" (capping is handled in the main process).
   */
  setUnreadBadge: (count: number) =>
    ipcRenderer.send("badge:set", Math.max(0, Math.floor(count))),
  /**
   * Subscribe to "open this inbox row" requests sent by the main process
   * when the user clicks an OS notification banner. Returns an unsubscribe
   * function. The payload echoes the `slug`, `itemId`, and `issueKey` that
   * were passed to `showNotification`.
   */
  onInboxOpen: (
    callback: (payload: {
      slug: string;
      itemId: string;
      issueKey: string;
    }) => void,
  ) => {
    const handler = (
      _event: Electron.IpcRendererEvent,
      payload: { slug: string; itemId: string; issueKey: string },
    ) => callback(payload);
    ipcRenderer.on("inbox:open", handler);
    return () => {
      ipcRenderer.removeListener("inbox:open", handler);
    };
  },
  /** Listen for native macOS back/forward swipe gestures. */
  onNavigationGesture: (callback: (gesture: NavigationGesture) => void) => {
    const handler = (_event: Electron.IpcRendererEvent, gesture: unknown) => {
      if (isNavigationGesture(gesture)) callback(gesture);
    };
    ipcRenderer.on(NAVIGATION_GESTURE_CHANNEL, handler);
    return () => {
      ipcRenderer.removeListener(NAVIGATION_GESTURE_CHANNEL, handler);
    };
  },
  /** Switch the frontend API to a remote server. The renderer must reload
   *  after this returns so the new URLs take effect. */
  switchRuntimeConfig: (config: {
    apiUrl: string;
    wsUrl: string;
    authToken?: string;
    workspaceId?: string;
  }): Promise<void> => ipcRenderer.invoke("runtime-config:switch", config),
  /** Retrieve the persisted auth token from remote_connection.json. */
  getRemoteAuthToken: (): Promise<string | null> =>
    ipcRenderer.invoke("runtime-config:get-auth-token"),
  /** Retrieve the list of previously connected remote servers. */
  getRemoteHistory: (): Promise<Array<{ apiUrl: string; authToken?: string; workspaceId?: string; label?: string; lastConnected: string }>> =>
    ipcRenderer.invoke("runtime-config:get-remote-history"),
  /** Remove a remote server from history. */
  removeRemoteServer: (apiUrl: string): Promise<Array<{ apiUrl: string; authToken?: string; workspaceId?: string; label?: string; lastConnected: string }>> =>
    ipcRenderer.invoke("runtime-config:remove-remote-server", apiUrl),
  /** Disconnect from the remote server and restore the local backend. */
  disconnectRuntimeConfig: (): Promise<void> =>
    ipcRenderer.invoke("runtime-config:disconnect"),
  /** Listen for runtime config changes pushed from the main process. */
  onRuntimeConfigChanged: (callback: (config: RuntimeConfigResult) => void) => {
    const handler = (_event: Electron.IpcRendererEvent, config: RuntimeConfigResult) =>
      callback(config);
    ipcRenderer.on("runtime-config:changed", handler);
    return () => {
      ipcRenderer.removeListener("runtime-config:changed", handler);
    };
  },
  /** Open the OS folder picker and return the chosen absolute path. */
  pickDirectory: (defaultPath?: string) =>
    ipcRenderer.invoke("local-directory:pick", defaultPath),
  /** Validate that a path is an existing readable+writable directory. */
  validateLocalDirectory: (path: string) =>
    ipcRenderer.invoke("local-directory:validate", path),
};

interface DaemonStatus {
  state: "running" | "stopped" | "starting" | "stopping" | "installing_cli" | "cli_not_found";
  pid?: number;
  uptime?: string;
  daemonId?: string;
  deviceName?: string;
  agents?: string[];
  workspaceCount?: number;
  profile?: string;
  serverUrl?: string;
}

const daemonAPI = {
  start: (): Promise<{ success: boolean; error?: string }> =>
    ipcRenderer.invoke("daemon:start"),
  stop: (): Promise<{ success: boolean; error?: string }> =>
    ipcRenderer.invoke("daemon:stop"),
  restart: (): Promise<{ success: boolean; error?: string }> =>
    ipcRenderer.invoke("daemon:restart"),
  /** Add a remote server for compute sharing without daemon restart. */
  addRemoteServer: (serverUrl: string, token: string, workspaceId: string): Promise<{ status: string; runtimes: unknown[] }> =>
    ipcRenderer.invoke("daemon:add-remote-server", serverUrl, token, workspaceId),
  /** Remove a remote server and deregister its runtimes. */
  removeRemoteServer: (serverUrl: string): Promise<void> =>
    ipcRenderer.invoke("daemon:remove-remote-server", serverUrl),
  getStatus: (): Promise<DaemonStatus> =>
    ipcRenderer.invoke("daemon:get-status"),
  getHostName: (): Promise<string> =>
    ipcRenderer.invoke("daemon:get-host-name"),
  onStatusChange: (callback: (status: DaemonStatus) => void) => {
    const handler = (_: unknown, status: DaemonStatus) => callback(status);
    ipcRenderer.on("daemon:status", handler);
    return () => ipcRenderer.removeListener("daemon:status", handler);
  },
  setTargetApiUrl: (url: string): Promise<void> =>
    ipcRenderer.invoke("daemon:set-target-api-url", url),
  syncToken: (token: string, userId: string): Promise<void> =>
    ipcRenderer.invoke("daemon:sync-token", token, userId),
  clearToken: (): Promise<void> =>
    ipcRenderer.invoke("daemon:clear-token"),
  isCliInstalled: (): Promise<boolean> =>
    ipcRenderer.invoke("daemon:is-cli-installed"),
  getPrefs: (): Promise<{ autoStart: boolean; autoStop: boolean }> =>
    ipcRenderer.invoke("daemon:get-prefs"),
  setPrefs: (prefs: Partial<{ autoStart: boolean; autoStop: boolean }>): Promise<{ autoStart: boolean; autoStop: boolean }> =>
    ipcRenderer.invoke("daemon:set-prefs", prefs),
  autoStart: (): Promise<void> =>
    ipcRenderer.invoke("daemon:auto-start"),
  retryInstall: (): Promise<void> =>
    ipcRenderer.invoke("daemon:retry-install"),
  startLogStream: () => ipcRenderer.send("daemon:start-log-stream"),
  stopLogStream: () => ipcRenderer.send("daemon:stop-log-stream"),
  onLogLine: (callback: (line: string) => void) => {
    const handler = (_: unknown, line: string) => callback(line);
    ipcRenderer.on("daemon:log-line", handler);
    return () => ipcRenderer.removeListener("daemon:log-line", handler);
  },
  openLogFile: (): Promise<{ success: boolean; error?: string }> =>
    ipcRenderer.invoke("daemon:open-log-file"),
};

const updaterAPI = {
  onUpdateAvailable: (callback: (info: { version: string; releaseNotes?: string }) => void) => {
    const handler = (_: unknown, info: { version: string; releaseNotes?: string }) => callback(info);
    ipcRenderer.on("updater:update-available", handler);
    return () => ipcRenderer.removeListener("updater:update-available", handler);
  },
  onDownloadProgress: (callback: (progress: { percent: number }) => void) => {
    const handler = (_: unknown, progress: { percent: number }) => callback(progress);
    ipcRenderer.on("updater:download-progress", handler);
    return () => ipcRenderer.removeListener("updater:download-progress", handler);
  },
  onUpdateDownloaded: (
    callback: (info: { version: string; releaseNotes?: string }) => void,
  ) => {
    const handler = (_: unknown, info: { version: string; releaseNotes?: string }) =>
      callback(info);
    ipcRenderer.on("updater:update-downloaded", handler);
    return () => ipcRenderer.removeListener("updater:update-downloaded", handler);
  },
  downloadUpdate: () => ipcRenderer.invoke("updater:download"),
  installUpdate: () => ipcRenderer.invoke("updater:install"),
  checkForUpdates: (): Promise<
    | { ok: true; currentVersion: string; latestVersion: string; available: boolean }
    | { ok: false; error: string }
  > => ipcRenderer.invoke("updater:check"),
};

if (process.contextIsolated) {
  contextBridge.exposeInMainWorld("electron", electronAPI);
  contextBridge.exposeInMainWorld("desktopAPI", desktopAPI);
  contextBridge.exposeInMainWorld("daemonAPI", daemonAPI);
  contextBridge.exposeInMainWorld("updater", updaterAPI);
} else {
  // @ts-expect-error - fallback for non-isolated context
  window.electron = electronAPI;
  // @ts-expect-error - fallback for non-isolated context
  window.desktopAPI = desktopAPI;
  // @ts-expect-error - fallback for non-isolated context
  window.daemonAPI = daemonAPI;
  // @ts-expect-error - fallback for non-isolated context
  window.updater = updaterAPI;
}
