import type { ForgeConfig } from '@electron-forge/shared-types';
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
import { VitePlugin } from '@electron-forge/plugin-vite';
import { FusesPlugin } from '@electron-forge/plugin-fuses';
import { FuseV1Options, FuseVersion } from '@electron/fuses';
import path from 'node:path';
import fs from 'node:fs';
import { execSync } from 'node:child_process';

// Packages externalized in vite.main.config.mts that must be installed at runtime.
// Keep this list in sync with the Vite external array.
const externalPackages = [
  'better-sqlite3',
  '@lancedb/lancedb',
  'ws',
  'electron-squirrel-startup',
  'electron-store',
];

const config: ForgeConfig = {
  packagerConfig: {
    asar: {
      unpack: '**/{*.node,*.dll,*.so,*.dylib}',
    },
    name: 'adiuvAI',
    // icon path without extension — Forge picks .ico (Win), .icns (Mac), .png (Linux)
    icon: 'assets/logo/logo-icon',
    // Ship Drizzle's generated migrations as a sibling of the asar so the
    // runtime migrator (drizzle-orm/better-sqlite3/migrator) can read them at
    // `<resourcesPath>/migrations/` in packaged builds. See src/main/db/index.ts.
    extraResource: ['./src/main/db/migrations'],
    // Deep-link protocol for OAuth callback: adiuvai://oauth/callback?code=...
    // macOS: written into Info.plist by Forge automatically.
    // Windows: registered by the Squirrel installer via packagerConfig.protocols.
    // Dev: app.setAsDefaultProtocolClient() in index.ts handles both platforms.
    protocols: [
      {
        name: 'AdiuvAI',
        schemes: ['adiuvai'],
      },
    ],
  },
  rebuildConfig: {},
  hooks: {
    packageAfterCopy: async (_forgeConfig, buildPath, _electronVersion, platform, arch) => {
      // The VitePlugin's ignore filter only copies .vite/ into the build.
      // Externalized packages need to be installed into node_modules here.
      // At this point, only .vite/ exists. The VitePlugin writes package.json
      // in its own afterCopy hook (which may run after ours). Read from source.
      const srcPjPath = path.resolve(__dirname, 'package.json');
      const pjPath = path.resolve(buildPath, 'package.json');
      const pj = JSON.parse(fs.readFileSync(srcPjPath, 'utf-8'));

      // Keep only externalized packages in dependencies
      const filtered: Record<string, string> = {};
      for (const pkg of externalPackages) {
        if (pj.dependencies?.[pkg]) {
          filtered[pkg] = pj.dependencies[pkg];
        }
      }
      pj.dependencies = filtered;
      delete pj.devDependencies;
      fs.writeFileSync(pjPath, JSON.stringify(pj, null, 2));

      // Copy lockfile for reproducible installs
      const lockSrc = path.resolve(buildPath, '..', '..', 'package-lock.json');
      if (fs.existsSync(lockSrc)) {
        fs.copyFileSync(lockSrc, path.resolve(buildPath, 'package-lock.json'));
      }

      // Install only the externalized runtime deps
      console.log('[forge] Installing externalized dependencies...');
      execSync('npm install --omit=dev', {
        cwd: buildPath,
        stdio: 'inherit',
        env: { ...process.env, npm_config_nodedir: '' },
      });

      const targetKey = `${platform}-${arch}`;

      // @lancedb/lancedb uses platform-specific optional deps (@lancedb/lancedb-<platform>-<arch>-*).
      // npm install on Linux only pulls the Linux variant. Force-install the target's.
      const platformNativePackages: Record<string, Record<string, string>> = {
        'win32-x64': {
          '@lancedb/lancedb-win32-x64-msvc': '',
        },
        'linux-x64': {
          '@lancedb/lancedb-linux-x64-gnu': '',
        },
        'darwin-x64': {
          '@lancedb/lancedb-darwin-x64': '',
        },
        'darwin-arm64': {
          '@lancedb/lancedb-darwin-arm64': '',
        },
      };
      const nativePkgs = platformNativePackages[targetKey];
      if (nativePkgs) {
        // Remove wrong-platform lancedb native packages
        const nmPath = path.join(buildPath, 'node_modules', '@lancedb');
        if (fs.existsSync(nmPath)) {
          for (const entry of fs.readdirSync(nmPath)) {
            if (entry.startsWith('lancedb-') && !Object.keys(nativePkgs).includes(`@lancedb/${entry}`)) {
              fs.rmSync(path.join(nmPath, entry), { recursive: true, force: true });
              console.log(`[forge] Removed non-target native package: @lancedb/${entry}`);
            }
          }
        }
        // Install correct platform packages
        const pkgsToInstall = Object.keys(nativePkgs).join(' ');
        console.log(`[forge] Installing platform-specific packages for ${targetKey}: ${pkgsToInstall}`);
        execSync(`npm install ${pkgsToInstall} --omit=dev --no-save --force`, {
          cwd: buildPath,
          stdio: 'inherit',
        });
      }

      // Remove cross-platform prebuilt binaries that don't match the target.
      // Keeping foreign-arch .node files breaks rpmbuild's strip step.
      const nodeModulesPath = path.join(buildPath, 'node_modules');
      const findPrebuilds = (dir: string): string[] => {
        const results: string[] = [];
        if (!fs.existsSync(dir)) return results;
        for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
          const full = path.join(dir, entry.name);
          if (entry.isDirectory()) {
            if (entry.name === 'prebuilds') {
              results.push(full);
            } else {
              results.push(...findPrebuilds(full));
            }
          }
        }
        return results;
      };

      for (const prebuildsDir of findPrebuilds(nodeModulesPath)) {
        for (const entry of fs.readdirSync(prebuildsDir)) {
          if (entry !== targetKey) {
            const fullPath = path.join(prebuildsDir, entry);
            fs.rmSync(fullPath, { recursive: true, force: true });
            console.log(`[forge] Removed non-target prebuild: ${entry}`);
          }
        }
      }

    },

    // ── Post-rebuild: fix native binaries for cross-compilation ──────
    // Forge runs @electron/rebuild AFTER packageAfterCopy, which
    // recompiles native addons for the BUILD platform (Linux).
    // packageAfterPrune runs AFTER rebuild+prune, so we can safely
    // replace the Linux .node files with the correct target prebuilts.
    packageAfterPrune: async (_forgeConfig, buildPath, _electronVersion, platform, arch) => {
      const targetKey = `${platform}-${arch}`;
      const buildKey = `${process.platform}-${process.arch}`;
      if (targetKey === buildKey) return; // native build — nothing to fix

      console.log(`[forge:afterPrune] Cross-compile fixup: ${buildKey} → ${targetKey}`);
      const electronVersion = JSON.parse(
        fs.readFileSync(path.resolve(__dirname, 'node_modules', 'electron', 'package.json'), 'utf-8'),
      ).version;

      // Replace native addons that @electron/rebuild compiled for the host.
      const nativeModules = ['better-sqlite3'];
      for (const mod of nativeModules) {
        const modDir = path.join(buildPath, 'node_modules', mod);
        if (!fs.existsSync(modDir)) continue;

        // Remove the host-platform binary left by @electron/rebuild
        const buildRelease = path.join(modDir, 'build', 'Release');
        if (fs.existsSync(buildRelease)) {
          fs.rmSync(buildRelease, { recursive: true, force: true });
          console.log(`[forge:afterPrune] Cleaned host-platform build/Release for ${mod}`);
        }

        // Download the correct prebuilt for the TARGET platform
        console.log(`[forge:afterPrune] Downloading ${mod} prebuilt for ${targetKey} (Electron ${electronVersion})...`);
        execSync(
          `npx --yes prebuild-install -r electron -t ${electronVersion} ` +
          `--platform ${platform} --arch ${arch} --tag-prefix v --verbose`,
          { cwd: modDir, stdio: 'inherit' },
        );

        // Verify the binary exists and is for the correct platform.
        const releaseDir = path.join(modDir, 'build', 'Release');
        if (!fs.existsSync(releaseDir)) {
          throw new Error(
            `[forge] FATAL: build/Release/ not found for ${mod} after prebuild-install. ` +
            `The native binary was not downloaded.`,
          );
        }
        const nodeFiles = fs.readdirSync(releaseDir).filter((f) => f.endsWith('.node'));
        if (nodeFiles.length === 0) {
          throw new Error(
            `[forge] FATAL: No .node files in build/Release/ for ${mod} after prebuild-install.`,
          );
        }
        for (const f of nodeFiles) {
          const buf = Buffer.alloc(4);
          const fd = fs.openSync(path.join(releaseDir, f), 'r');
          fs.readSync(fd, buf, 0, 4, 0);
          fs.closeSync(fd);
          // ELF magic: 0x7f 'E' 'L' 'F'
          if (buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46) {
            throw new Error(
              `[forge] FATAL: ${mod} build/Release/${f} is an ELF binary! ` +
              `Cross-compilation failed — refusing to package a Linux .node for Windows.`,
            );
          }
          // PE magic: 'M' 'Z' (0x4d 0x5a) — expected for win32
          if (platform === 'win32' && !(buf[0] === 0x4d && buf[1] === 0x5a)) {
            throw new Error(
              `[forge] FATAL: ${mod} build/Release/${f} is not a PE (Windows) binary! ` +
              `Magic bytes: ${buf.toString('hex')}. Refusing to package.`,
            );
          }
          console.log(`[forge:afterPrune] Verified ${mod}/${f} — correct platform ✓`);
        }
      }
    },
  },
  makers: [
    new MakerSquirrel({}),
    new MakerZIP({}, ['darwin']),
    new MakerRpm({}),
    new MakerDeb({}),
  ],
  plugins: [
    new AutoUnpackNativesPlugin({}),
    new VitePlugin({
      // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
      // If you are familiar with Vite configuration, it will look really familiar.
      build: [
        {
          // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
          entry: 'src/main/index.ts',
          config: 'vite.main.config.mts',
          target: 'main',
        },
        {
          entry: 'src/preload/index.ts',
          config: 'vite.preload.config.mts',
          target: 'preload',
        },
      ],
      renderer: [
        {
          name: 'main_window',
          config: 'vite.renderer.config.mts',
        },
      ],
    }),
    // Fuses are used to enable/disable various Electron functionality
    // at package time, before code signing the application
    new FusesPlugin({
      version: FuseVersion.V1,
      [FuseV1Options.RunAsNode]: false,
      [FuseV1Options.EnableCookieEncryption]: true,
      [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
      [FuseV1Options.EnableNodeCliInspectArguments]: false,
      [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
      [FuseV1Options.OnlyLoadAppFromAsar]: true,
    }),
  ],
};

export default config;
