// IPC surface for HUD mode (the chrome-free floating chat band). Extracted
// from main.ts; the HUD window handle and session-id latch stay injected
// because main.ts owns the window lifecycle and the close broadcast reads the
// latch when handing the session back to the app window.
import { type BrowserWindow, ipcMain, screen } from 'electron'

import { createHudDragSession } from './hud-drag'
import { normalizeHudResizeBounds } from './hud-geometry'
import { hudWindowingView, resolveHudWindowing } from './hud-windowing'
import { hudFrostFor, type TranslucencyState } from './translucency'

function hudWindowing() {
  return resolveHudWindowing(process.platform, process.env, process.argv)
}

export interface HudIpcDeps {
  isMac: boolean
  /** Main's authoritative translucency state (Settings → Appearance). */
  getTranslucencyState: () => TranslucencyState
  getHudWindow: () => BrowserWindow | null
  openHudWindow: (sessionId: null | string, profile: null | string) => void
  closeHudWindow: () => void
  resetHudLayout: () => boolean
  setHudSessionId: (sessionId: null | string) => void
}

export function registerHudIpc({
  isMac,
  getTranslucencyState,
  getHudWindow,
  openHudWindow,
  closeHudWindow,
  resetHudLayout,
  setHudSessionId
}: HudIpcDeps) {
  const hudDrag = createHudDragSession()

  // The renderer needs this before first paint so X11 never installs the
  // Chromium drag region that steals modifier-drag gestures from the WM.
  // Main answers because it owns the actual Ozone backend selection.
  ipcMain.on('hermes:hud:native-drag', event => {
    event.returnValue = hudWindowing().move === 'native-drag'
  })

  ipcMain.on('hermes:hud:windowing', event => {
    event.returnValue = hudWindowingView(hudWindowing())
  })

  // X11/KWin window transfer: a renderer-driven grab is temporarily sticky so
  // the user can keep Ctrl+primary-button held while invoking KDE's desktop
  // switch shortcut. Clearing sticky on release makes Chromium assign the
  // window to `_NET_CURRENT_DESKTOP`, exactly like releasing a native titlebar
  // drag on the destination desktop. Native Wayland owns its move loop and
  // Windows/macOS stay out of this Linux-specific bridge.
  ipcMain.on('hermes:hud:workspace-transfer', (event, transferring) => {
    const hudWindow = getHudWindow()

    if (
      !hudWindow ||
      hudWindow.isDestroyed() ||
      event.sender !== hudWindow.webContents ||
      !hudWindowing().workspaceTransfer
    ) {
      return
    }

    try {
      hudWindow.setVisibleOnAllWorkspaces(Boolean(transferring))
    } catch {
      // Workspace APIs are window-manager capabilities — best effort.
    }
  })

  // Whether the band currently covers the window below the bar. The renderer
  // is the only party that can know this (it measures the transcript), and it
  // is half of the frost decision — the other half is the user's setting,
  // which main owns. Latched so a Settings change can re-decide without
  // waiting for the HUD to report again.
  let bandShowing = false
  let applied: null | string = null
  let appliedTo: BrowserWindow | null = null

  // Real frosted glass behind the band — the thing CSS backdrop-filter cannot do,
  // because Chromium composites a transparent window's page against nothing and
  // the desktop is not in its backdrop root. The material IS the window's content
  // view, so it frosts the whole rectangle; the HUD's layout leaves no dead
  // margins for that reason, and it only turns on while the band is showing
  // (idle HUD mode must be the bar and nothing else).
  //
  // macOS ONLY. Windows' equivalent (setBackgroundMaterial → the DWM backdrop)
  // is mutually exclusive with window transparency, so it is not called at all
  // here — see the note at the bottom of this function.
  //
  // Diffed before issuing: `setVibrancy` carries a 150ms animation that restarts
  // if re-issued, so a repeated call would keep the material from ever settling
  // (the same churn the chat windows' native-diff contract exists to prevent).
  //
  // The diff is keyed to the WINDOW as well as the value. A HUD respawn (the
  // profile switch in openHudWindow destroys and rebuilds it) hands back a
  // fresh window carrying no material, and a latch that only remembered the
  // value would recognise its own last answer and skip — leaving the new HUD
  // unfrosted until something else happened to change the signature.
  const applyHudFrost = () => {
    const hudWindow = getHudWindow()

    if (!hudWindow || hudWindow.isDestroyed()) {
      applied = null
      appliedTo = null

      return
    }

    const frost = hudFrostFor(getTranslucencyState(), bandShowing)
    const signature = `${frost.vibrancy ?? 'off'}:${frost.backgroundMaterial}`

    if (applied === signature && appliedTo === hudWindow) {
      return
    }

    applied = signature
    appliedTo = hudWindow

    if (isMac && typeof hudWindow.setVibrancy === 'function') {
      hudWindow.setVibrancy(frost.vibrancy)
    }

    // Windows: never touch setBackgroundMaterial on the HUD. Live-verified on
    // Win11 (Electron 40.10.2, RTX 4090): ANY setBackgroundMaterial call on a
    // transparent window — including 'none', which is what the idle HUD asks
    // for — permanently kills per-pixel alpha, and every transparent pixel
    // composites as opaque white. Neither 'auto' nor a follow-up
    // setBackgroundColor('#00000000') restores it. The DWM backdrop and window
    // transparency are mutually exclusive, so the Windows HUD keeps the CSS
    // tint the sheet already paints and skips the native frost entirely.
  }

  ipcMain.handle('hermes:hud:open', async (_event, request) => {
    openHudWindow(
      typeof request?.sessionId === 'string' ? request.sessionId : null,
      typeof request?.profile === 'string' ? request.profile : null
    )

    return { ok: true }
  })

  ipcMain.handle('hermes:hud:frost', (_event, showing) => {
    bandShowing = Boolean(showing)
    applyHudFrost()

    return { ok: true }
  })

  // Let clicks fall through the HUD wherever it isn't really there. An
  // always-on-top window eats every click inside its rectangle, and most of that
  // rectangle is a faded-out band over whatever the user is actually working in.
  // `forward` keeps mousemove flowing so the renderer can re-arm when the cursor
  // reaches the bar.
  ipcMain.on('hermes:hud:ignore-mouse', (_event, ignore) => {
    const hudWindow = getHudWindow()

    if (!hudWindow || hudWindow.isDestroyed()) {
      return
    }

    // On X11 ignore-mouse is a one-way door: setIgnoreMouseEvents(false)
    // cannot restore the input region afterwards. Veto the request there so
    // the HUD stays a normal solid window. Native Wayland and macOS/Windows
    // keep the per-element path.
    if (Boolean(ignore) && !hudWindowing().ignoreMouse) {
      return
    }

    hudWindow.setIgnoreMouseEvents(Boolean(ignore), { forward: true })
  })

  ipcMain.on('hermes:hud:begin-move', event => {
    const hudWindow = getHudWindow()

    if (
      !hudWindow ||
      hudWindow.isDestroyed() ||
      event.sender !== hudWindow.webContents ||
      !hudWindowing().clientPlacement
    ) {
      return
    }

    const [x, y] = hudWindow.getPosition()
    hudDrag.begin(screen.getCursorScreenPoint(), { x, y })
  })

  ipcMain.on('hermes:hud:end-move', event => {
    const hudWindow = getHudWindow()

    if (hudWindow && !hudWindow.isDestroyed() && event.sender !== hudWindow.webContents) {
      return
    }

    hudDrag.end()
  })

  ipcMain.on('hermes:hud:move-by', (event, delta) => {
    const hudWindow = getHudWindow()

    if (!hudWindow || hudWindow.isDestroyed() || event.sender !== hudWindow.webContents) {
      return
    }

    const width = Number(delta?.width)
    const height = Number(delta?.height)

    if (!Number.isFinite(width) || !Number.isFinite(height) || !hudWindowing().clientPlacement) {
      return
    }

    const origin = hudDrag.origin(screen.getCursorScreenPoint())

    if (!origin) {
      return
    }

    // Cursor − grab offset in Electron DIP (see hud-drag.ts). setBounds —
    // NOT setPosition: on Windows, a transparent frameless window silently
    // grows ~1px per setPosition call (worse at >100% DPI). The renderer
    // snapshots outerWidth/outerHeight when the composer drag arms and
    // re-pins to that size on every move (same pattern as the pet overlay).
    hudWindow.setBounds({
      x: origin.x,
      y: origin.y,
      width: Math.round(width),
      height: Math.round(height)
    })
  })

  // Resize from the HUD's edge/corner handles. The window is created non-resizable
  // (see spawnHudWindow — a transparent frameless window must not expose a
  // system resize hot-zone, or dragging grows it), which on Windows/Linux also
  // blocks programmatic setBounds sizing — so briefly flip resizable on while
  // the size actually changes, exactly like the pet overlay's wheel-scale does.
  ipcMain.on('hermes:hud:set-bounds', (event, bounds) => {
    const hudWindow = getHudWindow()

    if (!hudWindow || hudWindow.isDestroyed() || event.sender !== hudWindow.webContents || !bounds) {
      return
    }

    const nextBounds = normalizeHudResizeBounds(bounds)

    if (!nextBounds) {
      return
    }

    const win = hudWindow
    const { width, height } = nextBounds
    const [curW, curH] = win.getSize()
    const resizing = width !== curW || height !== curH
    const restoreResizeLock = resizing && !win.isResizable()

    try {
      if (restoreResizeLock) {
        win.setResizable(true)
      }

      win.setBounds(nextBounds)
    } catch {
      // The window may disappear between validation and the native call.
    } finally {
      if (restoreResizeLock && !win.isDestroyed()) {
        win.setResizable(false)
      }
    }
  })

  ipcMain.handle('hermes:hud:reset-layout', event => {
    const hudWindow = getHudWindow()

    if (!hudWindow || hudWindow.isDestroyed() || event.sender !== hudWindow.webContents) {
      return { ok: false }
    }

    return { ok: resetHudLayout() }
  })

  // The HUD renderer reporting which session it is on, so the close broadcast
  // can hand it back to the app window (see hudSessionId).
  ipcMain.on('hermes:hud:session', (event, sessionId) => {
    const hudWindow = getHudWindow()

    if (hudWindow && !hudWindow.isDestroyed() && event.sender === hudWindow.webContents) {
      setHudSessionId(typeof sessionId === 'string' && sessionId ? sessionId : null)
    }
  })

  ipcMain.handle('hermes:hud:close', async () => {
    closeHudWindow()

    return { ok: true }
  })

  // Main re-applies the frost when the translucency SETTING changes, since the
  // band's own report only fires when the band itself moves.
  return { applyHudFrost }
}
