/**
 * Unified manifest types and operations for Nori skillsets and skills
 *
 * The nori.json format is used by both skillsets and skills.
 * This module provides type definitions and read/write utilities.
 */
import * as fs from "fs/promises";
import * as path from "path";
/**
 * Read and parse skillset metadata from a skillset directory
 *
 * Reads from nori.json.
 *
 * @param args - Function arguments
 * @param args.skillsetDir - Path to skillset directory
 *
 * @returns Parsed skillset metadata
 */
export const readSkillsetMetadata = async (args) => {
    const { skillsetDir } = args;
    const noriJsonPath = path.join(skillsetDir, "nori.json");
    const content = await fs.readFile(noriJsonPath, "utf-8");
    const metadata = JSON.parse(content);
    return metadata;
};
/**
 * Write skillset metadata to nori.json in a skillset directory
 *
 * @param args - Function arguments
 * @param args.skillsetDir - Path to skillset directory
 * @param args.metadata - Skillset metadata to write
 */
export const writeSkillsetMetadata = async (args) => {
    const { skillsetDir, metadata } = args;
    const noriJsonPath = path.join(skillsetDir, "nori.json");
    const normalized = normalizeMetadataForWrite({ metadata });
    await fs.writeFile(noriJsonPath, JSON.stringify(normalized, null, 2));
};
const sortRecord = (args) => {
    const { record } = args;
    return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
};
const normalizeMetadataForWrite = (args) => {
    const { metadata } = args;
    const result = { ...metadata };
    if (result.skills != null && result.skills.length > 0) {
        result.skills = [...result.skills].sort((a, b) => a.name.localeCompare(b.name));
    }
    if (result.subagents != null && result.subagents.length > 0) {
        result.subagents = [...result.subagents].sort((a, b) => a.name.localeCompare(b.name));
    }
    if (result.slashcommands != null && result.slashcommands.length > 0) {
        result.slashcommands = [...result.slashcommands].sort((a, b) => a.command.localeCompare(b.command));
    }
    if (result.keywords != null && result.keywords.length > 0) {
        result.keywords = [...result.keywords].sort((a, b) => a.localeCompare(b));
    }
    if (result.dependencies != null) {
        const deps = { ...result.dependencies };
        if (deps.skills != null) {
            deps.skills = sortRecord({ record: deps.skills });
        }
        if (deps.subagents != null) {
            deps.subagents = sortRecord({ record: deps.subagents });
        }
        if (deps.slashCommands != null) {
            deps.slashCommands = sortRecord({ record: deps.slashCommands });
        }
        result.dependencies = deps;
    }
    return result;
};
/**
 * Add or update a skill dependency in a skillset's nori.json
 *
 * If nori.json does not exist, creates a basic one using the skillset directory
 * name as the skillset name.
 *
 * @param args - Function arguments
 * @param args.skillsetDir - Path to skillset directory
 * @param args.skillName - Name of the skill to add
 * @param args.version - Version string (e.g., "*", "^1.0.0", "1.2.3")
 */
export const addSkillToNoriJson = async (args) => {
    const { skillsetDir, skillName, version } = args;
    const noriJsonPath = path.join(skillsetDir, "nori.json");
    let metadata;
    try {
        const content = await fs.readFile(noriJsonPath, "utf-8");
        metadata = JSON.parse(content);
    }
    catch (err) {
        if (err instanceof SyntaxError) {
            throw new Error(`nori.json exists but contains invalid JSON: ${err.message}`);
        }
        // File does not exist -- create a basic one
        metadata = {
            name: path.basename(skillsetDir),
            version: "1.0.0",
            type: "skillset",
        };
    }
    if (metadata.dependencies == null) {
        metadata.dependencies = {};
    }
    if (metadata.dependencies.skills == null) {
        metadata.dependencies.skills = {};
    }
    metadata.dependencies.skills[skillName] = version;
    await writeSkillsetMetadata({ skillsetDir, metadata });
};
/**
 * Add or update a subagent dependency in a skillset's nori.json
 *
 * If nori.json does not exist, creates a basic one using the skillset directory
 * name as the skillset name.
 *
 * @param args - Function arguments
 * @param args.skillsetDir - Path to skillset directory
 * @param args.subagentName - Name of the subagent to add
 * @param args.version - Version string (e.g., "*", "^1.0.0", "1.2.3")
 */
export const addSubagentToNoriJson = async (args) => {
    const { skillsetDir, subagentName, version } = args;
    const noriJsonPath = path.join(skillsetDir, "nori.json");
    let metadata;
    try {
        const content = await fs.readFile(noriJsonPath, "utf-8");
        metadata = JSON.parse(content);
    }
    catch (err) {
        if (err instanceof SyntaxError) {
            throw new Error(`nori.json exists but contains invalid JSON: ${err.message}`);
        }
        metadata = {
            name: path.basename(skillsetDir),
            version: "1.0.0",
            type: "skillset",
        };
    }
    if (metadata.dependencies == null) {
        metadata.dependencies = {};
    }
    if (metadata.dependencies.subagents == null) {
        metadata.dependencies.subagents = {};
    }
    metadata.dependencies.subagents[subagentName] = version;
    await writeSkillsetMetadata({ skillsetDir, metadata });
};
/**
 * Check whether a directory looks like a skillset (has a known config file, or both
 * skills/ and subagents/ subdirectories).
 *
 * @param args - Function arguments
 * @param args.skillsetDir - Path to the directory to check
 * @param args.configFileNames - Config file names to look for (defaults to ["AGENTS.md", "CLAUDE.md"])
 *
 * @returns True if the directory has skillset markers
 */
const looksLikeSkillset = async (args) => {
    const { skillsetDir } = args;
    const configFileNames = args.configFileNames ?? ["AGENTS.md", "CLAUDE.md"];
    for (const fileName of configFileNames) {
        try {
            await fs.access(path.join(skillsetDir, fileName));
            return true;
        }
        catch {
            // file not found — try next
        }
    }
    try {
        const [skillsStat, subagentsStat] = await Promise.all([
            fs.stat(path.join(skillsetDir, "skills")),
            fs.stat(path.join(skillsetDir, "subagents")),
        ]);
        return skillsStat.isDirectory() && subagentsStat.isDirectory();
    }
    catch {
        return false;
    }
};
/**
 * Ensure a nori.json manifest exists for a skillset directory.
 *
 * If the directory exists and looks like a skillset (has a known config file,
 * or both skills/ and subagents/ subdirectories) but has no nori.json, creates
 * one with the folder name as `name` and version "0.0.1".
 *
 * This is a backwards-compatibility shim for user-created skillsets that
 * were never given a nori.json manifest.
 *
 * @param args - Function arguments
 * @param args.skillsetDir - Path to skillset directory
 * @param args.configFileNames - Config file names to look for (defaults to ["AGENTS.md", "CLAUDE.md"])
 */
export const ensureNoriJson = async (args) => {
    const { skillsetDir, configFileNames } = args;
    const noriJsonPath = path.join(skillsetDir, "nori.json");
    try {
        await fs.access(noriJsonPath);
        return;
    }
    catch {
        // nori.json missing — check if we should create it
    }
    try {
        await fs.access(skillsetDir);
    }
    catch {
        return;
    }
    if (!(await looksLikeSkillset({ skillsetDir, configFileNames }))) {
        return;
    }
    const metadata = {
        name: path.basename(skillsetDir),
        version: "0.0.1",
        type: "skillset",
    };
    await writeSkillsetMetadata({ skillsetDir, metadata });
};
//# sourceMappingURL=nori.js.map