"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SKILL_SOURCE_VALUES = exports.SKILL_LIST_SCHEMA_VERSION = exports.SKILL_LIST_KIND = void 0;
exports.isValidBareSkillId = isValidBareSkillId;
exports.parseRequiredSkillRef = parseRequiredSkillRef;
exports.isValidRequiredSkillRef = isValidRequiredSkillRef;
exports.dedupeSkillRefs = dedupeSkillRefs;
exports.requiredSkillRefCanRunOnWorker = requiredSkillRefCanRunOnWorker;
exports.requiredSkillRefsCanRunOnWorker = requiredSkillRefsCanRunOnWorker;
exports.listAvailableSkills = listAvailableSkills;
const promises_1 = require("node:fs/promises");
const node_os_1 = __importDefault(require("node:os"));
const node_path_1 = __importDefault(require("node:path"));
const store_1 = require("../auth-profiles/store");
const control_plane_config_1 = require("../config/control-plane-config");
const requirement_exclusions_1 = require("../catalog/requirement-exclusions");
const paths_1 = require("../core/paths");
const skill_metadata_1 = require("../core/skill-metadata");
exports.SKILL_LIST_KIND = "alpaca.skillList";
exports.SKILL_LIST_SCHEMA_VERSION = 1;
exports.SKILL_SOURCE_VALUES = [
    "repo-local",
    "user-local",
    "builtin",
    "plugin",
];
const SKILL_ID_SEGMENT_PATTERN = /^(?=.{1,63}$)[a-z0-9]+(?:-[a-z0-9]+)*$/u;
const SOURCE_PRIORITY = {
    "repo-local": 0,
    "user-local": 1,
    builtin: 2,
    plugin: 3,
};
function isValidBareSkillId(skillId) {
    return SKILL_ID_SEGMENT_PATTERN.test(skillId);
}
function parseRequiredSkillRef(ref, configuredWorkers) {
    const trimmed = ref.trim();
    if (trimmed.length === 0) {
        return null;
    }
    const parts = trimmed.split(":");
    if (parts.length === 1) {
        return isValidBareSkillId(trimmed)
            ? { ref: trimmed, worker: null, skillRef: trimmed }
            : null;
    }
    const [worker, ...skillParts] = parts;
    if (worker === undefined) {
        return null;
    }
    if (!isValidRequiredSkillWorkerName(worker)) {
        return null;
    }
    if (configuredWorkers !== undefined && !configuredWorkers.includes(worker)) {
        return null;
    }
    if (skillParts.length === 0 ||
        skillParts.some((part) => !isValidBareSkillId(part))) {
        return null;
    }
    return {
        ref: trimmed,
        worker,
        skillRef: skillParts.join(":"),
    };
}
function isValidRequiredSkillWorkerName(worker) {
    return isValidBareSkillId(worker) && worker !== "all" && worker !== "default";
}
function isValidRequiredSkillRef(ref, configuredWorkers) {
    return parseRequiredSkillRef(ref, configuredWorkers) !== null;
}
function dedupeSkillRefs(skillRefs) {
    return [...new Set(skillRefs)];
}
function requiredSkillRefCanRunOnWorker(ref, worker) {
    const parsed = parseRequiredSkillRef(ref);
    return (parsed !== null && (parsed.worker === null || parsed.worker === worker));
}
function requiredSkillRefsCanRunOnWorker(refs, worker) {
    return refs.every((ref) => requiredSkillRefCanRunOnWorker(ref, worker));
}
async function listAvailableSkills(input) {
    const env = input.env ?? process.env;
    const homeDirectory = input.homeDirectory ?? node_os_1.default.homedir();
    const workers = [...new Set(input.workers)].sort();
    const configuredWorkerSet = new Set(input.configuredWorkers);
    const candidates = [];
    for (const worker of workers) {
        candidates.push(...(await scanRepoLocalWorkerSkills({
            repoRoot: input.repoRoot,
            worker,
            workerConfigured: configuredWorkerSet.has(worker),
        })));
    }
    candidates.push(...(await scanCanonicalRepoSkills({
        repoRoot: input.repoRoot,
        workers,
        configuredWorkerSet,
    })));
    if (workers.includes("codex")) {
        for (const root of collectCodexRoots(input.authProfiles ?? [], env, homeDirectory)) {
            candidates.push(...(await scanCodexUserRoot({
                root,
                workerConfigured: configuredWorkerSet.has("codex"),
            })));
        }
    }
    if (workers.includes("claude")) {
        for (const root of collectClaudeRoots(input.authProfiles ?? [], env, homeDirectory)) {
            candidates.push(...(await scanClaudeUserRoot({
                root,
                workerConfigured: configuredWorkerSet.has("claude"),
            })));
        }
    }
    for (const worker of workers.filter((worker) => !(0, control_plane_config_1.isBuiltInWorkerName)(worker))) {
        candidates.push(...(await scanCustomWorkerLocalRoot({
            homeDirectory,
            worker,
            workerConfigured: configuredWorkerSet.has(worker),
        })));
    }
    const exclusions = await (0, requirement_exclusions_1.loadEffectiveRequirementExclusions)({
        repoRoot: input.repoRoot,
        homeDirectory,
    });
    const allItems = await materializeSkillItems({
        candidates,
        exclusions,
    });
    const items = input.includeExcluded === true
        ? allItems
        : allItems.filter((item) => item.excludedFromRequires === undefined);
    const hiddenItems = allItems.filter((item) => item.excludedFromRequires !== undefined);
    const exclusionHint = input.includeExcluded === true
        ? undefined
        : (0, requirement_exclusions_1.buildRequirementExclusionHintForVisibleResult)({
            visibleCount: items.length,
            hiddenMatches: hiddenItems.flatMap((item) => item.excludedFromRequires === undefined
                ? []
                : [item.excludedFromRequires]),
        });
    return {
        kind: exports.SKILL_LIST_KIND,
        schemaVersion: exports.SKILL_LIST_SCHEMA_VERSION,
        repositoryRoot: input.repoRoot,
        workers,
        items,
        ...(exclusionHint === undefined ? {} : { exclusionHint }),
    };
}
async function scanRepoLocalWorkerSkills(input) {
    const directory = input.worker === "claude"
        ? node_path_1.default.join(input.repoRoot, ".claude", "skills")
        : node_path_1.default.join(input.repoRoot, ".agents", "skills");
    return scanSkillRoot({
        directory,
        worker: input.worker,
        source: "repo-local",
        workerConfigured: input.workerConfigured,
        usableForRequiredSkills: input.workerConfigured,
        buildRef: (skillId) => skillId,
    });
}
async function scanCanonicalRepoSkills(input) {
    const directory = node_path_1.default.join(input.repoRoot, "skills");
    const skillIds = await readSkillDirectoryIds(directory);
    return skillIds.flatMap((skillId) => input.workers.map((worker) => ({
        ref: skillId,
        skillId,
        worker,
        source: "repo-local",
        path: node_path_1.default.join(directory, skillId, "SKILL.md"),
        workerConfigured: input.configuredWorkerSet.has(worker),
        usableForRequiredSkills: input.configuredWorkerSet.has(worker),
    })));
}
async function scanCodexUserRoot(input) {
    return [
        ...(await scanSkillRoot({
            directory: node_path_1.default.join(input.root, "skills"),
            worker: "codex",
            source: "user-local",
            workerConfigured: input.workerConfigured,
            usableForRequiredSkills: input.workerConfigured,
            skipHidden: true,
            buildRef: (skillId) => `codex:${skillId}`,
        })),
        ...(await scanSkillRoot({
            directory: node_path_1.default.join(input.root, "skills", ".system"),
            worker: "codex",
            source: "builtin",
            workerConfigured: input.workerConfigured,
            usableForRequiredSkills: input.workerConfigured,
            buildRef: (skillId) => `codex:${skillId}`,
        })),
        ...(await scanCodexPluginSkills({
            root: input.root,
            workerConfigured: input.workerConfigured,
        })),
    ];
}
async function scanClaudeUserRoot(input) {
    return scanSkillRoot({
        directory: node_path_1.default.join(input.root, "skills"),
        worker: "claude",
        source: "user-local",
        workerConfigured: input.workerConfigured,
        usableForRequiredSkills: input.workerConfigured,
        skipHidden: true,
        buildRef: (skillId) => `claude:${skillId}`,
    });
}
async function scanCodexPluginSkills(input) {
    const cacheRoot = node_path_1.default.join(input.root, "plugins", "cache");
    const candidates = [];
    for (const publisher of await readDirectoryNames(cacheRoot)) {
        const publisherRoot = node_path_1.default.join(cacheRoot, publisher);
        for (const pluginId of await readDirectoryNames(publisherRoot)) {
            if (!isValidBareSkillId(pluginId)) {
                continue;
            }
            const pluginRoot = node_path_1.default.join(publisherRoot, pluginId);
            for (const version of await readDirectoryNames(pluginRoot)) {
                candidates.push(...(await scanSkillRoot({
                    directory: node_path_1.default.join(pluginRoot, version, "skills"),
                    worker: "codex",
                    source: "plugin",
                    workerConfigured: input.workerConfigured,
                    usableForRequiredSkills: input.workerConfigured,
                    buildRef: (skillId) => `codex:${pluginId}:${skillId}`,
                })));
            }
        }
    }
    return candidates;
}
async function scanCustomWorkerLocalRoot(input) {
    return scanSkillRoot({
        directory: node_path_1.default.join((0, paths_1.getControlPlaneHomePath)(input.homeDirectory), "pi-workers", input.worker, "skills"),
        worker: input.worker,
        source: "user-local",
        workerConfigured: input.workerConfigured,
        usableForRequiredSkills: input.workerConfigured,
        buildRef: (skillId) => `${input.worker}:${skillId}`,
    });
}
async function scanSkillRoot(input) {
    const skillIds = await readSkillDirectoryIds(input.directory);
    return skillIds
        .filter((skillId) => !(input.skipHidden === true && skillId.startsWith(".")))
        .map((skillId) => ({
        ref: input.buildRef(skillId),
        skillId,
        worker: input.worker,
        source: input.source,
        path: node_path_1.default.join(input.directory, skillId, "SKILL.md"),
        workerConfigured: input.workerConfigured,
        usableForRequiredSkills: input.usableForRequiredSkills,
    }))
        .filter((candidate) => isValidRequiredSkillRef(candidate.ref));
}
async function materializeSkillItems(input) {
    const deduped = dedupeSkillCandidates(input.candidates);
    const items = [];
    for (const candidate of deduped) {
        const metadata = await readSkillMetadata(candidate.path, candidate.skillId);
        if (metadata === null) {
            continue;
        }
        const exclusion = (0, requirement_exclusions_1.findRequirementExclusionMatch)(input.exclusions, "skill", {
            ref: candidate.ref,
            id: candidate.skillId,
            source: candidate.path,
        });
        items.push({
            ref: candidate.ref,
            skillId: candidate.skillId,
            worker: candidate.worker,
            source: candidate.source,
            name: metadata.name,
            description: metadata.description,
            path: candidate.path,
            workerConfigured: candidate.workerConfigured,
            usableForRequiredSkills: candidate.usableForRequiredSkills && exclusion === null,
            ...(exclusion === null ? {} : { excludedFromRequires: exclusion }),
        });
    }
    return items.sort(compareSkillListItems);
}
function dedupeSkillCandidates(candidates) {
    const byKey = new Map();
    for (const candidate of candidates) {
        const key = `${candidate.worker}\0${candidate.ref}`;
        const existing = byKey.get(key);
        if (existing === undefined ||
            compareSkillCandidatePriority(candidate, existing) < 0) {
            byKey.set(key, candidate);
        }
    }
    return [...byKey.values()];
}
function compareSkillCandidatePriority(left, right) {
    if (left.usableForRequiredSkills !== right.usableForRequiredSkills) {
        return left.usableForRequiredSkills ? -1 : 1;
    }
    return SOURCE_PRIORITY[left.source] - SOURCE_PRIORITY[right.source];
}
function compareSkillListItems(left, right) {
    return (left.worker.localeCompare(right.worker) ||
        left.ref.localeCompare(right.ref) ||
        left.path.localeCompare(right.path));
}
async function readSkillDirectoryIds(directory) {
    const names = await readDirectoryNames(directory);
    const existingSkillIds = [];
    for (const name of names) {
        if (!isValidBareSkillId(name)) {
            continue;
        }
        if (await fileReadable(node_path_1.default.join(directory, name, "SKILL.md"))) {
            existingSkillIds.push(name);
        }
    }
    return existingSkillIds.sort();
}
async function readDirectoryNames(directory) {
    try {
        const entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
        return entries
            .filter((entry) => entry.isDirectory())
            .map((entry) => entry.name)
            .sort();
    }
    catch {
        return [];
    }
}
async function readSkillMetadata(skillPath, fallbackSkillId) {
    let text;
    try {
        text = await (0, promises_1.readFile)(skillPath, "utf8");
    }
    catch {
        return null;
    }
    const metadata = (0, skill_metadata_1.readSkillMetadataFromMarkdown)(text);
    return {
        name: metadata?.name ?? fallbackSkillId,
        description: metadata?.description ?? null,
    };
}
async function fileReadable(filePath) {
    try {
        await (0, promises_1.readFile)(filePath, "utf8");
        return true;
    }
    catch {
        return false;
    }
}
function collectCodexRoots(authProfiles, env, homeDirectory) {
    return dedupePaths([
        ...authProfiles
            .filter((profile) => (0, store_1.getAuthProfileProvider)(profile) === "codex")
            .flatMap((profile) => profile.kind === "codex-home" ? [profile.codexHomePath] : []),
        readEnvValue(env, "CODEX_HOME"),
        node_path_1.default.join(homeDirectory, ".codex"),
    ]);
}
function collectClaudeRoots(authProfiles, env, homeDirectory) {
    return dedupePaths([
        ...authProfiles
            .filter((profile) => (0, store_1.getAuthProfileProvider)(profile) === "claude")
            .flatMap((profile) => profile.kind === "claude-cli" ? [profile.claudeConfigPath] : []),
        readEnvValue(env, "CLAUDE_CONFIG_DIR"),
        node_path_1.default.join(homeDirectory, ".claude"),
    ]);
}
function dedupePaths(paths) {
    return [
        ...new Set(paths
            .map((value) => value?.trim())
            .filter((value) => value !== undefined && value !== "")),
    ];
}
function readEnvValue(env, key) {
    return env[key];
}
