import chalk from 'chalk';
import { existsSync, readFileSync, statSync } from 'node:fs';
import { homedir } from 'node:os';
import { join, resolve, sep } from 'node:path';
import { CONFIG_DIR_NAME, getAgentDir } from '../../config.js';
import { createEventBus, createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from '../extensions/index.js';
import { SettingsManager } from '../settings-manager.js';
import { DefaultPackageManager } from './package-manager.js';
import { loadPromptTemplates } from './prompt-templates.js';
import { loadSkills } from './skills.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', 'CLAUDE.md'];
    for (const filename of candidates) {
        const filePath = join(dir, filename);
        if (existsSync(filePath)) {
            try {
                return {
                    path: filePath,
                    content: readFileSync(filePath, 'utf-8')
                };
            }
            catch (error) {
                console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
            }
        }
    }
    return null;
}
function loadProjectContextFiles(options = {}) {
    const resolvedCwd = options.cwd ?? process.cwd();
    const resolvedAgentDir = options.agentDir ?? getAgentDir();
    const contextFiles = [];
    const seenPaths = new Set();
    const globalContext = loadContextFileFromDir(resolvedAgentDir);
    if (globalContext) {
        contextFiles.push(globalContext);
        seenPaths.add(globalContext.path);
    }
    const projectContext = loadContextFileFromDir(resolvedCwd);
    if (projectContext && !seenPaths.has(projectContext.path)) {
        contextFiles.push(projectContext);
    }
    return contextFiles;
}
export class DefaultResourceLoader {
    cwd;
    agentDir;
    settingsManager;
    eventBus;
    packageManager;
    additionalExtensionPaths;
    additionalSkillPaths;
    additionalPromptTemplatePaths;
    extensionFactories;
    noExtensions;
    noSkills;
    noPromptTemplates;
    systemPromptSource;
    extensionsOverride;
    skillsOverride;
    promptsOverride;
    agentsFilesOverride;
    extensionsResult;
    skills;
    skillDiagnostics;
    prompts;
    promptDiagnostics;
    agentsFiles;
    systemPrompt;
    pathMetadata;
    lastSkillPaths;
    lastPromptPaths;
    constructor(options) {
        this.cwd = options.cwd ?? process.cwd();
        this.agentDir = options.agentDir ?? getAgentDir();
        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.extensionFactories = options.extensionFactories ?? [];
        this.noExtensions = options.noExtensions ?? false;
        this.noSkills = options.noSkills ?? false;
        this.noPromptTemplates = options.noPromptTemplates ?? false;
        this.systemPromptSource = options.systemPrompt;
        this.extensionsOverride = options.extensionsOverride;
        this.skillsOverride = options.skillsOverride;
        this.promptsOverride = options.promptsOverride;
        this.agentsFilesOverride = options.agentsFilesOverride;
        this.extensionsResult = { extensions: [], errors: [], runtime: createExtensionRuntime() };
        this.skills = [];
        this.skillDiagnostics = [];
        this.prompts = [];
        this.promptDiagnostics = [];
        this.agentsFiles = [];
        this.pathMetadata = new Map();
        this.lastSkillPaths = [];
        this.lastPromptPaths = [];
    }
    getExtensions() {
        return this.extensionsResult;
    }
    getSkills() {
        return { skills: this.skills, diagnostics: this.skillDiagnostics };
    }
    getPrompts() {
        return { prompts: this.prompts, diagnostics: this.promptDiagnostics };
    }
    getThemes() {
        return { themes: [], diagnostics: [] };
    }
    getAgentsFiles() {
        return { agentsFiles: this.agentsFiles };
    }
    getSystemPrompt() {
        return this.systemPrompt;
    }
    getPathMetadata() {
        return this.pathMetadata;
    }
    extendResources(paths) {
        const skillPaths = this.normalizeExtensionPaths(paths.skillPaths ?? []);
        const promptPaths = this.normalizeExtensionPaths(paths.promptPaths ?? []);
        if (skillPaths.length > 0) {
            this.lastSkillPaths = this.mergePaths(this.lastSkillPaths, skillPaths.map((entry) => entry.path));
            this.updateSkillsFromPaths(this.lastSkillPaths, skillPaths);
        }
        if (promptPaths.length > 0) {
            this.lastPromptPaths = this.mergePaths(this.lastPromptPaths, promptPaths.map((entry) => entry.path));
            this.updatePromptsFromPaths(this.lastPromptPaths, promptPaths);
        }
    }
    async reload() {
        const resolvedPaths = await this.packageManager.resolve();
        const cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {
            temporary: true
        });
        // Helper to extract enabled paths and store metadata
        const getEnabledResources = (resources) => {
            for (const r of resources) {
                if (!this.pathMetadata.has(r.path)) {
                    this.pathMetadata.set(r.path, r.metadata);
                }
            }
            return resources.filter((r) => r.enabled);
        };
        const getEnabledPaths = (resources) => getEnabledResources(resources).map((r) => r.path);
        // Store metadata and get enabled paths
        this.pathMetadata = new Map();
        const enabledExtensions = getEnabledPaths(resolvedPaths.extensions);
        const enabledSkillResources = getEnabledResources(resolvedPaths.skills);
        const enabledPrompts = getEnabledPaths(resolvedPaths.prompts);
        const mapSkillPath = (resource) => {
            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 (!this.pathMetadata.has(skillFile)) {
                    this.pathMetadata.set(skillFile, resource.metadata);
                }
                return skillFile;
            }
            return resource.path;
        };
        const enabledSkills = enabledSkillResources.map(mapSkillPath);
        // Add CLI paths metadata
        for (const r of cliExtensionPaths.extensions) {
            if (!this.pathMetadata.has(r.path)) {
                this.pathMetadata.set(r.path, { source: 'cli', scope: 'temporary', origin: 'top-level' });
            }
        }
        for (const r of cliExtensionPaths.skills) {
            if (!this.pathMetadata.has(r.path)) {
                this.pathMetadata.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 extensionPaths = this.noExtensions
            ? cliEnabledExtensions
            : this.mergePaths(enabledExtensions, cliEnabledExtensions);
        const extensionsResult = await loadExtensions(extensionPaths, this.cwd, this.eventBus);
        const inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);
        extensionsResult.extensions.push(...inlineExtensions.extensions);
        extensionsResult.errors.push(...inlineExtensions.errors);
        // 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 });
        }
        this.extensionsResult = this.extensionsOverride
            ? this.extensionsOverride(extensionsResult)
            : extensionsResult;
        const skillPaths = this.noSkills
            ? this.mergePaths(cliEnabledSkills, this.additionalSkillPaths)
            : this.mergePaths([...enabledSkills, ...cliEnabledSkills], this.additionalSkillPaths);
        this.lastSkillPaths = skillPaths;
        this.updateSkillsFromPaths(skillPaths);
        const promptPaths = this.noPromptTemplates
            ? this.mergePaths(cliEnabledPrompts, this.additionalPromptTemplatePaths)
            : this.mergePaths([...enabledPrompts, ...cliEnabledPrompts], this.additionalPromptTemplatePaths);
        this.lastPromptPaths = promptPaths;
        this.updatePromptsFromPaths(promptPaths);
        for (const extension of this.extensionsResult.extensions) {
            this.addDefaultMetadataForPath(extension.path);
        }
        const agentsFiles = {
            agentsFiles: loadProjectContextFiles({ cwd: this.cwd, agentDir: this.agentDir })
        };
        const resolvedAgentsFiles = this.agentsFilesOverride
            ? this.agentsFilesOverride(agentsFiles)
            : agentsFiles;
        this.agentsFiles = resolvedAgentsFiles.agentsFiles;
        this.systemPrompt = resolvePromptInput(this.systemPromptSource, 'system prompt');
    }
    normalizeExtensionPaths(entries) {
        return entries.map((entry) => ({
            path: this.resolveResourcePath(entry.path),
            metadata: entry.metadata
        }));
    }
    updateSkillsFromPaths(skillPaths, extensionPaths = []) {
        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;
        this.skillDiagnostics = resolvedSkills.diagnostics;
        this.applyExtensionMetadata(extensionPaths, this.skills.map((skill) => skill.filePath));
        for (const skill of this.skills) {
            this.addDefaultMetadataForPath(skill.filePath);
        }
    }
    updatePromptsFromPaths(promptPaths, extensionPaths = []) {
        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;
        this.promptDiagnostics = resolvedPrompts.diagnostics;
        this.applyExtensionMetadata(extensionPaths, this.prompts.map((prompt) => prompt.filePath));
        for (const prompt of this.prompts) {
            this.addDefaultMetadataForPath(prompt.filePath);
        }
    }
    applyExtensionMetadata(extensionPaths, resourcePaths) {
        if (extensionPaths.length === 0) {
            return;
        }
        const normalized = extensionPaths.map((entry) => ({
            path: resolve(entry.path),
            metadata: entry.metadata
        }));
        for (const entry of normalized) {
            if (!this.pathMetadata.has(entry.path)) {
                this.pathMetadata.set(entry.path, entry.metadata);
            }
        }
        for (const resourcePath of resourcePaths) {
            const normalizedResourcePath = resolve(resourcePath);
            if (this.pathMetadata.has(normalizedResourcePath) || this.pathMetadata.has(resourcePath)) {
                continue;
            }
            const match = normalized.find((entry) => normalizedResourcePath === entry.path ||
                normalizedResourcePath.startsWith(`${entry.path}${sep}`));
            if (match) {
                this.pathMetadata.set(normalizedResourcePath, match.metadata);
            }
        }
    }
    mergePaths(primary, additional) {
        const merged = [];
        const seen = new Set();
        for (const p of [...primary, ...additional]) {
            const resolved = this.resolveResourcePath(p);
            if (seen.has(resolved))
                continue;
            seen.add(resolved);
            merged.push(resolved);
        }
        return merged;
    }
    resolveResourcePath(p) {
        const trimmed = p.trim();
        let expanded = trimmed;
        if (trimmed === '~') {
            expanded = homedir();
        }
        else if (trimmed.startsWith('~/')) {
            expanded = join(homedir(), trimmed.slice(2));
        }
        else if (trimmed.startsWith('~')) {
            expanded = join(homedir(), trimmed.slice(1));
        }
        return resolve(this.cwd, expanded);
    }
    async loadExtensionFactories(runtime) {
        const extensions = [];
        const errors = [];
        for (const [index, factory] of this.extensionFactories.entries()) {
            const extensionPath = `<inline:${index + 1}>`;
            try {
                const extension = await loadExtensionFromFactory(factory, this.cwd, this.eventBus, runtime, extensionPath, { visibility: 'internal' });
                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 };
    }
    addDefaultMetadataForPath(filePath) {
        if (!filePath || filePath.startsWith('<')) {
            return;
        }
        const normalizedPath = resolve(filePath);
        if (this.pathMetadata.has(normalizedPath) || this.pathMetadata.has(filePath)) {
            return;
        }
        const agentRoots = [
            join(this.agentDir, 'skills'),
            join(this.agentDir, 'prompts'),
            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, 'extensions')
        ];
        for (const root of agentRoots) {
            if (this.isUnderPath(normalizedPath, root)) {
                this.pathMetadata.set(normalizedPath, {
                    source: 'local',
                    scope: 'user',
                    origin: 'top-level'
                });
                return;
            }
        }
        for (const root of projectRoots) {
            if (this.isUnderPath(normalizedPath, root)) {
                this.pathMetadata.set(normalizedPath, {
                    source: 'local',
                    scope: 'project',
                    origin: 'top-level'
                });
                return;
            }
        }
    }
    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, command, and flag
        const toolOwners = new Map();
        const commandOwners = 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 commands
            for (const commandName of ext.commands.keys()) {
                const existingOwner = commandOwners.get(commandName);
                if (existingOwner && existingOwner !== ext.path) {
                    conflicts.push({
                        path: ext.path,
                        message: `Command "/${commandName}" conflicts with ${existingOwner}`
                    });
                }
                else {
                    commandOwners.set(commandName, ext.path);
                }
            }
        }
        return conflicts;
    }
}
//# sourceMappingURL=resource-loader.js.map