/**
 * Gitagent Parser
 *
 * Reads a project directory following the gitagent spec (https://github.com/open-gitagent/gitagent)
 * and produces a GitagentDefinition object. All file I/O is dependency-injected so the parser
 * works for both local fs and relay file-read paths.
 *
 * @typedef {Object} GitagentDefinition
 * @property {Object} manifest       - Parsed agent.yaml
 * @property {string|null} soul      - SOUL.md content
 * @property {string|null} rules     - RULES.md content
 * @property {string|null} agents    - AGENTS.md content
 * @property {Array} skills          - Parsed skill definitions
 * @property {Array} tools           - Parsed tool schemas
 * @property {Object|null} knowledge - Knowledge index + always-loaded docs
 * @property {string|null} memory    - MEMORY.md content
 */

import yaml from 'js-yaml';

/**
 * Lightweight check: does agent.yaml exist in the project root?
 * @param {string} projectPath - Absolute path to the project
 * @param {(filePath: string) => Promise<boolean>} fileExists - Injected existence check
 * @returns {Promise<boolean>}
 */
export async function detectGitagent(projectPath, fileExists) {
  try {
    return await fileExists(join(projectPath, 'agent.yaml'));
  } catch {
    return false;
  }
}

/**
 * Full parse of a gitagent directory into a definition object.
 * @param {string} projectPath
 * @param {(filePath: string) => Promise<string|null>} readFile - Returns file content or null
 * @param {(dirPath: string) => Promise<string[]>} listDir - Returns entry names or []
 * @returns {Promise<GitagentDefinition|null>}
 */
export async function parseGitagentRepo(projectPath, readFile, listDir) {
  // Read manifest — required
  const agentYamlContent = await readFile(join(projectPath, 'agent.yaml'));
  if (!agentYamlContent) return null;

  let manifest;
  try {
    manifest = yaml.load(agentYamlContent);
  } catch {
    return null;
  }
  if (!manifest || typeof manifest !== 'object') return null;

  // Read core markdown files (all optional)
  const [soul, rules, agents, memory] = await Promise.all([
    safeRead(readFile, join(projectPath, 'SOUL.md')),
    safeRead(readFile, join(projectPath, 'RULES.md')),
    safeRead(readFile, join(projectPath, 'AGENTS.md')),
    safeRead(readFile, join(projectPath, 'memory', 'MEMORY.md')),
  ]);

  // Parse skills
  const skills = await parseSkills(projectPath, manifest.skills, readFile, listDir);

  // Parse tools
  const tools = await parseTools(projectPath, manifest.tools, readFile, listDir);

  // Parse knowledge
  const knowledge = await parseKnowledge(projectPath, readFile);

  return { manifest, soul, rules, agents, skills, tools, knowledge, memory };
}

// ── Internal helpers ────────────────────────────────────────────────

function join(...parts) {
  // Simple platform-agnostic join (works with / on all platforms in Node)
  return parts.join('/').replace(/\/+/g, '/');
}

async function safeRead(readFile, filePath) {
  try {
    return await readFile(filePath) || null;
  } catch {
    return null;
  }
}

/**
 * Parse skills from the skills/ directory.
 * Each skill is a subfolder containing SKILL.md with optional YAML frontmatter.
 */
async function parseSkills(projectPath, declaredSkills, readFile, listDir) {
  const skillsDir = join(projectPath, 'skills');
  let entries;
  try {
    entries = await listDir(skillsDir);
  } catch {
    return [];
  }
  if (!entries || entries.length === 0) return [];

  const skills = [];
  for (const entry of entries) {
    const skillMd = await safeRead(readFile, join(skillsDir, entry, 'SKILL.md'));
    if (!skillMd) continue;

    const { frontmatter, body } = parseFrontmatter(skillMd);
    skills.push({
      name: entry,
      ...(frontmatter || {}),
      description: body || null,
    });
  }
  return skills;
}

/**
 * Parse tool definitions from tools/ directory.
 * Each tool is a YAML file: tools/{name}.yaml
 */
async function parseTools(projectPath, declaredTools, readFile, listDir) {
  const toolsDir = join(projectPath, 'tools');
  let entries;
  try {
    entries = await listDir(toolsDir);
  } catch {
    return [];
  }
  if (!entries || entries.length === 0) return [];

  const tools = [];
  for (const entry of entries) {
    if (!entry.endsWith('.yaml') && !entry.endsWith('.yml')) continue;
    const content = await safeRead(readFile, join(toolsDir, entry));
    if (!content) continue;
    try {
      const schema = yaml.load(content);
      if (schema && typeof schema === 'object') {
        tools.push({ name: entry.replace(/\.ya?ml$/, ''), ...schema });
      }
    } catch {
      // skip invalid tool yaml
    }
  }
  return tools;
}

/**
 * Parse knowledge index and load always_load documents.
 */
async function parseKnowledge(projectPath, readFile) {
  const indexContent = await safeRead(readFile, join(projectPath, 'knowledge', 'index.yaml'));
  if (!indexContent) return null;

  let index;
  try {
    index = yaml.load(indexContent);
  } catch {
    return null;
  }
  if (!index || typeof index !== 'object') return null;

  // Load documents flagged as always_load
  const alwaysLoadDocs = [];
  const documents = index.documents || index.files || [];
  for (const doc of documents) {
    if (!doc.always_load) continue;
    const docPath = doc.path || doc.file;
    if (!docPath) continue;
    const content = await safeRead(readFile, join(projectPath, 'knowledge', docPath));
    if (content) {
      alwaysLoadDocs.push({ path: docPath, title: doc.title || docPath, content });
    }
  }

  return { index, alwaysLoadDocs };
}

/**
 * Parse YAML frontmatter from a markdown file.
 * Frontmatter is delimited by --- at the start of the file.
 * @returns {{ frontmatter: object|null, body: string }}
 */
function parseFrontmatter(content) {
  if (!content.startsWith('---')) {
    return { frontmatter: null, body: content.trim() };
  }
  const endIdx = content.indexOf('---', 3);
  if (endIdx === -1) {
    return { frontmatter: null, body: content.trim() };
  }
  const fmRaw = content.slice(3, endIdx).trim();
  const body = content.slice(endIdx + 3).trim();
  try {
    const frontmatter = yaml.load(fmRaw);
    return { frontmatter: frontmatter && typeof frontmatter === 'object' ? frontmatter : null, body };
  } catch {
    return { frontmatter: null, body: content.trim() };
  }
}
