import fs from 'node:fs';
import path from 'node:path';

import { PIPELINE_DIR } from './constants.js';
import type { BoardPersona, ForgeConfig } from './types.js';

/** Board agents directory within the pipeline assets. */
const BOARD_AGENTS_DIR = path.join(PIPELINE_DIR, 'agents', 'board');

/**
 * Convert a string to a URL-safe slug.
 */
export function slugify(value: string): string {
  const slug = value
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
  return slug || 'persona';
}

/**
 * Extract persona name from the first heading line in markdown.
 * Strips trailing em-dash or hyphen-separated subtitle.
 */
export function personaNameFromMarkdown(markdown: string, fallback: string): string {
  const firstLine = markdown.trim().split('\n')[0] ?? fallback;
  let heading = firstLine.replace(/^#+\s*/, '').trim();

  if (heading.includes('—')) {
    heading = heading.split('—')[0]!.trim();
  } else if (heading.includes('-')) {
    heading = heading.split('-')[0]!.trim();
  }

  return heading || fallback;
}

/**
 * Load board personas from the pipeline assets directory.
 * Returns sorted list of persona definitions.
 */
export function loadBoardPersonas(boardDir: string = BOARD_AGENTS_DIR): BoardPersona[] {
  if (!fs.existsSync(boardDir)) return [];

  const files = fs
    .readdirSync(boardDir)
    .filter((f) => f.endsWith('.md'))
    .sort();

  return files.map((file) => {
    const filePath = path.join(boardDir, file);
    const content = fs.readFileSync(filePath, 'utf-8').trim();
    const stem = path.basename(file, '.md');

    return {
      name: personaNameFromMarkdown(content, stem.toUpperCase()),
      slug: slugify(stem),
      description: content,
      path: path.relative(PIPELINE_DIR, filePath),
    };
  });
}

/**
 * Load project-level persona overrides from {projectRoot}/.forge/personas/.
 * Returns a map of slug → override content.
 */
export function loadPersonaOverrides(projectRoot: string): Record<string, string> {
  const overridesDir = path.join(projectRoot, '.forge', 'personas');
  if (!fs.existsSync(overridesDir)) return {};

  const result: Record<string, string> = {};
  const files = fs.readdirSync(overridesDir).filter((f) => f.endsWith('.md'));

  for (const file of files) {
    const slug = slugify(path.basename(file, '.md'));
    result[slug] = fs.readFileSync(path.join(overridesDir, file), 'utf-8').trim();
  }
  return result;
}

/**
 * Load project-level Forge config from {projectRoot}/.forge/config.yaml.
 * Parses simple YAML key-value pairs via regex (no YAML dependency).
 */
export function loadForgeConfig(projectRoot: string): ForgeConfig {
  const configPath = path.join(projectRoot, '.forge', 'config.yaml');
  if (!fs.existsSync(configPath)) return {};

  const text = fs.readFileSync(configPath, 'utf-8');
  const config: ForgeConfig = {};

  // Parse simple list values under board: and specialists: sections
  const boardAdditional = parseYamlList(text, 'additionalMembers');
  const boardSkip = parseYamlList(text, 'skipMembers');
  const specialistsInclude = parseYamlList(text, 'alwaysInclude');

  if (boardAdditional.length > 0 || boardSkip.length > 0) {
    config.board = {};
    if (boardAdditional.length > 0) config.board.additionalMembers = boardAdditional;
    if (boardSkip.length > 0) config.board.skipMembers = boardSkip;
  }
  if (specialistsInclude.length > 0) {
    config.specialists = { alwaysInclude: specialistsInclude };
  }

  return config;
}

/**
 * Parse a simple YAML list under a given key name.
 */
function parseYamlList(text: string, key: string): string[] {
  const pattern = new RegExp(`${key}:\\s*\\n((?:\\s+-\\s+.+\\n?)*)`, 'm');
  const match = text.match(pattern);
  if (!match?.[1]) return [];

  return match[1]
    .split('\n')
    .map((line) => line.trim().replace(/^-\s+/, '').trim())
    .filter(Boolean);
}

/**
 * Get effective board personas after applying project overrides and config.
 *
 * - Base personas loaded from pipeline/agents/board/
 * - Project overrides from {projectRoot}/.forge/personas/ APPENDED to base
 * - Config skipMembers removes personas; additionalMembers adds custom paths
 */
export function getEffectivePersonas(projectRoot: string, boardDir?: string): BoardPersona[] {
  let personas = loadBoardPersonas(boardDir);
  const overrides = loadPersonaOverrides(projectRoot);
  const config = loadForgeConfig(projectRoot);

  // Apply overrides — append project content to base persona description
  personas = personas.map((p) => {
    const override = overrides[p.slug];
    if (override) {
      return { ...p, description: `${p.description}\n\n${override}` };
    }
    return p;
  });

  // Apply config: skip members
  if (config.board?.skipMembers?.length) {
    const skip = new Set(config.board.skipMembers.map((s) => slugify(s)));
    personas = personas.filter((p) => !skip.has(p.slug));
  }

  return personas;
}
