/**
 * Prompts file store — `<dataDir>/prompts/<name>.yaml`.
 *
 * Same external-file pattern as pipelines: name in DB / schedule row,
 * configuration in YAML on disk. Edits don't require migrations.
 */

import { existsSync, readFileSync, readdirSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import YAML from 'yaml';
import { getDataDir } from '../dirs';
import type { Prompt, CreatePromptInput, UpdatePromptInput } from './types';

function dir(): string {
  return join(getDataDir(), 'prompts');
}

// Same constraint as pipeline names — also serves as filename `<name>.yaml`.
const NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;

function pathFor(name: string): string {
  return join(dir(), `${name}.yaml`);
}

function parsePrompt(yaml: string): Prompt {
  const raw = YAML.parse(yaml);
  if (!raw || typeof raw !== 'object') throw new Error('prompt yaml must be a mapping');
  if (typeof raw.name !== 'string' || !raw.name) throw new Error('prompt.name required');
  if (typeof raw.prompt !== 'string') throw new Error('prompt.prompt required (string)');

  const ex = raw.executor || {};
  const skills = Array.isArray(ex.skills)
    ? ex.skills.filter((s: unknown) => typeof s === 'string')
    : [];
  const now = new Date().toISOString();
  return {
    name: raw.name,
    prompt: raw.prompt,
    executor: {
      agent: typeof ex.agent === 'string' && ex.agent ? ex.agent : null,
      skills,
      config: ex.config && typeof ex.config === 'object' ? ex.config : undefined,
    },
    created_at: typeof raw.created_at === 'string' ? raw.created_at : now,
    updated_at: typeof raw.updated_at === 'string' ? raw.updated_at : now,
  };
}

function serialize(p: Prompt): string {
  const executorOut: Record<string, unknown> = {
    agent: p.executor.agent,
    skills: p.executor.skills,
  };
  if (p.executor.config) executorOut.config = p.executor.config;
  return YAML.stringify({
    name: p.name,
    prompt: p.prompt,
    executor: executorOut,
    created_at: p.created_at,
    updated_at: p.updated_at,
  });
}

export function listPrompts(): Prompt[] {
  const d = dir();
  if (!existsSync(d)) return [];
  const out: Prompt[] = [];
  for (const f of readdirSync(d).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))) {
    try {
      out.push(parsePrompt(readFileSync(join(d, f), 'utf-8')));
    } catch (e) {
      console.warn(`[listPrompts] skip ${f}: ${(e as Error).message}`);
    }
  }
  return out.sort((a, b) => a.name.localeCompare(b.name));
}

export function getPrompt(name: string): Prompt | null {
  if (!NAME_RE.test(name)) return null;
  const path = pathFor(name);
  if (!existsSync(path)) return null;
  try {
    return parsePrompt(readFileSync(path, 'utf-8'));
  } catch (e) {
    console.warn(`[getPrompt] parse ${name}: ${(e as Error).message}`);
    return null;
  }
}

export function createPrompt(input: CreatePromptInput): Prompt {
  if (!NAME_RE.test(input.name)) {
    throw new Error(`invalid name (allowed: letters/digits/._-, 1-64 chars): '${input.name}'`);
  }
  if (!input.prompt || !input.prompt.trim()) {
    throw new Error('prompt text required');
  }
  mkdirSync(dir(), { recursive: true });
  if (existsSync(pathFor(input.name))) {
    throw new Error(`prompt '${input.name}' already exists`);
  }
  const now = new Date().toISOString();
  const p: Prompt = {
    name: input.name,
    prompt: input.prompt,
    executor: {
      agent: input.executor?.agent ?? null,
      skills: input.executor?.skills ?? [],
      config: input.executor?.config,
    },
    created_at: now,
    updated_at: now,
  };
  writeFileSync(pathFor(input.name), serialize(p), 'utf-8');
  return p;
}

export function updatePrompt(name: string, patch: UpdatePromptInput): Prompt {
  const cur = getPrompt(name);
  if (!cur) throw new Error(`prompt '${name}' not found`);
  const next: Prompt = {
    ...cur,
    prompt: patch.prompt ?? cur.prompt,
    executor: patch.executor
      ? {
          agent: patch.executor.agent ?? cur.executor.agent,
          skills: patch.executor.skills ?? cur.executor.skills,
          config: patch.executor.config ?? cur.executor.config,
        }
      : cur.executor,
    updated_at: new Date().toISOString(),
  };
  writeFileSync(pathFor(name), serialize(next), 'utf-8');
  return next;
}

export function deletePrompt(name: string): boolean {
  if (!NAME_RE.test(name)) return false;
  const path = pathFor(name);
  if (!existsSync(path)) return false;
  unlinkSync(path);
  return true;
}
