/**
 * File-Based Agent I/O
 *
 * Writes context as input files before agent spawn and reads output files after completion.
 * Uses YAML frontmatter (gray-matter) for structured metadata and markdown bodies.
 *
 * Input: .cw/input/ — written by system before spawn
 * Output: .cw/output/ — written by agent during execution
 */

import { mkdirSync, writeFileSync, readdirSync, existsSync } from 'node:fs';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import matter from 'gray-matter';
import { nanoid } from 'nanoid';
import { tiptapJsonToMarkdown } from './content-serializer.js';
import type { AgentInputContext } from './types.js';

// Re-export for convenience
export type { AgentInputContext } from './types.js';

// =============================================================================
// TYPES
// =============================================================================

export interface WriteInputFilesOptions extends AgentInputContext {
  agentWorkdir: string;
}

export interface ParsedSummary {
  body: string;
  filesModified?: string[];
}

export interface ParsedPhaseFile {
  id: string;
  title: string;
  dependencies: string[];
  body: string;
}

export interface ParsedTaskFile {
  id: string;
  title: string;
  category: string;
  type: string;
  dependencies: string[];
  body: string;
}

export interface ParsedDecisionFile {
  id: string;
  topic: string;
  decision: string;
  reason: string;
  body: string;
}

export interface ParsedPageFile {
  pageId: string;
  title: string;
  summary: string;
  body: string;
}

// =============================================================================
// ID GENERATION
// =============================================================================

export function generateId(): string {
  return nanoid();
}

// =============================================================================
// INPUT FILE WRITING
// =============================================================================

function formatFrontmatter(data: Record<string, unknown>, body: string = ''): string {
  const lines: string[] = ['---'];
  for (const [key, value] of Object.entries(data)) {
    if (value === undefined || value === null) continue;
    if (Array.isArray(value)) {
      if (value.length === 0) {
        lines.push(`${key}: []`);
      } else {
        lines.push(`${key}:`);
        for (const item of value) {
          lines.push(`  - ${String(item)}`);
        }
      }
    } else if (value instanceof Date) {
      lines.push(`${key}: "${value.toISOString()}"`);
    } else if (typeof value === 'string' && (value.includes('\n') || value.includes(':'))) {
      lines.push(`${key}: ${JSON.stringify(value)}`);
    } else {
      lines.push(`${key}: ${String(value)}`);
    }
  }
  lines.push('---');
  if (body) {
    lines.push('');
    lines.push(body);
  }
  return lines.join('\n') + '\n';
}

export function writeInputFiles(options: WriteInputFilesOptions): void {
  const inputDir = join(options.agentWorkdir, '.cw', 'input');
  mkdirSync(inputDir, { recursive: true });

  // Write expected working directory marker for verification
  writeFileSync(
    join(inputDir, '../expected-pwd.txt'),
    options.agentWorkdir,
    'utf-8'
  );

  const manifestFiles: string[] = [];

  if (options.initiative) {
    const ini = options.initiative;
    const content = formatFrontmatter(
      {
        id: ini.id,
        name: ini.name,
        status: ini.status,
        mergeRequiresApproval: ini.mergeRequiresApproval,
        branch: ini.branch,
      },
      '',
    );
    writeFileSync(join(inputDir, 'initiative.md'), content, 'utf-8');
    manifestFiles.push('initiative.md');
  }

  if (options.pages && options.pages.length > 0) {
    const pagesDir = join(inputDir, 'pages');
    mkdirSync(pagesDir, { recursive: true });

    for (const page of options.pages) {
      let bodyMarkdown = '';
      if (page.content) {
        try {
          const parsed = JSON.parse(page.content);
          bodyMarkdown = tiptapJsonToMarkdown(parsed);
        } catch {
          // Invalid JSON content — skip
        }
      }

      const content = formatFrontmatter(
        {
          title: page.title,
          parentPageId: page.parentPageId,
          sortOrder: page.sortOrder,
        },
        bodyMarkdown,
      );
      const filename = `pages/${page.id}.md`;
      writeFileSync(join(pagesDir, `${page.id}.md`), content, 'utf-8');
      manifestFiles.push(filename);
    }
  }

  if (options.phase) {
    const ph = options.phase;
    let bodyMarkdown = '';
    if (ph.content) {
      try {
        bodyMarkdown = tiptapJsonToMarkdown(JSON.parse(ph.content));
      } catch {
        // Invalid JSON content — skip
      }
    }
    const content = formatFrontmatter(
      {
        id: ph.id,
        name: ph.name,
        status: ph.status,
      },
      bodyMarkdown,
    );
    writeFileSync(join(inputDir, 'phase.md'), content, 'utf-8');
    manifestFiles.push('phase.md');
  }

  if (options.task) {
    const t = options.task;
    const content = formatFrontmatter(
      {
        id: t.id,
        name: t.name,
        category: t.category,
        type: t.type,
        priority: t.priority,
        status: t.status,
      },
      t.description ?? '',
    );
    writeFileSync(join(inputDir, 'task.md'), content, 'utf-8');
    manifestFiles.push('task.md');
  }

  // Write read-only context directories
  const contextFiles: string[] = [];

  if (options.phases && options.phases.length > 0) {
    const phasesDir = join(inputDir, 'context', 'phases');
    mkdirSync(phasesDir, { recursive: true });

    for (const ph of options.phases) {
      let bodyMarkdown = '';
      if (ph.content) {
        try {
          bodyMarkdown = tiptapJsonToMarkdown(JSON.parse(ph.content));
        } catch {
          // Invalid JSON content — skip
        }
      }
      const content = formatFrontmatter(
        {
          id: ph.id,
          name: ph.name,
          status: ph.status,
          dependsOn: ph.dependsOn ?? [],
        },
        bodyMarkdown,
      );
      const filename = `context/phases/${ph.id}.md`;
      writeFileSync(join(phasesDir, `${ph.id}.md`), content, 'utf-8');
      contextFiles.push(filename);
    }
  }

  if (options.tasks && options.tasks.length > 0) {
    const tasksDir = join(inputDir, 'context', 'tasks');
    mkdirSync(tasksDir, { recursive: true });

    for (const t of options.tasks) {
      const content = formatFrontmatter(
        {
          id: t.id,
          name: t.name,
          phaseId: t.phaseId,
          parentTaskId: t.parentTaskId,
          category: t.category,
          type: t.type,
          priority: t.priority,
          status: t.status,
        },
        t.description ?? '',
      );
      const filename = `context/tasks/${t.id}.md`;
      writeFileSync(join(tasksDir, `${t.id}.md`), content, 'utf-8');
      contextFiles.push(filename);
    }
  }

  // Write manifest listing exactly which files were created
  writeFileSync(
    join(inputDir, 'manifest.json'),
    JSON.stringify({
      files: manifestFiles,
      contextFiles,
      agentId: options.agentId ?? null,
      agentName: options.agentName ?? null,
    }) + '\n',
    'utf-8',
  );
}

// =============================================================================
// OUTPUT FILE READING
// =============================================================================

export function readFrontmatterFile(filePath: string): { data: Record<string, unknown>; body: string } | null {
  try {
    const raw = readFileSync(filePath, 'utf-8');
    const parsed = matter(raw);
    return { data: parsed.data as Record<string, unknown>, body: parsed.content.trim() };
  } catch {
    return null;
  }
}

function readFrontmatterDir<T>(
  dirPath: string,
  mapper: (data: Record<string, unknown>, body: string, filename: string) => T | null,
): T[] {
  if (!existsSync(dirPath)) return [];

  const results: T[] = [];
  try {
    const entries = readdirSync(dirPath);
    for (const entry of entries) {
      if (!entry.endsWith('.md')) continue;
      const filePath = join(dirPath, entry);
      const parsed = readFrontmatterFile(filePath);
      if (!parsed) continue;
      const mapped = mapper(parsed.data, parsed.body, entry);
      if (mapped) results.push(mapped);
    }
  } catch {
    // Directory read error — return empty
  }
  return results;
}

export function readSummary(agentWorkdir: string): ParsedSummary | null {
  const filePath = join(agentWorkdir, '.cw', 'output', 'SUMMARY.md');
  const parsed = readFrontmatterFile(filePath);
  if (!parsed) return null;

  const filesModified = parsed.data.files_modified;
  return {
    body: parsed.body,
    filesModified: Array.isArray(filesModified) ? filesModified.map(String) : undefined,
  };
}

export function readPhaseFiles(agentWorkdir: string): ParsedPhaseFile[] {
  const dirPath = join(agentWorkdir, '.cw', 'output', 'phases');
  return readFrontmatterDir(dirPath, (data, body, filename) => {
    const id = filename.replace(/\.md$/, '');
    const deps = Array.isArray(data.dependencies) ? data.dependencies.map(String) : [];
    return {
      id,
      title: String(data.title ?? ''),
      dependencies: deps,
      body,
    };
  });
}

export function readTaskFiles(agentWorkdir: string): ParsedTaskFile[] {
  const dirPath = join(agentWorkdir, '.cw', 'output', 'tasks');
  return readFrontmatterDir(dirPath, (data, body, filename) => {
    const id = filename.replace(/\.md$/, '');
    const deps = Array.isArray(data.dependencies) ? data.dependencies.map(String) : [];
    return {
      id,
      title: String(data.title ?? ''),
      category: String(data.category ?? 'execute'),
      type: String(data.type ?? 'auto'),
      dependencies: deps,
      body,
    };
  });
}

export function readDecisionFiles(agentWorkdir: string): ParsedDecisionFile[] {
  const dirPath = join(agentWorkdir, '.cw', 'output', 'decisions');
  return readFrontmatterDir(dirPath, (data, body, filename) => {
    const id = filename.replace(/\.md$/, '');
    return {
      id,
      topic: String(data.topic ?? ''),
      decision: String(data.decision ?? ''),
      reason: String(data.reason ?? ''),
      body,
    };
  });
}

export function readPageFiles(agentWorkdir: string): ParsedPageFile[] {
  const dirPath = join(agentWorkdir, '.cw', 'output', 'pages');
  return readFrontmatterDir(dirPath, (data, body, filename) => {
    const pageId = filename.replace(/\.md$/, '');
    return {
      pageId,
      title: String(data.title ?? ''),
      summary: String(data.summary ?? ''),
      body,
    };
  });
}
