import { execFile } from 'node:child_process'
import { appendFile, mkdir } from 'node:fs/promises'
import { homedir } from 'node:os'
import { dirname, join } from 'node:path'

const textEncoder = new TextEncoder()
let readBuffer = Buffer.alloc(0)
let pendingRequests = 0
let stdinEnded = false

function sendMessage(payload) {
  const body = Buffer.from(JSON.stringify(payload), 'utf8')
  const len = Buffer.alloc(4)
  len.writeUInt32LE(body.length, 0)
  process.stdout.write(len)
  process.stdout.write(body)
}

function parseHeaders(lines = []) {
  const map = new Map()
  for (const line of lines) {
    const idx = line.indexOf(':')
    if (idx <= 0) continue
    const key = line.slice(0, idx).trim().toLowerCase()
    const value = line.slice(idx + 1).trim()
    if (!key || !value) continue
    map.set(key, value)
  }
  return map
}

function parseCookieHeader(lines = []) {
  const headers = parseHeaders(lines)
  return headers.get('cookie') || ''
}

function queueFilePath() {
  return join(homedir(), '.gdown', 'external_add_queue.jsonl')
}

async function enqueueExternalAdd(payload) {
  const filePath = queueFilePath()
  await mkdir(dirname(filePath), { recursive: true })
  await appendFile(filePath, `${JSON.stringify(payload)}\n`, 'utf8')
}

function focusGdownApp() {
  return new Promise((resolve) => {
    if (process.platform !== 'darwin') {
      resolve({ ok: true, note: 'focus noop on non-macos in step1 host' })
      return
    }

    const attempts = [
      ['osascript', ['-e', 'tell application id "com.tauri.dev" to activate']],
      ['osascript', ['-e', 'tell application "gdown" to activate']],
      ['osascript', ['-e', 'tell application "System Events" to set frontmost of first process whose name is "gdown" to true']],
      ['osascript', ['-e', 'tell application "System Events" to set frontmost of first process whose name is "Gdown" to true']],
      ['osascript', ['-e', 'tell application "System Events" to set frontmost of first process whose name is "app" to true']],
      ['open', ['-b', 'com.tauri.dev']],
      ['open', ['-a', 'gdown']],
    ]

    const run = (index) => {
      if (index >= attempts.length) {
        resolve({ ok: false, message: 'all focus strategies failed' })
        return
      }

      const [bin, args] = attempts[index]
      execFile(bin, args, (error) => {
        if (!error) {
          resolve({ ok: true, strategy: `${bin} ${args.join(' ')}` })
          return
        }
        run(index + 1)
      })
    }

    run(0)
  })
}

async function handleRequest(message) {
  const action = String(message?.action || '').trim()

  if (action === 'ping') {
    return {
      ok: true,
      version: '0.1.0',
      host: 'org.gdown.nativehost',
      capabilities: ['ping', 'addUri', 'focus'],
    }
  }

  if (action === 'addUri') {
    const url = String(message?.url || '').trim()
    if (!url) return { ok: false, error: 'url is required' }
    const referer = String(message?.referer || '').trim()
    const userAgent = String(message?.userAgent || '').trim()
    const out = String(message?.out || '').trim()
    const dir = String(message?.dir || '').trim()
    const cookie = String(message?.cookie || '').trim()
    const authorization = String(message?.authorization || '').trim()
    const proxy = String(message?.proxy || '').trim()
    const split = Number(message?.split || 0)

    const parsedCookie = parseCookieHeader(Array.isArray(message?.headers) ? message.headers : [])
    const cookieValue = cookie || parsedCookie

    await enqueueExternalAdd({
      url,
      referer: referer || undefined,
      userAgent: userAgent || undefined,
      out: out || undefined,
      dir: dir || undefined,
      cookie: cookieValue || undefined,
      authorization: authorization || undefined,
      proxy: proxy || undefined,
      split: Number.isFinite(split) && split > 0 ? Math.round(split) : undefined,
    })
    await focusGdownApp()

    return {
      ok: true,
      pending: true,
      mode: 'prompt',
      requestId: `pending-${Date.now()}`,
    }
  }

  if (action === 'focus') {
    return focusGdownApp()
  }

  return { ok: false, error: `unknown action: ${action || '(empty)'}` }
}

async function drainBuffer() {
  while (readBuffer.length >= 4) {
    const bodyLength = readBuffer.readUInt32LE(0)
    if (readBuffer.length < 4 + bodyLength) return
    const body = readBuffer.subarray(4, 4 + bodyLength)
    readBuffer = readBuffer.subarray(4 + bodyLength)

    let payload
    try {
      payload = JSON.parse(body.toString('utf8'))
    } catch (error) {
      sendMessage({ ok: false, error: `invalid JSON payload: ${String(error)}` })
      continue
    }

    try {
      pendingRequests += 1
      const result = await handleRequest(payload)
      sendMessage(result)
    } catch (error) {
      sendMessage({ ok: false, error: String(error) })
    } finally {
      pendingRequests -= 1
      if (stdinEnded && pendingRequests === 0) {
        process.exit(0)
      }
    }
  }
}

process.stdin.on('data', async (chunk) => {
  readBuffer = Buffer.concat([readBuffer, chunk])
  await drainBuffer()
})

process.stdin.on('end', () => {
  stdinEnded = true
  if (pendingRequests === 0) {
    process.exit(0)
  }
})

// Emit one line for manual shell smoke visibility (not used by native messaging framing).
process.stderr.write(`[native-host] started pid=${process.pid}\n`)
