// ---------------------------------------------------------------------------
// Source-aware skill fetcher for vskill update command.
// Routes fetch to the correct origin based on ParsedSource type.
// ---------------------------------------------------------------------------
import { createHash } from "node:crypto";
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { getDefaultBranch } from "../discovery/github-tree.js";
import { getPluginSource, getPluginVersion } from "../marketplace/index.js";
import { getSkill } from "../api/client.js";
import { findCoreSkillsDir, listCoreSkills } from "../core-skills/sync.js";
import { extractFrontmatterVersion } from "../utils/version.js";
function buildAuthHeader() {
    const token = process.env["GITHUB_TOKEN"];
    if (token)
        return { Authorization: `token ${token}` };
    return {};
}
async function fetchRaw(url) {
    try {
        const res = await fetch(url, { headers: buildAuthHeader() });
        if (!res.ok)
            return null;
        return await res.text();
    }
    catch {
        return null;
    }
}
export function normalizeContent(content) {
    return content.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
}
export function computeSha(input) {
    const h = createHash("sha256");
    if (typeof input === "string") {
        h.update(normalizeContent(input));
    }
    else {
        const sorted = Object.keys(input).sort();
        for (const path of sorted) {
            h.update(`${path}\0${normalizeContent(input[path])}\n`);
        }
    }
    return h.digest("hex");
}
// ---------------------------------------------------------------------------
// Registry: delegate to getSkill()
// ---------------------------------------------------------------------------
async function fetchRegistry(skillName, entry) {
    try {
        const remote = await getSkill(skillName);
        if (!remote.content)
            return null;
        const files = { "SKILL.md": remote.content };
        const sha = computeSha(files);
        return {
            content: remote.content,
            version: remote.version || entry.version,
            sha,
            tier: remote.tier || entry.tier,
            files,
        };
    }
    catch {
        return null;
    }
}
// ---------------------------------------------------------------------------
// GitHub flat: fetch SKILL.md from raw.githubusercontent.com
// ---------------------------------------------------------------------------
async function fetchGitHubFlat(owner, repo, skillName, entry) {
    const branch = entry.sourceBranch || await getDefaultBranch(owner, repo);
    const base = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
    // Honor the lockfile-recorded path first when present. Installs that record
    // a non-conventional path (e.g. `plugins/skills/skills/<name>/SKILL.md`)
    // would otherwise 404 on both fallback paths and the update would silently
    // claim "could not fetch update from any source".
    let content = null;
    if (entry.sourceSkillPath) {
        content = await fetchRaw(`${base}/${entry.sourceSkillPath}`);
    }
    // Try skill-specific path next
    if (!content) {
        content = await fetchRaw(`${base}/skills/${skillName}/SKILL.md`);
    }
    // Fallback to root SKILL.md
    if (!content) {
        content = await fetchRaw(`${base}/SKILL.md`);
    }
    if (!content)
        return null;
    const files = { "SKILL.md": content };
    return {
        content,
        version: extractFrontmatterVersion(content) || entry.version,
        sha: computeSha(files),
        tier: entry.tier,
        files,
    };
}
// ---------------------------------------------------------------------------
// GitHub plugin / marketplace: re-fetch via marketplace.json + Trees API
// ---------------------------------------------------------------------------
async function fetchPlugin(owner, repo, pluginName, entry) {
    const branch = entry.sourceBranch || await getDefaultBranch(owner, repo);
    const base = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
    const apiBase = `https://api.github.com/repos/${owner}/${repo}`;
    // 1. Fetch marketplace.json
    const manifestContent = await fetchRaw(`${base}/.claude-plugin/marketplace.json`);
    if (!manifestContent)
        return null;
    // 2. Resolve plugin source path (e.g. "./plugins/frontend")
    const pluginSourcePath = getPluginSource(pluginName, manifestContent);
    const version = getPluginVersion(pluginName, manifestContent) ?? entry.version;
    if (!pluginSourcePath)
        return null;
    // Normalise: "./plugins/frontend" → "plugins/frontend"
    const normalizedPath = pluginSourcePath.replace(/^\.\//, "");
    // 3. Discover all skills under the plugin via Trees API
    let tree = [];
    try {
        const treeRes = await fetch(`${apiBase}/git/trees/${branch}?recursive=1`, { headers: { Accept: "application/vnd.github.v3+json", ...buildAuthHeader() } });
        if (treeRes.ok) {
            const data = (await treeRes.json());
            if (Array.isArray(data?.tree)) {
                tree = data.tree;
            }
        }
    }
    catch {
        return null;
    }
    // 4. Collect paths matching plugins/{name}/skills/*/SKILL.md
    const skillPattern = new RegExp(`^${normalizedPath}/skills/([^/]+)/SKILL\\.md$`);
    const skillPaths = tree
        .filter((e) => e.type === "blob" && skillPattern.test(e.path))
        .map((e) => e.path);
    if (skillPaths.length === 0)
        return null;
    // 5. Fetch all skill files in parallel
    const fetched = await Promise.all(skillPaths.map(async (path) => {
        const content = await fetchRaw(`${base}/${path}`);
        return { path, content };
    }));
    const successful = fetched.filter((f) => f.content !== null);
    if (successful.length === 0)
        return null;
    // 6. Build files map and combine content (sorted by path for deterministic SHA)
    successful.sort((a, b) => a.path.localeCompare(b.path));
    const files = {};
    for (const f of successful) {
        files[f.path] = f.content;
    }
    const combined = successful.map((f) => f.content).join("\n");
    return {
        content: combined,
        version,
        sha: computeSha(files),
        tier: entry.tier,
        files,
    };
}
// ---------------------------------------------------------------------------
// Local: fetch from specweave plugin cache
// ---------------------------------------------------------------------------
function fetchLocal(skillName, entry) {
    const coreDir = findCoreSkillsDir();
    if (!coreDir)
        return null;
    // The lockfile skill name (e.g. "sw") is a plugin-level entry.
    // Find all individual skills under the plugin's skills directory.
    const skillNames = listCoreSkills(coreDir);
    if (skillNames.length === 0)
        return null;
    const files = {};
    for (const name of skillNames) {
        const skillDir = join(coreDir, name);
        const skillMdPath = join(skillDir, "SKILL.md");
        if (!existsSync(skillMdPath))
            continue;
        files[`${name}/SKILL.md`] = readFileSync(skillMdPath, "utf-8");
        // Include agents/*.md if present
        const agentsDir = join(skillDir, "agents");
        if (existsSync(agentsDir) && statSync(agentsDir).isDirectory()) {
            for (const file of readdirSync(agentsDir)) {
                if (file.endsWith(".md")) {
                    files[`${name}/agents/${file}`] = readFileSync(join(agentsDir, file), "utf-8");
                }
            }
        }
    }
    if (Object.keys(files).length === 0)
        return null;
    const sha = computeSha(files);
    const combined = Object.values(files).join("\n");
    return {
        content: combined,
        version: entry.version,
        sha,
        tier: entry.tier,
        files,
    };
}
// ---------------------------------------------------------------------------
// Main dispatch
// ---------------------------------------------------------------------------
export async function fetchFromSource(parsed, skillName, entry) {
    switch (parsed.type) {
        case "registry":
            return fetchRegistry(parsed.skillName, entry);
        case "github":
            return fetchGitHubFlat(parsed.owner, parsed.repo, skillName, entry);
        case "github-plugin":
            return fetchPlugin(parsed.owner, parsed.repo, parsed.pluginName, entry);
        case "marketplace":
            return fetchPlugin(parsed.owner, parsed.repo, parsed.pluginName, entry);
        case "local":
            return fetchLocal(skillName, entry);
        case "unknown":
            return null;
    }
}
//# sourceMappingURL=source-fetcher.js.map