/**
 * src/http/editor.ts (v0.6) — launch a desktop editor on the HOST for a project.
 *
 * POST /open-in-editor runs `<cfg.editorCmd> <repoPath>` (default `code`) so the
 * Projects panel's VS Code button opens the repo where the files actually live —
 * the host machine, regardless of which device clicked. This is a state-changing
 * route, so server.ts guards it with the Origin whitelist (CSRF defence).
 *
 * Safety: execFile (NOT a shell) — the path is passed as an argv element, never
 * interpolated into a command line, so no shell-injection surface. The path is
 * validated to be an existing absolute directory before spawning. This adds no
 * privilege beyond what the app already grants (a full shell on the host).
 */

import { execFile } from 'node:child_process'
import fs from 'node:fs/promises'
import path from 'node:path'
import type { Config } from '../types.js'

export interface OpenEditorResult {
  ok: boolean
  status: number // HTTP status to return
  error?: string // user-facing reason on failure
}

/**
 * Validate `rawPath` (must be an absolute, existing directory) then launch the
 * configured editor on it (fire-and-forget; the GUI process is detached). Never
 * throws — returns a structured result the route maps to an HTTP response.
 */
export async function openInEditor(cfg: Config, rawPath: unknown): Promise<OpenEditorResult> {
  if (typeof rawPath !== 'string' || rawPath.trim() === '') {
    return { ok: false, status: 400, error: 'path is required' }
  }
  if (!path.isAbsolute(rawPath)) {
    return { ok: false, status: 400, error: 'path must be absolute' }
  }

  let stat
  try {
    stat = await fs.stat(rawPath)
  } catch {
    return { ok: false, status: 404, error: 'path not found' }
  }
  if (!stat.isDirectory()) {
    return { ok: false, status: 400, error: 'path is not a directory' }
  }

  try {
    // No shell: argv array, so the path can never be interpreted as a command.
    const child = execFile(cfg.editorCmd, [rawPath], { windowsHide: true })
    // A ChildProcess 'error' (e.g. editor binary not in PATH) is emitted async;
    // handle it so it never bubbles to an uncaughtException, and don't keep the
    // server alive waiting on the GUI process.
    child.on('error', (err) => {
      console.error('[editor] failed to launch', JSON.stringify(cfg.editorCmd), '-', err.message)
    })
    child.unref()
    return { ok: true, status: 204 }
  } catch (err) {
    return { ok: false, status: 500, error: err instanceof Error ? err.message : String(err) }
  }
}
