import { existsSync, readFileSync } from 'fs';
import { homedir, platform, release } from 'os';
import { basename, dirname, isAbsolute, join, normalize, resolve } from 'path';
import { fileURLToPath } from 'url';
import { isNanoProfile, isSingleToolProfile } from '../tools/tool-profiles.js';
import { TUNE_DEFAULTS } from '../types/config.js';
import { getLogger } from '../utils/logging/index.js';
import { getSubagentDescriptions } from '../utils/prompt-processor.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const sectionsDir = join(__dirname, '../../source/app/prompts/sections');
// Cache loaded sections to avoid re-reading files
const sectionCache = new Map();
function getSectionFilePath(name) {
    const normalizedName = normalize(name).replace(/^([/\\])+/, '');
    const safeName = basename(normalizedName);
    return join(sectionsDir, `${safeName}.md`);
}
function loadSection(name) {
    const cached = sectionCache.get(name);
    if (cached !== undefined)
        return cached;
    const filePath = getSectionFilePath(name);
    try {
        const content = readFileSync(filePath, 'utf-8').trim();
        sectionCache.set(name, content);
        return content;
    }
    catch (error) {
        const logger = getLogger();
        logger.warn(`Failed to load prompt section "${name}": ${String(error)}`);
        sectionCache.set(name, '');
        return '';
    }
}
/** Reset section cache for testing. */
export function resetSectionCache() {
    sectionCache.clear();
}
// Cache the last-built system prompt so token-counting callers
// (e.g. /status, /usage, /compact) can access it without needing
// developmentMode/tune/tools as arguments.
let lastBuiltPrompt = null;
/**
 * Get the last system prompt produced by buildSystemPrompt().
 * Falls back to a minimal prompt if buildSystemPrompt hasn't been called yet.
 */
export function getLastBuiltPrompt() {
    return (lastBuiltPrompt ?? 'You are Nanocoder, a terminal-based AI coding agent.');
}
/**
 * Update the cached prompt after post-processing (e.g. XML tool injection).
 * Ensures token-counting callers see the full prompt the model receives.
 */
export function setLastBuiltPrompt(prompt) {
    lastBuiltPrompt = prompt;
}
function generateSystemInfo(slim = false) {
    const now = new Date();
    const dateStr = now.toISOString().split('T')[0];
    const getDefaultShell = () => {
        if (process.env.SHELL)
            return process.env.SHELL;
        if (platform() === 'win32')
            return process.env.COMSPEC || 'cmd.exe';
        if (platform() === 'darwin')
            return '/bin/zsh';
        return '/bin/bash';
    };
    const getOSName = () => {
        switch (platform()) {
            case 'darwin':
                return 'macOS';
            case 'win32':
                return 'Windows';
            case 'linux':
                return 'Linux';
            default:
                return platform();
        }
    };
    if (slim) {
        return `## SYSTEM
OS: ${getOSName()} | Shell: ${getDefaultShell()} | CWD: ${process.cwd()} | Date: ${dateStr}`;
    }
    return `## SYSTEM INFORMATION

Operating System: ${getOSName()}
OS Version: ${release()}
Platform: ${platform()}
Default Shell: ${getDefaultShell()}
Home Directory: ${homedir()}
Current Working Directory: ${process.cwd()}
Current Date: ${dateStr}`;
}
function appendAgentsMd(prompt) {
    const agentsPath = join(process.cwd(), 'AGENTS.md');
    if (existsSync(agentsPath)) {
        try {
            const agentsContent = readFileSync(agentsPath, 'utf-8');
            return `${prompt}\n\nAdditional Context...\n\n${agentsContent}`;
        }
        catch {
            // Silently skip if unreadable
        }
    }
    return prompt;
}
// Search/discovery tools that justify a "prefer native over bash" instruction
// read_file alone doesn't count — the model needs search tools for the advice to be meaningful
const NATIVE_SEARCH_TOOLS = new Set([
    'find_files',
    'search_file_contents',
    'list_directory',
]);
function hasNativeSearchTools(toolSet) {
    for (const tool of NATIVE_SEARCH_TOOLS) {
        if (toolSet.has(tool))
            return true;
    }
    return false;
}
function hasAnyGitTool(toolSet) {
    for (const name of toolSet) {
        if (name.startsWith('git_'))
            return true;
    }
    return false;
}
/**
 * Resolve the override content from a SystemPromptConfig: inline content wins
 * over file. Returns the prompt string, or null if neither is usable.
 */
function resolveSystemPromptOverride(override) {
    if (override.content !== undefined) {
        if (override.file !== undefined) {
            getLogger().warn('systemPrompt: both `content` and `file` set — using `content`.');
        }
        return override.content;
    }
    if (override.file !== undefined) {
        // Path comes from the user's own agents.config.json (trusted config), same model as source/config/index.ts
        const filePath = isAbsolute(override.file)
            ? override.file
            : resolve(process.cwd(), override.file); // nosemgrep
        try {
            return readFileSync(filePath, 'utf-8');
        }
        catch (error) {
            getLogger().warn(`systemPrompt: failed to read file "${filePath}": ${String(error)}`);
            return null;
        }
    }
    return null;
}
/**
 * Build a system prompt dynamically based on development mode, tune config, and available tools.
 *
 * Sections are full quality — the prompt gets smaller only because sections for
 * unavailable tools are excluded entirely, not because content is truncated.
 *
 * When `systemPromptOverride` is provided, the user's custom prompt either replaces
 * the built-in prompt entirely (mode="replace", the default) or is appended to it
 * (mode="append").
 */
export function buildSystemPrompt(developmentMode, tuneConfig, availableToolNames, toolsDisabled = false, systemPromptOverride, model) {
    const overrideContent = systemPromptOverride
        ? resolveSystemPromptOverride(systemPromptOverride)
        : null;
    const overrideMode = systemPromptOverride?.mode ?? 'replace';
    if (overrideContent !== null && overrideMode === 'replace') {
        lastBuiltPrompt = overrideContent;
        return overrideContent;
    }
    const tune = tuneConfig ?? TUNE_DEFAULTS;
    const singleTool = tune.enabled && isSingleToolProfile(tune.toolProfile, model);
    const nano = tune.enabled && isNanoProfile(tune.toolProfile, model);
    const toolSet = new Set(availableToolNames);
    const sections = [];
    // Always included
    sections.push(loadSection('identity'));
    // Core principles — dropped under nano (identity + tool rules cover the essentials)
    if (!nano) {
        sections.push(loadSection('core-principles'));
    }
    // Mode-specific task approach (nano variant when active)
    sections.push(loadSection(nano
        ? `task-approach-nano-${developmentMode}`
        : `task-approach-${developmentMode}`));
    // Tool rules — XML variant when native tool calling is disabled
    let toolRules = loadSection(toolsDisabled ? 'tool-rules-xml' : 'tool-rules');
    if (singleTool) {
        toolRules +=
            '\n- **IMPORTANT**: Call exactly ONE tool per response. Wait for the result before calling the next tool.';
    }
    sections.push(toolRules);
    // File operations — only if any file mutation tools are available
    if (toolSet.has('string_replace') ||
        toolSet.has('write_file') ||
        toolSet.has('file_op')) {
        sections.push(loadSection(nano ? 'file-editing-nano' : 'file-editing'));
    }
    // Native tool preference — only if bash AND search/discovery tools are both available.
    // Skipped under nano: nano profile has no native search/discovery tools by design.
    if (!nano && toolSet.has('execute_bash') && hasNativeSearchTools(toolSet)) {
        sections.push(loadSection('native-tool-preference'));
    }
    // Git tools — only if any git tools are available
    if (hasAnyGitTool(toolSet)) {
        // Plan mode only has read-only git tools — use plan-specific section
        sections.push(loadSection(developmentMode === 'plan' ? 'git-tools-readonly' : 'git-tools'));
    }
    // Task management — only if write_tasks is available AND not in plan mode
    if (toolSet.has('write_tasks') && developmentMode !== 'plan') {
        sections.push(loadSection('task-management'));
    }
    // Web tools — only if web_search or fetch_url are available
    if (toolSet.has('web_search') || toolSet.has('fetch_url')) {
        sections.push(loadSection('web-tools'));
    }
    // Diagnostics — only if lsp_get_diagnostics is available
    if (toolSet.has('lsp_get_diagnostics')) {
        // Plan mode: check for existing issues, not "fix what you introduce"
        sections.push(loadSection(developmentMode === 'plan' ? 'diagnostics-readonly' : 'diagnostics'));
    }
    // Asking questions — only if ask_user is available
    if (toolSet.has('ask_user')) {
        sections.push(loadSection('asking-questions'));
    }
    // Coding practices and constraints — not needed in plan mode
    // (plan task approach already covers the relevant guidance).
    // Under nano, drop coding-practices and use the shortened constraints.
    if (developmentMode !== 'plan') {
        if (!nano) {
            sections.push(loadSection('coding-practices'));
        }
        sections.push(loadSection(nano ? 'constraints-nano' : 'constraints'));
    }
    // Subagents — only if the agent tool is available
    if (toolSet.has('agent')) {
        const subagentSection = loadSection('subagents');
        const subagentInfo = `${subagentSection}

### Available subagents:

${getSubagentDescriptions()}`;
        sections.push(subagentInfo);
    }
    // System info (dynamic) — slim variant under nano
    sections.push(generateSystemInfo(nano));
    // Compose and (optionally) append AGENTS.md.
    // Nano omits AGENTS.md by default; users can override via tune.includeAgentsMd.
    let prompt = sections.filter(Boolean).join('\n\n');
    const includeAgentsMd = tune.includeAgentsMd ?? (nano ? false : true);
    if (includeAgentsMd) {
        prompt = appendAgentsMd(prompt);
    }
    // Append-mode user override (replace mode is handled at the top of the function).
    if (overrideContent !== null && overrideMode === 'append') {
        prompt = `${prompt}\n\n${overrideContent}`;
    }
    // Cache for token-counting callers that don't have access to the inputs
    lastBuiltPrompt = prompt;
    return prompt;
}
//# sourceMappingURL=prompt-builder.js.map