// Preload bridge. Exposes a single `window.electronAPI` object to the
// renderer that mirrors the CHANNELS surface from ipc-types.ts. Each
// CHANNEL becomes a typed method that calls `ipcRenderer.invoke`; the
// three event channels (shortcut fired/released + updater progress) get
// `on*` subscribers that return an unsubscribe function.
//
// contextIsolation is ON so the renderer never sees `ipcRenderer` or
// Node directly — only this curated surface.

import { contextBridge, ipcRenderer } from 'electron';

import {
  CHANNELS,
  ELECTRON_RUNTIME_MARKER,
  type AudioLoopbackChunk,
  type AudioLoopbackStartResult,
  type FsPath,
  type FsRenameArgs,
  type FsWriteArgs,
  type NotifyArgs,
  type ScreenSource,
  type SecureStoreHandle,
  type SecureStoreOpenArgs,
  type ShortcutEvent,
  type ShortcutRegisterArgs,
  type SqlExecuteResult,
  type SqlSelectResult,
  type UpdateCheckResult,
  type UpdateProgress,
} from './ipc-types';
// Static import of the desktop package.json so the bundler inlines the
// version string at build time. The previous `process.env.npm_package_-
// version` approach worked in dev (pnpm sets it as a script env var) but
// fell back to '0.0.0' in packaged builds — every installed user got
// flagged as "Update verfügbar" against their own actually-current
// version. resolveJsonModule + esModuleInterop are on in tsconfig.node.
import pkg from '../package.json';

type Unsubscribe = () => void;

function on<T>(channel: string, cb: (payload: T) => void): Unsubscribe {
  const handler = (_evt: Electron.IpcRendererEvent, payload: T): void => cb(payload);
  ipcRenderer.on(channel, handler);
  return () => ipcRenderer.removeListener(channel, handler);
}

const api = {
  platform: ELECTRON_RUNTIME_MARKER,
  osPlatform: process.platform as NodeJS.Platform,
  appVersion: pkg.version,

  // Screen sources ---------------------------------------------------------
  getScreenSources: (): Promise<ScreenSource[]> => ipcRenderer.invoke(CHANNELS.SCREEN_GET_SOURCES),
  getScreenThumbnail: (sourceId: string): Promise<string | null> =>
    ipcRenderer.invoke(CHANNELS.SCREEN_GET_THUMBNAIL, sourceId),
  /** Tell main which source the user picked in our Discord-style modal,
   *  BEFORE calling getDisplayMedia. The display-media handler reads this
   *  and routes the matching desktopCapturer Source into LiveKit. Pass
   *  null on cancel/error to clear the pending state. */
  setPendingShareSource: (sourceId: string | null): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.SCREEN_SET_PENDING_SOURCE, sourceId),

  // Audio loopback ---------------------------------------------------------
  resolveLoopbackSource: (): Promise<{ sourceId: string } | null> =>
    ipcRenderer.invoke(CHANNELS.AUDIO_LOOPBACK_RESOLVE_SOURCE),

  /** Native WASAPI process-loopback (Windows only). Captures all system
   *  audio EXCEPT our own PID tree so peers don't hear themselves echoed
   *  back. Throws on platforms where the addon isn't available — caller
   *  is expected to fall back to the renderer-driven getUserMedia path
   *  (see lib/screenAudio.ts). */
  audioLoopback: {
    start: (): Promise<AudioLoopbackStartResult> =>
      ipcRenderer.invoke(CHANNELS.AUDIO_LOOPBACK_START),
    /** Window-share variant: capture only the picked window's process
     *  tree (INCLUDE_TARGET_PROCESS_TREE). hwnd is the decimal HWND
     *  parsed from desktopCapturer's `window:<HWND>:0` source id. */
    startForWindow: (hwnd: number): Promise<AudioLoopbackStartResult> =>
      ipcRenderer.invoke(CHANNELS.AUDIO_LOOPBACK_START_FOR_WINDOW, { hwnd }),
    stop: (captureId: number): Promise<void> =>
      ipcRenderer.invoke(CHANNELS.AUDIO_LOOPBACK_STOP, captureId),
    onChunk: (cb: (payload: AudioLoopbackChunk) => void): Unsubscribe =>
      on<AudioLoopbackChunk>(CHANNELS.AUDIO_LOOPBACK_CHUNK, cb),
  },

  // Shortcuts --------------------------------------------------------------
  registerShortcut: (args: ShortcutRegisterArgs): Promise<boolean> =>
    ipcRenderer.invoke(CHANNELS.SHORTCUT_REGISTER, args),
  unregisterShortcut: (id: string): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.SHORTCUT_UNREGISTER, id),
  isShortcutRegistered: (id: string): Promise<boolean> =>
    ipcRenderer.invoke(CHANNELS.SHORTCUT_IS_REGISTERED, id),
  onShortcutFired: (cb: (evt: ShortcutEvent) => void): Unsubscribe =>
    on<ShortcutEvent>(CHANNELS.SHORTCUT_EVT_FIRED, cb),
  onShortcutReleased: (cb: (evt: ShortcutEvent) => void): Unsubscribe =>
    on<ShortcutEvent>(CHANNELS.SHORTCUT_EVT_RELEASED, cb),

  // Notifications ----------------------------------------------------------
  notify: (args: NotifyArgs): Promise<void> => ipcRenderer.invoke(CHANNELS.NOTIFY_SHOW, args),
  getNotificationPermission: (): Promise<'granted' | 'denied' | 'default'> =>
    ipcRenderer.invoke(CHANNELS.NOTIFY_PERMISSION),

  // Tray -------------------------------------------------------------------
  // `badgeDataUrl` (optional): renderer-painted PNG (data:image/png;base64)
  // that main applies as the Windows taskbar overlay icon. We render in the
  // renderer because main has no Canvas2D; passing a finished image avoids
  // bundling a native canvas backend just for a 32×32 badge.
  setTrayUnread: (count: number, badgeDataUrl?: string | null): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.TRAY_UNREAD, count, badgeDataUrl ?? null),

  // Secure store -----------------------------------------------------------
  secureStoreOpen: (args: SecureStoreOpenArgs): Promise<SecureStoreHandle> =>
    ipcRenderer.invoke(CHANNELS.SECURE_STORE_OPEN, args),
  secureStoreGet: (handle: string, key: string): Promise<string | null> =>
    ipcRenderer.invoke(CHANNELS.SECURE_STORE_GET, handle, key),
  secureStoreSet: (handle: string, key: string, value: string): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.SECURE_STORE_SET, handle, key, value),
  secureStoreRemove: (handle: string, key: string): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.SECURE_STORE_REMOVE, handle, key),
  secureStoreClose: (handle: string): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.SECURE_STORE_CLOSE, handle),

  // Filesystem -------------------------------------------------------------
  fsRead: (p: FsPath): Promise<string | null> => ipcRenderer.invoke(CHANNELS.FS_READ, p),
  fsWrite: (args: FsWriteArgs): Promise<void> => ipcRenderer.invoke(CHANNELS.FS_WRITE, args),
  fsExists: (p: FsPath): Promise<boolean> => ipcRenderer.invoke(CHANNELS.FS_EXISTS, p),
  fsMkdir: (p: FsPath): Promise<void> => ipcRenderer.invoke(CHANNELS.FS_MKDIR, p),
  fsRename: (args: FsRenameArgs): Promise<void> => ipcRenderer.invoke(CHANNELS.FS_RENAME, args),
  fsRemove: (p: FsPath): Promise<void> => ipcRenderer.invoke(CHANNELS.FS_REMOVE, p),
  fsAppLocalDataDir: (): Promise<string> => ipcRenderer.invoke(CHANNELS.FS_APP_LOCAL_DATA_DIR),

  // SQL --------------------------------------------------------------------
  sqlLoad: (args: { name: string }): Promise<string> =>
    ipcRenderer.invoke(CHANNELS.SQL_LOAD, args),
  sqlExecute: (args: {
    handle: string;
    query: string;
    bindings?: unknown[];
  }): Promise<SqlExecuteResult> => ipcRenderer.invoke(CHANNELS.SQL_EXECUTE, args),
  sqlSelect: (args: {
    handle: string;
    query: string;
    bindings?: unknown[];
  }): Promise<SqlSelectResult> => ipcRenderer.invoke(CHANNELS.SQL_SELECT, args),
  sqlClose: (handle: string): Promise<void> => ipcRenderer.invoke(CHANNELS.SQL_CLOSE, handle),

  // Updater ----------------------------------------------------------------
  checkForUpdate: (): Promise<UpdateCheckResult> => ipcRenderer.invoke(CHANNELS.UPDATER_CHECK),
  downloadInstallUpdate: (): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.UPDATER_DOWNLOAD_INSTALL),
  onUpdaterProgress: (cb: (p: UpdateProgress) => void): Unsubscribe =>
    on<UpdateProgress>(CHANNELS.UPDATER_EVT_PROGRESS, cb),

  // Autostart --------------------------------------------------------------
  isAutoStartEnabled: (): Promise<boolean> => ipcRenderer.invoke(CHANNELS.AUTOSTART_IS_ENABLED),
  setAutoStart: (enabled: boolean): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.AUTOSTART_SET, enabled),

  // Window fullscreen ------------------------------------------------------
  setFullscreen: (enabled: boolean): Promise<void> =>
    ipcRenderer.invoke(CHANNELS.WINDOW_SET_FULLSCREEN, enabled),
} as const;

export type ElectronAPI = typeof api;

contextBridge.exposeInMainWorld('electronAPI', api);
