import {
  cpSync,
  existsSync,
  mkdirSync,
  readFileSync,
  writeFileSync
} from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const electrobunDir = path.join(rootDir, 'node_modules/electrobun')
const sharedSrcDir = path.join(electrobunDir, 'src/shared')
const sharedDistDir = path.join(electrobunDir, 'dist/api/shared')

if (!existsSync(sharedDistDir)) {
  console.error('patch-electrobun-src: electrobun dist/api/shared not found')
  process.exit(1)
}

mkdirSync(sharedSrcDir, { recursive: true })

for (const fileName of [
  'cef-version.ts',
  'bun-version.ts',
  'electrobun-version.ts',
  'naming.ts',
  'rpc.ts'
]) {
  const source = path.join(sharedDistDir, fileName)
  const destination = path.join(sharedSrcDir, fileName)
  if (existsSync(source)) {
    cpSync(source, destination)
  }
}

const platformPatch = `import { platform, arch } from "os";

export type SupportedOS = "macos" | "win" | "linux";
export type SupportedArch = "arm64" | "x64";

const platformName = platform();
const archName = arch();

export const OS: SupportedOS = (() => {
  switch (platformName) {
    case "win32":
      return "win";
    case "darwin":
      return "macos";
    case "linux":
      return "linux";
    default:
      throw new Error(\`Unsupported platform: \${platformName}\`);
  }
})();

function normalizeForcedArch(value) {
  if (value === "arm64" || value === "aarch64") {
    return "arm64";
  }
  if (value === "x64" || value === "amd64") {
    return "x64";
  }
  return null;
}

export const ARCH: SupportedArch = (() => {
  const forcedArch = normalizeForcedArch(
    process.env.ELECTROBUN_FORCE_ARCH || process.env.ELECTROBUN_TARGET_ARCH,
  );
  if (forcedArch) {
    return forcedArch;
  }

  if (OS === "win") {
    return "x64";
  }

  switch (archName) {
    case "arm64":
      return "arm64";
    case "x64":
      return "x64";
    default:
      throw new Error(\`Unsupported architecture: \${archName}\`);
  }
})();

export const HOST_ARCH: SupportedArch = (() => {
  if (OS === "win") {
    return "x64";
  }

  switch (archName) {
    case "arm64":
      return "arm64";
    case "x64":
      return "x64";
    default:
      throw new Error(\`Unsupported architecture: \${archName}\`);
  }
})();

export function getPlatformOS(): SupportedOS {
  return OS;
}

export function getPlatformArch(): SupportedArch {
  return ARCH;
}
`

writeFileSync(path.join(sharedSrcDir, 'platform.ts'), platformPatch)

const templatesDir = path.join(electrobunDir, 'src/cli/templates')
mkdirSync(templatesDir, { recursive: true })
writeFileSync(
  path.join(templatesDir, 'embedded.ts'),
  `export function getTemplateNames() {
  return [];
}

export function getTemplate(name: string) {
  throw new Error(\`Template not available in patched electrobun CLI: \${name}\`);
}
`
)

const RCEDIT_RESOLVER_FN = `function resolveProjectRceditPkgPath(projectRoot) {
	const candidates = [
		join(projectRoot, "node_modules/rcedit/package.json"),
		join(projectRoot, "node_modules/electrobun/node_modules/rcedit/package.json"),
	];
	for (const candidate of candidates) {
		if (existsSync(candidate)) {
			return candidate;
		}
	}
	throw new Error(
		"rcedit not found under " + projectRoot + ". Install rcedit in the project (bun add -d rcedit).",
	);
}`

const cliPath = path.join(electrobunDir, 'src/cli/index.ts')
let cliSource = readFileSync(cliPath, 'utf8')
if (!cliSource.includes('HOST_ARCH')) {
  cliSource = cliSource.replace(
    'import { OS, ARCH } from "../shared/platform";',
    'import { OS, ARCH, HOST_ARCH } from "../shared/platform";'
  )
  cliSource = cliSource.replace(
    'const hostPaths = getPlatformPaths(OS, ARCH);',
    'const hostPaths = getPlatformPaths(OS, HOST_ARCH);'
  )
}
if (!cliSource.includes('toolPaths')) {
  cliSource = cliSource.replace(
    'const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);',
    'const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);\n\t\tconst toolPaths = getPlatformPaths(currentTarget.os, ARCH !== HOST_ARCH ? HOST_ARCH : ARCH);'
  )
  cliSource = cliSource.replaceAll(
    'const zstdPath = targetPaths.ZSTD;',
    'const zstdPath = toolPaths.ZSTD;'
  )
  cliSource = cliSource.replaceAll(
    'const bsdiffpath = targetPaths.BSDIFF;',
    'const bsdiffpath = toolPaths.BSDIFF;'
  )
  cliSource = cliSource.replace(
    'zigAsarCli = join(targetPaths.BSPATCH).replace("bspatch", "zig-asar");',
    'zigAsarCli = join(toolPaths.BSPATCH).replace("bspatch", "zig-asar");'
  )
  writeFileSync(cliPath, cliSource)
  cliSource = readFileSync(cliPath, 'utf8')
}
if (!cliSource.includes('hookBunBinary')) {
  cliSource = cliSource.replace(
    `const hostPaths = getPlatformPaths(OS, HOST_ARCH);

			const result = Bun.spawnSync([hostPaths.BUN_BINARY, hookScript],`,
    `const hostPaths = getPlatformPaths(OS, HOST_ARCH);
			const hookBunBinary = existsSync(hostPaths.BUN_BINARY)
				? hostPaths.BUN_BINARY
				: process.execPath;

			const result = Bun.spawnSync([hookBunBinary, hookScript],`
  )
  cliSource = cliSource.replace(
    'console.error("Tried to run with bun at:", hostPaths.BUN_BINARY);',
    'console.error("Tried to run with bun at:", hookBunBinary);'
  )
  writeFileSync(cliPath, cliSource)
  cliSource = readFileSync(cliPath, 'utf8')
}

if (!cliSource.includes('resolveProjectRceditPkgPath')) {
  cliSource = cliSource.replaceAll(
    'const rceditPkgPath = require.resolve("rcedit/package.json");',
    'const rceditPkgPath = resolveProjectRceditPkgPath(projectRoot);'
  )
  cliSource = cliSource.replace(
    'function getPlatformPaths(',
    `${RCEDIT_RESOLVER_FN}

function getPlatformPaths(`
  )
  writeFileSync(cliPath, cliSource)
  cliSource = readFileSync(cliPath, 'utf8')
} else {
  cliSource = cliSource.replace(
    /function resolveProjectRceditPkgPath\(projectRoot\) \{[\s\S]*?\n\}/m,
    RCEDIT_RESOLVER_FN
  )
  writeFileSync(cliPath, cliSource)
}

const electrobunCjsPath = path.join(electrobunDir, 'bin/electrobun.cjs')
let electrobunCjs = readFileSync(electrobunCjsPath, 'utf8')

if (!electrobunCjs.includes('farmcontrol-use-patched-cli')) {
  electrobunCjs = electrobunCjs.replace(
    `async function main() {
  try {
    const args = process.argv.slice(2);
    const cliPath = await ensureCliBinary();

    // Replace this process with the actual CLI
    const child = spawn(cliPath, args, {
      stdio: 'inherit',
      cwd: process.cwd()
    });`,
    `async function main() {
  try {
    const args = process.argv.slice(2);
    const patchedCliPath = join(electrobunDir, 'src', 'cli', 'index.ts');

    if (existsSync(patchedCliPath)) {
      // farmcontrol-use-patched-cli: bundled electrobun.exe cannot resolve project rcedit
      const bunBinary = process.env.BUN_INSTALL
        ? join(process.env.BUN_INSTALL, 'bin', 'bun' + binExt)
        : 'bun';
      const child = spawn(bunBinary, [patchedCliPath, ...args], {
        stdio: 'inherit',
        cwd: process.cwd(),
        env: process.env,
        shell: platform === 'win',
      });

      child.on('exit', (code) => {
        process.exit(code || 0);
      });

      child.on('error', (error) => {
        console.error('Failed to start electrobun patched CLI:', error.message);
        process.exit(1);
      });

      return;
    }

    const cliPath = await ensureCliBinary();

    // Replace this process with the actual CLI
    const child = spawn(cliPath, args, {
      stdio: 'inherit',
      cwd: process.cwd()
    });`
  )
  writeFileSync(electrobunCjsPath, electrobunCjs)
}

const rceditSrc = path.join(rootDir, 'node_modules/rcedit')
const rceditDest = path.join(electrobunDir, 'node_modules/rcedit')
if (existsSync(rceditSrc)) {
  mkdirSync(path.join(electrobunDir, 'node_modules'), { recursive: true })
  cpSync(rceditSrc, rceditDest, { recursive: true, force: true })
}

function patchNativeWrapperPath(nativeTsPath) {
  if (!existsSync(nativeTsPath)) {
    return
  }

  let source = readFileSync(nativeTsPath, 'utf8')

  if (source.includes('function resolveNativeWrapperPath()')) {
    return
  }

  if (!source.includes('existsSync')) {
    if (source.includes('import { dirname, join } from "path";')) {
      source = source.replace(
        'import { dirname, join } from "path";',
        'import { existsSync } from "fs";\nimport { dirname, join } from "path";'
      )
    } else {
      source = source.replace(
        'import { join } from "path";',
        'import { existsSync } from "fs";\nimport { dirname, join } from "path";'
      )
    }
  } else if (!source.includes('dirname')) {
    source = source.replace(
      'import { join } from "path";',
      'import { dirname, join } from "path";'
    )
  }

  const helper = `
function resolveNativeWrapperPath() {
\tconst fileName = \`libNativeWrapper.\${suffix}\`;
\tconst candidates = [
\t\tjoin(dirname(process.execPath), fileName),
\t\tjoin(process.cwd(), fileName),
\t];
\tfor (const candidate of candidates) {
\t\tif (existsSync(candidate)) {
\t\t\treturn candidate;
\t\t}
\t}
\treturn candidates[0]!;
}
`

  source = source.replace(
    'export const native = (() => {',
    `${helper}\nexport const native = (() => {`
  )

  source = source.replace(
    /const nativeWrapperPath = join\((?:dirname\(process\.execPath\)|process\.cwd\(\)), `libNativeWrapper\.\$\{suffix\}`\);/,
    'const nativeWrapperPath = resolveNativeWrapperPath();'
  )

  writeFileSync(nativeTsPath, source)
}

for (const distDir of ['dist', 'dist-macos-arm64', 'dist-win-x64']) {
  patchNativeWrapperPath(
    path.join(electrobunDir, distDir, 'api/bun/proc/native.ts')
  )
}

const markerPath = path.join(electrobunDir, '.farmcontrol-electrobun-patched')
writeFileSync(markerPath, `patched-at=${new Date().toISOString()}\n`)

console.log(
  'patch-electrobun-src: electrobun src/shared ready for cross-arch builds'
)
