import { writeFileSync } from 'node:fs'
import {
  clearMarkdownCache,
  stopMarkdownCacheCleanup,
} from '#cli/core/markdown'
import { stopEventLoopLagLogger } from '#cli/core/observability/event-loop-lag'
import { clearDiffHighlightInFlight } from '#cli/core/store/diff-highlight'
import { clearMarkdownCodeHighlightState } from '#cli/core/store/markdown-code-highlight'
import { clearReadHighlightInFlight } from '#cli/core/store/read-highlight'
import { exitImmediately } from './exit-controller'

export type DebugLogger = Readonly<{
  enabled: boolean
  logPath: string
  log: (message: string, data?: unknown) => void
}>

type RuntimeGlobals = typeof globalThis & {
  Bun?: unknown
  __DIMCODE_UPGRADE_TOAST__?: (text: string) => void
}

function runtimeGlobals(): RuntimeGlobals {
  return globalThis as RuntimeGlobals
}

function unrefTimer(timer: ReturnType<typeof setTimeout>): void {
  if (
    typeof timer === 'object'
    && 'unref' in timer
    && typeof timer.unref === 'function'
  ) {
    timer.unref()
  }
}

export function setupCrashHandlers(logger: DebugLogger): void {
  if (!logger.enabled)
    return

  writeFileSync(
    logger.logPath,
    `=== DIM Debug Log ===\nStarted at: ${new Date().toISOString()}\nPID: ${process.pid}\nArgv: ${JSON.stringify(process.argv)}\nBun: ${typeof runtimeGlobals().Bun !== 'undefined'}\n\n`,
  )

  process.on('uncaughtException', (err, origin) => {
    logger.log('UNCAUGHT EXCEPTION', {
      origin,
      error: err.message,
      stack: err.stack,
    })
    console.error('\n\n=== CRASH: Uncaught Exception ===')
    console.error('Error:', err.message)
    console.error('Stack:', err.stack)
    console.error(`\nDebug log written to: ${logger.logPath}`)
    requestFatalExit(1)
  })

  process.on('unhandledRejection', (reason, _promise) => {
    const err = reason instanceof Error ? reason : new Error(String(reason))
    logger.log('UNHANDLED REJECTION', { error: err.message, stack: err.stack })
    console.error('\n\n=== CRASH: Unhandled Promise Rejection ===')
    console.error('Reason:', err.message)
    console.error('Stack:', err.stack)
    console.error(`\nDebug log written to: ${logger.logPath}`)
    requestFatalExit(1)
  })

  process.on('SIGINT', () => {
    logger.log('Received SIGINT')
    cleanupResources()
  })
  process.on('SIGTERM', () => {
    logger.log('Received SIGTERM')
    cleanupResources()
  })
  process.on('exit', () => {
    cleanupResources()
  })

  logger.log('Crash handlers installed')
}

export function cleanupResources(): void {
  try {
    stopMarkdownCacheCleanup()
    clearMarkdownCache()
    clearDiffHighlightInFlight()
    clearReadHighlightInFlight()
    clearMarkdownCodeHighlightState()
    stopEventLoopLagLogger()
  }
  catch {
    // ignore cleanup errors
  }
}

export function requestFatalExit(code: number): void {
  const nextCode = Number.isFinite(code) ? Math.trunc(code) : 1
  process.exitCode = nextCode

  try {
    // If the terminal UI is active, prefer its exit path so it can restore raw mode / cursor / alt-screen.
    exitImmediately()
    return
  }
  catch {
    // Fall back to a hard-exit after giving other handlers a chance.
  }

  const t = setTimeout(() => {
    try {
      process.exit(nextCode)
    }
    catch {
      // ignore
    }
  }, 1500)
  unrefTimer(t)
}

export function queueUpgradeToast(message: string): void {
  const text = String(message ?? '').trim()
  if (!text)
    return
  const handler = runtimeGlobals().__DIMCODE_UPGRADE_TOAST__
  if (typeof handler === 'function') {
    handler(text)
    return
  }
  if (!process.env.DIMCODE_STARTUP_UPGRADE_TOAST)
    process.env.DIMCODE_STARTUP_UPGRADE_TOAST = text
}

export async function withTimeout<T>(
  promise: Promise<T>,
  ms: number,
): Promise<T | null> {
  return await Promise.race([
    promise,
    new Promise<null>(resolve => setTimeout(() => resolve(null), ms)),
  ])
}
