/**
 * Recipe Loader
 *
 * Discovers and loads recipe files from all sources:
 *   1. Bundled recipes (src/recipes/bundled/)
 *   2. User recipes (~/.sandcode/recipes/)
 *   3. Project recipes (.sandcode/recipes/)
 *
 * Recipes are markdown files with YAML frontmatter defining steps.
 */

import { readdir, readFile } from 'fs/promises'
import { basename, join } from 'path'
import { getSandcodeConfigHomeDir } from '../utils/envUtils.js'
import { getCwd } from '../utils/cwd.js'
import { logForDebugging } from '../utils/debug.js'
import type { Recipe, RecipeFrontmatter, RecipeStep } from './types.js'
import YAML from 'yaml'

/**
 * Load all available recipes from all sources.
 * Project recipes override user recipes, which override bundled recipes (by id).
 */
export async function loadAllRecipes(): Promise<Recipe[]> {
  const bundled = await loadRecipesFromDir(
    join(__dirname, 'bundled'),
    'bundled',
  )
  const user = await loadRecipesFromDir(
    join(getSandcodeConfigHomeDir(), 'recipes'),
    'user',
  )
  const project = await loadRecipesFromDir(
    join(getCwd(), '.sandcode', 'recipes'),
    'project',
  )

  // Merge with priority: project > user > bundled
  const recipeMap = new Map<string, Recipe>()
  for (const recipe of [...bundled, ...user, ...project]) {
    recipeMap.set(recipe.id, recipe)
  }

  return Array.from(recipeMap.values())
}

/**
 * Load a single recipe by ID from any source.
 */
export async function loadRecipeById(id: string): Promise<Recipe | null> {
  const all = await loadAllRecipes()
  return all.find(r => r.id === id) ?? null
}

/**
 * Load recipes from a directory.
 */
async function loadRecipesFromDir(
  dir: string,
  source: Recipe['source'],
): Promise<Recipe[]> {
  const recipes: Recipe[] = []

  let entries: string[]
  try {
    entries = await readdir(dir)
  } catch {
    return recipes // Directory doesn't exist yet
  }

  for (const entry of entries) {
    if (!entry.endsWith('.md')) continue

    try {
      const filePath = join(dir, entry)
      const content = await readFile(filePath, 'utf-8')
      const recipe = parseRecipeFile(content, filePath, source)
      if (recipe) {
        recipes.push(recipe)
      }
    } catch (error) {
      logForDebugging(
        `Failed to load recipe ${entry}: ${error instanceof Error ? error.message : String(error)}`,
      )
    }
  }

  return recipes
}

/**
 * Parse a recipe markdown file into a Recipe object.
 */
function parseRecipeFile(
  content: string,
  sourcePath: string,
  source: Recipe['source'],
): Recipe | null {
  const frontmatterMatch = content.match(
    /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/,
  )
  if (!frontmatterMatch) {
    logForDebugging(`Recipe ${sourcePath}: no frontmatter found`)
    return null
  }

  const [, yamlStr, body] = frontmatterMatch

  let parsed: Record<string, unknown>
  try {
    parsed = YAML.parse(yamlStr!) as Record<string, unknown>
  } catch (error) {
    logForDebugging(
      `Recipe ${sourcePath}: invalid YAML: ${error instanceof Error ? error.message : String(error)}`,
    )
    return null
  }

  if (!parsed.name || !parsed.description || !parsed.steps) {
    logForDebugging(
      `Recipe ${sourcePath}: missing required fields (name, description, steps)`,
    )
    return null
  }

  const steps: RecipeStep[] = (parsed.steps as Array<Record<string, unknown>>).map(
    (step) => ({
      prompt: String(step.prompt ?? ''),
      name: step.name ? String(step.name) : undefined,
      confirm: step.confirm === true,
      when: step.when ? String(step.when) : undefined,
    }),
  )

  const metadata: RecipeFrontmatter = {
    name: String(parsed.name),
    description: String(parsed.description),
    tags: Array.isArray(parsed.tags)
      ? parsed.tags.map(String)
      : undefined,
    steps,
    requires: Array.isArray(parsed.requires)
      ? parsed.requires.map(String)
      : undefined,
    estimate: parsed.estimate ? String(parsed.estimate) : undefined,
  }

  const id = basename(sourcePath, '.md')

  return {
    id,
    metadata,
    body: (body ?? '').trim(),
    sourcePath,
    source,
  }
}
