"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SKILL_SEARCH_SCHEMA_VERSION = exports.SKILL_SEARCH_KIND = void 0;
exports.searchSkills = searchSkills;
const node_os_1 = __importDefault(require("node:os"));
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const claude_cli_1 = require("../auth-profiles/claude-cli");
const environment_1 = require("../auth-profiles/environment");
const store_1 = require("../auth-profiles/store");
const requirement_exclusions_1 = require("../catalog/requirement-exclusions");
const metadata_1 = require("../catalog/metadata");
const app_server_client_1 = require("../codex/app-server-client");
const command_1 = require("../core/command");
const search_text_1 = require("../core/search-text");
const transient_network_error_1 = require("../core/transient-network-error");
const skill_inventory_1 = require("../workflows/skill-inventory");
const catalog_1 = require("./catalog");
const source_uri_1 = require("./source-uri");
exports.SKILL_SEARCH_KIND = "alpaca.skillSearch";
exports.SKILL_SEARCH_SCHEMA_VERSION = 6;
const CLAUDE_OFFICIAL_MARKETPLACE_URL = "https://raw.githubusercontent.com/anthropics/claude-plugins-official/main/.claude-plugin/marketplace.json";
async function searchSkills(input) {
    const env = input.env ?? process.env;
    const homeDirectory = input.homeDirectory ?? node_os_1.default.homedir();
    const authProfiles = input.authProfiles ?? (await (0, store_1.loadAuthProfiles)(homeDirectory)).profiles;
    const targets = [...new Set(input.targets)].sort();
    const mode = input.mode ?? "all";
    const topics = await (0, metadata_1.loadEffectiveCatalogTopics)({ repoRoot: input.repoRoot });
    const topicDescriptions = (0, metadata_1.toCatalogTopicMap)(topics);
    const errors = [];
    const items = [];
    if (mode !== "installable") {
        items.push(...(await searchInstalledSkills({
            ...input,
            authProfiles,
            env,
            homeDirectory,
            targets,
        })));
    }
    if (mode !== "installed") {
        items.push(...(await searchCatalogSkills({
            repoRoot: input.repoRoot,
            targets,
        })));
    }
    if (mode !== "installed" && targets.includes("claude")) {
        const result = await collectSourceErrors("claude_plugin", searchClaudePlugins({
            authProfiles,
            env,
            cwd: input.repoRoot,
            homeDirectory,
        }));
        items.push(...result.items);
        errors.push(...result.errors);
    }
    if (mode !== "installed" && targets.includes("codex")) {
        try {
            const result = await searchCodexPlugins({
                authProfiles,
                env,
                cwd: input.repoRoot,
            });
            items.push(...result.items);
            errors.push(...result.errors);
        }
        catch (error) {
            errors.push({
                sourceKind: "codex_plugin",
                message: error instanceof Error ? error.message : String(error),
                recoverable: (0, transient_network_error_1.isTransientNetworkError)(error),
            });
        }
    }
    const exclusions = await (0, requirement_exclusions_1.loadEffectiveRequirementExclusions)({
        repoRoot: input.repoRoot,
        homeDirectory,
    });
    const allItems = applySkillSearchRequirementExclusions({
        items,
        exclusions,
    });
    const sortedItems = dedupeSearchItems(filterAndSortItems(allItems, input.query, topicDescriptions, input.forcedIds ?? []));
    const visibleItems = input.includeExcluded === true
        ? sortedItems
        : sortedItems.filter((item) => item.excludedFromRequires === undefined);
    const hiddenItems = sortedItems.filter((item) => item.excludedFromRequires !== undefined);
    const exclusionHint = input.includeExcluded === true
        ? undefined
        : (0, requirement_exclusions_1.buildRequirementExclusionHintForVisibleResult)({
            visibleCount: visibleItems.length,
            hiddenMatches: hiddenItems.flatMap((item) => item.excludedFromRequires === undefined
                ? []
                : [item.excludedFromRequires]),
        });
    return {
        kind: exports.SKILL_SEARCH_KIND,
        schemaVersion: exports.SKILL_SEARCH_SCHEMA_VERSION,
        query: input.query,
        targets,
        items: visibleItems,
        errors,
        ...(exclusionHint === undefined ? {} : { exclusionHint }),
    };
}
async function collectSourceErrors(sourceKind, promise) {
    try {
        return { items: await promise, errors: [] };
    }
    catch (error) {
        return {
            items: [],
            errors: [
                {
                    sourceKind,
                    message: error instanceof Error ? error.message : String(error),
                    recoverable: (0, transient_network_error_1.isTransientNetworkError)(error),
                },
            ],
        };
    }
}
async function searchInstalledSkills(input) {
    const authProfiles = input.authProfiles ??
        (await (0, store_1.loadAuthProfiles)(input.homeDirectory)).profiles;
    const result = await (0, skill_inventory_1.listAvailableSkills)({
        repoRoot: input.repoRoot,
        workers: input.targets,
        configuredWorkers: input.configuredWorkers,
        authProfiles,
        env: input.env,
        homeDirectory: input.homeDirectory,
        includeExcluded: true,
    });
    return result.items.map(installedSkillToSearchItem);
}
function installedSkillToSearchItem(item) {
    return {
        id: item.skillId,
        title: item.name,
        description: item.description,
        sourceKind: mapInstalledSourceKind(item),
        targets: [item.worker],
        installed: true,
        installable: false,
        source: (0, source_uri_1.formatFileSourceUri)(item.path),
        topics: [],
        ...(item.excludedFromRequires === undefined
            ? {}
            : { excludedFromRequires: item.excludedFromRequires }),
    };
}
function mapInstalledSourceKind(item) {
    if (item.source === "builtin") {
        return "codex_builtin";
    }
    if (item.source === "plugin") {
        return item.worker === "codex" ? "codex_plugin" : "claude_plugin";
    }
    return "local";
}
async function searchCatalogSkills(input) {
    const catalog = await (0, catalog_1.loadEffectiveSkillCatalog)({ repoRoot: input.repoRoot });
    return catalog.entries.flatMap((entry) => catalogEntryToSearchItems(entry, input.targets));
}
function catalogEntryToSearchItems(entry, targets) {
    return targets.flatMap((target) => {
        const source = (0, catalog_1.resolveSkillCatalogSourceForTarget)(entry, target);
        return source === null
            ? []
            : [
                {
                    id: entry.id,
                    title: entry.id,
                    description: entry.description,
                    sourceKind: entry.origin === "user" ? "user_catalog" : "system_catalog",
                    targets: [target],
                    installed: false,
                    installable: true,
                    source,
                    topics: [...(entry.topics ?? [])],
                    ...(entry.quality?.stars === undefined
                        ? {}
                        : { stars: entry.quality.stars }),
                    ...(entry.quality?.updatedAt === undefined
                        ? {}
                        : { updatedAt: entry.quality.updatedAt }),
                    ...(entry.quality?.createdAt === undefined
                        ? {}
                        : { createdAt: entry.quality.createdAt }),
                    ...(entry.quality?.sourceUrl === undefined
                        ? {}
                        : { sourceUrl: entry.quality.sourceUrl }),
                },
            ];
    });
}
async function searchClaudePlugins(input) {
    const pluginList = await collectClaudePluginCandidates(input);
    return pluginList.map(claudePluginToSearchItem);
}
async function collectClaudePluginCandidates(input) {
    const candidates = [];
    for (const env of collectClaudePluginCommandEnvironments(input)) {
        try {
            const result = await (0, command_1.runCommand)({
                command: "claude",
                args: ["plugin", "list", "--available", "--json"],
                cwd: input.cwd,
                env,
                timeoutMs: 30_000,
            });
            candidates.push(...parseClaudePluginList(result.stdout));
        }
        catch {
            // Fall through to cache and official marketplace sources.
        }
    }
    candidates.push(...(await readClaudeMarketplaceCache(input)));
    return candidates.length > 0
        ? candidates
        : await fetchClaudeOfficialMarketplace();
}
function collectClaudePluginCommandEnvironments(input) {
    const cliProfiles = input.authProfiles?.filter((profile) => profile.kind === "claude-cli") ??
        [];
    if (cliProfiles.length === 0) {
        return [input.env];
    }
    return cliProfiles.map((profile) => (0, claude_cli_1.buildClaudeCliAuthEnvironment)(input.env, profile.claudeConfigPath));
}
function parseClaudePluginList(stdout) {
    const parsed = JSON.parse(stdout);
    return parsed.available ?? [];
}
async function readClaudeMarketplaceCache(input) {
    const entries = [];
    for (const root of collectClaudeConfigRoots(input)) {
        const marketplacesRoot = node_path_1.default.join(root, "plugins", "marketplaces");
        for (const marketplaceDirectoryName of await readDirectoryNames(marketplacesRoot)) {
            const marketplaceJsonPath = node_path_1.default.join(marketplacesRoot, marketplaceDirectoryName, ".claude-plugin", "marketplace.json");
            const parsed = await readClaudeMarketplaceFile(marketplaceJsonPath, marketplaceDirectoryName);
            entries.push(...parsed);
        }
    }
    return entries;
}
async function readClaudeMarketplaceFile(filePath, fallbackMarketplaceName) {
    let parsed;
    try {
        parsed = JSON.parse(await (0, promises_1.readFile)(filePath, "utf8"));
    }
    catch {
        return [];
    }
    const marketplaceName = parsed.name ?? fallbackMarketplaceName;
    return (parsed.plugins ?? []).map((plugin) => ({
        ...(plugin.name === undefined ? {} : { name: plugin.name }),
        ...(plugin.description === undefined
            ? {}
            : { description: plugin.description }),
        marketplaceName,
    }));
}
async function fetchClaudeOfficialMarketplace() {
    const response = await fetch(CLAUDE_OFFICIAL_MARKETPLACE_URL);
    if (!response.ok) {
        throw new Error(`Claude plugin marketplace request failed: HTTP ${response.status}`);
    }
    const parsed = (await response.json());
    const marketplaceName = parsed.name ?? "claude-plugins-official";
    return (parsed.plugins ?? []).map((plugin) => ({
        ...(plugin.name === undefined ? {} : { name: plugin.name }),
        ...(plugin.description === undefined
            ? {}
            : { description: plugin.description }),
        marketplaceName,
    }));
}
function claudePluginToSearchItem(plugin) {
    const pluginName = plugin.name ?? plugin.pluginId?.split("@")[0] ?? "";
    const marketplaceName = plugin.marketplaceName ?? plugin.pluginId?.split("@")[1] ?? "unknown";
    return {
        id: pluginName,
        title: pluginName,
        description: plugin.description ?? null,
        sourceKind: "claude_plugin",
        targets: ["claude"],
        installed: false,
        installable: true,
        source: (0, source_uri_1.formatClaudePluginSourceUri)({
            marketplace: marketplaceName,
            plugin: pluginName,
        }),
        topics: [],
    };
}
async function searchCodexPlugins(input) {
    const result = await collectCodexPluginMarketplaces(input);
    return {
        items: result.marketplaces.flatMap(codexMarketplaceToSearchItems),
        errors: result.errors,
    };
}
async function collectCodexPluginMarketplaces(input) {
    const marketplaces = [];
    const searchErrors = [];
    const errors = [];
    for (const env of collectCodexPluginCommandEnvironments(input)) {
        try {
            const result = await (0, app_server_client_1.listCodexPlugins)({
                cwd: input.cwd,
                env,
                timeoutMs: 3_000,
            });
            marketplaces.push(...result.marketplaces);
            searchErrors.push(...(result.marketplaceLoadErrors ?? []).map((error) => ({
                sourceKind: "codex_plugin",
                message: `${error.marketplacePath}: ${error.message}`,
                recoverable: false,
            })));
        }
        catch (error) {
            errors.push(error);
        }
    }
    if (marketplaces.length === 0 && errors.length > 0) {
        const error = errors[0];
        throw error instanceof Error ? error : new Error(String(error));
    }
    return {
        marketplaces,
        errors: searchErrors,
    };
}
function collectCodexPluginCommandEnvironments(input) {
    const environments = input.authProfiles
        ?.filter((profile) => (0, store_1.getAuthProfileProvider)(profile) === "codex")
        .map((profile) => (0, environment_1.buildAuthProfileEnvironment)(profile, input.env))
        .filter((env) => env !== null) ?? [];
    return environments.length > 0 ? environments : [input.env];
}
function codexMarketplaceToSearchItems(marketplace) {
    return marketplace.plugins
        .filter(isInstallableCodexPlugin)
        .map((plugin) => codexPluginToSearchItem(marketplace, plugin));
}
function isInstallableCodexPlugin(plugin) {
    return (plugin.installPolicy !== "NOT_AVAILABLE" &&
        (plugin.installed !== true || plugin.enabled !== true));
}
function codexPluginToSearchItem(marketplace, plugin) {
    return {
        id: plugin.name,
        title: plugin.interface?.displayName ?? plugin.name,
        description: plugin.interface?.shortDescription ??
            plugin.interface?.longDescription ??
            null,
        sourceKind: "codex_plugin",
        targets: ["codex"],
        installed: false,
        installable: true,
        source: (0, source_uri_1.formatCodexPluginSourceUri)({
            marketplace: marketplace.name,
            plugin: plugin.name,
            marketplacePath: marketplace.path,
        }),
        topics: [],
    };
}
function filterAndSortItems(items, query, topicDescriptions, forcedIds) {
    const forcedIdSet = new Set(forcedIds);
    return [...items]
        .filter((item) => item.id.length > 0 &&
        ((0, search_text_1.searchTextMatches)({ ...item, topicDescriptions }, query) ||
            forcedIdSet.has(item.id)))
        .sort((left, right) => compareSearchItems(left, right, query, topicDescriptions));
}
function applySkillSearchRequirementExclusions(input) {
    return input.items.flatMap((item) => {
        const exclusion = item.excludedFromRequires ??
            (0, requirement_exclusions_1.findRequirementExclusionMatch)(input.exclusions, "skill", {
                id: item.id,
                source: item.source,
            });
        return [
            {
                ...item,
                ...(exclusion === null ? {} : { excludedFromRequires: exclusion }),
            },
        ];
    });
}
function dedupeSearchItems(items) {
    const byKey = new Map();
    for (const item of items) {
        const key = [
            item.id,
            item.source,
            item.sourceKind,
            String(item.installed),
            String(item.installable),
        ].join("\0");
        const existing = byKey.get(key);
        if (existing === undefined) {
            byKey.set(key, item);
            continue;
        }
        byKey.set(key, {
            ...existing,
            targets: [...new Set([...existing.targets, ...item.targets])].sort(),
        });
    }
    return [...byKey.values()];
}
function compareSearchItems(left, right, query, topicDescriptions) {
    const normalizedQuery = (0, search_text_1.normalizeSearchText)(query);
    const leftExact = (0, search_text_1.normalizeSearchText)(left.id) === normalizedQuery ? 0 : 1;
    const rightExact = (0, search_text_1.normalizeSearchText)(right.id) === normalizedQuery ? 0 : 1;
    return (leftExact - rightExact ||
        (0, search_text_1.scoreSearchText)({ ...right, topicDescriptions }, query) -
            (0, search_text_1.scoreSearchText)({ ...left, topicDescriptions }, query) ||
        Number(right.installed) - Number(left.installed) ||
        sourcePriority(left.sourceKind) - sourcePriority(right.sourceKind) ||
        compareQualityMetadata(left, right) ||
        left.id.localeCompare(right.id) ||
        left.source.localeCompare(right.source));
}
function compareQualityMetadata(left, right) {
    return ((right.stars ?? -1) - (left.stars ?? -1) ||
        compareOptionalDateDesc(left.updatedAt, right.updatedAt) ||
        compareOptionalDateAsc(left.createdAt, right.createdAt));
}
function compareOptionalDateDesc(left, right) {
    const leftTimestamp = parseOptionalTimestamp(left);
    const rightTimestamp = parseOptionalTimestamp(right);
    if (leftTimestamp === rightTimestamp) {
        return 0;
    }
    if (leftTimestamp === null) {
        return 1;
    }
    if (rightTimestamp === null) {
        return -1;
    }
    return rightTimestamp - leftTimestamp;
}
function compareOptionalDateAsc(left, right) {
    const leftTimestamp = parseOptionalTimestamp(left);
    const rightTimestamp = parseOptionalTimestamp(right);
    if (leftTimestamp === rightTimestamp) {
        return 0;
    }
    if (leftTimestamp === null) {
        return 1;
    }
    if (rightTimestamp === null) {
        return -1;
    }
    return leftTimestamp - rightTimestamp;
}
function parseOptionalTimestamp(value) {
    if (value === undefined) {
        return null;
    }
    const timestamp = Date.parse(value);
    return Number.isNaN(timestamp) ? null : timestamp;
}
function sourcePriority(sourceKind) {
    switch (sourceKind) {
        case "local":
            return 0;
        case "codex_builtin":
            return 1;
        case "user_catalog":
            return 2;
        case "system_catalog":
            return 3;
        case "codex_plugin":
            return 4;
        case "claude_plugin":
            return 5;
    }
}
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 [];
    }
}
function collectClaudeConfigRoots(input) {
    return [
        ...new Set([
            ...(input.authProfiles
                ?.filter((profile) => profile.kind === "claude-cli")
                .map((profile) => profile.claudeConfigPath) ?? []),
            readEnvValue(input.env, "CLAUDE_CONFIG_DIR"),
            node_path_1.default.join(input.homeDirectory, ".claude"),
        ]
            .map((value) => value?.trim())
            .filter((value) => value !== undefined && value !== "")),
    ];
}
function readEnvValue(env, key) {
    return env[key];
}
