// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
//
// WU-2733 (INIT-060 Phase 3, ADR-013 §1 abort + §6 governance):
// Sidecar persistence for remote-control session state (pause marker,
// autonomy level, abort record, inflight approval handle). Kept
// separate from the core orchestration workflow state store so the
// existing strict schema validator doesn't need schema-version bumps
// and so remote-control operations remain independently observable
// via a dedicated on-disk artefact (`.agent-runtime/remote-controls/`).

import { mkdir, readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';

export const REMOTE_CONTROL_STATE_DIRECTORY = path.join('.agent-runtime', 'remote-controls');

/** Canonical autonomy bands. Declaration order = low→high. */
export const AGENT_RUNTIME_AUTONOMY_LEVELS = {
  MANUAL: 'manual',
  ASSISTED: 'assisted',
  AUTO: 'auto',
} as const;

export type AgentRuntimeAutonomyLevel =
  (typeof AGENT_RUNTIME_AUTONOMY_LEVELS)[keyof typeof AGENT_RUNTIME_AUTONOMY_LEVELS];

export const AGENT_RUNTIME_AUTONOMY_LEVEL_VALUES = [
  AGENT_RUNTIME_AUTONOMY_LEVELS.MANUAL,
  AGENT_RUNTIME_AUTONOMY_LEVELS.ASSISTED,
  AGENT_RUNTIME_AUTONOMY_LEVELS.AUTO,
] as const satisfies readonly AgentRuntimeAutonomyLevel[];

export const DEFAULT_AUTONOMY_LEVEL: AgentRuntimeAutonomyLevel =
  AGENT_RUNTIME_AUTONOMY_LEVELS.ASSISTED;

export interface RemoteControlPauseState {
  paused: boolean;
  paused_at: string | null;
  resumed_at: string | null;
  reason: string | null;
}

export interface RemoteControlAbortState {
  aborted: boolean;
  aborted_at: string | null;
  cleanup_status: 'clean' | 'partial' | 'needs_recovery' | null;
  recovery_action: string | null;
  reason: string | null;
}

export interface RemoteControlInflightApproval {
  request_id: string;
  tool_name: string;
  requested_at: string;
}

export interface RemoteControlSessionState {
  schema_version: 1;
  session_id: string;
  autonomy_level: AgentRuntimeAutonomyLevel;
  pause: RemoteControlPauseState;
  abort: RemoteControlAbortState;
  inflight_approvals: RemoteControlInflightApproval[];
  updated_at: string;
}

const SCHEMA_VERSION_V1 = 1 as const;

export interface RemoteControlStateStore {
  load(sessionId: string): Promise<RemoteControlSessionState | null>;
  save(state: RemoteControlSessionState): Promise<void>;
  loadOrDefault(sessionId: string, now: string): Promise<RemoteControlSessionState>;
}

export interface CreateRemoteControlStateStoreInput {
  workspaceRoot: string;
}

export function createRemoteControlStateStore(
  input: CreateRemoteControlStateStoreInput,
): RemoteControlStateStore {
  const stateRoot = path.join(input.workspaceRoot, REMOTE_CONTROL_STATE_DIRECTORY);

  async function load(sessionId: string): Promise<RemoteControlSessionState | null> {
    const filePath = sessionFile(stateRoot, sessionId);
    try {
      const raw = await readFile(filePath, 'utf8');
      return parseState(JSON.parse(raw), filePath);
    } catch (error) {
      const nodeError = error as NodeJS.ErrnoException;
      if (nodeError.code === 'ENOENT') {
        return null;
      }
      throw error;
    }
  }

  async function save(state: RemoteControlSessionState): Promise<void> {
    const filePath = sessionFile(stateRoot, state.session_id);
    await mkdir(stateRoot, { recursive: true });
    await writeFile(filePath, JSON.stringify(state), 'utf8');
  }

  async function loadOrDefault(sessionId: string, now: string): Promise<RemoteControlSessionState> {
    const existing = await load(sessionId);
    if (existing) {
      return existing;
    }
    return buildDefaultState(sessionId, now);
  }

  return { load, save, loadOrDefault };
}

export function buildDefaultState(sessionId: string, now: string): RemoteControlSessionState {
  return {
    schema_version: SCHEMA_VERSION_V1,
    session_id: sessionId,
    autonomy_level: DEFAULT_AUTONOMY_LEVEL,
    pause: {
      paused: false,
      paused_at: null,
      resumed_at: null,
      reason: null,
    },
    abort: {
      aborted: false,
      aborted_at: null,
      cleanup_status: null,
      recovery_action: null,
      reason: null,
    },
    inflight_approvals: [],
    updated_at: now,
  };
}

function sessionFile(stateRoot: string, sessionId: string): string {
  return path.join(stateRoot, `${sessionId}.json`);
}

function parseState(value: unknown, filePath: string): RemoteControlSessionState {
  if (value === null || typeof value !== 'object') {
    throw new Error(
      `Failed to parse remote-control state at ${filePath}: expected an object payload.`,
    );
  }
  const record = value as Record<string, unknown>;
  if (record.schema_version !== SCHEMA_VERSION_V1) {
    throw new Error(
      `Failed to parse remote-control state at ${filePath}: unsupported schema_version "${String(record.schema_version)}".`,
    );
  }
  const sessionId = requireString(record.session_id, `${filePath}.session_id`);
  const autonomyLevel = requireAutonomyLevel(record.autonomy_level, `${filePath}.autonomy_level`);
  const pause = parsePauseState(record.pause, `${filePath}.pause`);
  const abort = parseAbortState(record.abort, `${filePath}.abort`);
  const inflightApprovals = parseInflightApprovals(
    record.inflight_approvals,
    `${filePath}.inflight_approvals`,
  );
  const updatedAt = requireString(record.updated_at, `${filePath}.updated_at`);
  return {
    schema_version: SCHEMA_VERSION_V1,
    session_id: sessionId,
    autonomy_level: autonomyLevel,
    pause,
    abort,
    inflight_approvals: inflightApprovals,
    updated_at: updatedAt,
  };
}

function parsePauseState(value: unknown, filePath: string): RemoteControlPauseState {
  if (value === null || typeof value !== 'object') {
    throw new Error(`Failed to parse remote-control state at ${filePath}: expected pause object.`);
  }
  const record = value as Record<string, unknown>;
  return {
    paused: typeof record.paused === 'boolean' ? record.paused : false,
    paused_at: typeof record.paused_at === 'string' ? record.paused_at : null,
    resumed_at: typeof record.resumed_at === 'string' ? record.resumed_at : null,
    reason: typeof record.reason === 'string' ? record.reason : null,
  };
}

function parseAbortState(value: unknown, filePath: string): RemoteControlAbortState {
  if (value === null || typeof value !== 'object') {
    throw new Error(`Failed to parse remote-control state at ${filePath}: expected abort object.`);
  }
  const record = value as Record<string, unknown>;
  const cleanupStatus = record.cleanup_status;
  return {
    aborted: typeof record.aborted === 'boolean' ? record.aborted : false,
    aborted_at: typeof record.aborted_at === 'string' ? record.aborted_at : null,
    cleanup_status:
      cleanupStatus === 'clean' || cleanupStatus === 'partial' || cleanupStatus === 'needs_recovery'
        ? cleanupStatus
        : null,
    recovery_action: typeof record.recovery_action === 'string' ? record.recovery_action : null,
    reason: typeof record.reason === 'string' ? record.reason : null,
  };
}

function parseInflightApprovals(value: unknown, filePath: string): RemoteControlInflightApproval[] {
  if (!Array.isArray(value)) {
    return [];
  }
  return value
    .map((entry, index) => parseInflightApproval(entry, `${filePath}[${index}]`))
    .filter((entry): entry is RemoteControlInflightApproval => entry !== null);
}

function parseInflightApproval(
  value: unknown,
  filePath: string,
): RemoteControlInflightApproval | null {
  if (value === null || typeof value !== 'object') {
    return null;
  }
  const record = value as Record<string, unknown>;
  if (
    typeof record.request_id !== 'string' ||
    typeof record.tool_name !== 'string' ||
    typeof record.requested_at !== 'string'
  ) {
    throw new Error(
      `Failed to parse remote-control state at ${filePath}: malformed inflight approval entry.`,
    );
  }
  return {
    request_id: record.request_id,
    tool_name: record.tool_name,
    requested_at: record.requested_at,
  };
}

function requireString(value: unknown, filePath: string): string {
  if (typeof value !== 'string' || value.length === 0) {
    throw new Error(
      `Failed to parse remote-control state at ${filePath}: expected a non-empty string.`,
    );
  }
  return value;
}

function requireAutonomyLevel(value: unknown, filePath: string): AgentRuntimeAutonomyLevel {
  if (
    value === AGENT_RUNTIME_AUTONOMY_LEVELS.MANUAL ||
    value === AGENT_RUNTIME_AUTONOMY_LEVELS.ASSISTED ||
    value === AGENT_RUNTIME_AUTONOMY_LEVELS.AUTO
  ) {
    return value;
  }
  throw new Error(
    `Failed to parse remote-control state at ${filePath}: unknown autonomy level "${String(value)}".`,
  );
}
