#!/usr/bin/env node
// @ts-check
import { ChildProcess, spawn } from 'child_process'
import dotenv from 'dotenv'
import { existsSync, readFileSync, watch } from 'fs'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const rootDir = resolve(__dirname, '..')

const args = process.argv.slice(2)

/* ========= Print version and exit ===== */
if (args.includes('--version') || args.includes('-v')) {
	const packagePath = resolve(rootDir, 'package.json')
	const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'))
	console.log(packageJson.version, isLinked() ? '(linked)' : '')
	process.exit(0)
}

/* ========= Start Bosun ============== */
/** @type {ChildProcess | null} */
let backendProcess = null

/** @type {ChildProcess | null} */
let frontendProcess = null

/** @type any */
let restartTimer = null

const envPath = resolve(process.cwd(), '.env')
const envConfig = existsSync(envPath) ? dotenv.parse(readFileSync(envPath)) : {}

main()

/* ========= Implementation =========== */

function main() {
	// Initial start
	try {
		// Start backend
		restartBackend()

		if (!args.includes('--backend-only')) {
			// Start frontend once
			frontendProcess = startFrontend()
		}
		console.log('✅ Bosun started')

			// Watch for changes
			const watchPath = resolve(process.cwd(), 'src')
			watch(watchPath, { recursive: true }, (_, file) => {
				if (typeof file === 'string' && file.endsWith('.ts')) {
					console.log(`🔄 Detected change, restarting... (${file})`)
					debouncedRestart()
				}
			})
		if (isLinked()) {
			watch(resolve(rootDir, 'src/backend'), { recursive: true }, (_, file) => {
				console.log(`🔄 change in server, restarting... (${file})`)
				debouncedRestart()
			})
		}
	} catch (error) {
		console.error('❌ Error:', String(error))
		process.exit(1)
	}
}

function startBackend() {
	const tsxBin = process.platform === 'win32' ? 'tsx.cmd' : 'tsx'
	const tsxPath = resolve(rootDir, 'node_modules', '.bin', tsxBin)
	const serverPath = resolve(rootDir, 'src', 'backend', 'server.ts')

	return spawn(tsxPath, [serverPath], {
		cwd: process.cwd(),
		stdio: 'inherit',
		env: {
			...process.env,
			...envConfig,
			BOSUN_NPX_RUN: 'true'
		},
		shell: process.platform === 'win32'
	})
}

function startFrontend() {
	const frontPath = resolve(rootDir, 'build', 'index.js')
	const bodySizeLimit = envConfig.BODY_SIZE_LIMIT || process.env.BODY_SIZE_LIMIT || 'Infinity'

	return spawn('node', [frontPath], {
		cwd: process.cwd(),
		stdio: 'inherit',
		env: {
			...process.env,
			...envConfig,
			HOST: 'localhost',
			PORT: '5177',
			// Latency recordings are uploaded as WAV files and can be large.
			BODY_SIZE_LIMIT: bodySizeLimit
		},
		shell: process.platform === 'win32'
	})
}

function restartBackend() {
	if (backendProcess) {
		backendProcess.kill()
	}
	try {
		backendProcess = startBackend()
	} catch (error) {
		console.error('❌ Failed to start Bosun backend:', String(error))
		process.exit(1)
	}
}

function debouncedRestart() {
	if (restartTimer) {
		clearTimeout(restartTimer)
	}
	restartTimer = setTimeout(() => {
		restartBackend()
	}, 300)
}

// Handle cleanup
process.on('SIGINT', () => {
	if (backendProcess) backendProcess.kill()
	if (frontendProcess) frontendProcess.kill()
	process.exit(0)
})

function isLinked() {
	return !__filename.includes('node_modules/@recombine-ai/bosun')
}

/**
 * @param {number} ms
 */
function sleep(ms) {
	return new Promise((r) => setTimeout(r, ms))
}
