#!/usr/bin/env node

/**
 * Electron Builder Agent — SF-06
 *
 * 将 SF-05 Fullstack Composer 输出的 Web 全栈项目封装为 Electron 桌面应用。
 *
 * 职责单一：Web Fullstack Project × Desktop Shell = 桌面软件
 * 不重新实现：页面生成、API 生成、数据库生成、Auth 生成、CRUD 生成
 *
 * Usage:
 *   node scripts/electron-builder-agent.mjs --input <fullstack-dir> [options]
 *
 * @module electron-builder-agent
 */

import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(resolve(fileURLToPath(import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");

// ═══════════════════════════════════════════════════════
// 1. Package.json Generator
// ═══════════════════════════════════════════════════════

function generatePackageJson(projectName, webPort, apiPort) {
  const safeName = (projectName || "app").toLowerCase().replace(/[^a-z0-9-]/g, "-");
  const displayName = projectName || "Desktop App";

  return JSON.stringify({
    name: safeName + "-desktop",
    version: "1.0.0",
    private: true,
    description: displayName + " — Desktop Application powered by Electron",
    main: "dist/electron/main.js",
    scripts: {
      dev: 'concurrently "npm run dev:web" "npm run dev:api" "npm run dev:electron"',
      "dev:electron": "tsc && electron .",
      "dev:web": "cd ../web && npm run dev",
      "dev:api": "cd ../api && npm run dev",
      build: "npm run build:web && npm run build:api && npm run build:electron",
      "build:web": "cd ../web && npm run build",
      "build:api": "cd ../api && npm run build",
      "build:electron": "tsc && electron-builder --config electron-builder.yml",
      "build:electron:win": "tsc && electron-builder --win --config electron-builder.yml",
      "build:electron:mac": "tsc && electron-builder --mac --config electron-builder.yml",
      "build:electron:linux": "tsc && electron-builder --linux --config electron-builder.yml",
      pack: "tsc && electron-builder --dir --config electron-builder.yml",
      typecheck: "tsc --noEmit",
      lint: "eslint electron/",
    },
    dependencies: {
      "electron-updater": "^6.3.9",
      "electron-store": "^10.0.0",
    },
    devDependencies: {
      electron: "^35.0.0",
      "electron-builder": "^25.1.8",
      concurrently: "^9.1.0",
      typescript: "^5.7.0",
      "@types/node": "^22.10.0",
    },
    build: {
      productName: displayName,
      appId: "com." + safeName + ".desktop",
      directories: {
        output: "release",
      },
    },
  }, null, 2);
}

// ═══════════════════════════════════════════════════════
// 2. electron-builder.yml Generator
// ═══════════════════════════════════════════════════════

function generateElectronBuilderYml(projectName) {
  const safeName = (projectName || "app").toLowerCase().replace(/[^a-z0-9-]/g, "-");
  const displayName = projectName || "Desktop App";

  return [
    "# electron-builder.yml — Auto-generated by SF-06",
    "",
    "appId: com." + safeName + ".desktop",
    "productName: " + displayName,
    'copyright: "Copyright © 2026 ' + displayName + '"',
    "",
    "# ─── Directories ─────────────────────────────────",
    "directories:",
    "  output: release",
    "  buildResources: assets",
    "",
    "# ─── Files ───────────────────────────────────────",
    "files:",
    "  - dist/electron/**/*",
    '  - "!node_modules/**/*"',
    '  - "**/*.node"',
    "",
    "# ─── Extra Resources ─────────────────────────────",
    "extraResources:",
    '  - from: "../web/out"',
    '    to: "web"',
    "    filter:",
    '      - "**/*"',
    '  - from: "../api/dist"',
    '    to: "api"',
    "    filter:",
    '      - "**/*"',
    '  - from: "../api/data"',
    '    to: "data"',
    "    filter:",
    '      - "**/*"',
    "",
    "# ─── macOS ───────────────────────────────────────",
    "mac:",
    "  category: public.app-category.productivity",
    "  icon: assets/icon.png",
    "  target:",
    "    - target: dmg",
    "      arch:",
    "        - x64",
    "        - arm64",
    "    - target: zip",
    "      arch:",
    "        - x64",
    "        - arm64",
    "  darkModeSupport: true",
    "  hardenedRuntime: true",
    "  gatekeeperAssess: false",
    "  entitlements: entitlements.mac.plist",
    "  entitlementsInherit: entitlements.mac.plist",
    "",
    "# ─── Windows ─────────────────────────────────────",
    "win:",
    "  icon: assets/icon.png",
    "  target:",
    "    - target: nsis",
    "      arch:",
    "        - x64",
    "    - target: portable",
    "      arch:",
    "        - x64",
    "  publisherName: " + displayName,
    "",
    "nsis:",
    "  oneClick: false",
    "  perMachine: false",
    "  allowToChangeInstallationDirectory: true",
    "  installerIcon: assets/icon.ico",
    "  uninstallerIcon: assets/icon.ico",
    "  installerHeaderIcon: assets/icon.ico",
    "  createDesktopShortcut: true",
    "  createStartMenuShortcut: true",
    "  shortcutName: " + displayName,
    "",
    "# ─── Linux ───────────────────────────────────────",
    "linux:",
    "  icon: assets/icon.png",
    "  category: Utility",
    "  target:",
    "    - target: AppImage",
    "      arch:",
    "        - x64",
    "    - target: deb",
    "      arch:",
    "        - x64",
    "    - target: rpm",
    "      arch:",
    "        - x64",
    "  desktop:",
    "    Name: " + displayName,
    "    Comment: " + displayName + " Desktop Application",
    "    Terminal: false",
    "    Type: Application",
    "    Categories: Utility;",
    "",
    "# ─── Auto Update ─────────────────────────────────",
    "publish:",
    "  provider: generic",
    "  url: https://releases.example.com/" + safeName + "/",
    "  channel: latest",
    "",
  ].join("\n");
}

// ═══════════════════════════════════════════════════════
// 3. main.ts — Main Process
// ═══════════════════════════════════════════════════════

function generateMainTs(projectName, webPort, apiPort) {
  const displayName = projectName || "Desktop App";

  return 'import { app, BrowserWindow, Menu, nativeImage, ipcMain } from "electron";\n' +
    'import { join } from "path";\n' +
    'import { existsSync } from "fs";\n' +
    'import { setupTray } from "./tray";\n' +
    'import { setupIPC } from "./ipc";\n' +
    'import { setupUpdater } from "./updater";\n' +
    'import { checkSingleInstance, getWebUrl, getApiPath, isDev } from "./utils";\n' +
    '\n' +
    '// ─── Constants ───────────────────────────────────\n' +
    'const WEB_PORT = ' + webPort + ';\n' +
    'const API_PORT = ' + apiPort + ';\n' +
    'const APP_NAME = "' + displayName + '";\n' +
    '\n' +
    '// ─── Single Instance Lock ────────────────────────\n' +
    'if (!checkSingleInstance()) {\n' +
    '  app.quit();\n' +
    '  process.exit(0);\n' +
    '}\n' +
    '\n' +
    '// ─── State ───────────────────────────────────────\n' +
    'let mainWindow: BrowserWindow | null = null;\n' +
    'let apiProcess: ReturnType<typeof import("child_process").spawn> | null = null;\n' +
    '\n' +
    '// ─── Create Window ───────────────────────────────\n' +
    'function createWindow(): BrowserWindow {\n' +
    '  const preloadPath = join(__dirname, "preload.js");\n' +
    '\n' +
    '  const win = new BrowserWindow({\n' +
    '    title: APP_NAME,\n' +
    '    width: 1400,\n' +
    '    height: 900,\n' +
    '    minWidth: 800,\n' +
    '    minHeight: 600,\n' +
    '    show: false,\n' +
    '    icon: getIconPath(),\n' +
    '    webPreferences: {\n' +
    '      preload: preloadPath,\n' +
    '      contextIsolation: true,\n' +
    '      nodeIntegration: false,\n' +
    '      sandbox: false,\n' +
    '    },\n' +
    '    titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "default",\n' +
    '    trafficLightPosition: { x: 16, y: 16 },\n' +
    '  });\n' +
    '\n' +
    '  // ── Load content ──\n' +
    '  const url = getWebUrl(isDev(), WEB_PORT);\n' +
    '  if (url.startsWith("http")) {\n' +
    '    win.loadURL(url);\n' +
    '  } else {\n' +
    '    win.loadFile(url);\n' +
    '  }\n' +
    '\n' +
    '  // ── Show when ready ──\n' +
    '  win.once("ready-to-show", () => {\n' +
    '    win.show();\n' +
    '    if (isDev()) {\n' +
    '      win.webContents.openDevTools({ mode: "detach" });\n' +
    '    }\n' +
    '  });\n' +
    '\n' +
    '  // ── External links → default browser ──\n' +
    '  win.webContents.setWindowOpenHandler(({ url }) => {\n' +
    '    if (url.startsWith("http")) {\n' +
    '      require("electron").shell.openExternal(url);\n' +
    '    }\n' +
    '    return { action: "deny" };\n' +
    '  });\n' +
    '\n' +
    '  // ── Prevent navigation away ──\n' +
    '  win.webContents.on("will-navigate", (event, url) => {\n' +
    '    const allowed = isDev()\n' +
    '      ? url.startsWith("http://localhost:' + webPort + '")\n' +
    '      : url.startsWith("file://");\n' +
    '    if (!allowed) event.preventDefault();\n' +
    '  });\n' +
    '\n' +
    '  return win;\n' +
    '}\n' +
    '\n' +
    '// ─── Icon Path ───────────────────────────────────\n' +
    'function getIconPath(): string {\n' +
    '  const candidates = [\n' +
    '    join(__dirname, "..", "assets", "icon.png"),\n' +
    '    join(__dirname, "..", "assets", "icon.icns"),\n' +
    '    join(__dirname, "..", "assets", "icon.ico"),\n' +
    '  ];\n' +
    '  for (const p of candidates) {\n' +
    '    if (existsSync(p)) return p;\n' +
    '  }\n' +
    '  return "";\n' +
    '}\n' +
    '\n' +
    '// ─── API Server (Production) ─────────────────────\n' +
    'function startApiServer(): void {\n' +
    '  if (isDev()) return;\n' +
    '\n' +
    '  const apiPath = getApiPath();\n' +
    '  if (!apiPath || !existsSync(apiPath)) {\n' +
    '    console.warn("[main] API server not found at", apiPath);\n' +
    '    return;\n' +
    '  }\n' +
    '\n' +
    '  const { spawn } = require("child_process");\n' +
    '  apiProcess = spawn("node", [apiPath], {\n' +
    '    env: {\n' +
    '      ...process.env,\n' +
    '      NODE_ENV: "production",\n' +
    '      PORT: String(API_PORT),\n' +
    '      HOST: "127.0.0.1",\n' +
    '    },\n' +
    '    stdio: ["ignore", "pipe", "pipe"],\n' +
    '  });\n' +
    '\n' +
    '  apiProcess!.stdout?.on("data", (data: Buffer) => {\n' +
    '    console.log("[api]", data.toString().trim());\n' +
    '  });\n' +
    '  apiProcess!.stderr?.on("data", (data: Buffer) => {\n' +
    '    console.error("[api]", data.toString().trim());\n' +
    '  });\n' +
    '  apiProcess!.on("exit", (code: number) => {\n' +
    '    console.warn("[main] API server exited with code", code);\n' +
    '    apiProcess = null;\n' +
    '  });\n' +
    '}\n' +
    '\n' +
    'function stopApiServer(): void {\n' +
    '  if (apiProcess) {\n' +
    '    apiProcess.kill("SIGTERM");\n' +
    '    apiProcess = null;\n' +
    '  }\n' +
    '}\n' +
    '\n' +
    '// ─── Menu ────────────────────────────────────────\n' +
    'function buildMenu(): void {\n' +
    '  const isMac = process.platform === "darwin";\n' +
    '\n' +
    '  const template: Electron.MenuItemConstructorOptions[] = [\n' +
    '    ...(isMac\n' +
    '      ? [{\n' +
    '          label: APP_NAME,\n' +
    '          submenu: [\n' +
    '            { role: "about" as const },\n' +
    '            { type: "separator" as const },\n' +
    '            { role: "services" as const },\n' +
    '            { type: "separator" as const },\n' +
    '            { role: "hide" as const },\n' +
    '            { role: "hideOthers" as const },\n' +
    '            { role: "unhide" as const },\n' +
    '            { type: "separator" as const },\n' +
    '            { role: "quit" as const },\n' +
    '          ],\n' +
    '        }]\n' +
    '      : []),\n' +
    '    {\n' +
    '      label: "Edit",\n' +
    '      submenu: [\n' +
    '        { role: "undo" },\n' +
    '        { role: "redo" },\n' +
    '        { type: "separator" },\n' +
    '        { role: "cut" },\n' +
    '        { role: "copy" },\n' +
    '        { role: "paste" },\n' +
    '        { role: "selectAll" },\n' +
    '      ],\n' +
    '    },\n' +
    '    {\n' +
    '      label: "View",\n' +
    '      submenu: [\n' +
    '        { role: "reload" },\n' +
    '        { role: "forceReload" },\n' +
    '        { role: "toggleDevTools" },\n' +
    '        { type: "separator" },\n' +
    '        { role: "resetZoom" },\n' +
    '        { role: "zoomIn" },\n' +
    '        { role: "zoomOut" },\n' +
    '        { type: "separator" },\n' +
    '        { role: "togglefullscreen" },\n' +
    '      ],\n' +
    '    },\n' +
    '    {\n' +
    '      label: "Window",\n' +
    '      submenu: [\n' +
    '        { role: "minimize" },\n' +
    '        { role: "zoom" },\n' +
    '        ...(isMac\n' +
    '          ? [\n' +
    '              { type: "separator" as const },\n' +
    '              { role: "front" as const },\n' +
    '            ]\n' +
    '          : [{ role: "close" as const }]),\n' +
    '      ],\n' +
    '    },\n' +
    '  ];\n' +
    '\n' +
    '  Menu.setApplicationMenu(Menu.buildFromTemplate(template));\n' +
    '}\n' +
    '\n' +
    '// ─── App Lifecycle ───────────────────────────────\n' +
    'app.whenReady().then(() => {\n' +
    '  startApiServer();\n' +
    '  mainWindow = createWindow();\n' +
    '  buildMenu();\n' +
    '  if (mainWindow) {\n' +
    '    setupTray(mainWindow, APP_NAME);\n' +
    '    setupIPC(mainWindow);\n' +
    '  }\n' +
    '  setupUpdater(APP_NAME);\n' +
    '\n' +
    '  app.on("activate", () => {\n' +
    '    if (BrowserWindow.getAllWindows().length === 0) {\n' +
    '      mainWindow = createWindow();\n' +
    '      if (mainWindow) {\n' +
    '        setupTray(mainWindow, APP_NAME);\n' +
    '        setupIPC(mainWindow);\n' +
    '      }\n' +
    '    }\n' +
    '  });\n' +
    '});\n' +
    '\n' +
    'app.on("window-all-closed", () => {\n' +
    '  stopApiServer();\n' +
    '  if (process.platform !== "darwin") {\n' +
    '    app.quit();\n' +
    '  }\n' +
    '});\n' +
    '\n' +
    'app.on("before-quit", () => {\n' +
    '  stopApiServer();\n' +
    '});\n' +
    '\n' +
    'app.on("gpu-process-crashed" as any, () => {\n' +
    '  console.warn("[main] GPU process crashed — continuing");\n' +
    '});\n' +
    '\n' +
    'process.on("exit", () => {\n' +
    '  stopApiServer();\n' +
    '});\n';
}

// ═══════════════════════════════════════════════════════
// 4. preload.ts — Secure IPC Bridge
// ═══════════════════════════════════════════════════════

function generatePreloadTs() {
  return 'import { contextBridge, ipcRenderer } from "electron";\n' +
    '\n' +
    '// ─── Expose safe API to renderer ─────────────────\n' +
    'contextBridge.exposeInMainWorld("electronAPI", {\n' +
    '  // ── App Info ──\n' +
    '  getAppVersion: () => ipcRenderer.invoke("app:version"),\n' +
    '  getPlatform: () => ipcRenderer.invoke("app:platform"),\n' +
    '  getIsDev: () => ipcRenderer.invoke("app:isDev"),\n' +
    '\n' +
    '  // ── Window Controls ──\n' +
    '  minimizeWindow: () => ipcRenderer.send("window:minimize"),\n' +
    '  maximizeWindow: () => ipcRenderer.send("window:maximize"),\n' +
    '  closeWindow: () => ipcRenderer.send("window:close"),\n' +
    '  isMaximized: () => ipcRenderer.invoke("window:isMaximized"),\n' +
    '\n' +
    '  // ── File Dialogs ──\n' +
    '  openFile: (options?: Electron.OpenDialogOptions) =>\n' +
    '    ipcRenderer.invoke("dialog:openFile", options),\n' +
    '  saveFile: (options?: Electron.SaveDialogOptions) =>\n' +
    '    ipcRenderer.invoke("dialog:saveFile", options),\n' +
    '\n' +
    '  // ── Notifications ──\n' +
    '  showNotification: (title: string, body: string) =>\n' +
    '    ipcRenderer.send("notification:show", { title, body }),\n' +
    '\n' +
    '  // ── Shell ──\n' +
    '  openExternal: (url: string) => ipcRenderer.send("shell:openExternal", url),\n' +
    '  openPath: (path: string) => ipcRenderer.send("shell:openPath", path),\n' +
    '\n' +
    '  // ── Store ──\n' +
    '  storeGet: (key: string) => ipcRenderer.invoke("store:get", key),\n' +
    '  storeSet: (key: string, value: unknown) =>\n' +
    '    ipcRenderer.invoke("store:set", key, value),\n' +
    '  storeDelete: (key: string) => ipcRenderer.invoke("store:delete", key),\n' +
    '\n' +
    '  // ── Updates ──\n' +
    '  checkForUpdates: () => ipcRenderer.invoke("updater:check"),\n' +
    '  downloadUpdate: () => ipcRenderer.invoke("updater:download"),\n' +
    '  quitAndInstall: () => ipcRenderer.send("updater:quitAndInstall"),\n' +
    '\n' +
    '  // ── Update Events ──\n' +
    '  onUpdateAvailable: (callback: (info: unknown) => void) => {\n' +
    '    ipcRenderer.on("updater:available", (_event, info) => callback(info));\n' +
    '  },\n' +
    '  onUpdateDownloaded: (callback: (info: unknown) => void) => {\n' +
    '    ipcRenderer.on("updater:downloaded", (_event, info) => callback(info));\n' +
    '  },\n' +
    '  onUpdateProgress: (callback: (progress: unknown) => void) => {\n' +
    '    ipcRenderer.on("updater:progress", (_event, progress) => callback(progress));\n' +
    '  },\n' +
    '  onUpdateError: (callback: (error: string) => void) => {\n' +
    '    ipcRenderer.on("updater:error", (_event, error) => callback(error));\n' +
    '  },\n' +
    '\n' +
    '  // ── Remove listener ──\n' +
    '  removeAllListeners: (channel: string) => {\n' +
    '    ipcRenderer.removeAllListeners(channel);\n' +
    '  },\n' +
    '});\n' +
    '\n' +
    '// ─── Type declarations for renderer ──────────────\n' +
    'export interface ElectronAPI {\n' +
    '  getAppVersion: () => Promise<string>;\n' +
    '  getPlatform: () => Promise<string>;\n' +
    '  getIsDev: () => Promise<boolean>;\n' +
    '  minimizeWindow: () => void;\n' +
    '  maximizeWindow: () => void;\n' +
    '  closeWindow: () => void;\n' +
    '  isMaximized: () => Promise<boolean>;\n' +
    '  openFile: (options?: Electron.OpenDialogOptions) => Promise<string[] | undefined>;\n' +
    '  saveFile: (options?: Electron.SaveDialogOptions) => Promise<string | undefined>;\n' +
    '  showNotification: (title: string, body: string) => void;\n' +
    '  openExternal: (url: string) => void;\n' +
    '  openPath: (path: string) => void;\n' +
    '  storeGet: (key: string) => Promise<unknown>;\n' +
    '  storeSet: (key: string, value: unknown) => Promise<void>;\n' +
    '  storeDelete: (key: string) => Promise<void>;\n' +
    '  checkForUpdates: () => Promise<void>;\n' +
    '  downloadUpdate: () => Promise<void>;\n' +
    '  quitAndInstall: () => void;\n' +
    '  onUpdateAvailable: (callback: (info: unknown) => void) => void;\n' +
    '  onUpdateDownloaded: (callback: (info: unknown) => void) => void;\n' +
    '  onUpdateProgress: (callback: (progress: unknown) => void) => void;\n' +
    '  onUpdateError: (callback: (error: string) => void) => void;\n' +
    '  removeAllListeners: (channel: string) => void;\n' +
    '}\n' +
    '\n' +
    'declare global {\n' +
    '  interface Window {\n' +
    '    electronAPI: ElectronAPI;\n' +
    '  }\n' +
    '}\n';
}

// ═══════════════════════════════════════════════════════
// 5. ipc.ts — IPC Handlers
// ═══════════════════════════════════════════════════════

function generateIpcTs() {
  return 'import { ipcMain, app, dialog, shell, BrowserWindow, Notification } from "electron";\n' +
    'import Store from "electron-store";\n' +
    '\n' +
    'const store = new Store() as any;\n' +
    '\n' +
    'export function setupIPC(mainWindow: BrowserWindow): void {\n' +
    '  // ── App Info ──\n' +
    '  ipcMain.handle("app:version", () => app.getVersion());\n' +
    '  ipcMain.handle("app:platform", () => process.platform);\n' +
    '  ipcMain.handle("app:isDev", () => !app.isPackaged);\n' +
    '\n' +
    '  // ── Window Controls ──\n' +
    '  ipcMain.on("window:minimize", () => {\n' +
    '    mainWindow?.minimize();\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.on("window:maximize", () => {\n' +
    '    if (mainWindow?.isMaximized()) {\n' +
    '      mainWindow.unmaximize();\n' +
    '    } else {\n' +
    '      mainWindow?.maximize();\n' +
    '    }\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.on("window:close", () => {\n' +
    '    mainWindow?.close();\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.handle("window:isMaximized", () => {\n' +
    '    return mainWindow?.isMaximized() ?? false;\n' +
    '  });\n' +
    '\n' +
    '  // ── File Dialogs ──\n' +
    '  ipcMain.handle("dialog:openFile", async (_event, options?) => {\n' +
    '    const result = await dialog.showOpenDialog(mainWindow, {\n' +
    '      properties: ["openFile"],\n' +
    '      ...options,\n' +
    '    });\n' +
    '    return result.canceled ? undefined : result.filePaths;\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.handle("dialog:saveFile", async (_event, options?) => {\n' +
    '    const result = await dialog.showSaveDialog(mainWindow, {\n' +
    '      ...options,\n' +
    '    });\n' +
    '    return result.canceled ? undefined : result.filePath;\n' +
    '  });\n' +
    '\n' +
    '  // ── Notifications ──\n' +
    '  ipcMain.on("notification:show", (_event, { title, body }) => {\n' +
    '    if (Notification.isSupported()) {\n' +
    '      new Notification({ title, body }).show();\n' +
    '    }\n' +
    '  });\n' +
    '\n' +
    '  // ── Shell ──\n' +
    '  ipcMain.on("shell:openExternal", (_event, url: string) => {\n' +
    '    shell.openExternal(url);\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.on("shell:openPath", (_event, path: string) => {\n' +
    '    shell.openPath(path);\n' +
    '  });\n' +
    '\n' +
    '  // ── Persistent Store ──\n' +
    '  ipcMain.handle("store:get", (_event, key: string) => {\n' +
    '    return store.get(key);\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.handle("store:set", (_event, key: string, value: unknown) => {\n' +
    '    store.set(key, value);\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.handle("store:delete", (_event, key: string) => {\n' +
    '    store.delete(key);\n' +
    '  });\n' +
    '}\n';
}

// ═══════════════════════════════════════════════════════
// 6. tray.ts — System Tray
// ═══════════════════════════════════════════════════════

function generateTrayTs() {
  return 'import { Tray, Menu, nativeImage, BrowserWindow, app } from "electron";\n' +
    'import { join } from "path";\n' +
    'import { existsSync } from "fs";\n' +
    '\n' +
    'let tray: Tray | null = null;\n' +
    '\n' +
    'export function setupTray(mainWindow: BrowserWindow, appName: string): void {\n' +
    '  const iconPath = getTrayIcon();\n' +
    '  if (!iconPath) {\n' +
    '    console.warn("[tray] No icon found, skipping tray setup");\n' +
    '    return;\n' +
    '  }\n' +
    '\n' +
    '  const icon = nativeImage.createFromPath(iconPath);\n' +
    '  tray = new Tray(icon.resize({ width: 16, height: 16 }));\n' +
    '  tray.setToolTip(appName);\n' +
    '\n' +
    '  const contextMenu = Menu.buildFromTemplate([\n' +
    '    {\n' +
    '      label: "Show " + appName,\n' +
    '      click: () => {\n' +
    '        mainWindow.show();\n' +
    '        mainWindow.focus();\n' +
    '      },\n' +
    '    },\n' +
    '    { type: "separator" },\n' +
    '    {\n' +
    '      label: "Quit",\n' +
    '      click: () => {\n' +
    '        app.quit();\n' +
    '      },\n' +
    '    },\n' +
    '  ]);\n' +
    '\n' +
    '  tray.setContextMenu(contextMenu);\n' +
    '\n' +
    '  tray.on("click", () => {\n' +
    '    if (mainWindow.isVisible()) {\n' +
    '      mainWindow.focus();\n' +
    '    } else {\n' +
    '      mainWindow.show();\n' +
    '    }\n' +
    '  });\n' +
    '\n' +
    '  tray.on("double-click", () => {\n' +
    '    mainWindow.show();\n' +
    '    mainWindow.focus();\n' +
    '  });\n' +
    '}\n' +
    '\n' +
    'function getTrayIcon(): string | null {\n' +
    '  const candidates = [\n' +
    '    join(__dirname, "..", "assets", "tray-icon.png"),\n' +
    '    join(__dirname, "..", "assets", "icon.png"),\n' +
    '    join(__dirname, "..", "assets", "icon.ico"),\n' +
    '  ];\n' +
    '  for (const p of candidates) {\n' +
    '    if (existsSync(p)) return p;\n' +
    '  }\n' +
    '  return null;\n' +
    '}\n';
}

// ═══════════════════════════════════════════════════════
// 7. updater.ts — Auto Updater
// ═══════════════════════════════════════════════════════

function generateUpdaterTs() {
  return 'import { autoUpdater } from "electron-updater";\n' +
    'import { ipcMain, BrowserWindow } from "electron";\n' +
    '\n' +
    'let updateAvailable = false;\n' +
    'let updateDownloaded = false;\n' +
    '\n' +
    'export function setupUpdater(appName: string): void {\n' +
    '  autoUpdater.autoDownload = false;\n' +
    '  autoUpdater.autoInstallOnAppQuit = true;\n' +
    '  autoUpdater.logger = {\n' +
    '    info: (msg) => console.log("[updater]", msg),\n' +
    '    warn: (msg) => console.warn("[updater]", msg),\n' +
    '    error: (msg) => console.error("[updater]", msg),\n' +
    '    debug: (msg) => console.debug("[updater]", msg),\n' +
    '  };\n' +
    '\n' +
    '  autoUpdater.on("checking-for-update", () => {\n' +
    '    console.log("[updater] Checking for updates...");\n' +
    '  });\n' +
    '\n' +
    '  autoUpdater.on("update-available", (info) => {\n' +
    '    updateAvailable = true;\n' +
    '    console.log("[updater] Update available:", info.version);\n' +
    '    broadcast("updater:available", info);\n' +
    '  });\n' +
    '\n' +
    '  autoUpdater.on("update-not-available", (info) => {\n' +
    '    console.log("[updater] Up to date:", info.version);\n' +
    '  });\n' +
    '\n' +
    '  autoUpdater.on("download-progress", (progress) => {\n' +
    '    broadcast("updater:progress", {\n' +
    '      percent: progress.percent,\n' +
    '      bytesPerSecond: progress.bytesPerSecond,\n' +
    '      transferred: progress.transferred,\n' +
    '      total: progress.total,\n' +
    '    });\n' +
    '  });\n' +
    '\n' +
    '  autoUpdater.on("update-downloaded", (info) => {\n' +
    '    updateDownloaded = true;\n' +
    '    console.log("[updater] Update downloaded:", info.version);\n' +
    '    broadcast("updater:downloaded", info);\n' +
    '  });\n' +
    '\n' +
    '  autoUpdater.on("error", (err) => {\n' +
    '    console.error("[updater] Error:", err.message);\n' +
    '    broadcast("updater:error", err.message);\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.handle("updater:check", async () => {\n' +
    '    try {\n' +
    '      const result = await autoUpdater.checkForUpdates();\n' +
    '      return result?.updateInfo ?? null;\n' +
    '    } catch (err) {\n' +
    '      console.error("[updater] Check failed:", err);\n' +
    '      return null;\n' +
    '    }\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.handle("updater:download", async () => {\n' +
    '    if (!updateAvailable) return;\n' +
    '    try {\n' +
    '      await autoUpdater.downloadUpdate();\n' +
    '    } catch (err) {\n' +
    '      console.error("[updater] Download failed:", err);\n' +
    '    }\n' +
    '  });\n' +
    '\n' +
    '  ipcMain.on("updater:quitAndInstall", () => {\n' +
    '    if (updateDownloaded) {\n' +
    '      autoUpdater.quitAndInstall(false, true);\n' +
    '    }\n' +
    '  });\n' +
    '\n' +
    '  setTimeout(() => {\n' +
    '    autoUpdater.checkForUpdates().catch(() => {});\n' +
    '  }, 10_000);\n' +
    '}\n' +
    '\n' +
    'function broadcast(channel: string, data: unknown): void {\n' +
    '  for (const win of BrowserWindow.getAllWindows()) {\n' +
    '    if (!win.isDestroyed()) {\n' +
    '      win.webContents.send(channel, data);\n' +
    '    }\n' +
    '  }\n' +
    '}\n';
}

// ═══════════════════════════════════════════════════════
// 8. utils.ts — Shared Utilities
// ═══════════════════════════════════════════════════════

function generateUtilsTs(webPort, apiPort) {
  return 'import { app } from "electron";\n' +
    'import { join } from "path";\n' +
    'import { existsSync } from "fs";\n' +
    '\n' +
    '// ─── Single Instance Lock ────────────────────────\n' +
    'export function checkSingleInstance(): boolean {\n' +
    '  const gotLock = app.requestSingleInstanceLock();\n' +
    '  if (!gotLock) {\n' +
    '    app.quit();\n' +
    '    return false;\n' +
    '  }\n' +
    '\n' +
    '  app.on("second-instance", (_event, _argv, _cwd) => {\n' +
    '    const windows = require("electron").BrowserWindow.getAllWindows();\n' +
    '    if (windows.length > 0) {\n' +
    '      const win = windows[0];\n' +
    '      if (win.isMinimized()) win.restore();\n' +
    '      win.focus();\n' +
    '    }\n' +
    '  });\n' +
    '\n' +
    '  return true;\n' +
    '}\n' +
    '\n' +
    '// ─── Dev mode detection ──────────────────────────\n' +
    'export function isDev(): boolean {\n' +
    '  return !app.isPackaged;\n' +
    '}\n' +
    '\n' +
    '// ─── Web URL ─────────────────────────────────────\n' +
    'export function getWebUrl(dev: boolean, port: number): string {\n' +
    '  if (dev) {\n' +
    '    return "http://localhost:" + port;\n' +
    '  }\n' +
    '\n' +
    '  const appPath = app.isPackaged\n' +
    '    ? join(process.resourcesPath, "web")\n' +
    '    : join(__dirname, "..", "..", "web", "out");\n' +
    '\n' +
    '  const staticIndex = join(appPath, "index.html");\n' +
    '  if (existsSync(staticIndex)) {\n' +
    '    return staticIndex;\n' +
    '  }\n' +
    '\n' +
    '  const standalone = join(appPath, ".next", "standalone", "server.js");\n' +
    '  if (existsSync(standalone)) {\n' +
    '    return "http://localhost:' + webPort + '";\n' +
    '  }\n' +
    '\n' +
    '  const fallback = join(appPath, "index.html");\n' +
    '  if (existsSync(fallback)) {\n' +
    '    return fallback;\n' +
    '  }\n' +
    '\n' +
    '  console.warn("[utils] No web build found at", appPath);\n' +
    '  return "http://localhost:' + webPort + '";\n' +
    '}\n' +
    '\n' +
    '// ─── API Path ────────────────────────────────────\n' +
    'export function getApiPath(): string | null {\n' +
    '  if (app.isPackaged) {\n' +
    '    const packed = join(process.resourcesPath, "api", "dist", "index.js");\n' +
    '    if (existsSync(packed)) return packed;\n' +
    '    const packedSrc = join(process.resourcesPath, "api", "src", "index.js");\n' +
    '    if (existsSync(packedSrc)) return packedSrc;\n' +
    '  }\n' +
    '\n' +
    '  const devPath = join(__dirname, "..", "..", "api", "dist", "index.js");\n' +
    '  if (existsSync(devPath)) return devPath;\n' +
    '\n' +
    '  const devSrc = join(__dirname, "..", "..", "api", "src", "index.js");\n' +
    '  if (existsSync(devSrc)) return devSrc;\n' +
    '\n' +
    '  return null;\n' +
    '}\n' +
    '\n' +
    '// ─── Get API port from env ───────────────────────\n' +
    'export function getApiPort(): number {\n' +
    '  return ' + apiPort + ';\n' +
    '}\n';
}

// ═══════════════════════════════════════════════════════
// 9. tsconfig.json Generator
// ═══════════════════════════════════════════════════════

function generateTsConfig() {
  return JSON.stringify({
    compilerOptions: {
      target: "ES2022",
      module: "commonjs",
      moduleResolution: "node",
      lib: ["ES2022"],
      outDir: "dist",
      rootDir: "electron",
      strict: true,
      esModuleInterop: true,
      skipLibCheck: true,
      forceConsistentCasingInFileNames: true,
      resolveJsonModule: true,
      declaration: false,
      sourceMap: true,
      noUnusedLocals: false,
      noUnusedParameters: false,
      types: ["node"],
    },
    include: ["electron/**/*.ts"],
    exclude: ["node_modules", "dist", "release"],
  }, null, 2);
}

// ═══════════════════════════════════════════════════════
// 10. README.md Generator
// ═══════════════════════════════════════════════════════

function generateReadme(projectName) {
  const displayName = projectName || "Desktop App";
  const bt = String.fromCharCode(96); // backtick
  const fence = bt + bt + bt;

  return "# " + displayName + " — Desktop\n" +
    "\n" +
    "> Electron desktop application wrapping the " + displayName + " fullstack web project.\n" +
    "\n" +
    "## Architecture\n" +
    "\n" +
    fence + "\n" +
    "Web Fullstack Project\n" +
    "        ×\n" +
    "   Desktop Shell (Electron)\n" +
    "        =\n" +
    "    Desktop Application\n" +
    fence + "\n" +
    "\n" +
    "- **Main Process** — Window management, menu, tray, IPC, auto-update\n" +
    "- **Preload** — Secure bridge between main and renderer\n" +
    "- **Renderer** — Your existing web frontend (Next.js)\n" +
    "\n" +
    "## Development\n" +
    "\n" +
    fence + "bash\n" +
    "# Install dependencies\n" +
    "npm install\n" +
    "\n" +
    "# Start dev mode (web + api + electron concurrently)\n" +
    "npm run dev\n" +
    fence + "\n" +
    "\n" +
    "Dev mode:\n" +
    "- Web frontend: " + bt + "http://localhost:3000" + bt + "\n" +
    "- API backend: " + bt + "http://localhost:3001" + bt + "\n" +
    "- Electron loads the web URL directly\n" +
    "\n" +
    "## Build\n" +
    "\n" +
    fence + "bash\n" +
    "# Build for current platform\n" +
    "npm run build\n" +
    "\n" +
    "# Platform-specific builds\n" +
    "npm run build:electron:mac\n" +
    "npm run build:electron:win\n" +
    "npm run build:electron:linux\n" +
    fence + "\n" +
    "\n" +
    "Output: " + bt + "release/" + bt + " directory with platform installers.\n" +
    "\n" +
    "## Project Structure\n" +
    "\n" +
    fence + "\n" +
    "electron/\n" +
    "├── main.ts      — Main process (BrowserWindow, Menu, lifecycle)\n" +
    "├── preload.ts   — Secure IPC bridge (contextBridge)\n" +
    "├── ipc.ts       — IPC handlers (dialogs, store, notifications)\n" +
    "├── tray.ts      — System tray\n" +
    "├── updater.ts   — Auto-update (electron-updater)\n" +
    "└── utils.ts     — Shared utilities\n" +
    fence + "\n" +
    "\n" +
    "## IPC API (available in renderer)\n" +
    "\n" +
    fence + "typescript\n" +
    "window.electronAPI.getAppVersion()\n" +
    "window.electronAPI.getPlatform()\n" +
    "window.electronAPI.minimizeWindow()\n" +
    "window.electronAPI.maximizeWindow()\n" +
    "window.electronAPI.closeWindow()\n" +
    "window.electronAPI.openFile(options?)\n" +
    "window.electronAPI.saveFile(options?)\n" +
    "window.electronAPI.showNotification(title, body)\n" +
    "window.electronAPI.openExternal(url)\n" +
    "window.electronAPI.storeGet(key)\n" +
    "window.electronAPI.storeSet(key, value)\n" +
    "window.electronAPI.checkForUpdates()\n" +
    "window.electronAPI.downloadUpdate()\n" +
    "window.electronAPI.quitAndInstall()\n" +
    fence + "\n" +
    "\n" +
    "## Auto Update\n" +
    "\n" +
    "Configured via " + bt + "electron-builder.yml" + bt + ". Uses " + bt + "electron-updater" + bt + " with generic provider.\n" +
    "Set your release URL in the " + bt + "publish" + bt + " section.\n";
}

// ═══════════════════════════════════════════════════════
// 11. Placeholder Icon Generator
// ═══════════════════════════════════════════════════════

function generatePlaceholderIcon() {
  // Minimal valid 1x1 transparent PNG
  return Buffer.from([
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
    0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
    0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4,
    0x89, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41,
    0x54, 0x78, 0x9c, 0x62, 0x00, 0x00, 0x00, 0x02,
    0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 0x00,
    0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,
    0x60, 0x82,
  ]);
}

// ═══════════════════════════════════════════════════════
// 12. macOS Entitlements
// ═══════════════════════════════════════════════════════

function generateEntitlements() {
  return '<?xml version="1.0" encoding="UTF-8"?>\n' +
    '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n' +
    '<plist version="1.0">\n' +
    '<dict>\n' +
    '  <key>com.apple.security.cs.allow-jit</key>\n' +
    '  <true/>\n' +
    '  <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n' +
    '  <true/>\n' +
    '  <key>com.apple.security.cs.allow-dyld-environment-variables</key>\n' +
    '  <true/>\n' +
    '  <key>com.apple.security.network.client</key>\n' +
    '  <true/>\n' +
    '  <key>com.apple.security.network.server</key>\n' +
    '  <true/>\n' +
    '  <key>com.apple.security.files.user-selected.read-write</key>\n' +
    '  <true/>\n' +
    '</dict>\n' +
    '</plist>\n';
}

// ═══════════════════════════════════════════════════════
// 13. Main Builder Function
// ═══════════════════════════════════════════════════════

/**
 * Build Electron desktop app wrapper for a fullstack project.
 *
 * @param {object} options
 * @param {string} options.projectName — Project display name
 * @param {number} [options.webPort=3000] — Dev web port
 * @param {number} [options.apiPort=3001] — Dev API port
 * @returns {object} { files, stats, error, message }
 */
export function buildElectron(options = {}) {
  const {
    projectName = "Desktop App",
    webPort = 3000,
    apiPort = 3001,
  } = options;

  const files = {};

  files["package.json"] = generatePackageJson(projectName, webPort, apiPort);
  files["electron-builder.yml"] = generateElectronBuilderYml(projectName);
  files["tsconfig.json"] = generateTsConfig();
  files["electron/main.ts"] = generateMainTs(projectName, webPort, apiPort);
  files["electron/preload.ts"] = generatePreloadTs();
  files["electron/ipc.ts"] = generateIpcTs();
  files["electron/tray.ts"] = generateTrayTs();
  files["electron/updater.ts"] = generateUpdaterTs();
  files["electron/utils.ts"] = generateUtilsTs(webPort, apiPort);
  files["assets/icon.png"] = generatePlaceholderIcon();
  files["entitlements.mac.plist"] = generateEntitlements();
  files["README.md"] = generateReadme(projectName);

  const allPaths = Object.keys(files);
  const stats = {
    totalFiles: allPaths.length,
    electronFiles: allPaths.filter(p => p.startsWith("electron/")).length,
    configFiles: allPaths.filter(p =>
      p.endsWith(".json") || p.endsWith(".yml") || p.endsWith(".plist")
    ).length,
  };

  return { files, stats, error: null, message: null };
}

// ═══════════════════════════════════════════════════════
// 14. File I/O
// ═══════════════════════════════════════════════════════

export function writeElectron(result, outputDir) {
  mkdirSync(outputDir, { recursive: true });
  for (const [relPath, content] of Object.entries(result.files)) {
    const fullPath = resolve(outputDir, relPath);
    mkdirSync(dirname(fullPath), { recursive: true });
    if (typeof content === "string") {
      writeFileSync(fullPath, content, "utf-8");
    } else {
      writeFileSync(fullPath, content);
    }
  }
}

// ═══════════════════════════════════════════════════════
// 15. CLI Entry
// ═══════════════════════════════════════════════════════

function parseArgs() {
  const args = process.argv.slice(2);
  const opts = {
    input: null,
    prd: null,
    arch: null,
    output: null,
    webPort: 3000,
    apiPort: 3001,
    verbose: false,
    help: false,
  };
  for (let i = 0; i < args.length; i++) {
    if (args[i] === "--input" && args[i + 1]) opts.input = args[++i];
    else if (args[i] === "--prd" && args[i + 1]) opts.prd = args[++i];
    else if (args[i] === "--arch" && args[i + 1]) opts.arch = args[++i];
    else if (args[i] === "--output" && args[i + 1]) opts.output = args[++i];
    else if (args[i] === "--web-port" && args[i + 1]) opts.webPort = parseInt(args[++i], 10);
    else if (args[i] === "--api-port" && args[i + 1]) opts.apiPort = parseInt(args[++i], 10);
    else if (args[i] === "--verbose") opts.verbose = true;
    else if (args[i] === "--help" || args[i] === "-h") opts.help = true;
  }
  return opts;
}

async function main() {
  const opts = parseArgs();

  if (opts.help) {
    console.log(
      "\nElectron Builder Agent — SF-06\n\n" +
      "Wrap a fullstack web project as an Electron desktop app.\n\n" +
      "Usage:\n" +
      "  node scripts/electron-builder-agent.mjs --input <fullstack-dir> [options]\n" +
      "  node scripts/electron-builder-agent.mjs --prd <path> --arch <path> [options]\n\n" +
      "Options:\n" +
      "  --input <dir>         SF-05 output directory (apps/web, apps/api, packages/)\n" +
      "  --prd <path>          PRD JSON (SF-01 output)\n" +
      "  --arch <path>         Architecture JSON (SF-02 output)\n" +
      "  --output <dir>        Output directory (default: <input>/apps/desktop)\n" +
      "  --web-port <port>     Dev web port (default: 3000)\n" +
      "  --api-port <port>     Dev API port (default: 3001)\n" +
      "  --verbose             Verbose output\n" +
      "  --help                Show this help\n"
    );
    return;
  }

  let projectName = "Desktop App";

  if (opts.prd) {
    try {
      const prd = JSON.parse(readFileSync(opts.prd, "utf-8"));
      if (prd.projectName) projectName = prd.projectName;
    } catch {}
  }

  if (opts.input) {
    try {
      const rootPkg = JSON.parse(readFileSync(resolve(opts.input, "package.json"), "utf-8"));
      if (rootPkg.name) projectName = rootPkg.name;
    } catch {}
  }

  const result = buildElectron({
    projectName,
    webPort: opts.webPort,
    apiPort: opts.apiPort,
  });

  if (result.error) {
    console.error(result.message);
    process.exit(1);
  }

  const outputDir = opts.output
    ? resolve(opts.output)
    : opts.input
    ? resolve(opts.input, "apps", "desktop")
    : resolve(WORKSPACE, "desktop");

  writeElectron(result, outputDir);

  if (opts.verbose) {
    console.error("Project: " + projectName);
    console.error("Files: " + result.stats.totalFiles);
    console.error("Electron files: " + result.stats.electronFiles);
    console.error("Config files: " + result.stats.configFiles);
  }

  console.log(JSON.stringify({
    projectName,
    outputDir,
    stats: result.stats,
  }, null, 2));
}

if (process.argv[1] && (import.meta.url === "file://" + process.argv[1] || import.meta.url.endsWith(process.argv[1]))) {
  main();
}
