const { app, BrowserWindow, ipcMain, dialog, shell, clipboard } = require('electron')
const fs = require('fs')
const path = require('path')
const { pathToFileURL } = require('url')

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

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

const 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 (no self-spawn).
// This way main process startup doesn't consume parallel time, vite stdout is shown by mixone CLI.
const DEV_SERVER_URL = process.env.MIXONE_DEV_SERVER_URL || null
const SPLASH_MIN_MS = 600 // prevent ultra-short splash flash

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

function isHttpUrl(value) {
  return /^https?:\/\//i.test(String(value || ''))
}

function isFileUrl(value) {
  return /^file:\/\//i.test(String(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) {
  const raw = String(url || '')
  if (isHttpUrl(raw) || isFileUrl(raw)) return raw
  // Split hash (for frontend routing), hash is not passed to pathToFileURL
  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, payload = {}, modal = false) {
  const win = new BrowserWindow({
    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
    }
  })

  win.loadURL(resolveWindowUrl(payload.url))
  win.once('ready-to-show', () => {
    if (!win.isDestroyed()) {
      win.show()
    }
  })
  return win
}

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

  ipcMain.handle('window:open-modal', (event, payload = {}) => {
    const parent = BrowserWindow.fromWebContents(event.sender)
    const win = createChildWindow(parent, 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:`. All methods use ipcMain.handle (two-way invoke/handle).
 * Param types are declared in `src/mixone-ipc.d.ts` (renderer gets typed proxy via `#mixone/ipc`).
 *
 * Security policy:
 *   - fs only exposes read + write to user-selected paths via dialog, no arbitrary path access
 *   - shell.openExternal / openPath use system default program, paths are validated
 *   - app provides meta info, restart and quit, no file system access
 */

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

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

  ipcMain.handle('mixone:dialog:save-file', (event, options = {}) => {
    const parent = pickParentWindow(event)
    const result = dialog.showSaveDialogSync(parent || undefined, {
      title: options.title,
      defaultPath: options.defaultPath,
      filters: options.filters
    })
    if (!result) return { filePath: null, canceled: true }
    return { filePath: result, canceled: false }
  })

  ipcMain.handle('mixone:dialog:message-box', async (event, options = {}) => {
    const parent = pickParentWindow(event)
    const result = await dialog.showMessageBox(parent || undefined, {
      type: options.type || 'info',
      title: options.title,
      message: options.message || '',
      detail: options.detail,
      buttons: options.buttons || ['OK'],
      defaultId: options.defaultId,
      cancelId: options.cancelId
    })
    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, options = {}) => {
    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, content, options = {}) => {
    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) => {
    return fs.existsSync(filePath)
  })

  ipcMain.handle('mixone:fs:stat', (event, filePath) => {
    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) => {
    try {
      if (fs.existsSync(filePath)) {
        fs.rmSync(filePath, { force: true })
        return { success: !fs.existsSync(filePath) }
      }
      return { success: true }
    } catch (e) {
      return { success: false }
    }
  })

  ipcMain.handle('mixone:fs:read-dir', (event, dirPath) => {
    try {
      const entries = fs.readdirSync(dirPath, { withFileTypes: true })
      return {
        entries: entries.map(e => ({
          name: e.name,
          isFile: e.isFile(),
          isDirectory: e.isDirectory(),
          isSymbolicLink: e.isSymbolicLink()
        }))
      }
    } catch (e) {
      return { entries: [], error: e.message }
    }
  })

  ipcMain.handle('mixone:fs:mkdir', (event, dirPath, options = {}) => {
    try {
      fs.mkdirSync(dirPath, { recursive: options.recursive !== false })
      return { success: true }
    } catch (e) {
      return { success: false, error: e.message }
    }
  })

  ipcMain.handle('mixone:fs:rename', (event, oldPath, newPath) => {
    try {
      fs.renameSync(oldPath, newPath)
      return { success: true }
    } catch (e) {
      return { success: false, error: e.message }
    }
  })

  ipcMain.handle('mixone:fs:copy-file', (event, src, dest) => {
    try {
      fs.copyFileSync(src, dest)
      return { success: true }
    } catch (e) {
      return { success: false, error: e.message }
    }
  })

  ipcMain.handle('mixone:fs:append-text', (event, filePath, content, options = {}) => {
    try {
      const encoding = options.encoding || 'utf-8'
      fs.appendFileSync(filePath, content, { encoding })
      return { success: true }
    } catch (e) {
      return { success: false, error: e.message }
    }
  })

  ipcMain.handle('mixone:fs:read-json', (event, filePath) => {
    try {
      const content = fs.readFileSync(filePath, 'utf-8')
      return { data: JSON.parse(content), error: null }
    } catch (e) {
      return { data: null, error: e.message }
    }
  })

  ipcMain.handle('mixone:fs:write-json', (event, filePath, data, options = {}) => {
    try {
      const space = options.space ?? 2
      const content = JSON.stringify(data, null, space)
      fs.writeFileSync(filePath, content, 'utf-8')
      return { success: true }
    } catch (e) {
      return { success: false, error: e.message }
    }
  })

  /* --- clipboard --- */
  ipcMain.handle('mixone:clipboard:read-text', () => clipboard.readText())
  ipcMain.handle('mixone:clipboard:write-text', (event, text) => {
    clipboard.writeText(String(text))
    return { success: true }
  })
  ipcMain.handle('mixone:clipboard:read-html', () => clipboard.readHTML())
  ipcMain.handle('mixone:clipboard:write-html', (event, html) => {
    clipboard.writeHTML(String(html))
    return { success: true }
  })
  ipcMain.handle('mixone:clipboard:read-image', () => {
    const img = clipboard.readImage()
    return { empty: img.isEmpty(), size: img.isEmpty() ? null : { width: img.getWidth(), height: img.getHeight() } }
  })
  ipcMain.handle('mixone:clipboard:clear', () => {
    clipboard.clear()
    return { success: true }
  })

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

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

  ipcMain.handle('mixone:shell:open-path', async (event, fullPath) => {
    const err = await shell.openPath(path.resolve(fullPath))
    return err // empty string = success, non-empty = error message
  })

  ipcMain.handle('mixone:shell:beep', () => {
    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,
      cwd: process.cwd()
    }
  })

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

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

/* ========== Splash screen + main window ==========
 * Note: in mixone dev mode, vite is pre-spawned by mixone CLI.
 * When main process starts, MIXONE_DEV_SERVER_URL is ready. The first window is a HTML splash
 * (instant), then main window loadURL(dev server or dist/index.html).
 * After 'did-finish-load', immediately switch over.
 */

/* ========== Splash screen ==========
 * 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() {
  // 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() {
  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) {
  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() {
  registerWindowIpc()
  registerMixoneIpc()

  // Splash appears immediately (dev server pre-ready by mixone CLI; production loads local files)
  const splash = createSplashWindow()
  const splashOpenedAt = Date.now()

  const mainWin = createMainWindow(DEV_SERVER_URL)

  const finishSplash = () => {
    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()
  }
})