import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { basename, dirname, join, resolve, sep } from "node:path";
import chalk from "chalk";
import { CONFIG_DIR_NAME } from "../config.js";
import { loadThemeFromPath } from "../modes/interactive/theme/theme.js";
import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
import { createEventBus } from "./event-bus.js";
import { clearExtensionCache, createExtensionRuntime, loadExtensionFromFactory, loadExtensionsCached, } from "./extensions/loader.js";
import { findGitPaths } from "./footer-data-provider.js";
import { DefaultPackageManager } from "./package-manager.js";
import { loadPromptTemplates } from "./prompt-templates.js";
import { SettingsManager } from "./settings-manager.js";
import { loadSkills } from "./skills.js";
import { createSourceInfo } from "./source-info.js";
import { resetTimings } from "./timings.js";
function resolvePromptInput(input, description) {
    if (!input) {
        return undefined;
    }
    if (existsSync(input)) {
        try {
            return readFileSync(input, "utf-8");
        }
        catch (error) {
            console.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));
            return input;
        }
    }
    return input;
}
function loadContextFileFromDir(dir) {
    const candidates = ["AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"];
    for (const filename of candidates) {
        const filePath = join(dir, filename);
        if (existsSync(filePath)) {
            try {
                if (!statSync(filePath).isFile()) {
                    continue;
                }
                return {
                    path: filePath,
                    content: readFileSync(filePath, "utf-8"),
                };
            }
            catch (error) {
                console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
            }
        }
    }
    return null;
}
/**
 * The main repo's context file that a nested linked worktree's own copy shadows: both
 * are the same tracked AGENTS.md/CLAUDE.md, so loading both loads it twice. Returns
 * undefined when nothing is shadowed, leaving normal ancestor inheritance alone.
 *
 * Returned canonicalized (realpath), because `git worktree add` writes the `.git`
 * file's `gitdir:` target in realpath form while cwd may still be symlinked
 * (macOS `/tmp` -> `/private/tmp`).
 */
function findShadowedContextFile(cwd) {
    const gitPaths = findGitPaths(cwd);
    if (!gitPaths)
        return undefined;
    const commonGitDir = canonicalizePath(gitPaths.commonGitDir);
    const worktreeRoot = canonicalizePath(gitPaths.repoDir);
    const mainRepoRoot = dirname(commonGitDir);
    // False for an ordinary repo, where the two are the same dir, and for a sibling
    // worktree (`git worktree add ../feat`), whose main repo is not an ancestor.
    if (!worktreeRoot.startsWith(`${mainRepoRoot}${sep}`))
        return undefined;
    // dirname of the common git dir is the main worktree root only when that dir is
    // itself checked out from the same repo. In a bare layout (`proj/.bare` +
    // `proj/main`) it is just the directory holding `.bare`, which tracks nothing; a
    // submodule's gitdir has no `commondir`, so it lands under `.git/modules`.
    if (canonicalizePath(join(mainRepoRoot, ".git")) !== commonGitDir)
        return undefined;
    const worktreeContextFile = loadContextFileFromDir(worktreeRoot);
    return worktreeContextFile ? join(mainRepoRoot, basename(worktreeContextFile.path)) : undefined;
}
export function loadProjectContextFiles(options) {
    const resolvedCwd = resolvePath(options.cwd);
    const resolvedAgentDir = resolvePath(options.agentDir);
    const contextFiles = [];
    const seenPaths = new Set();
    const globalContext = loadContextFileFromDir(resolvedAgentDir);
    if (globalContext) {
        contextFiles.push(globalContext);
        seenPaths.add(globalContext.path);
    }
    const ancestorContextFiles = [];
    const shadowedContextFile = findShadowedContextFile(resolvedCwd);
    let currentDir = resolvedCwd;
    while (true) {
        const contextFile = loadContextFileFromDir(currentDir);
        const isShadowed = shadowedContextFile !== undefined && canonicalizePath(contextFile?.path ?? "") === shadowedContextFile;
        if (contextFile && !isShadowed && !seenPaths.has(contextFile.path)) {
            ancestorContextFiles.unshift(contextFile);
            seenPaths.add(contextFile.path);
        }
        const parentDir = dirname(currentDir);
        if (parentDir === currentDir)
            break;
        currentDir = parentDir;
    }
    contextFiles.push(...ancestorContextFiles);
    return contextFiles;
}
export class DefaultResourceLoader {
    cwd;
    agentDir;
    settingsManager;
    eventBus;
    packageManager;
    additionalExtensionPaths;
    additionalSkillPaths;
    additionalPromptTemplatePaths;
    additionalThemePaths;
    extensionFactories;
    noExtensions;
    noSkills;
    noPromptTemplates;
    noThemes;
    noContextFiles;
    systemPromptSource;
    appendSystemPromptSource;
    extensionsOverride;
    skillsOverride;
    promptsOverride;
    themesOverride;
    agentsFilesOverride;
    systemPromptOverride;
    appendSystemPromptOverride;
    extensionsResult;
    skills;
    skillDiagnostics;
    prompts;
    promptDiagnostics;
    themes;
    themeDiagnostics;
    agentsFiles;
    systemPrompt;
    systemPromptSourcePath;
    appendSystemPrompt;
    appendSystemPromptSourcePaths;
    lastSkillPaths;
    extensionSkillSourceInfos;
    extensionPromptSourceInfos;
    extensionThemeSourceInfos;
    resourceMetadataByPath;
    lastPromptPaths;
    lastThemePaths;
    loaded;
    constructor(options) {
        this.cwd = resolvePath(options.cwd);
        this.agentDir = resolvePath(options.agentDir);
        this.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);
        this.eventBus = options.eventBus ?? createEventBus();
        this.packageManager = new DefaultPackageManager({
            cwd: this.cwd,
            agentDir: this.agentDir,
            settingsManager: this.settingsManager,
        });
        this.additionalExtensionPaths = options.additionalExtensionPaths ?? [];
        this.additionalSkillPaths = options.additionalSkillPaths ?? [];
        this.additionalPromptTemplatePaths = options.additionalPromptTemplatePaths ?? [];
        this.additionalThemePaths = options.additionalThemePaths ?? [];
        this.extensionFactories = options.extensionFactories ?? [];
        this.noExtensions = options.noExtensions ?? false;
        this.noSkills = options.noSkills ?? false;
        this.noPromptTemplates = options.noPromptTemplates ?? false;
        this.noThemes = options.noThemes ?? false;
        this.noContextFiles = options.noContextFiles ?? false;
        this.systemPromptSource = options.systemPrompt;
        this.appendSystemPromptSource = options.appendSystemPrompt;
        this.extensionsOverride = options.extensionsOverride;
        this.skillsOverride = options.skillsOverride;
        this.promptsOverride = options.promptsOverride;
        this.themesOverride = options.themesOverride;
        this.agentsFilesOverride = options.agentsFilesOverride;
        this.systemPromptOverride = options.systemPromptOverride;
        this.appendSystemPromptOverride = options.appendSystemPromptOverride;
        this.extensionsResult = { extensions: [], errors: [], runtime: createExtensionRuntime() };
        this.skills = [];
        this.skillDiagnostics = [];
        this.prompts = [];
        this.promptDiagnostics = [];
        this.themes = [];
        this.themeDiagnostics = [];
        this.agentsFiles = [];
        this.appendSystemPrompt = [];
        this.appendSystemPromptSourcePaths = [];
        this.lastSkillPaths = [];
        this.extensionSkillSourceInfos = new Map();
        this.extensionPromptSourceInfos = new Map();
        this.extensionThemeSourceInfos = new Map();
        this.resourceMetadataByPath = new Map();
        this.lastPromptPaths = [];
        this.lastThemePaths = [];
        this.loaded = false;
    }
    getExtensions() {
        return this.extensionsResult;
    }
    getSkills() {
        return { skills: this.skills, diagnostics: this.skillDiagnostics };
    }
    getPrompts() {
        return { prompts: this.prompts, diagnostics: this.promptDiagnostics };
    }
    getThemes() {
        return { themes: this.themes, diagnostics: this.themeDiagnostics };
    }
    getAgentsFiles() {
        return { agentsFiles: this.agentsFiles };
    }
    getSystemPrompt() {
        return this.systemPrompt;
    }
    getSystemPromptSource() {
        return this.systemPromptSourcePath ? { path: this.systemPromptSourcePath } : undefined;
    }
    getAppendSystemPrompt() {
        return this.appendSystemPrompt;
    }
    getAppendSystemPromptSources() {
        return this.appendSystemPromptSourcePaths.map((path) => ({ path }));
    }
    extendResources(paths) {
        const skillPaths = this.normalizeExtensionPaths(paths.skillPaths ?? []);
        const promptPaths = this.normalizeExtensionPaths(paths.promptPaths ?? []);
        const themePaths = this.normalizeExtensionPaths(paths.themePaths ?? []);
        for (const entry of skillPaths) {
            this.extensionSkillSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));
        }
        for (const entry of promptPaths) {
            this.extensionPromptSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));
        }
        for (const entry of themePaths) {
            this.extensionThemeSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));
        }
        if (skillPaths.length > 0) {
            this.lastSkillPaths = this.mergePaths(this.lastSkillPaths, skillPaths.map((entry) => entry.path));
            this.updateSkillsFromPaths(this.lastSkillPaths, this.resourceMetadataByPath);
        }
        if (promptPaths.length > 0) {
            this.lastPromptPaths = this.mergePaths(this.lastPromptPaths, promptPaths.map((entry) => entry.path));
            this.updatePromptsFromPaths(this.lastPromptPaths, this.resourceMetadataByPath);
        }
        if (themePaths.length > 0) {
            this.lastThemePaths = this.mergePaths(this.lastThemePaths, themePaths.map((entry) => entry.path));
            this.updateThemesFromPaths(this.lastThemePaths, this.resourceMetadataByPath);
        }
    }
    async loadProjectTrustExtensions() {
        // Force untrusted project settings for the bootstrap pass. This keeps project-local
        // extensions/packages out while still loading user/global and temporary CLI extensions.
        this.settingsManager.setProjectTrusted(false);
        await this.settingsManager.reload();
        return this.loadCurrentExtensionSet({ includeInlineFactories: true });
    }
    async reload(options) {
        resetTimings("extensions");
        if (this.loaded) {
            clearExtensionCache();
        }
        let preTrustExtensions;
        if (options?.resolveProjectTrust) {
            preTrustExtensions = await this.loadProjectTrustExtensions();
            const projectTrusted = await options.resolveProjectTrust({ extensionsResult: preTrustExtensions });
            this.settingsManager.setProjectTrusted(projectTrusted);
        }
        // reload() preserves SettingsManager.projectTrusted and reloads settings for that trust state.
        await this.settingsManager.reload();
        const resolvedPaths = await this.packageManager.resolve();
        const cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {
            temporary: true,
        });
        // Kept on the instance so post-reload passes (extendResources) can still resolve package metadata.
        this.resourceMetadataByPath = new Map();
        const metadataByPath = this.resourceMetadataByPath;
        this.extensionSkillSourceInfos = new Map();
        this.extensionPromptSourceInfos = new Map();
        this.extensionThemeSourceInfos = new Map();
        // Helper to extract enabled paths and store metadata
        const getEnabledResources = (resources) => {
            for (const r of resources) {
                if (!metadataByPath.has(r.path)) {
                    metadataByPath.set(r.path, r.metadata);
                }
            }
            return resources.filter((r) => r.enabled);
        };
        const getEnabledPaths = (resources) => getEnabledResources(resources).map((r) => r.path);
        const enabledExtensions = getEnabledPaths(resolvedPaths.extensions);
        const enabledSkillResources = getEnabledResources(resolvedPaths.skills);
        const enabledPrompts = getEnabledPaths(resolvedPaths.prompts);
        const enabledThemes = getEnabledPaths(resolvedPaths.themes);
        const enabledSkills = enabledSkillResources.map((resource) => this.mapSkillPath(resource, metadataByPath));
        // Add CLI paths metadata
        for (const r of cliExtensionPaths.extensions) {
            if (!metadataByPath.has(r.path)) {
                metadataByPath.set(r.path, { source: "cli", scope: "temporary", origin: "top-level" });
            }
        }
        for (const r of cliExtensionPaths.skills) {
            if (!metadataByPath.has(r.path)) {
                metadataByPath.set(r.path, { source: "cli", scope: "temporary", origin: "top-level" });
            }
        }
        const cliEnabledExtensions = getEnabledPaths(cliExtensionPaths.extensions);
        const cliEnabledSkills = getEnabledPaths(cliExtensionPaths.skills);
        const cliEnabledPrompts = getEnabledPaths(cliExtensionPaths.prompts);
        const cliEnabledThemes = getEnabledPaths(cliExtensionPaths.themes);
        const extensionPaths = this.noExtensions
            ? cliEnabledExtensions
            : this.mergePaths(cliEnabledExtensions, enabledExtensions);
        const extensionsResult = await this.loadFinalExtensionSet(extensionPaths, preTrustExtensions);
        for (const p of this.additionalExtensionPaths) {
            if (isLocalPath(p)) {
                const resolved = this.resolveResourcePath(p);
                if (!existsSync(resolved)) {
                    extensionsResult.errors.push({ path: resolved, error: `Extension path does not exist: ${resolved}` });
                }
            }
        }
        this.extensionsResult = this.extensionsOverride ? this.extensionsOverride(extensionsResult) : extensionsResult;
        this.applyExtensionSourceInfo(this.extensionsResult.extensions, metadataByPath);
        const skillPaths = this.noSkills
            ? this.mergePaths(cliEnabledSkills, this.additionalSkillPaths)
            : this.mergePaths([...cliEnabledSkills, ...enabledSkills], this.additionalSkillPaths);
        this.lastSkillPaths = skillPaths;
        this.updateSkillsFromPaths(skillPaths, metadataByPath);
        for (const p of this.additionalSkillPaths) {
            if (isLocalPath(p)) {
                const resolved = this.resolveResourcePath(p);
                if (!existsSync(resolved) && !this.skillDiagnostics.some((d) => d.path === resolved)) {
                    this.skillDiagnostics.push({ type: "error", message: "Skill path does not exist", path: resolved });
                }
            }
        }
        const promptPaths = this.noPromptTemplates
            ? this.mergePaths(cliEnabledPrompts, this.additionalPromptTemplatePaths)
            : this.mergePaths([...cliEnabledPrompts, ...enabledPrompts], this.additionalPromptTemplatePaths);
        this.lastPromptPaths = promptPaths;
        this.updatePromptsFromPaths(promptPaths, metadataByPath);
        for (const p of this.additionalPromptTemplatePaths) {
            if (isLocalPath(p)) {
                const resolved = this.resolveResourcePath(p);
                if (!existsSync(resolved) && !this.promptDiagnostics.some((d) => d.path === resolved)) {
                    this.promptDiagnostics.push({
                        type: "error",
                        message: "Prompt template path does not exist",
                        path: resolved,
                    });
                }
            }
        }
        const themePaths = this.noThemes
            ? this.mergePaths(cliEnabledThemes, this.additionalThemePaths)
            : this.mergePaths([...cliEnabledThemes, ...enabledThemes], this.additionalThemePaths);
        this.lastThemePaths = themePaths;
        this.updateThemesFromPaths(themePaths, metadataByPath);
        for (const p of this.additionalThemePaths) {
            const resolved = this.resolveResourcePath(p);
            if (!existsSync(resolved) && !this.themeDiagnostics.some((d) => d.path === resolved)) {
                this.themeDiagnostics.push({ type: "error", message: "Theme path does not exist", path: resolved });
            }
        }
        const agentsFiles = {
            agentsFiles: this.noContextFiles
                ? []
                : loadProjectContextFiles({
                    cwd: this.cwd,
                    agentDir: this.agentDir,
                }),
        };
        const resolvedAgentsFiles = this.agentsFilesOverride ? this.agentsFilesOverride(agentsFiles) : agentsFiles;
        this.agentsFiles = resolvedAgentsFiles.agentsFiles;
        const systemPromptSource = this.systemPromptSource ?? this.discoverSystemPromptFile();
        const baseSystemPrompt = resolvePromptInput(systemPromptSource, "system prompt");
        this.systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(baseSystemPrompt) : baseSystemPrompt;
        this.systemPromptSourcePath =
            systemPromptSource && existsSync(systemPromptSource) ? resolvePath(systemPromptSource) : undefined;
        let appendSources = this.appendSystemPromptSource;
        if (!appendSources) {
            const discoveredAppendSystemPromptFile = this.discoverAppendSystemPromptFile();
            appendSources = discoveredAppendSystemPromptFile ? [discoveredAppendSystemPromptFile] : [];
        }
        const baseAppend = appendSources
            .map((s) => resolvePromptInput(s, "append system prompt"))
            .filter((s) => s !== undefined);
        this.appendSystemPrompt = this.appendSystemPromptOverride
            ? this.appendSystemPromptOverride(baseAppend)
            : baseAppend;
        this.appendSystemPromptSourcePaths = appendSources
            .filter((source) => existsSync(source))
            .map((source) => resolvePath(source));
        this.loaded = true;
    }
    async loadCurrentExtensionSet(options) {
        const resolvedPaths = await this.packageManager.resolve();
        const cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {
            temporary: true,
        });
        const enabledExtensions = resolvedPaths.extensions.filter((r) => r.enabled).map((r) => r.path);
        const cliEnabledExtensions = cliExtensionPaths.extensions.filter((r) => r.enabled).map((r) => r.path);
        const extensionPaths = this.noExtensions
            ? cliEnabledExtensions
            : this.mergePaths(cliEnabledExtensions, enabledExtensions);
        const extensionsResult = await loadExtensionsCached(extensionPaths, this.cwd, this.eventBus);
        if (!options.includeInlineFactories) {
            return extensionsResult;
        }
        const inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);
        extensionsResult.extensions.push(...inlineExtensions.extensions);
        extensionsResult.errors.push(...inlineExtensions.errors);
        return extensionsResult;
    }
    resolveExtensionLoadPath(path) {
        return resolvePath(path, this.cwd, { normalizeUnicodeSpaces: true });
    }
    async loadFinalExtensionSet(extensionPaths, preTrustExtensions) {
        if (!preTrustExtensions) {
            const extensionsResult = await loadExtensionsCached(extensionPaths, this.cwd, this.eventBus);
            const inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);
            extensionsResult.extensions.push(...inlineExtensions.extensions);
            extensionsResult.errors.push(...inlineExtensions.errors);
            this.addExtensionConflictDiagnostics(extensionsResult);
            return extensionsResult;
        }
        const preloadedByPath = new Map(preTrustExtensions.extensions
            .filter((extension) => !extension.path.startsWith("<inline:"))
            .map((extension) => [extension.resolvedPath, extension]));
        const failedPreloadPaths = new Set(preTrustExtensions.errors.map((error) => this.resolveExtensionLoadPath(error.path)));
        const remainingPaths = extensionPaths.filter((path) => {
            const resolvedPath = this.resolveExtensionLoadPath(path);
            return !preloadedByPath.has(resolvedPath) && !failedPreloadPaths.has(resolvedPath);
        });
        const remainingExtensions = await loadExtensionsCached(remainingPaths, this.cwd, this.eventBus, preTrustExtensions.runtime);
        const loadedByPath = new Map(preloadedByPath);
        for (const extension of remainingExtensions.extensions) {
            loadedByPath.set(extension.resolvedPath, extension);
        }
        const inlineExtensions = preTrustExtensions.extensions.filter((extension) => extension.path.startsWith("<inline:"));
        const orderedExtensions = extensionPaths
            .map((path) => loadedByPath.get(this.resolveExtensionLoadPath(path)))
            .filter((extension) => extension !== undefined);
        orderedExtensions.push(...inlineExtensions);
        const extensionsResult = {
            extensions: orderedExtensions,
            errors: [...preTrustExtensions.errors, ...remainingExtensions.errors],
            runtime: preTrustExtensions.runtime,
        };
        this.addExtensionConflictDiagnostics(extensionsResult);
        return extensionsResult;
    }
    addExtensionConflictDiagnostics(extensionsResult) {
        // Detect extension conflicts (tools, commands, flags with same names from different extensions)
        // Keep all extensions loaded. Conflicts are reported as diagnostics, and precedence is handled by load order.
        const conflicts = this.detectExtensionConflicts(extensionsResult.extensions);
        for (const conflict of conflicts) {
            extensionsResult.errors.push({ path: conflict.path, error: conflict.message });
        }
    }
    mapSkillPath(resource, metadataByPath) {
        if (resource.metadata.source !== "auto" && resource.metadata.origin !== "package") {
            return resource.path;
        }
        try {
            const stats = statSync(resource.path);
            if (!stats.isDirectory()) {
                return resource.path;
            }
        }
        catch {
            return resource.path;
        }
        const skillFile = join(resource.path, "SKILL.md");
        if (existsSync(skillFile)) {
            if (!metadataByPath.has(skillFile)) {
                metadataByPath.set(skillFile, resource.metadata);
            }
            return skillFile;
        }
        return resource.path;
    }
    normalizeExtensionPaths(entries) {
        return entries.map((entry) => {
            const metadata = entry.metadata.baseDir
                ? { ...entry.metadata, baseDir: this.resolveResourcePath(entry.metadata.baseDir) }
                : entry.metadata;
            return {
                path: this.resolveResourcePath(entry.path),
                metadata,
            };
        });
    }
    updateSkillsFromPaths(skillPaths, metadataByPath) {
        let skillsResult;
        if (this.noSkills && skillPaths.length === 0) {
            skillsResult = { skills: [], diagnostics: [] };
        }
        else {
            skillsResult = loadSkills({
                cwd: this.cwd,
                agentDir: this.agentDir,
                skillPaths,
                includeDefaults: false,
            });
        }
        const resolvedSkills = this.skillsOverride ? this.skillsOverride(skillsResult) : skillsResult;
        this.skills = resolvedSkills.skills.map((skill) => ({
            ...skill,
            sourceInfo: this.findSourceInfoForPath(skill.filePath, this.extensionSkillSourceInfos, metadataByPath) ??
                skill.sourceInfo ??
                this.getDefaultSourceInfoForPath(skill.filePath),
        }));
        this.skillDiagnostics = resolvedSkills.diagnostics;
    }
    updatePromptsFromPaths(promptPaths, metadataByPath) {
        let promptsResult;
        if (this.noPromptTemplates && promptPaths.length === 0) {
            promptsResult = { prompts: [], diagnostics: [] };
        }
        else {
            const allPrompts = loadPromptTemplates({
                cwd: this.cwd,
                agentDir: this.agentDir,
                promptPaths,
                includeDefaults: false,
            });
            promptsResult = this.dedupePrompts(allPrompts);
        }
        const resolvedPrompts = this.promptsOverride ? this.promptsOverride(promptsResult) : promptsResult;
        this.prompts = resolvedPrompts.prompts.map((prompt) => ({
            ...prompt,
            sourceInfo: this.findSourceInfoForPath(prompt.filePath, this.extensionPromptSourceInfos, metadataByPath) ??
                prompt.sourceInfo ??
                this.getDefaultSourceInfoForPath(prompt.filePath),
        }));
        this.promptDiagnostics = resolvedPrompts.diagnostics;
    }
    updateThemesFromPaths(themePaths, metadataByPath) {
        let themesResult;
        if (this.noThemes && themePaths.length === 0) {
            themesResult = { themes: [], diagnostics: [] };
        }
        else {
            const loaded = this.loadThemes(themePaths, false);
            const deduped = this.dedupeThemes(loaded.themes);
            themesResult = { themes: deduped.themes, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };
        }
        const resolvedThemes = this.themesOverride ? this.themesOverride(themesResult) : themesResult;
        this.themes = resolvedThemes.themes.map((theme) => {
            const sourcePath = theme.sourcePath;
            theme.sourceInfo = sourcePath
                ? (this.findSourceInfoForPath(sourcePath, this.extensionThemeSourceInfos, metadataByPath) ??
                    theme.sourceInfo ??
                    this.getDefaultSourceInfoForPath(sourcePath))
                : theme.sourceInfo;
            return theme;
        });
        this.themeDiagnostics = resolvedThemes.diagnostics;
    }
    applyExtensionSourceInfo(extensions, metadataByPath) {
        for (const extension of extensions) {
            extension.sourceInfo =
                this.findSourceInfoForPath(extension.path, undefined, metadataByPath) ??
                    this.getDefaultSourceInfoForPath(extension.path);
            for (const command of extension.commands.values()) {
                command.sourceInfo = extension.sourceInfo;
            }
            for (const tool of extension.tools.values()) {
                tool.sourceInfo = extension.sourceInfo;
            }
        }
    }
    findSourceInfoForPath(resourcePath, extraSourceInfos, metadataByPath) {
        if (!resourcePath) {
            return undefined;
        }
        if (resourcePath.startsWith("<")) {
            return this.getDefaultSourceInfoForPath(resourcePath);
        }
        const normalizedResourcePath = resolve(resourcePath);
        if (extraSourceInfos) {
            for (const [sourcePath, sourceInfo] of extraSourceInfos.entries()) {
                const normalizedSourcePath = resolve(sourcePath);
                if (normalizedResourcePath === normalizedSourcePath ||
                    normalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)) {
                    return { ...sourceInfo, path: resourcePath };
                }
            }
        }
        if (metadataByPath) {
            const exact = metadataByPath.get(normalizedResourcePath) ?? metadataByPath.get(resourcePath);
            if (exact) {
                return createSourceInfo(resourcePath, exact);
            }
            for (const [sourcePath, metadata] of metadataByPath.entries()) {
                const normalizedSourcePath = resolve(sourcePath);
                if (normalizedResourcePath === normalizedSourcePath ||
                    normalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)) {
                    return createSourceInfo(resourcePath, metadata);
                }
            }
        }
        return undefined;
    }
    getDefaultSourceInfoForPath(filePath) {
        if (filePath.startsWith("<") && filePath.endsWith(">")) {
            return {
                path: filePath,
                source: filePath.slice(1, -1).split(":")[0] || "temporary",
                scope: "temporary",
                origin: "top-level",
            };
        }
        const normalizedPath = resolve(filePath);
        const agentRoots = [
            join(this.agentDir, "skills"),
            join(this.agentDir, "prompts"),
            join(this.agentDir, "themes"),
            join(this.agentDir, "extensions"),
        ];
        const projectRoots = [
            join(this.cwd, CONFIG_DIR_NAME, "skills"),
            join(this.cwd, CONFIG_DIR_NAME, "prompts"),
            join(this.cwd, CONFIG_DIR_NAME, "themes"),
            join(this.cwd, CONFIG_DIR_NAME, "extensions"),
        ];
        for (const root of agentRoots) {
            if (this.isUnderPath(normalizedPath, root)) {
                return { path: filePath, source: "local", scope: "user", origin: "top-level", baseDir: root };
            }
        }
        for (const root of projectRoots) {
            if (this.isUnderPath(normalizedPath, root)) {
                return { path: filePath, source: "local", scope: "project", origin: "top-level", baseDir: root };
            }
        }
        return {
            path: filePath,
            source: "local",
            scope: "temporary",
            origin: "top-level",
            baseDir: statSync(normalizedPath).isDirectory() ? normalizedPath : resolve(normalizedPath, ".."),
        };
    }
    mergePaths(primary, additional) {
        const merged = [];
        const seen = new Set();
        for (const p of [...primary, ...additional]) {
            const resolved = this.resolveResourcePath(p);
            const canonicalPath = canonicalizePath(resolved);
            if (seen.has(canonicalPath))
                continue;
            seen.add(canonicalPath);
            merged.push(resolved);
        }
        return merged;
    }
    resolveResourcePath(p) {
        return resolvePath(p, this.cwd, { trim: true });
    }
    loadThemes(paths, includeDefaults = true) {
        const themes = [];
        const diagnostics = [];
        if (includeDefaults) {
            const defaultDirs = [join(this.agentDir, "themes"), join(this.cwd, CONFIG_DIR_NAME, "themes")];
            for (const dir of defaultDirs) {
                this.loadThemesFromDir(dir, themes, diagnostics);
            }
        }
        for (const p of paths) {
            const resolved = this.resolveResourcePath(p);
            if (!existsSync(resolved)) {
                diagnostics.push({ type: "warning", message: "theme path does not exist", path: resolved });
                continue;
            }
            try {
                const stats = statSync(resolved);
                if (stats.isDirectory()) {
                    this.loadThemesFromDir(resolved, themes, diagnostics);
                }
                else if (stats.isFile() && resolved.endsWith(".json")) {
                    this.loadThemeFromFile(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 };
    }
    loadThemesFromDir(dir, themes, diagnostics) {
        if (!existsSync(dir)) {
            return;
        }
        try {
            const entries = readdirSync(dir, { withFileTypes: true });
            for (const entry of entries) {
                let isFile = entry.isFile();
                if (entry.isSymbolicLink()) {
                    try {
                        isFile = statSync(join(dir, entry.name)).isFile();
                    }
                    catch {
                        continue;
                    }
                }
                if (!isFile) {
                    continue;
                }
                if (!entry.name.endsWith(".json")) {
                    continue;
                }
                this.loadThemeFromFile(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 });
        }
    }
    loadThemeFromFile(filePath, themes, diagnostics) {
        try {
            themes.push(loadThemeFromPath(filePath));
        }
        catch (error) {
            const message = error instanceof Error ? error.message : "failed to load theme";
            diagnostics.push({ type: "warning", message, path: filePath });
        }
    }
    async loadExtensionFactories(runtime) {
        const extensions = [];
        const errors = [];
        for (const [index, input] of this.extensionFactories.entries()) {
            const isNamed = typeof input !== "function";
            const factory = isNamed ? input.factory : input;
            const extensionPath = `<inline:${isNamed ? input.name : index + 1}>`;
            try {
                const extension = await loadExtensionFromFactory(factory, this.cwd, this.eventBus, runtime, extensionPath);
                extension.hidden = isNamed && input.hidden;
                extensions.push(extension);
            }
            catch (error) {
                const message = error instanceof Error ? error.message : "failed to load extension";
                errors.push({ path: extensionPath, error: message });
            }
        }
        return { extensions, errors };
    }
    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 };
    }
    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 };
    }
    discoverSystemPromptFile() {
        const projectPath = join(this.cwd, CONFIG_DIR_NAME, "SYSTEM.md");
        if (this.settingsManager.isProjectTrusted() && existsSync(projectPath)) {
            return projectPath;
        }
        const globalPath = join(this.agentDir, "SYSTEM.md");
        if (existsSync(globalPath)) {
            return globalPath;
        }
        return undefined;
    }
    discoverAppendSystemPromptFile() {
        const projectPath = join(this.cwd, CONFIG_DIR_NAME, "APPEND_SYSTEM.md");
        if (this.settingsManager.isProjectTrusted() && existsSync(projectPath)) {
            return projectPath;
        }
        const globalPath = join(this.agentDir, "APPEND_SYSTEM.md");
        if (existsSync(globalPath)) {
            return globalPath;
        }
        return undefined;
    }
    isUnderPath(target, root) {
        const normalizedRoot = resolve(root);
        if (target === normalizedRoot) {
            return true;
        }
        const prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;
        return target.startsWith(prefix);
    }
    detectExtensionConflicts(extensions) {
        const conflicts = [];
        // Track which extension registered each tool and flag
        const toolOwners = new Map();
        const flagOwners = new Map();
        for (const ext of extensions) {
            // Check tools
            for (const toolName of ext.tools.keys()) {
                const existingOwner = toolOwners.get(toolName);
                if (existingOwner && existingOwner !== ext.path) {
                    conflicts.push({
                        path: ext.path,
                        message: `Tool "${toolName}" conflicts with ${existingOwner}`,
                    });
                }
                else {
                    toolOwners.set(toolName, ext.path);
                }
            }
            // Check flags
            for (const flagName of ext.flags.keys()) {
                const existingOwner = flagOwners.get(flagName);
                if (existingOwner && existingOwner !== ext.path) {
                    conflicts.push({
                        path: ext.path,
                        message: `Flag "--${flagName}" conflicts with ${existingOwner}`,
                    });
                }
                else {
                    flagOwners.set(flagName, ext.path);
                }
            }
        }
        return conflicts;
    }
}
//# sourceMappingURL=resource-loader.js.map