/**
 * variant/pool.mjs — resolve N typeui-sh style slugs for a variant run (C1).
 *
 * The C1 variant flow (`apt:mockup --variants N`) needs a deterministic way
 * to answer: *given taste.yaml and the 57 vendored typeui-sh styles, which N
 * should I seed the parallel sub-agents with?*  This module is that resolver.
 *
 * Rules:
 *   1. N must be between 2 and 5 (we clamp the input).  Below 2 there's no
 *      choice; above 5 the user can't scan thumbnails without fatigue.
 *   2. If `taste.direction` matches a known key, return the curated slug pool
 *      for that direction (in order of recommendation, never less than 3).
 *      Unknown or missing directions fall through to the DEFAULT_POOL.
 *   3. Every returned slug must exist on disk under `stylesDir/<slug>/SKILL.md`.
 *      Missing slugs are skipped (warnings surface), and we keep picking until
 *      we have N or run out of candidates.
 *   4. The resolver never invents slugs.  The direction pools + defaults are
 *      hand-curated against the 57 vendored slugs (docs/frameworks/
 *      Design-system/examples/by-source/typeui-sh/).
 *
 * Returns: { slugs: [{ slug, name, description, path }, ...], warnings: [] }
 */
import { existsSync, readFileSync } from 'node:fs';
import { resolve as resolvePath } from 'node:path';
import { parse as parseYaml } from 'yaml';
// Curated per-direction pools, hand-picked from the 57 vendored typeui-sh
// slugs.  Each list is ordered by "most representative of direction" first.
// When the requested N exceeds a direction pool's length, we backfill from
// DEFAULT_POOL (deduped).
const DIRECTION_POOLS = {
    brutalist: ['neobrutalism', 'brutalism', 'mono', 'dithered', 'retro'],
    editorial: ['editorial', 'paper', 'publication', 'storytelling', 'refined'],
    'enterprise-clean': ['clean', 'minimal', 'professional', 'corporate', 'flat'],
    playful: ['friendly', 'energetic', 'expressive', 'colorful', 'doodle'],
};
// Default 5-slug triplet-plus: visually distinct, covers the design space,
// returns sensible results when no taste.yaml is declared.
const DEFAULT_POOL = ['clean', 'bento', 'paper', 'neobrutalism', 'editorial'];
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n/;
/**
 * @typedef {{ slug: string, name: string, description: string, path: string }} VariantStyle
 * @typedef {{ slugs: VariantStyle[], warnings: string[] }} PoolResolution
 */
/**
 * Clamp N to the [MIN, MAX] range.  Exported so CLI + tests share the
 * exact same clamp.
 */
export const VARIANT_N_MIN = 2;
export const VARIANT_N_MAX = 5;
export function clampVariantN(n) {
    const num = Number.parseInt(String(n), 10);
    if (!Number.isFinite(num) || Number.isNaN(num))
        return 3;
    if (num < VARIANT_N_MIN)
        return VARIANT_N_MIN;
    if (num > VARIANT_N_MAX)
        return VARIANT_N_MAX;
    return num;
}
/**
 * Read a single typeui-sh style SKILL.md and extract its frontmatter.
 * Returns null when the file doesn't exist or the frontmatter is absent.
 *
 * @param {string} stylesDir
 * @param {string} slug
 * @returns {VariantStyle | null}
 */
export function readStyle(stylesDir, slug) {
    const path = resolvePath(stylesDir, slug, 'SKILL.md');
    if (!existsSync(path))
        return null;
    let text;
    try {
        text = readFileSync(path, 'utf8');
    }
    catch {
        return null;
    }
    const m = text.match(FRONTMATTER_RE);
    if (!m)
        return null;
    let parsed;
    try {
        parsed = parseYaml(m[1]);
    }
    catch {
        return null;
    }
    if (parsed === null ||
        parsed === undefined ||
        typeof parsed !== 'object' ||
        Array.isArray(parsed)) {
        return null;
    }
    const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : slug;
    const description = typeof parsed.description === 'string' && parsed.description.length > 0
        ? parsed.description
        : '';
    return { slug, name, description, path };
}
/**
 * Return the curated slug pool for a direction (case-insensitive).
 * Unknown direction returns an empty array so callers can fall through.
 */
export function poolForDirection(direction) {
    if (!direction || typeof direction !== 'string')
        return [];
    const key = direction.trim().toLowerCase();
    return DIRECTION_POOLS[key] ?? [];
}
/**
 * Deduplicate a slug list in-place preserving first occurrences.
 */
function uniq(list) {
    const seen = new Set();
    const out = [];
    for (const s of list) {
        if (!seen.has(s)) {
            seen.add(s);
            out.push(s);
        }
    }
    return out;
}
/**
 * Resolve the N-slug pool for a variant run.
 *
 * @param {object} opts
 * @param {number} [opts.n=3] — target pool size, clamped to [2..5]
 * @param {{ direction?: string } | null} [opts.taste=null] — parsed taste.yaml contents (or null)
 * @param {string} opts.stylesDir — absolute path to the vendored typeui-sh dir
 * @returns {PoolResolution}
 */
export function resolveVariantPool({ n = 3, taste = null, stylesDir }) {
    if (!stylesDir || typeof stylesDir !== 'string') {
        throw new Error('resolveVariantPool: stylesDir is required');
    }
    const clamped = clampVariantN(n);
    const warnings = [];
    const directionPool = poolForDirection(taste?.direction);
    const candidates = uniq([...directionPool, ...DEFAULT_POOL]);
    const picked = [];
    for (const slug of candidates) {
        if (picked.length >= clamped)
            break;
        const style = readStyle(stylesDir, slug);
        if (!style) {
            warnings.push(`style "${slug}" not found at ${stylesDir}/${slug}/SKILL.md — skipping`);
            continue;
        }
        picked.push(style);
    }
    if (picked.length < clamped) {
        warnings.push(`resolved only ${picked.length}/${clamped} variant slugs from stylesDir=${stylesDir}; check the typeui-sh directory`);
    }
    return { slugs: picked, warnings };
}
//# sourceMappingURL=pool.mjs.map