import { copyFileSync, existsSync, unlinkSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import rcedit from 'rcedit'

const PATCH_TARGETS = new Set(['launcher.exe'])
const MAX_ATTEMPTS = 6
const BASE_DELAY_MS = 500

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

async function patchExecutable(executablePath) {
  let lastError

  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
    const tempPath = `${executablePath}.rcedit-${process.pid}-${attempt}.tmp`

    try {
      // Patch a copy then replace — avoids AV/indexer locks on the live PE.
      copyFileSync(executablePath, tempPath)
      await rcedit(tempPath, {
        'requested-execution-level': 'asInvoker'
      })
      copyFileSync(tempPath, executablePath)
      return
    } catch (error) {
      lastError = error
      const message = error instanceof Error ? error.message : String(error)
      console.warn(
        `patch-windows-binaries: attempt ${attempt}/${MAX_ATTEMPTS} failed for ${executablePath}: ${message}`
      )

      if (attempt < MAX_ATTEMPTS) {
        await sleep(BASE_DELAY_MS * 2 ** (attempt - 1))
      }
    } finally {
      try {
        unlinkSync(tempPath)
      } catch {
        // Ignore cleanup failures.
      }
    }
  }

  throw lastError
}

export async function patchWindowsBinaries(appDir) {
  if (process.platform !== 'win32') {
    return
  }

  const binDir = path.join(appDir, 'bin')
  if (!existsSync(binDir)) {
    throw new Error(`patch-windows-binaries: bin directory not found at ${binDir}`)
  }

  for (const executable of PATCH_TARGETS) {
    const executablePath = path.join(binDir, executable)
    if (!existsSync(executablePath)) {
      console.warn(`patch-windows-binaries: skipping missing ${executablePath}`)
      continue
    }

    try {
      await patchExecutable(executablePath)
      console.log(`patch-windows-binaries: set asInvoker on ${executablePath}`)
    } catch (error) {
      console.warn(
        `patch-windows-binaries: failed to patch ${executablePath}:`,
        error instanceof Error ? error.message : error
      )
    }
  }
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const appDirArg = process.argv[2]
  if (!appDirArg) {
    console.error('Usage: bun scripts/patch-windows-binaries.mjs <app-dir>')
    process.exit(1)
  }

  await patchWindowsBinaries(path.resolve(appDirArg))
}
