/**
 * Daemon intent files — one per section, atomic-rename per file.
 *
 * Two intent kinds the reconciler drains every tick:
 *   - [restart]  → `<stateDir>/intent.restart.toml`
 *   - [update]   → `<stateDir>/intent.update.toml`
 *
 * Each section is a single file written via atomicWriteFileSync. Concurrent
 * writers never read each other's section, so there is no read-modify-write
 * race like the single-file `intent.toml` had — a `myco restart` and a
 * `myco update --target-version` overlapping at the wall clock land in
 * separate files and both survive.
 *
 * Runtime shape guards (isRestartIntent / isUpdateIntent) reject malformed
 * but valid TOML — a structurally-wrong file is treated as empty, never
 * returned as an `Intent` via `as` cast.
 *
 * ## TRUST MODEL — these files drive code execution
 *
 * intent.update.toml interpolates `target_version` into
 * `npm install -g @goondocks/myco@<value>` and spawns the resulting
 * shell command. intent.restart.toml triggers the daemon to exit and be
 * re-spawned by the supervisor. Both files are daemon-EXECUTED, not
 * merely daemon-READ.
 *
 * Protections (single-machine, single-user trust boundary):
 *   - Files live under `~/.myco/service/` (chmod 0o700 — see
 *     `service-state.ts:writeDaemonState`) so directory enumeration is
 *     restricted to the owner.
 *   - Each file is chmod 0o600 (see writers below) so even with
 *     directory descend, the bytes are owner-only.
 *   - `target_version` is gated by strict semver at BOTH writers (HTTP
 *     `POST /api/daemon/intent/update` in `api/intent.ts`, and the CLI
 *     `myco update --target-version` in `cli/update.ts`) to defeat
 *     npm-spec injection (`file:`, `git+ssh:`, etc.).
 *   - `isRestartIntent` / `isUpdateIntent` shape guards reject
 *     structurally-malformed TOML before the reconciler reads either as
 *     an `Intent`.
 *
 * Any future writer of these files must preserve the chmod 0o600 + the
 * semver gate. Any future field interpolated into a shell command must
 * be validated at the writer (defense in depth: also at the reader).
 */

import { existsSync, readFileSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import { parse, stringify } from 'smol-toml';
import { atomicWriteFileSync } from '../utils/atomic-write.js';
import type { DaemonServiceState } from './service-state.js';

export interface RestartIntent {
  requested_at: string; // ISO8601
  reason?: string;
}

export interface UpdateIntent {
  target_version: string;
  requested_at: string;
}

/**
 * Refresh the global launchers at `~/.myco/launcher.cjs` and
 * `~/.myco/mcp-launcher.cjs`. Raised by the version-drift / update path
 * so a refreshed launcher template lands without racing the running
 * daemon's hook traffic. The daemon's self-reconcile loop drains this
 * intent on its own thread, then deletes it.
 *
 * Carries no payload — the launcher content is determined by the
 * daemon's bundled template, not by the intent file.
 */
export interface RefreshLaunchersIntent {
  requested_at: string;
  reason?: string;
}

export interface Intent {
  restart?: RestartIntent;
  update?: UpdateIntent;
  refresh_launchers?: RefreshLaunchersIntent;
}

const RESTART_INTENT_FILENAME = 'intent.restart.toml';
const UPDATE_INTENT_FILENAME = 'intent.update.toml';
const REFRESH_LAUNCHERS_INTENT_FILENAME = 'intent.refresh-launchers.toml';

function restartIntentPath(daemonService: DaemonServiceState): string {
  return join(daemonService.stateDir, RESTART_INTENT_FILENAME);
}

function updateIntentPath(daemonService: DaemonServiceState): string {
  return join(daemonService.stateDir, UPDATE_INTENT_FILENAME);
}

function refreshLaunchersIntentPath(daemonService: DaemonServiceState): string {
  return join(daemonService.stateDir, REFRESH_LAUNCHERS_INTENT_FILENAME);
}

function isRestartIntent(value: unknown): value is RestartIntent {
  if (!value || typeof value !== 'object') return false;
  const v = value as Record<string, unknown>;
  if (typeof v.requested_at !== 'string') return false;
  if (v.reason !== undefined && typeof v.reason !== 'string') return false;
  return true;
}

function isUpdateIntent(value: unknown): value is UpdateIntent {
  if (!value || typeof value !== 'object') return false;
  const v = value as Record<string, unknown>;
  if (typeof v.target_version !== 'string' || v.target_version === '') return false;
  if (typeof v.requested_at !== 'string') return false;
  return true;
}

function isRefreshLaunchersIntent(value: unknown): value is RefreshLaunchersIntent {
  if (!value || typeof value !== 'object') return false;
  const v = value as Record<string, unknown>;
  if (typeof v.requested_at !== 'string') return false;
  if (v.reason !== undefined && typeof v.reason !== 'string') return false;
  return true;
}

function readSectionFile<T>(filePath: string, guard: (value: unknown) => value is T): T | undefined {
  if (!existsSync(filePath)) return undefined;
  try {
    const parsed = parse(readFileSync(filePath, 'utf-8'));
    return guard(parsed) ? parsed : undefined;
  } catch {
    // Malformed TOML — treat as absent. The next writer overwrites.
    return undefined;
  }
}

export function readRestartIntent(daemonService: DaemonServiceState): RestartIntent | undefined {
  return readSectionFile(restartIntentPath(daemonService), isRestartIntent);
}

export function readUpdateIntent(daemonService: DaemonServiceState): UpdateIntent | undefined {
  return readSectionFile(updateIntentPath(daemonService), isUpdateIntent);
}

export function readRefreshLaunchersIntent(
  daemonService: DaemonServiceState,
): RefreshLaunchersIntent | undefined {
  return readSectionFile(refreshLaunchersIntentPath(daemonService), isRefreshLaunchersIntent);
}

export function readIntent(daemonService: DaemonServiceState): Intent {
  const restart = readRestartIntent(daemonService);
  const update = readUpdateIntent(daemonService);
  const refresh_launchers = readRefreshLaunchersIntent(daemonService);
  return {
    ...(restart ? { restart } : {}),
    ...(update ? { update } : {}),
    ...(refresh_launchers ? { refresh_launchers } : {}),
  };
}

export function writeRestartIntent(daemonService: DaemonServiceState, intent: RestartIntent): void {
  atomicWriteFileSync(
    restartIntentPath(daemonService),
    stringify(intent as unknown as Record<string, unknown>),
    { mode: 0o600 },
  );
}

export function writeUpdateIntent(daemonService: DaemonServiceState, intent: UpdateIntent): void {
  atomicWriteFileSync(
    updateIntentPath(daemonService),
    stringify(intent as unknown as Record<string, unknown>),
    { mode: 0o600 },
  );
}

export function writeRefreshLaunchersIntent(
  daemonService: DaemonServiceState,
  intent: RefreshLaunchersIntent,
): void {
  atomicWriteFileSync(
    refreshLaunchersIntentPath(daemonService),
    stringify(intent as unknown as Record<string, unknown>),
    { mode: 0o600 },
  );
}

export function clearIntentSection(
  daemonService: DaemonServiceState,
  section: 'restart' | 'update' | 'refresh_launchers',
): void {
  const filePath = section === 'restart'
    ? restartIntentPath(daemonService)
    : section === 'update'
      ? updateIntentPath(daemonService)
      : refreshLaunchersIntentPath(daemonService);
  try { unlinkSync(filePath); } catch { /* already gone */ }
}
