import { access, readdir, readFile, stat } from "node:fs/promises";
import { join } from "node:path";
import { getProjectConfigDirs } from "../config.js";
import { loadThemeFromContent } from "../modes/interactive/theme/theme.js";
import { yieldToEventLoopIfSlow } from "../utils/event-loop.js";
import { loadPromptTemplatesAsync } from "./prompt-templates-async.js";
import { loadSkillsAsync } from "./skills-async.js";
import { resourceInternals } from "./resource-loader-internals.js";
import { getLoaderAgentDirs, resolveResourcePath } from "./resource-loader-paths.js";
import { findSourceInfoForPath, getDefaultSourceInfoForPath } from "./resource-loader-source-info.js";
const RESOURCE_LOAD_YIELD_AFTER_MS = 8;
async function existsAsync(path) {
    try {
        await access(path);
        return true;
    }
    catch {
        return false;
    }
}
function applySkillsResult(loader, skillsResult, metadataByPath) {
    const state = resourceInternals(loader);
    const resolvedSkills = state.skillsOverride ? state.skillsOverride(skillsResult) : skillsResult;
    state.skills = resolvedSkills.skills.map((skill) => ({
        ...skill,
        sourceInfo: findSourceInfoForPath(loader, skill.filePath, state.extensionSkillSourceInfos, metadataByPath) ??
            skill.sourceInfo ??
            getDefaultSourceInfoForPath(loader, skill.filePath),
    }));
    state.skillDiagnostics = resolvedSkills.diagnostics;
}
export async function updateSkillsFromPathsAsync(loader, skillPaths, metadataByPath) {
    const state = resourceInternals(loader);
    const skillsResult = state.noSkills && skillPaths.length === 0
        ? { skills: [], diagnostics: [] }
        : await loadSkillsAsync({ cwd: state.cwd, agentDir: state.agentDir, skillPaths, includeDefaults: false });
    applySkillsResult(loader, skillsResult, metadataByPath);
}
function applyPromptsResult(loader, promptsResult, metadataByPath) {
    const state = resourceInternals(loader);
    const resolvedPrompts = state.promptsOverride ? state.promptsOverride(promptsResult) : promptsResult;
    state.prompts = resolvedPrompts.prompts.map((prompt) => ({
        ...prompt,
        sourceInfo: findSourceInfoForPath(loader, prompt.filePath, state.extensionPromptSourceInfos, metadataByPath) ??
            prompt.sourceInfo ??
            getDefaultSourceInfoForPath(loader, prompt.filePath),
    }));
    state.promptDiagnostics = resolvedPrompts.diagnostics;
}
export async function updatePromptsFromPathsAsync(loader, promptPaths, metadataByPath) {
    const state = resourceInternals(loader);
    const promptsResult = state.noPromptTemplates && promptPaths.length === 0
        ? { prompts: [], diagnostics: [] }
        : dedupePrompts(await loadPromptTemplatesAsync({ cwd: state.cwd, agentDir: state.agentDir, promptPaths, includeDefaults: false }));
    applyPromptsResult(loader, promptsResult, metadataByPath);
}
function applyThemesResult(loader, themesResult, metadataByPath) {
    const state = resourceInternals(loader);
    const resolvedThemes = state.themesOverride ? state.themesOverride(themesResult) : themesResult;
    state.themes = resolvedThemes.themes.map((theme) => {
        const sourcePath = theme.sourcePath;
        theme.sourceInfo = sourcePath
            ? (findSourceInfoForPath(loader, sourcePath, state.extensionThemeSourceInfos, metadataByPath) ??
                theme.sourceInfo ??
                getDefaultSourceInfoForPath(loader, sourcePath))
            : theme.sourceInfo;
        return theme;
    });
    state.themeDiagnostics = resolvedThemes.diagnostics;
}
export async function updateThemesFromPathsAsync(loader, themePaths, metadataByPath) {
    const state = resourceInternals(loader);
    const themesResult = state.noThemes && themePaths.length === 0
        ? { themes: [], diagnostics: [] }
        : loadAndDedupeThemes(await loadThemesAsync(loader, themePaths, false));
    applyThemesResult(loader, themesResult, metadataByPath);
}
function loadAndDedupeThemes(loaded) {
    const deduped = dedupeThemes(loaded.themes);
    return { themes: deduped.themes, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };
}
async function loadThemesAsync(loader, paths, includeDefaults = true) {
    const state = resourceInternals(loader);
    const themes = [];
    const diagnostics = [];
    if (includeDefaults) {
        for (const dir of [...getLoaderAgentDirs(state.agentDir).map((agentDir) => join(agentDir, "themes")), ...getProjectThemeDirs(state.cwd)]) {
            await loadThemesFromDirAsync(dir, themes, diagnostics);
        }
    }
    const startedAt = Date.now();
    for (const p of paths) {
        await yieldToEventLoopIfSlow(startedAt, RESOURCE_LOAD_YIELD_AFTER_MS);
        const resolved = resolveResourcePath(state.cwd, p);
        if (!(await existsAsync(resolved))) {
            diagnostics.push({ type: "warning", message: "theme path does not exist", path: resolved });
            continue;
        }
        try {
            const stats = await stat(resolved);
            if (stats.isDirectory())
                await loadThemesFromDirAsync(resolved, themes, diagnostics);
            else if (stats.isFile() && resolved.endsWith(".json"))
                await loadThemeFromFileAsync(resolved, themes, diagnostics);
            else
                diagnostics.push({ type: "warning", message: "theme path is not a json file", path: resolved });
        }
        catch (error) {
            const message = error instanceof Error ? error.message : "failed to read theme path";
            diagnostics.push({ type: "warning", message, path: resolved });
        }
    }
    return { themes, diagnostics };
}
function getProjectThemeDirs(cwd) {
    return getProjectConfigDirs(cwd).map((configDir) => join(configDir, "themes"));
}
async function loadThemesFromDirAsync(dir, themes, diagnostics) {
    if (!(await existsAsync(dir)))
        return;
    const startedAt = Date.now();
    try {
        const entries = await readdir(dir, { withFileTypes: true });
        for (const entry of entries) {
            await yieldToEventLoopIfSlow(startedAt, RESOURCE_LOAD_YIELD_AFTER_MS);
            let isFile = entry.isFile();
            if (entry.isSymbolicLink()) {
                try {
                    isFile = (await stat(join(dir, entry.name))).isFile();
                }
                catch {
                    continue;
                }
            }
            if (isFile && entry.name.endsWith(".json"))
                await loadThemeFromFileAsync(join(dir, entry.name), themes, diagnostics);
        }
    }
    catch (error) {
        const message = error instanceof Error ? error.message : "failed to read theme directory";
        diagnostics.push({ type: "warning", message, path: dir });
    }
}
async function loadThemeFromFileAsync(filePath, themes, diagnostics) {
    try {
        themes.push(loadThemeFromContent(filePath, await readFile(filePath, "utf-8")));
    }
    catch (error) {
        const message = error instanceof Error ? error.message : "failed to load theme";
        diagnostics.push({ type: "warning", message, path: filePath });
    }
}
function dedupePrompts(prompts) {
    const seen = new Map();
    const diagnostics = [];
    for (const prompt of prompts) {
        const existing = seen.get(prompt.name);
        if (existing) {
            diagnostics.push({
                type: "collision",
                message: `name "/${prompt.name}" collision`,
                path: prompt.filePath,
                collision: { resourceType: "prompt", name: prompt.name, winnerPath: existing.filePath, loserPath: prompt.filePath },
            });
        }
        else {
            seen.set(prompt.name, prompt);
        }
    }
    return { prompts: Array.from(seen.values()), diagnostics };
}
function dedupeThemes(themes) {
    const seen = new Map();
    const diagnostics = [];
    for (const t of themes) {
        const name = t.name ?? "unnamed";
        const existing = seen.get(name);
        if (existing) {
            diagnostics.push({
                type: "collision",
                message: `name "${name}" collision`,
                path: t.sourcePath,
                collision: { resourceType: "theme", name, winnerPath: existing.sourcePath ?? "<builtin>", loserPath: t.sourcePath ?? "<builtin>" },
            });
        }
        else {
            seen.set(name, t);
        }
    }
    return { themes: Array.from(seen.values()), diagnostics };
}
//# sourceMappingURL=resource-loader-assets.js.map