import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron'
import {
  IpcChannels,
  type AppUpdateInfo,
  type AppUpdateDownload,
  type AppUpdateProgress,
  type YtdlpVersionResult,
  type YtdlpUpdateChannel,
  type YtdlpUpdateResult,
  type YtdlpAutoUpdateStatus,
  type FfmpegVersionResult,
  type FfmpegSetupStatus,
  type FfmpegSetupProgress,
  type FfmpegSetupResult,
  type ProbeResult,
  type StartDownloadOptions,
  type StartDownloadResult,
  type DownloadEvent,
  type Settings,
  type HistoryEntry,
  type CookiesStatus,
  type CookiesLoginResult,
  type CommandTemplate,
  type MediaProfile,
  type CommandPreviewResult,
  type ErrorLogEntry,
  type BackupExportResult,
  type BackupImportResult,
  type SystemThemeInfo,
  type Source,
  type MediaItem,
  type IndexProgress,
  type IndexSourceResult,
  type SyncResult,
  type ScheduledSyncStatus,
  type TerminalEvent,
  type TerminalRunResult,
  type TaskbarProgress,
  type PersistedQueueItem
} from '@shared/ipc'

// The surface exposed to the renderer. Keep this thin: it only forwards to IPC.
const api = {
  /** AeroFetch's own version string (e.g. '0.3.1'). */
  getAppVersion: (): Promise<string> => ipcRenderer.invoke(IpcChannels.appVersion),

  /** Check the configured Gitea repo for a newer AeroFetch release. */
  checkForAppUpdate: (): Promise<AppUpdateInfo> => ipcRenderer.invoke(IpcChannels.appUpdateCheck),

  /** Download the latest release's installer to a temp file. */
  downloadAppUpdate: (url: string): Promise<AppUpdateDownload> =>
    ipcRenderer.invoke(IpcChannels.appUpdateDownload, url),

  /** Abort the in-flight installer download (the update card's Cancel button). */
  cancelAppUpdate: (): Promise<void> => ipcRenderer.invoke(IpcChannels.appUpdateCancel),

  /** Launch a downloaded installer and quit so it can replace the app. */
  runAppUpdate: (filePath: string): Promise<{ ok: boolean; error?: string }> =>
    ipcRenderer.invoke(IpcChannels.appUpdateRun, filePath),

  /** Subscribe to installer download progress. Returns an unsubscribe function. */
  onAppUpdateProgress: (cb: (p: AppUpdateProgress) => void): (() => void) => {
    const listener = (_e: IpcRendererEvent, p: AppUpdateProgress): void => cb(p)
    ipcRenderer.on(IpcChannels.appUpdateProgress, listener)
    return () => ipcRenderer.removeListener(IpcChannels.appUpdateProgress, listener)
  },

  getYtdlpVersion: (): Promise<YtdlpVersionResult> => ipcRenderer.invoke(IpcChannels.ytdlpVersion),

  /** Versions of the managed ffmpeg + ffprobe (display-only; not auto-updated). */
  getFfmpegVersions: (): Promise<FfmpegVersionResult> =>
    ipcRenderer.invoke(IpcChannels.ffmpegVersion),

  /** Readiness (both binaries present) + versions of the managed ffmpeg/ffprobe. */
  getFfmpegSetupStatus: (): Promise<FfmpegSetupStatus> =>
    ipcRenderer.invoke(IpcChannels.ffmpegSetupStatus),

  /** Download + unpack ffmpeg/ffprobe from the pinned upstream archive (first-run setup). */
  downloadFfmpeg: (): Promise<FfmpegSetupResult> =>
    ipcRenderer.invoke(IpcChannels.ffmpegSetupDownload),

  /** Abort the in-flight ffmpeg archive download (the setup card's Cancel). */
  cancelFfmpegDownload: (): Promise<void> => ipcRenderer.invoke(IpcChannels.ffmpegSetupCancel),

  /** Offline fallback: pick a folder holding ffmpeg.exe + ffprobe.exe and install from it. */
  locateFfmpeg: (): Promise<FfmpegSetupResult> => ipcRenderer.invoke(IpcChannels.ffmpegSetupLocate),

  /** Subscribe to ffmpeg setup progress (download %, then extract). Returns an unsubscribe fn. */
  onFfmpegSetupProgress: (cb: (p: FfmpegSetupProgress) => void): (() => void) => {
    const listener = (_e: IpcRendererEvent, p: FfmpegSetupProgress): void => cb(p)
    ipcRenderer.on(IpcChannels.ffmpegSetupProgress, listener)
    return () => ipcRenderer.removeListener(IpcChannels.ffmpegSetupProgress, listener)
  },

  /** Whether the optional aria2c external downloader is present in this build. */
  aria2cAvailable: (): Promise<boolean> => ipcRenderer.invoke(IpcChannels.aria2cAvailable),

  probe: (url: string): Promise<ProbeResult> => ipcRenderer.invoke(IpcChannels.probe, url),

  startDownload: (opts: StartDownloadOptions): Promise<StartDownloadResult> =>
    ipcRenderer.invoke(IpcChannels.downloadStart, opts),

  cancelDownload: (id: string): Promise<void> => ipcRenderer.invoke(IpcChannels.downloadCancel, id),

  pauseDownload: (id: string): Promise<void> => ipcRenderer.invoke(IpcChannels.downloadPause, id),

  chooseFolder: (current?: string): Promise<string | null> =>
    ipcRenderer.invoke(IpcChannels.chooseFolder, current),

  openPath: (path: string): Promise<string> => ipcRenderer.invoke(IpcChannels.openPath, path),

  openUrl: (url: string): Promise<void> => ipcRenderer.invoke(IpcChannels.openUrl, url),

  showInFolder: (path: string): Promise<string> =>
    ipcRenderer.invoke(IpcChannels.showInFolder, path),

  readClipboard: (): Promise<string> => ipcRenderer.invoke(IpcChannels.clipboardRead),

  /** Forward a renderer-side failure to the main diagnostic log (CC8). Fire-and-forget. */
  logError: (op: string, detail: string): void =>
    ipcRenderer.send(IpcChannels.logWrite, op, detail),

  getSettings: (): Promise<Settings> => ipcRenderer.invoke(IpcChannels.settingsGet),

  setSettings: (partial: Partial<Settings>): Promise<Settings> =>
    ipcRenderer.invoke(IpcChannels.settingsSet, partial),

  listHistory: (): Promise<HistoryEntry[]> => ipcRenderer.invoke(IpcChannels.historyList),

  addHistory: (entry: HistoryEntry): Promise<HistoryEntry[]> =>
    ipcRenderer.invoke(IpcChannels.historyAdd, entry),

  removeHistory: (id: string): Promise<HistoryEntry[]> =>
    ipcRenderer.invoke(IpcChannels.historyRemove, id),

  removeManyHistory: (ids: string[]): Promise<HistoryEntry[]> =>
    ipcRenderer.invoke(IpcChannels.historyRemoveMany, ids),

  clearHistory: (): Promise<HistoryEntry[]> => ipcRenderer.invoke(IpcChannels.historyClear),

  /** Open the built-in sign-in window; resolves once the user closes it. */
  cookiesLogin: (url: string): Promise<CookiesLoginResult> =>
    ipcRenderer.invoke(IpcChannels.cookiesLogin, url),

  cookiesStatus: (): Promise<CookiesStatus> => ipcRenderer.invoke(IpcChannels.cookiesStatus),

  cookiesClear: (): Promise<void> => ipcRenderer.invoke(IpcChannels.cookiesClear),

  listTemplates: (): Promise<CommandTemplate[]> => ipcRenderer.invoke(IpcChannels.templatesList),

  saveTemplate: (template: CommandTemplate): Promise<CommandTemplate[]> =>
    ipcRenderer.invoke(IpcChannels.templatesSave, template),

  removeTemplate: (id: string): Promise<CommandTemplate[]> =>
    ipcRenderer.invoke(IpcChannels.templatesRemove, id),

  listProfiles: (): Promise<MediaProfile[]> => ipcRenderer.invoke(IpcChannels.profilesList),

  saveProfile: (profile: MediaProfile): Promise<MediaProfile[]> =>
    ipcRenderer.invoke(IpcChannels.profilesSave, profile),

  removeProfile: (id: string): Promise<MediaProfile[]> =>
    ipcRenderer.invoke(IpcChannels.profilesRemove, id),

  /** Build the exact yt-dlp command line for the given form state, without running it. */
  previewCommand: (opts: StartDownloadOptions): Promise<CommandPreviewResult> =>
    ipcRenderer.invoke(IpcChannels.commandPreview, opts),

  updateYtdlp: (channel: YtdlpUpdateChannel): Promise<YtdlpUpdateResult> =>
    ipcRenderer.invoke(IpcChannels.ytdlpUpdate, channel),

  /** Subscribe to background yt-dlp auto-update status. Returns an unsubscribe function. */
  onYtdlpAutoUpdateStatus: (cb: (s: YtdlpAutoUpdateStatus) => void): (() => void) => {
    const listener = (_e: IpcRendererEvent, s: YtdlpAutoUpdateStatus): void => cb(s)
    ipcRenderer.on(IpcChannels.ytdlpAutoUpdateStatus, listener)
    return () => ipcRenderer.removeListener(IpcChannels.ytdlpAutoUpdateStatus, listener)
  },

  listErrorLog: (): Promise<ErrorLogEntry[]> => ipcRenderer.invoke(IpcChannels.errorLogList),

  clearErrorLog: (): Promise<ErrorLogEntry[]> => ipcRenderer.invoke(IpcChannels.errorLogClear),

  /** Load the persisted download queue on launch (M4). */
  listQueue: (): Promise<PersistedQueueItem[]> => ipcRenderer.invoke(IpcChannels.queueList),

  /** Mirror the renderer's durable queue items to disk so they survive a quit (M4). */
  saveQueue: (items: PersistedQueueItem[]): Promise<void> =>
    ipcRenderer.invoke(IpcChannels.queueSave, items),

  /** Opens a save dialog and writes settings + templates to the chosen JSON file. */
  exportBackup: (): Promise<BackupExportResult> => ipcRenderer.invoke(IpcChannels.backupExport),

  /** Opens an open dialog and restores settings + templates from the chosen JSON file. */
  importBackup: (): Promise<BackupImportResult> => ipcRenderer.invoke(IpcChannels.backupImport),

  /** Subscribe to live download events. Returns an unsubscribe function. */
  onDownloadEvent: (cb: (ev: DownloadEvent) => void): (() => void) => {
    const listener = (_e: IpcRendererEvent, ev: DownloadEvent): void => cb(ev)
    ipcRenderer.on(IpcChannels.downloadEvent, listener)
    return () => ipcRenderer.removeListener(IpcChannels.downloadEvent, listener)
  },

  getSystemTheme: (): Promise<SystemThemeInfo> => ipcRenderer.invoke(IpcChannels.systemThemeGet),

  /** Subscribe to OS theme/contrast changes. Returns an unsubscribe function. */
  onSystemThemeUpdate: (cb: (info: SystemThemeInfo) => void): (() => void) => {
    const listener = (_e: IpcRendererEvent, info: SystemThemeInfo): void => cb(info)
    ipcRenderer.on(IpcChannels.systemThemeUpdate, listener)
    return () => ipcRenderer.removeListener(IpcChannels.systemThemeUpdate, listener)
  },

  /** Opens Windows' "Contrast themes" accessibility settings page. */
  openHighContrastSettings: (): Promise<void> =>
    ipcRenderer.invoke(IpcChannels.openHighContrastSettings),

  /** Subscribe to links handed to AeroFetch from outside (aerofetch:// or a
   *  "Send to" .url file). Returns an unsubscribe function. */
  onExternalUrl: (cb: (url: string) => void): (() => void) => {
    const listener = (_e: IpcRendererEvent, url: string): void => cb(url)
    ipcRenderer.on(IpcChannels.externalUrl, listener)
    return () => ipcRenderer.removeListener(IpcChannels.externalUrl, listener)
  },

  // --- Media-manager sources (Pinchflat-style) ---

  listSources: (): Promise<Source[]> => ipcRenderer.invoke(IpcChannels.sourcesList),

  /** Index (or re-index) a channel/playlist URL into the persisted source list. */
  indexSource: (url: string): Promise<IndexSourceResult> =>
    ipcRenderer.invoke(IpcChannels.sourceIndex, url),

  /** Re-index an existing source by id (refreshes its media-item list). */
  reindexSource: (id: string): Promise<IndexSourceResult> =>
    ipcRenderer.invoke(IpcChannels.sourceReindex, id),

  /** Abort the in-flight index/reindex walk (the add-source "Cancel" button). */
  cancelIndex: (): Promise<void> => ipcRenderer.invoke(IpcChannels.sourceIndexCancel),

  removeSource: (id: string): Promise<Source[]> => ipcRenderer.invoke(IpcChannels.sourceRemove, id),

  listSourceItems: (sourceId: string): Promise<MediaItem[]> =>
    ipcRenderer.invoke(IpcChannels.sourceItems, sourceId),

  /** Persist that a media item has finished downloading (drives incremental sync).
   *  Named to match the main handler `setMediaItemDownloaded` (L92). */
  setMediaItemDownloaded: (id: string, filePath?: string, quality?: string): Promise<MediaItem[]> =>
    ipcRenderer.invoke(IpcChannels.sourceItemDownloaded, id, filePath, quality),

  /** Toggle whether a source is watched for new uploads. */
  setSourceWatched: (id: string, watched: boolean): Promise<Source[]> =>
    ipcRenderer.invoke(IpcChannels.sourceSetWatched, id, watched),

  /** Point a source at a MediaProfile, or clear it (profileId = null). */
  setSourceProfile: (id: string, profileId: string | null): Promise<Source[]> =>
    ipcRenderer.invoke(IpcChannels.sourceSetProfile, id, profileId),

  /** Prune (delete files of) the given media items; returns the source's updated list + count. */
  pruneMediaItems: (
    sourceId: string,
    itemIds: string[]
  ): Promise<{ items: MediaItem[]; deleted: number }> =>
    ipcRenderer.invoke(IpcChannels.sourcePruneItems, sourceId, itemIds),

  /** Re-index all watched sources; resolves with the videos found new. Named to
   *  match the main function `syncWatchedSources` (L92). */
  syncWatchedSources: (): Promise<SyncResult> => ipcRenderer.invoke(IpcChannels.sourcesSync),

  /** Read / write the Windows daily-sync scheduled task. `time` is the 24h 'HH:MM'
   *  start time (L51); omitted = the default. Re-creating with a new time updates it. */
  getScheduledSync: (): Promise<ScheduledSyncStatus> =>
    ipcRenderer.invoke(IpcChannels.scheduledSyncGet),
  setScheduledSync: (enabled: boolean, time?: string): Promise<ScheduledSyncStatus> =>
    ipcRenderer.invoke(IpcChannels.scheduledSyncSet, enabled, time),

  /** Subscribe to live indexing progress. Returns an unsubscribe function. */
  onIndexProgress: (cb: (p: IndexProgress) => void): (() => void) => {
    const listener = (_e: IpcRendererEvent, p: IndexProgress): void => cb(p)
    ipcRenderer.on(IpcChannels.indexProgress, listener)
    return () => ipcRenderer.removeListener(IpcChannels.indexProgress, listener)
  },

  /** Subscribe to the "run watched-source sync now" nudge a scheduled `--sync` relaunch
   *  sends when it hits the already-running instance. Returns an unsubscribe function. */
  onRunWatchedSync: (cb: () => void): (() => void) => {
    const listener = (): void => cb()
    ipcRenderer.on(IpcChannels.runWatchedSync, listener)
    return () => ipcRenderer.removeListener(IpcChannels.runWatchedSync, listener)
  },

  // --- Built-in yt-dlp terminal (Phase N) ---

  /** Run the bundled yt-dlp with raw args; output streams via onTerminalOutput. */
  runTerminal: (id: string, args: string): Promise<TerminalRunResult> =>
    ipcRenderer.invoke(IpcChannels.terminalRun, id, args),

  cancelTerminal: (id: string): Promise<void> => ipcRenderer.invoke(IpcChannels.terminalCancel, id),

  /** Subscribe to live terminal output. Returns an unsubscribe function. */
  onTerminalOutput: (cb: (ev: TerminalEvent) => void): (() => void) => {
    const listener = (_e: IpcRendererEvent, ev: TerminalEvent): void => cb(ev)
    ipcRenderer.on(IpcChannels.terminalOutput, listener)
    return () => ipcRenderer.removeListener(IpcChannels.terminalOutput, listener)
  },

  /** Reflect overall queue progress on the Windows taskbar. */
  setTaskbarProgress: (p: TaskbarProgress): Promise<void> =>
    ipcRenderer.invoke(IpcChannels.taskbarProgress, p),

  /** Open a YouTube WebView to automatically extract a PO token (Phase P). */
  mintPoToken: (): Promise<string | null> =>
    ipcRenderer.invoke(IpcChannels.youtubePoTokenMint) as Promise<string | null>
}

export type Api = typeof api

// A minimal presence marker. The renderer only checks `window.electron` for
// truthiness to tell the real app apart from the browser UI preview, so there's
// no reason to expose the full ipcRenderer/webFrame/process surface here.
const electron = { isElectron: true } as const
export type ElectronMarker = typeof electron

if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electron)
    contextBridge.exposeInMainWorld('api', api)
  } catch (error) {
    // M30: a broken bridge causes the renderer to silently run in preview/mock mode.
    // Notify the main process so it can show a hard error dialog — without this the
    // failure is completely invisible to the user.
    console.error('[AeroFetch preload] contextBridge failed:', error)
    try {
      ipcRenderer.send(IpcChannels.preloadBridgeFailure, String(error))
    } catch {
      /* if IPC itself is broken there is nothing more we can do */
    }
  }
} else {
  // @ts-ignore window.electron is declared in index.d.ts
  window.electron = electron
  // @ts-ignore window.api is declared in index.d.ts
  window.api = api
}
