import type {
  ComputerUseHostAdapter,
  Logger,
  LoggerDetail,
} from '@ant/computer-use-mcp/types'
import { format } from 'util'
import { logForDebugging } from '../debug.js'
import { COMPUTER_USE_MCP_SERVER_NAME } from './common.js'
import { createCliExecutor } from './executor.js'
import { getChicagoEnabled, getChicagoSubGates } from './gates.js'
import { requireComputerUseSwift } from './swiftLoader.js'

class DebugLogger implements Logger {
  silly(message: string, detail?: LoggerDetail): void {
    logForDebugging(format(message, detail ?? ''), { level: 'debug' })
  }
  debug(message: string, detail?: LoggerDetail): void {
    logForDebugging(format(message, detail ?? ''), { level: 'debug' })
  }
  info(message: string, detail?: LoggerDetail): void {
    logForDebugging(format(message, detail ?? ''), { level: 'info' })
  }
  warn(message: string, detail?: LoggerDetail): void {
    logForDebugging(format(message, detail ?? ''), { level: 'warn' })
  }
  error(message: string, detail?: LoggerDetail): void {
    logForDebugging(format(message, detail ?? ''), { level: 'error' })
  }
}

// ---------------------------------------------------------------------------
// JXA-based TCC permission probes (fallback when native .node module absent)
// ---------------------------------------------------------------------------

/** Probe accessibility by asking System Events for a process list. */
function checkAccessibilityJXA(): boolean {
  try {
    const result = Bun.spawnSync({
      cmd: [
        'osascript',
        '-e',
        'tell application "System Events" to get name of every process whose background only is false',
      ],
      stdout: 'pipe',
      stderr: 'pipe',
    })
    return result.exitCode === 0
  } catch {
    return false
  }
}

/** Probe screen recording by attempting a 1x1 screencapture. */
function checkScreenRecordingJXA(): boolean {
  try {
    const result = Bun.spawnSync({
      cmd: ['screencapture', '-x', '-R', '0,0,1,1', '/dev/null'],
      stdout: 'pipe',
      stderr: 'pipe',
    })
    return result.exitCode === 0
  } catch {
    return false
  }
}

let cached: ComputerUseHostAdapter | undefined

/**
 * Process-lifetime singleton. Built once on first CU tool call; native modules
 * (both `@ant/computer-use-input` and `@ant/computer-use-swift`) are loaded
 * here via the executor factory, which throws on load failure — there is no
 * degraded mode.
 */
export function getComputerUseHostAdapter(): ComputerUseHostAdapter {
  if (cached) return cached
  cached = {
    serverName: COMPUTER_USE_MCP_SERVER_NAME,
    logger: new DebugLogger(),
    executor: createCliExecutor({
      getMouseAnimationEnabled: () => getChicagoSubGates().mouseAnimation,
      getHideBeforeActionEnabled: () => getChicagoSubGates().hideBeforeAction,
    }),
    ensureOsPermissions: async () => {
      if (process.platform !== 'darwin') return { granted: true }
      const cu = requireComputerUseSwift()
      const tcc = (cu as any).tcc
      // Native Swift .node module provides tcc.checkAccessibility/checkScreenRecording.
      // When absent (decompiled/reverse-engineered build), fall back to JXA probes.
      if (tcc) {
        const accessibility = tcc.checkAccessibility()
        const screenRecording = tcc.checkScreenRecording()
        return accessibility && screenRecording
          ? { granted: true }
          : { granted: false, accessibility, screenRecording }
      }
      // JXA fallback: try to query System Events (accessibility) and screencapture (screen recording).
      const accessibility = checkAccessibilityJXA()
      const screenRecording = checkScreenRecordingJXA()
      return accessibility && screenRecording
        ? { granted: true }
        : { granted: false, accessibility, screenRecording }
    },
    isDisabled: () => !getChicagoEnabled(),
    getSubGates: getChicagoSubGates,
    // cleanup.ts always unhides at turn end — no user preference to disable it.
    getAutoUnhideEnabled: () => true,

    // Pixel-validation JPEG decode+crop. MUST be synchronous (the package
    // does `patch1.equals(patch2)` directly on the return value). Cowork uses
    // Electron's `nativeImage` (sync); our `image-processor-napi` is
    // sharp-compatible and async-only. Returning null → validation skipped,
    // click proceeds — the designed fallback per `PixelCompareResult.skipped`.
    // The sub-gate defaults to false anyway.
    cropRawPatch: () => null,
  }
  return cached
}
