// engine/marketplace-fetcher/anthropic-source.ts — SPEC-681: Fetch skills from Anthropic GitHub repo
// Wraps fetchAnthropicSkillContent from anthropic-adapter.ts and converts to FetchedSkill[].
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { assertEnglishOnlyArtifactText } from '../spec-language/english-only.js';
import { fetchAnthropicSkillContent } from '../skill-registry/anthropic-adapter.js';
import { readCache, writeCache } from './cache.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const DEFAULT_SKILL_NAMES = [];
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Fetch skills from the Anthropic GitHub skills repo.
 * Respects cache TTL unless forceRefresh is set.
 * Returns { fetched, errors } — never throws.
 */
export async function fetchAnthropicSkills(projectPath, options = {}) {
    const { skillNames = DEFAULT_SKILL_NAMES, forceRefresh = false, cacheTtlMs, timeoutMs = 5000, } = options;
    const fetched = [];
    const errors = [];
    // If no specific skill names provided, we can't enumerate all skills here
    // (listing is a separate concern). Return empty for no-names case.
    if (skillNames.length === 0) {
        return { fetched, errors };
    }
    for (const skillName of skillNames) {
        try {
            // Check cache first (unless forceRefresh)
            if (!forceRefresh) {
                const cached = await readCache(projectPath, 'anthropic', skillName, cacheTtlMs);
                if (cached) {
                    const installedPath = await writeSkillFile(projectPath, skillName, cached.content);
                    fetched.push({ skillName, source: 'anthropic', installedPath, created: false });
                    continue;
                }
            }
            // Fetch from remote with timeout
            const content = await fetchWithTimeout(skillName, timeoutMs);
            if (content === null) {
                // null means 404 / not found — per-skill error, not source failure (Scenario 3b)
                errors.push({
                    source: 'anthropic',
                    skillName,
                    message: `not-found: skill "${skillName}" does not exist in Anthropic repository`,
                    kind: 'not-found',
                });
                continue;
            }
            // Validate bundle has skillMd field (Scenario 3d)
            if (!content.skillMd || typeof content.skillMd !== 'string') {
                errors.push({
                    source: 'anthropic',
                    skillName,
                    message: `malformed: response missing skillMd field for "${skillName}"`,
                    kind: 'malformed',
                });
                continue;
            }
            // Write to cache (Scenario 4)
            await writeCache(projectPath, 'anthropic', skillName, content.skillMd);
            // Write to .claude/skills/
            const installedPath = await writeSkillFile(projectPath, skillName, content.skillMd);
            fetched.push({ skillName, source: 'anthropic', installedPath, created: true });
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            const kind = classifyError(message);
            errors.push({
                source: 'anthropic',
                skillName,
                message: `${kind}: ${message}`,
                kind,
            });
        }
    }
    return { fetched, errors };
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Fetch with AbortController timeout. */
async function fetchWithTimeout(skillName, timeoutMs) {
    const controller = new AbortController();
    const timer = setTimeout(() => {
        controller.abort();
    }, timeoutMs);
    try {
        // fetchAnthropicSkillContent uses its own internal fetch — wrap with abort race
        const result = await Promise.race([
            fetchAnthropicSkillContent(skillName),
            new Promise((_, reject) => {
                controller.signal.addEventListener('abort', () => {
                    reject(new Error('timeout: request exceeded timeout'));
                });
            }),
        ]);
        return result;
    }
    finally {
        clearTimeout(timer);
    }
}
/** Write skill content to .claude/skills/{skillName}.md */
async function writeSkillFile(projectPath, skillName, content) {
    const skillsDir = join(projectPath, '.claude', 'skills');
    await mkdir(skillsDir, { recursive: true });
    const filePath = join(skillsDir, `${skillName}.md`);
    assertEnglishOnlyArtifactText(`${skillName}\n\n${content}`, 'skill');
    await writeFile(filePath, content, 'utf-8');
    return filePath;
}
/** Classify an error message into a FetchSourceError kind. */
function classifyError(message) {
    const lower = message.toLowerCase();
    if (lower.includes('timeout') || lower.includes('abort')) {
        return 'timeout';
    }
    if (lower.includes('not-found') || lower.includes('404')) {
        return 'not-found';
    }
    if (lower.includes('malformed')) {
        return 'malformed';
    }
    if (lower.includes('network') || lower.includes('fetch') || lower.includes('econnrefused')) {
        return 'network';
    }
    if (lower.includes('registry-write-failed')) {
        return 'registry-write-failed';
    }
    return 'unknown';
}
//# sourceMappingURL=anthropic-source.js.map