/* mixone TS main process template (tsc compiles to main.js, supports bytenode bytecode) */
import { app, BrowserWindow, ipcMain, dialog, shell, clipboard, BrowserWindowConstructorOptions } from 'electron'
import * as fs from 'fs'
import * as path from 'path'
import { pathToFileURL } from 'url'
import type { AppPaths } from './appConfig'

// Bytecode loader: prefer .jsc, fallback to .js
function loadModule(modPath: string): any {
  const jsc = modPath.replace(/\.js$/, '.jsc')
  if (fs.existsSync(jsc)) return require(jsc)
  return require(modPath)
}

const { configureAppPaths } = loadModule('./appConfig')

const appPaths: AppPaths = configureAppPaths(app)
app.commandLine.appendSwitch('disk-cache-dir', appPaths.chromiumCachePath)
// Compatibility mode: skip GPU subprocess and sandbox to avoid FATAL crashes on some Windows
app.disableHardwareAcceleration()
app.commandLine.appendSwitch('disable-gpu')
app.commandLine.appendSwitch('disable-software-rasterizer')
app.commandLine.appendSwitch('no-sandbox')

// dev: mixone CLI pre-spawns vite dev server, main process only reads URL to connect
const DEV_SERVER_URL: string | null = process.env.MIXONE_DEV_SERVER_URL || null
const SPLASH_MIN_MS: number = 600

/* ========== Window / Modal IPC ========== */

interface WindowOpenPayload {
  url?: string
  width?: number
  height?: number
  title?: string
}

function isHttpUrl(value: string): boolean {
  return /^https?:\/\//i.test(value)
}

function isFileUrl(value: string): boolean {
  return /^file:\/\//i.test(value)
}

// When opening a new window, URL can be http(s) web page, file:// form (including hash routes),
// or local file path (auto-convert to file://, preserve hash for frontend routing).
// Fix: original impl treated file:// URLs as relative paths causing double "file://" URLs and hash being lost.
function resolveWindowUrl(url: unknown): string {
  const raw = String(url ?? '')
  if (isHttpUrl(raw) || isFileUrl(raw)) return raw
  const hashIdx = raw.indexOf('#')
  const pathPart = hashIdx >= 0 ? raw.slice(0, hashIdx) : raw
  const hash = hashIdx >= 0 ? raw.slice(hashIdx) : ''
  const abs = pathToFileURL(path.resolve(pathPart)).toString()
  return abs + hash
}

function createChildWindow(parentWindow: BrowserWindow | null, payload: WindowOpenPayload, modal: boolean): BrowserWindow {
  const opts: BrowserWindowConstructorOptions = {
    width: payload.width || 800,
    height: payload.height || 600,
    title: payload.title || '',
    parent: parentWindow || undefined,
    modal,
    show: false,
    autoHideMenuBar: true,
    backgroundColor: '#1e1e2e',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: false
    }
  }
  const win = new BrowserWindow(opts)
  win.loadURL(resolveWindowUrl(payload.url))
  win.once('ready-to-show', () => {
    if (!win.isDestroyed()) win.show()
  })
  return win
}

function registerWindowIpc(): void {
  ipcMain.handle('window:open', (event, payload: WindowOpenPayload = {}) => {
    const parent = BrowserWindow.fromWebContents(event.sender)
    const win = createChildWindow(parent ?? null, payload, false)
    return { id: win.id }
  })

  ipcMain.handle('window:open-modal', (event, payload: WindowOpenPayload = {}) => {
    const parent = BrowserWindow.fromWebContents(event.sender)
    const win = createChildWindow(parent ?? null, payload, true)
    return { id: win.id }
  })

  ipcMain.handle('window:close-current', (event) => {
    const win = BrowserWindow.fromWebContents(event.sender)
    if (win && !win.isDestroyed()) win.close()
    return { success: true }
  })

  ipcMain.handle('window:minimize-current', (event) => {
    BrowserWindow.fromWebContents(event.sender)?.minimize()
    return { success: true }
  })

  ipcMain.handle('window:maximize-current', (event) => {
    const win = BrowserWindow.fromWebContents(event.sender)
    if (!win) return { success: false }
    if (win.isMaximized()) win.unmaximize()
    else win.maximize()
    return { success: true }
  })

  ipcMain.handle('window:is-maximized', (event) => {
    return { maximized: !!BrowserWindow.fromWebContents(event.sender)?.isMaximized() }
  })
}

/* ========== mixone IPC: dialog / fs / clipboard / shell / app ==========
 * External channel unified prefix `mixone:`. Types in `src/mixone-ipc.d.ts`.
 * Renderer imports via `import { dialog, fs, clipboard, shell, app } from '#mixone/ipc'` (preload-exposed bridge).
 */

function pickParentWindow(event: Electron.IpcMainInvokeEvent): BrowserWindow | undefined {
  return BrowserWindow.fromWebContents(event.sender) || undefined
}

// ---- Type aliases (matching src/mixone-ipc.d.ts; local to avoid cross-boundary d.ts import) ----
type DialogOpenOptions = {
  title?: string
  defaultPath?: string
  properties?: Array<'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles'>
  filters?: Array<{ name: string; extensions: string[] }>
}
type DialogOpenResult = { filePaths: string[]; canceled: boolean }
type DialogSaveOptions = { title?: string; defaultPath?: string; filters?: Array<{ name: string; extensions: string[] }> }
type DialogSaveResult = { filePath: string | null; canceled: boolean }
type DialogMessageOptions = {
  type?: 'none' | 'info' | 'error' | 'question' | 'warning'
  title?: string
  message: string
  detail?: string
  buttons?: string[]
  defaultId?: number
  cancelId?: number
}
type DialogMessageResult = { response: number; checkboxResponse: boolean; canceled: boolean }
type FsReadTextOptions = { encoding?: BufferEncoding }
type FsReadTextResult = { content: string; size: number }
type FsWriteTextOptions = { encoding?: BufferEncoding }
type FsStats = { size: number; isFile: boolean; isDirectory: boolean; mtime: number }

function registerMixoneIpc(): void {
  /* --- dialog --- */
  ipcMain.handle('mixone:dialog:open-file', (event, options: DialogOpenOptions = {}): DialogOpenResult => {
    const parent = pickParentWindow(event)
    const dialogOpts: Electron.OpenDialogSyncOptions = {
      title: options.title,
      defaultPath: options.defaultPath,
      properties: options.properties || ['openFile'],
      filters: options.filters as Electron.FileFilter[] | undefined
    }
    const result = parent
      ? dialog.showOpenDialogSync(parent, dialogOpts)
      : dialog.showOpenDialogSync(dialogOpts)
    if (!result || result.length === 0) return { filePaths: [], canceled: true }
    return { filePaths: result, canceled: false }
  })

  ipcMain.handle('mixone:dialog:save-file', (event, options: DialogSaveOptions = {}): DialogSaveResult => {
    const parent = pickParentWindow(event)
    const dialogOpts: Electron.SaveDialogSyncOptions = {
      title: options.title,
      defaultPath: options.defaultPath,
      filters: options.filters as Electron.FileFilter[] | undefined
    }
    const result = parent
      ? dialog.showSaveDialogSync(parent, dialogOpts)
      : dialog.showSaveDialogSync(dialogOpts)
    if (!result) return { filePath: null, canceled: true }
    return { filePath: result, canceled: false }
  })

  ipcMain.handle('mixone:dialog:message-box', async (event, options: DialogMessageOptions): Promise<DialogMessageResult> => {
    const parent = pickParentWindow(event)
    const msgOpts: Electron.MessageBoxOptions = {
      type: options.type || 'info',
      title: options.title,
      message: options.message || '',
      detail: options.detail,
      buttons: options.buttons || ['OK'],
      defaultId: options.defaultId,
      cancelId: options.cancelId
    }
    const result = parent
      ? await dialog.showMessageBox(parent, msgOpts)
      : await dialog.showMessageBox(msgOpts)
    return {
      response: result.response,
      checkboxResponse: result.checkboxChecked,
      canceled: options.cancelId !== undefined && result.response === options.cancelId
    }
  })

  /* --- fs (restricted: read-only + write to user-selected paths) --- */
  ipcMain.handle('mixone:fs:read-text', (event, filePath: string, options: FsReadTextOptions = {}): FsReadTextResult => {
    const encoding = options.encoding || 'utf-8'
    const buf = fs.readFileSync(filePath)
    return { content: buf.toString(encoding), size: buf.length }
  })

  ipcMain.handle('mixone:fs:write-text', (event, filePath: string, content: string, options: FsWriteTextOptions = {}): { bytesWritten: number } => {
    const encoding = options.encoding || 'utf-8'
    const buf = Buffer.from(content, encoding)
    fs.writeFileSync(filePath, buf)
    return { bytesWritten: buf.length }
  })

  ipcMain.handle('mixone:fs:exists', (event, filePath: string): boolean => fs.existsSync(filePath))

  ipcMain.handle('mixone:fs:stat', (event, filePath: string): FsStats => {
    const stat = fs.statSync(filePath)
    return {
      size: stat.size,
      isFile: stat.isFile(),
      isDirectory: stat.isDirectory(),
      mtime: stat.mtimeMs
    }
  })

  ipcMain.handle('mixone:fs:unlink-safe', (event, filePath: string): { success: boolean } => {
    try {
      if (fs.existsSync(filePath)) {
        fs.rmSync(filePath, { force: true })
        return { success: !fs.existsSync(filePath) }
      }
      return { success: true }
    } catch (e) {
      return { success: false }
    }
  })

  /* --- clipboard --- */
  ipcMain.handle('mixone:clipboard:read-text', (): string => clipboard.readText())
  ipcMain.handle('mixone:clipboard:write-text', (event, text: string): { success: true } => {
    clipboard.writeText(String(text))
    return { success: true }
  })
  ipcMain.handle('mixone:clipboard:clear', (): { success: true } => {
    clipboard.clear()
    return { success: true }
  })

  /* --- shell --- */
  ipcMain.handle('mixone:shell:open-external', async (event, url: string, options: { activate?: boolean } = {}): Promise<{ success: true }> => {
    await shell.openExternal(String(url), { activate: options.activate !== false })
    return { success: true }
  })

  ipcMain.handle('mixone:shell:show-item-in-folder', (event, fullPath: string): { success: true } => {
    if (fs.existsSync(fullPath)) shell.showItemInFolder(path.resolve(fullPath))
    return { success: true }
  })

  ipcMain.handle('mixone:shell:open-path', async (event, fullPath: string): Promise<string> => {
    return await shell.openPath(path.resolve(fullPath))
  })

  ipcMain.handle('mixone:shell:beep', (): { success: true } => {
    shell.beep()
    return { success: true }
  })

  /* --- app --- */
  ipcMain.handle('mixone:app:info', () => {
    return {
      name: app.getName(),
      version: app.getVersion(),
      electron: process.versions.electron,
      node: process.versions.node,
      chrome: process.versions.chrome,
      platform: process.platform as NodeJS.Platform,
      cwd: process.cwd()
    }
  })

  ipcMain.handle('mixone:app:relaunch', async (): Promise<{ success: true }> => {
    app.relaunch()
    app.exit(0)
    return { success: true }
  })

  ipcMain.handle('mixone:app:quit', async (): Promise<{ success: true }> => {
    app.quit()
    return { success: true }
  })
}

/* ========== Splash screen + main window ==========
 * Custom: create splash.html in project root to replace the default splash.
 * splash.html supports full HTML/CSS/JS, window size controlled by createSplashWindow.
 * If splash.html doesn't exist, use built-in default style.
 */

function getSplashHtml(): string {
  // Prefer project root splash.html
  const customSplash = path.join(__dirname, 'splash.html')
  if (fs.existsSync(customSplash)) {
    return fs.readFileSync(customSplash, 'utf-8')
  }
  // Default splash
  return `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <style>
      * { box-sizing: border-box; }
      body {
        margin: 0;
        height: 100vh;
        display: flex;
        align-items: center;
        justify-content: center;
        background: radial-gradient(circle at top, rgba(59,130,246,0.32), transparent 42%),
          linear-gradient(180deg, #0f172a 0%, #111827 100%);
        color: #e2e8f0;
        font-family: system-ui, 'Segoe UI', sans-serif;
        overflow: hidden;
      }
      .box { text-align: center; }
      h1 { margin: 0 0 6px; font-size: 22px; color: #93c5fd; }
      p { margin: 0 0 20px; color: #94a3b8; font-size: 13px; }
      .spin {
        width: 34px;
        height: 34px;
        margin: 0 auto;
        border: 3px solid rgba(148,163,184,0.2);
        border-top-color: #60a5fa;
        border-radius: 50%;
        animation: spin 0.8s linear infinite;
      }
      @keyframes spin { to { transform: rotate(360deg); } }
    </style>
  </head>
  <body>
    <div class="box">
      <h1>{{appName}}</h1>
      <p>Loading...</p>
      <div class="spin"></div>
    </div>
  </body>
</html>`
}

function createSplashWindow(): BrowserWindow {
  const win = new BrowserWindow({
    width: 440,
    height: 300,
    frame: false,
    resizable: false,
    movable: true,
    show: true,
    center: true,
    alwaysOnTop: true,
    skipTaskbar: true,
    backgroundColor: '#0f172a',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: false
    }
  })
  win.loadURL(`data:text/html;charset=UTF-8,${encodeURIComponent(getSplashHtml())}`)
  win.removeMenu()
  return win
}

/* ========== Main window ========== */

function createMainWindow(rendererUrl: string | null): BrowserWindow {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 900,
    minHeight: 600,
    title: '{{appName}}',
    show: false,
    autoHideMenuBar: true,
    backgroundColor: '#1e1e2e',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: false
    }
  })

  if (rendererUrl) win.loadURL(rendererUrl)
  else win.loadURL(pathToFileURL(path.join(__dirname, 'dist', 'index.html')).toString())

  return win
}

function onAppReady(): void {
  registerWindowIpc()
  registerMixoneIpc()

  const splash = createSplashWindow()
  const splashOpenedAt = Date.now()
  const mainWin = createMainWindow(DEV_SERVER_URL)

  const finishSplash = (): void => {
    const elapsed = Date.now() - splashOpenedAt
    const wait = Math.max(0, SPLASH_MIN_MS - elapsed)
    if (wait > 0) {
      setTimeout(() => {
        if (!splash.isDestroyed()) splash.close()
        if (!mainWin.isDestroyed()) {
          mainWin.show()
          mainWin.focus()
        }
      }, wait)
      return
    }
    if (!splash.isDestroyed()) splash.close()
    if (!mainWin.isDestroyed()) {
      mainWin.show()
      mainWin.focus()
    }
  }

  if (mainWin.webContents.isLoading()) {
    mainWin.webContents.once('did-finish-load', finishSplash)
    mainWin.webContents.once('did-fail-load', () => {
      console.error('[mixone] main window failed to load')
      finishSplash()
    })
  } else {
    finishSplash()
  }

  mainWin.on('closed', () => {
    if (!splash.isDestroyed()) splash.close()
  })
}

app.whenReady().then(onAppReady)

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    const win = createMainWindow(DEV_SERVER_URL)
    win.once('ready-to-show', () => {
      if (!win.isDestroyed()) win.show()
    })
  }
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})