/**
 * macOS launchd plist writer — PLAN_RELAY_AGENT T17. The service runs as the LOGGED-IN USER, not
 * root (EXPLORE §4d least privilege); no secrets in the plist (key/cert stay in the keystore).
 *
 * PLAN_NATIVE_TUNNEL S2: the plist can inject a caller-supplied per-host env map
 * (BIND_HOST, ALLOWED_ORIGINS, PORT, SHELL_PATH, IDLE_TTL, USE_TMUX, …) as a launchd
 * `<key>EnvironmentVariables</key><dict>…</dict>` block. Values are XML-escaped and keys are
 * sorted for deterministic, immutable output.
 *
 * PLAN_TUNNEL_AUTOMATION B5 (FIX M-host-2service): the writer is PARAMETERIZED on `label` +
 * `programArguments`, so a native-tunnel install emits TWO distinct plists — the base-app
 * (`node dist/server.js`, carrying the base-app env) and the agent (`<bin> run`, which supervises
 * frpc). Base-app env is routed to the base-app plist ONLY by the caller (`install.ts`).
 */

/** Shared env-map shape for the durable-service writers (launchd + systemd). Immutable. */
export type ServiceEnv = Readonly<Record<string, string>>

/** The native-tunnel agent unit (supervises frpc). */
const AGENT_LABEL = 'com.web-terminal.agent'
/** The base-app unit (`node dist/server.js`, loopback-bound). */
const BASE_APP_LABEL = 'com.web-terminal.base-app'

export function agentLabel(): string {
  return AGENT_LABEL
}
export function baseAppLabel(): string {
  return BASE_APP_LABEL
}
/** Back-compat alias for the agent label. */
export function launchdLabel(): string {
  return AGENT_LABEL
}

export function launchdPlistPath(homedir: string, label: string = AGENT_LABEL): string {
  return `${homedir}/Library/LaunchAgents/${label}.plist`
}

/** Escape the five XML-significant characters so env keys/values/paths are plist-safe. */
function escapeXml(value: string): string {
  return value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;')
}

/** Build the `<key>EnvironmentVariables</key><dict>…</dict>` lines (empty array when env is empty). */
function environmentVariablesBlock(env: ServiceEnv): readonly string[] {
  const entries = Object.entries(env).sort(([a], [b]) => a.localeCompare(b))
  if (entries.length === 0) return []
  const lines = ['  <key>EnvironmentVariables</key>', '  <dict>']
  for (const [key, value] of entries) {
    lines.push(`    <key>${escapeXml(key)}</key>`)
    lines.push(`    <string>${escapeXml(value)}</string>`)
  }
  lines.push('  </dict>')
  return lines
}

/** Build the `<key>ProgramArguments</key><array>…</array>` lines. */
function programArgumentsBlock(programArguments: readonly string[]): readonly string[] {
  const lines = ['  <key>ProgramArguments</key>', '  <array>']
  for (const arg of programArguments) {
    lines.push(`    <string>${escapeXml(arg)}</string>`)
  }
  lines.push('  </array>')
  return lines
}

/**
 * Build a plist for `label` running `programArguments` (e.g. `[bin, 'run']` or `[node, serverJs]`);
 * RunAtLoad + KeepAlive (restart on failure). When `env` is non-empty, a launchd
 * `EnvironmentVariables` dict is injected. Pure/immutable — returns a fresh string.
 */
export function buildLaunchdPlist(
  programArguments: readonly string[],
  env: ServiceEnv = {},
  label: string = AGENT_LABEL,
  logPath?: string,
): string {
  return [
    '<?xml version="1.0" encoding="UTF-8"?>',
    '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
    '<plist version="1.0">',
    '<dict>',
    '  <key>Label</key>',
    `  <string>${escapeXml(label)}</string>`,
    ...programArgumentsBlock(programArguments),
    '  <key>RunAtLoad</key>',
    '  <true/>',
    '  <key>KeepAlive</key>',
    '  <true/>',
    // launchd's default has no log sink (unlike systemd's journald), so a crashing unit is silent.
    // Route stdout+stderr to a file so `pair --install` failures are diagnosable out of the box.
    ...(logPath !== undefined
      ? [
          '  <key>StandardOutPath</key>',
          `  <string>${escapeXml(logPath)}</string>`,
          '  <key>StandardErrorPath</key>',
          `  <string>${escapeXml(logPath)}</string>`,
        ]
      : []),
    ...environmentVariablesBlock(env),
    '</dict>',
    '</plist>',
    '',
  ].join('\n')
}

export function launchdLoadCommand(plistPath: string): { cmd: string; args: readonly string[] } {
  return { cmd: 'launchctl', args: ['load', plistPath] }
}
export function launchdUnloadCommand(plistPath: string): { cmd: string; args: readonly string[] } {
  return { cmd: 'launchctl', args: ['unload', plistPath] }
}
