import { readdir } from 'node:fs/promises';
import { join } from 'node:path';
import { itemsDir } from '../../data-path';

// File-backed agent store. An agent is a directory under $OFFICER_ITEMS_DIR/agents/<dirName>/ holding
// an AGENT.md: YAML frontmatter plus a prose body that IS the agent's opening prompt. Same shape and
// same parser conventions as a task, but a different concept — a task is a job the platform executes,
// an agent is a single-purpose Claude session the platform opens on your behalf.
//
// The directory may hold anything else the prompt refers to (scripts, reference files). Unlike a
// script task, nothing is materialised into a temp dir: the agent works in place with absolute paths,
// so siblings are simply there to be read.

const YAML = (Bun as unknown as { YAML: { parse(input: string): unknown } }).YAML;

// Chat model ids are `<provider>/<tier>` (see api/chat/list-models.ts); claude-manager splits on '/'
// and passes the tail to the CLI as --model. Agents default to opus rather than inheriting the bare
// 'claude-code' default used elsewhere, which leaves the tier up to the CLI.
export const DEFAULT_AGENT_MODEL = 'claude-code/opus';

export type AgentFrontmatter = {
  name: string;
  description: string | null;
  category: string | null;
  version: number;
  model: string;
  inputs: unknown;
  trigger: unknown;
  tags: string[] | null;
  // Declared for the author's intent and for the UI to display. NOTHING ENFORCES EITHER YET — there is
  // no runner-side scheduler or wall-clock guard. Do not read a `concurrency: 1` here as protection.
  concurrency: number | null;
  timeout: number | null;
};

export type AgentRecord = AgentFrontmatter & {
  dirName: string;
  /** The prose body — this is the prompt the run opens with. */
  body: string;
  filePath: string;
};

export type AgentSummary = {
  dirName: string;
  name: string;
  description: string | null;
  category: string | null;
  version: number;
  trigger: unknown;
};

const agentsRoot = () => itemsDir('agents');
const agentDir = (dirName: string) => join(agentsRoot(), dirName);
const agentFile = (dirName: string) => join(agentDir(dirName), 'AGENT.md');

const asArray = (v: unknown): string[] | null => (Array.isArray(v) ? v.map(String) : null);

const asNumber = (v: unknown): number | null => {
  if (v == null) return null;
  const n = typeof v === 'number' ? v : Number(v);
  return Number.isFinite(n) ? n : null;
};

function parseAgentMd(raw: string): { fm: AgentFrontmatter; body: string } {
  const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
  const body = match ? match[2]! : raw;
  const yaml = match ? match[1]! : '';

  let parsed: Record<string, unknown> = {};
  if (yaml.trim()) {
    try {
      parsed = (YAML.parse(yaml) as Record<string, unknown>) ?? {};
    } catch {
      parsed = {};
    }
  }

  const versionRaw = parsed.version;
  const version = typeof versionRaw === 'number' ? versionRaw : Number(versionRaw) || 1;

  return {
    fm: {
      name: parsed.name == null ? '' : String(parsed.name),
      description: parsed.description == null ? null : String(parsed.description),
      category: parsed.category == null ? null : String(parsed.category),
      version,
      model: parsed.model == null ? DEFAULT_AGENT_MODEL : String(parsed.model),
      inputs: parsed.inputs ?? null,
      trigger: parsed.trigger ?? null,
      tags: asArray(parsed.tags),
      concurrency: asNumber(parsed.concurrency),
      timeout: asNumber(parsed.timeout),
    },
    body,
  };
}

export async function listAgents(): Promise<AgentSummary[]> {
  let entries;
  try {
    entries = await readdir(agentsRoot(), { withFileTypes: true });
  } catch {
    return [];
  }

  const summaries: AgentSummary[] = [];
  for (const entry of entries) {
    if (!entry.isDirectory()) continue;
    const file = Bun.file(agentFile(entry.name));
    if (!(await file.exists())) continue;
    const { fm } = parseAgentMd(await file.text());
    summaries.push({
      dirName: entry.name,
      name: fm.name || entry.name,
      description: fm.description,
      category: fm.category,
      version: fm.version,
      trigger: fm.trigger ?? [],
    });
  }

  summaries.sort((a, b) => a.name.localeCompare(b.name));
  return summaries;
}

export async function getAgentByDirName(dirName: string): Promise<AgentRecord | null> {
  const file = Bun.file(agentFile(dirName));
  if (!(await file.exists())) return null;
  const { fm, body } = parseAgentMd(await file.text());
  return { ...fm, dirName, body, filePath: agentFile(dirName) };
}
