//! System instructions and prompt management.
//!
//! Prompt variants share one canonical base contract plus thin mode deltas and
//! compact runtime addenda. Richer behavior comes from AGENTS.md, dynamic tool
//! guidance, skill metadata, and runtime notices.

use crate::config::constants::prompt_budget as prompt_budget_constants;
use crate::config::types::SystemPromptMode;
use crate::llm::providers::gemini::wire::Content;
use crate::project_doc::read_project_doc;
use crate::prompts::context::PromptContext;
use crate::prompts::guidelines::generate_tool_guidelines;
use crate::prompts::output_styles::OutputStyleApplier;
use crate::prompts::resources::{apply_system_prompt_layers, resolve_system_prompt_layers};
use crate::prompts::system_prompt_cache::PROMPT_CACHE;
use crate::prompts::temporal::generate_temporal_context;
use crate::skills::render::render_prompt_skills_section;
use std::env;
use std::path::Path;
use std::sync::OnceLock;
use tracing::warn;

/// Shared Plan Mode header used by both static and incremental prompt builders.
pub const PLAN_MODE_READ_ONLY_HEADER: &str = "# PLAN MODE (READ-ONLY)";
/// Shared Plan Mode notice line describing strict read-only enforcement.
pub const PLAN_MODE_READ_ONLY_NOTICE_LINE: &str = "Plan Mode is active. Mutating tools are blocked except for optional plan artifact writes under `.vtcode/plans/` (or an explicit custom plan path).";
/// Shared Plan Mode instruction line for transitioning to implementation.
pub const PLAN_MODE_EXIT_INSTRUCTION_LINE: &str =
    "Call `exit_plan_mode` when ready to transition to implementation.";
/// Shared Plan Mode instruction line for decision-complete planning output.
pub const PLAN_MODE_PLAN_QUALITY_LINE: &str = "Explore repository facts first, ask only material blocking questions, keep planning read-only, and emit exactly one decision-complete `<proposed_plan>` block with a summary, implementation steps, test cases, and assumptions/defaults. If something is still unresolved, end with `Next open decision: ...`.";
/// Shared Plan Mode policy line requiring context-aware interview closure before final plans.
pub const PLAN_MODE_INTERVIEW_POLICY_LINE: &str = "In Plan Mode, prefer model-generated `request_user_input` interview questions informed by discovered repository context, keep custom notes/free-form responses available as first-class input, and continue interviewing until material scope/decomposition/verification decisions are closed before finalizing `<proposed_plan>`.";
/// Shared Plan Mode policy line for runtimes where `request_user_input` is unavailable.
pub const PLAN_MODE_NO_REQUEST_USER_INPUT_POLICY_LINE: &str = "In this runtime, `request_user_input` is unavailable. In Plan Mode, continue exploring repository facts in read-only mode, finish any unblocked planning work, and surface material blockers explicitly in plain text instead of emitting interview tool calls.";
/// Shared Plan Mode guard line requiring explicit transition from planning to execution.
pub const PLAN_MODE_NO_AUTO_EXIT_LINE: &str = "Do not auto-exit Plan Mode just because a plan exists; wait for explicit implementation intent.";
/// Shared Plan Mode task-tracking line clarifying availability and aliasing.
pub const PLAN_MODE_TASK_TRACKER_LINE: &str =
    "`task_tracker` remains available in Plan Mode (`plan_task_tracker` is a compatibility alias).";
/// Shared reminder appended when presenting plans while still in Plan Mode.
pub const PLAN_MODE_IMPLEMENT_REMINDER: &str = "• Still in Plan Mode (read-only). Say “implement” to execute, or “stay in plan mode” to revise. If automatic Plan->Edit switching fails, manually switch with `/plan off` or `/mode` (or press `Shift+Tab`/`Alt+M` in interactive mode).";

const PROMPT_TITLE: &str = "# VT Code";
const PROMPT_INTRO: &str = "VT Code. Be concise and safe.";
const CONTRACT_HEADER: &str = "## Contract";

const OPENAI_GPT55_CONTRACT_HEADER: &str = "## GPT-5.5 OpenAI Addendum";
const OPENAI_GPT55_CONTRACT_LINES: &[&str] = &[
    "State the outcome, constraints, evidence, and output shape up front; avoid over-prescribing the path unless the exact steps matter.",
    "If context is missing, say so plainly; use the smallest missing detail that would change the result, and finish any unblocked portion first.",
    "Verify changes yourself with the smallest relevant check; never claim a check passed unless you ran it.",
    "Before multi-step tool work, send a brief progress update that names the first step.",
    "Use retrieved evidence for citation-sensitive work; use the minimum evidence sufficient to answer correctly, then stop.",
];

const DEFAULT_CONTRACT_LINES: &[&str] = &[
    "Start with `AGENTS.md`; inspect code first, match local patterns, use `@file`.",
    "If context is missing, say so, do not guess, finish unblocked slices first.",
    "Take safe, reversible steps; ask only for material behavior, API, UX, or credential changes.",
    "Keep control on the main thread. Delegate bounded, independent work only.",
    "Verify changes yourself; never claim a check passed unless you ran it.",
    "Keep outputs concise. Keep user updates brief and high-signal.",
    "Use retrieved evidence for citation-sensitive work. Preserve task goal, tracker state, touched files, verification status, and decisions across compaction.",
    "NEVER use emoji. Use plain text only.",
    "Read files before answering. Never speculate about code you have not opened.",
    "Make only requested changes. Keep solutions simple. Implement by default; suggest only when intent is unclear.",
];

const MINIMAL_CONTRACT_LINES: &[&str] = &[
    "Use `AGENTS.md`; inspect code first.",
    "If context is missing, say so, do not guess, finish unblocked slices.",
    "Take safe, reversible steps; verify changes yourself.",
    "Keep delegation bounded and explicit.",
    "Preserve tracker state, touched files, and verification status across compaction.",
    "Use retrieved evidence when citation-sensitive.",
    "Keep outputs concise.",
];

const DEFAULT_MODE_DELTA: &str = r#"## Mode

- Use `task_tracker` for non-trivial work.
- Treat completion language as a checkpoint, not proof; only stop when the tracker is current and verification is resolved.
- Use Plan Mode for research/spec work; stay read-only until implementation intent is explicit."#;

const MINIMAL_MODE_DELTA: &str = r#"## Mode

- Stay precise; use `task_tracker` once work stops being trivial.
- Treat completion language as a checkpoint, not proof.
- Use `AGENTS.md` as the map; open repo docs only when structural rules matter."#;

const LIGHTWEIGHT_MODE_DELTA: &str = r#"## Mode

- Act and verify in one thread.
- Completion language is a checkpoint.
- Use `task_tracker` for nontrivial work."#;

const SPECIALIZED_MODE_DELTA: &str = r#"## Mode

- Explore, plan, then execute.
- Use `task_tracker` for multi-step work and Plan Mode when scope or verification is still open.
- Treat completion language as a checkpoint, not proof; only stop when tracker state, verification, and resumable state agree.
- End plan work with one `<proposed_plan>` block; if a path stalls, re-plan into smaller verified slices.
- Use `AGENTS.md` and `docs/harness/ARCHITECTURAL_INVARIANTS.md` when repo-wide invariants matter."#;

static DEFAULT_SYSTEM_PROMPT: OnceLock<String> = OnceLock::new();
static MINIMAL_SYSTEM_PROMPT: OnceLock<String> = OnceLock::new();
static DEFAULT_LIGHTWEIGHT_PROMPT: OnceLock<String> = OnceLock::new();
static DEFAULT_SPECIALIZED_PROMPT: OnceLock<String> = OnceLock::new();

pub fn default_system_prompt() -> &'static str {
    static_mode_prompt(SystemPromptMode::Default)
}

pub fn minimal_system_prompt() -> &'static str {
    static_mode_prompt(SystemPromptMode::Minimal)
}

pub fn default_lightweight_prompt() -> &'static str {
    static_mode_prompt(SystemPromptMode::Lightweight)
}

pub fn specialized_system_prompt() -> &'static str {
    static_mode_prompt(SystemPromptMode::Specialized)
}

pub fn minimal_instruction_text() -> String {
    minimal_system_prompt().to_string()
}

pub fn lightweight_instruction_text() -> String {
    default_lightweight_prompt().to_string()
}

pub fn specialized_instruction_text() -> String {
    specialized_system_prompt().to_string()
}

pub fn openai_gpt55_contract_addendum() -> String {
    let lines_len = OPENAI_GPT55_CONTRACT_LINES
        .iter()
        .map(|line| line.len())
        .sum::<usize>();
    let mut prompt = String::with_capacity(
        OPENAI_GPT55_CONTRACT_HEADER.len() + lines_len + OPENAI_GPT55_CONTRACT_LINES.len() * 3 + 8,
    );
    prompt.push_str(OPENAI_GPT55_CONTRACT_HEADER);
    prompt.push_str("\n\n");
    for line in OPENAI_GPT55_CONTRACT_LINES {
        prompt.push_str("- ");
        prompt.push_str(line);
        prompt.push('\n');
    }
    prompt.pop();
    prompt
}

const STRUCTURED_REASONING_INSTRUCTIONS: &str = r#"
## Structured Reasoning

Use tags when helpful: `<analysis>` facts/options, `<plan>` steps, `<uncertainty>` blockers, `<verification>` checks.
"#;

/// System instruction configuration
#[derive(Debug, Clone, Default)]
pub struct SystemPromptConfig;

/// Generate system instruction
pub async fn generate_system_instruction(_config: &SystemPromptConfig) -> Content {
    // OPTIMIZATION: default_system_prompt() is &'static str, use directly
    let instruction = default_system_prompt().to_string();

    // Apply output style if possible (using current directory as project root)
    if let Ok(current_dir) = env::current_dir() {
        let styled_instruction = apply_output_style(instruction, None, &current_dir).await;
        Content::system_text(styled_instruction)
    } else {
        Content::system_text(instruction)
    }
}

/// Read AGENTS.md file if present and extract agent guidelines
pub async fn read_agent_guidelines(project_root: &Path) -> Option<String> {
    let max_bytes = prompt_budget_constants::DEFAULT_MAX_BYTES;
    match read_project_doc(project_root, max_bytes).await {
        Ok(Some(bundle)) => Some(bundle.contents),
        Ok(None) => None,
        Err(err) => {
            warn!("failed to load project documentation: {err:#}");
            None
        }
    }
}

/// Compose the base system instruction plus compact tool/skill/environment addenda.
pub async fn compose_system_instruction_text(
    _project_root: &Path,
    vtcode_config: Option<&crate::config::VTCodeConfig>,
    prompt_context: Option<&PromptContext>,
) -> String {
    let prompt_mode = vtcode_config
        .map(|c| c.agent.system_prompt_mode)
        .unwrap_or(SystemPromptMode::Default);
    let static_base_prompt = static_mode_prompt(prompt_mode);
    let resolved_layers = resolve_system_prompt_layers(_project_root).await;
    let base_prompt = apply_system_prompt_layers(static_base_prompt, &resolved_layers);

    tracing::trace!(
        mode = ?prompt_mode,
        base_tokens_approx = base_prompt.len() / 4, // rough token estimate
        "Selected system prompt mode"
    );

    let base_len = base_prompt.len();
    let config_overhead = vtcode_config.map_or(0, |_| 1024);
    let estimated_capacity = base_len + config_overhead + 1024;
    let mut instruction = String::with_capacity(estimated_capacity);
    instruction.push_str(&base_prompt);
    if should_include_structured_reasoning(vtcode_config, prompt_mode) {
        append_prompt_section(&mut instruction, STRUCTURED_REASONING_INSTRUCTIONS);
    }

    if let Some(ctx) = prompt_context {
        let guidelines = generate_tool_guidelines(&ctx.available_tools, ctx.capability_level);
        if !guidelines.is_empty() {
            append_prompt_section(&mut instruction, guidelines.trim_start_matches('\n'));
        }
        if let Some(skills_section) = render_prompt_skills_section(&ctx.available_skill_metadata) {
            append_prompt_section(&mut instruction, &skills_section);
        }
    }

    if let Some(environment_section) = render_environment_addenda(vtcode_config, prompt_context) {
        append_prompt_section(&mut instruction, &environment_section);
    }

    instruction
}

fn append_prompt_section(prompt: &mut String, section: &str) {
    prompt.push_str("\n\n");
    prompt.push_str(section);
}

fn static_mode_prompt(prompt_mode: SystemPromptMode) -> &'static str {
    match prompt_mode {
        SystemPromptMode::Default => DEFAULT_SYSTEM_PROMPT.get_or_init(|| {
            build_mode_prompt(
                &build_contract_prompt(DEFAULT_CONTRACT_LINES),
                DEFAULT_MODE_DELTA,
            )
        }),
        SystemPromptMode::Minimal => MINIMAL_SYSTEM_PROMPT.get_or_init(|| {
            build_mode_prompt(
                &build_contract_prompt(MINIMAL_CONTRACT_LINES),
                MINIMAL_MODE_DELTA,
            )
        }),
        SystemPromptMode::Lightweight => DEFAULT_LIGHTWEIGHT_PROMPT.get_or_init(|| {
            build_mode_prompt(
                &build_contract_prompt(DEFAULT_CONTRACT_LINES),
                LIGHTWEIGHT_MODE_DELTA,
            )
        }),
        SystemPromptMode::Specialized => DEFAULT_SPECIALIZED_PROMPT.get_or_init(|| {
            build_mode_prompt(
                &build_contract_prompt(DEFAULT_CONTRACT_LINES),
                SPECIALIZED_MODE_DELTA,
            )
        }),
    }
}

fn build_contract_prompt(contract_lines: &[&str]) -> String {
    let lines_len = contract_lines.iter().map(|line| line.len()).sum::<usize>();
    let mut prompt = String::with_capacity(
        PROMPT_TITLE.len()
            + PROMPT_INTRO.len()
            + CONTRACT_HEADER.len()
            + lines_len
            + contract_lines.len() * 3
            + 8,
    );
    prompt.push_str(PROMPT_TITLE);
    prompt.push_str("\n\n");
    prompt.push_str(PROMPT_INTRO);
    prompt.push_str("\n\n");
    prompt.push_str(CONTRACT_HEADER);
    prompt.push_str("\n\n");

    for line in contract_lines {
        prompt.push_str("- ");
        prompt.push_str(line);
        prompt.push('\n');
    }

    if !contract_lines.is_empty() {
        prompt.pop();
    }
    prompt
}

fn build_mode_prompt(base_prompt: &str, mode_delta: &str) -> String {
    let mut prompt = String::with_capacity(base_prompt.len() + mode_delta.len() + 2);
    prompt.push_str(base_prompt);
    prompt.push_str("\n\n");
    prompt.push_str(mode_delta);
    prompt
}

fn render_environment_addenda(
    vtcode_config: Option<&crate::config::VTCodeConfig>,
    prompt_context: Option<&PromptContext>,
) -> Option<String> {
    let mut lines = Vec::new();

    if let Some(ctx) = prompt_context
        && !ctx.languages.is_empty()
    {
        lines.push(format!(
            "- Languages: {}. Match structural-search `lang` when needed.",
            ctx.languages.join(", ")
        ));
    }

    if let Some(cfg) = vtcode_config {
        if let Some(interaction_line) = render_interaction_addendum(cfg) {
            lines.push(interaction_line);
        }

        if cfg.mcp.enabled {
            lines.push("- Sources: prefer MCP before external fetches when available.".to_string());
        }

        if cfg.agent.include_temporal_context && !cfg.prompt_cache.cache_friendly_prompt_shaping {
            lines.push(
                generate_temporal_context(cfg.agent.temporal_context_use_utc)
                    .trim()
                    .replacen("Current date and time", "- Time", 1)
                    .to_string(),
            );
        }

        if cfg.agent.include_working_directory
            && let Some(ctx) = prompt_context
            && let Some(cwd) = &ctx.current_directory
        {
            lines.push(format!("- Working directory: {}", cwd.display()));
        }
    }

    if lines.is_empty() {
        None
    } else {
        Some(format!("## Environment\n{}", lines.join("\n")))
    }
}

fn render_interaction_addendum(cfg: &crate::config::VTCodeConfig) -> Option<String> {
    match (cfg.security.human_in_the_loop, cfg.chat.ask_questions.enabled) {
        (true, true) => None,
        (true, false) => Some(
            "- Interaction: approval may gate sensitive actions; no `request_user_input`, so make reasonable assumptions unless Plan Mode needs follow-up.".to_string(),
        ),
        (false, true) => Some(
            "- Interaction: approval reduced by config; use `request_user_input` for material blockers.".to_string(),
        ),
        (false, false) => Some(
            "- Interaction: approval reduced by config; no `request_user_input`, so make reasonable assumptions unless Plan Mode needs follow-up.".to_string(),
        ),
    }
}

fn should_include_structured_reasoning(
    vtcode_config: Option<&crate::config::VTCodeConfig>,
    mode: SystemPromptMode,
) -> bool {
    if let Some(cfg) = vtcode_config {
        return cfg.agent.should_include_structured_reasoning_tags();
    }

    // Backward-compatible fallback when no config is available.
    matches!(mode, SystemPromptMode::Specialized)
}

/// Generate the stable base system instruction with configuration-aware sections.
///
/// Note: This function maintains backward compatibility by not accepting prompt_context.
/// For enhanced prompts with dynamic guidelines, call `compose_system_instruction_text` directly.
pub async fn generate_system_instruction_with_config(
    _config: &SystemPromptConfig,
    project_root: &Path,
    vtcode_config: Option<&crate::config::VTCodeConfig>,
) -> Content {
    let cache_key = cache_key(project_root, vtcode_config, None);
    let instruction = match PROMPT_CACHE.get(&cache_key) {
        Some(cached) => cached,
        None => {
            let built = compose_system_instruction_text(project_root, vtcode_config, None).await;
            PROMPT_CACHE.insert(cache_key, built.clone());
            built
        }
    };

    // Apply output style if configured
    let styled_instruction = apply_output_style(instruction, vtcode_config, project_root).await;
    Content::system_text(styled_instruction)
}

/// Generate the stable base system instruction without workspace configuration.
pub async fn generate_system_instruction_with_guidelines(
    _config: &SystemPromptConfig,
    project_root: &Path,
) -> Content {
    let cache_key = cache_key(project_root, None, None);
    let instruction = match PROMPT_CACHE.get(&cache_key) {
        Some(cached) => cached,
        None => {
            let built = compose_system_instruction_text(project_root, None, None).await;
            PROMPT_CACHE.insert(cache_key, built.clone());
            built
        }
    };
    // Apply output style if configured
    let styled_instruction = apply_output_style(instruction, None, project_root).await;
    Content::system_text(styled_instruction)
}

/// Apply output style to a generated system instruction
pub async fn apply_output_style(
    instruction: String,
    vtcode_config: Option<&crate::config::VTCodeConfig>,
    project_root: &Path,
) -> String {
    if let Some(config) = vtcode_config {
        let output_style_applier = OutputStyleApplier::new();
        if let Err(e) = output_style_applier
            .load_styles_from_config(config, project_root)
            .await
        {
            tracing::warn!("Failed to load output styles: {}", e);
            instruction // Return original if loading fails
        } else {
            output_style_applier
                .apply_style(&config.output_style.active_style, &instruction, config)
                .await
        }
    } else {
        instruction // Return original if no config
    }
}

/// Build a cache key for the system prompt.
///
/// `catalog_epoch` is the tool-catalog version at the time of the request. When
/// the tool set changes (e.g. plan mode is toggled, MCP tools are refreshed), the
/// epoch advances and the old cached prompt is superseded rather than served stale.
/// Pass `None` to get the same behaviour as before epoch tracking was introduced.
fn cache_key(
    project_root: &Path,
    vtcode_config: Option<&crate::config::VTCodeConfig>,
    catalog_epoch: Option<u64>,
) -> String {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};

    let mut hasher = DefaultHasher::new();

    // Core key: project root
    project_root.hash(&mut hasher);

    if let Some(cfg) = vtcode_config {
        // Config fields that affect prompt generation
        cfg.agent.include_working_directory.hash(&mut hasher);
        cfg.agent.include_temporal_context.hash(&mut hasher);
        cfg.prompt_cache
            .cache_friendly_prompt_shaping
            .hash(&mut hasher);
        cfg.agent
            .include_structured_reasoning_tags
            .hash(&mut hasher);
        // Use discriminant since SystemPromptMode doesn't derive Hash
        std::mem::discriminant(&cfg.agent.system_prompt_mode).hash(&mut hasher);
    } else {
        "default".hash(&mut hasher);
    }

    // Invalidate the cached prompt when the tool catalog changes (plan mode toggle,
    // MCP refresh, permission grant/revoke).
    catalog_epoch.unwrap_or(0).hash(&mut hasher);

    format!("sys_prompt:{:016x}", hasher.finish())
}

/// Generate a minimal system instruction (pi-inspired, <1K tokens)
pub fn generate_minimal_instruction() -> Content {
    Content::system_text(minimal_instruction_text())
}

/// Generate a lightweight system instruction for simple operations
pub fn generate_lightweight_instruction() -> Content {
    Content::system_text(lightweight_instruction_text())
}

/// Generate a specialized system instruction for advanced operations
pub fn generate_specialized_instruction() -> Content {
    Content::system_text(specialized_instruction_text())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::VTCodeConfig;
    use crate::config::types::SystemPromptMode;
    use std::path::PathBuf;

    #[tokio::test]
    async fn test_minimal_mode_selection() {
        let mut config = VTCodeConfig::default();
        config.agent.system_prompt_mode = SystemPromptMode::Minimal;
        // Disable enhancements for base prompt size testing
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        config.agent.instruction_max_bytes = 0;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        // Minimal prompt should remain compact and deterministic without AGENTS.md injection
        assert!(
            result.len() < 2200,
            "Minimal mode should produce <2.2K chars (was {} chars)",
            result.len()
        );
        assert!(
            result.contains("VT Code") || result.contains("VT Code"),
            "Should contain VT Code identifier"
        );
    }

    #[tokio::test]
    async fn test_default_mode_selection() {
        let mut config = VTCodeConfig::default();
        config.agent.system_prompt_mode = SystemPromptMode::Default;
        // Disable enhancements for base prompt size testing
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        config.agent.instruction_max_bytes = 0;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            result.len() <= 1700,
            "Default mode should stay sparse (<=1.7K chars, was {} chars)",
            result.len()
        );
        assert!(result.contains("task_tracker"));
        assert!(result.contains("@file"));
        assert!(result.contains("Plan Mode"));
    }

    #[tokio::test]
    async fn test_lightweight_mode_selection() {
        let mut config = VTCodeConfig::default();
        config.agent.system_prompt_mode = SystemPromptMode::Lightweight;
        // Disable enhancements for base prompt size testing
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        config.agent.instruction_max_bytes = 0;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(result.len() > 100, "Lightweight should be >100 chars");
        assert!(
            result.len() < 1550,
            "Lightweight should be compact (<1.55K chars, was {} chars)",
            result.len()
        );
        assert!(result.contains("task_tracker"));
        assert!(result.contains("@file"));
        assert!(result.contains("Act and verify in one thread"));
    }

    #[tokio::test]
    async fn test_lightweight_mode_skips_structured_reasoning_by_default() {
        let mut config = VTCodeConfig::default();
        config.agent.system_prompt_mode = SystemPromptMode::Lightweight;
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        config.agent.instruction_max_bytes = 0;
        config.agent.include_structured_reasoning_tags = None;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            !result.contains("## Structured Reasoning"),
            "Lightweight mode should omit structured reasoning by default"
        );
    }

    #[tokio::test]
    async fn test_lightweight_mode_allows_explicit_structured_reasoning() {
        let mut config = VTCodeConfig::default();
        config.agent.system_prompt_mode = SystemPromptMode::Lightweight;
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        config.agent.instruction_max_bytes = 0;
        config.agent.include_structured_reasoning_tags = Some(true);

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            result.contains("## Structured Reasoning"),
            "Lightweight mode should include structured reasoning when explicitly enabled"
        );
    }

    #[tokio::test]
    async fn test_default_mode_omits_structured_reasoning_by_default() {
        let mut config = VTCodeConfig::default();
        config.agent.system_prompt_mode = SystemPromptMode::Default;
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        config.agent.instruction_max_bytes = 0;
        config.agent.include_structured_reasoning_tags = None;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            !result.contains("## Structured Reasoning"),
            "Default mode should omit structured reasoning by default"
        );
    }

    #[tokio::test]
    async fn test_specialized_mode_selection() {
        let mut config = VTCodeConfig::default();
        config.agent.system_prompt_mode = SystemPromptMode::Specialized;
        // Disable enhancements for base prompt size testing
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        config.agent.instruction_max_bytes = 0;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            result.len() <= 2050,
            "Specialized should stay sparse (<=2.05K chars, was {} chars)",
            result.len()
        );
        assert!(result.contains("task_tracker"));
        assert!(result.contains("<proposed_plan>"));
        assert!(result.contains("ARCHITECTURAL_INVARIANTS"));
    }

    #[test]
    fn test_prompt_mode_enum_parsing() {
        assert_eq!(
            SystemPromptMode::parse("minimal"),
            Some(SystemPromptMode::Minimal)
        );
        assert_eq!(
            SystemPromptMode::parse("LIGHTWEIGHT"),
            Some(SystemPromptMode::Lightweight)
        );
        assert_eq!(
            SystemPromptMode::parse("Default"),
            Some(SystemPromptMode::Default)
        );
        assert_eq!(
            SystemPromptMode::parse("specialized"),
            Some(SystemPromptMode::Specialized)
        );
        assert_eq!(SystemPromptMode::parse("invalid"), None);
    }

    #[test]
    fn test_minimal_prompt_token_count() {
        // Rough estimate: 1 token ≈ 4 characters
        let approx_tokens = minimal_system_prompt().len() / 4;
        assert!(
            approx_tokens < 220,
            "Minimal prompt should stay compact, got ~{}",
            approx_tokens
        );
    }

    #[test]
    fn test_default_prompt_token_count() {
        let approx_tokens = default_system_prompt().len() / 4;
        assert!(
            approx_tokens < 420,
            "Default prompt should stay compact, got ~{}",
            approx_tokens
        );
    }

    #[tokio::test]
    async fn test_default_live_prompt_budget_with_instruction_summary() {
        use crate::project_doc::build_instruction_appendix_with_context;

        let workspace = tempfile::TempDir::new().expect("workspace");
        std::fs::write(workspace.path().join(".git"), "gitdir: /tmp/git").expect("git marker");
        std::fs::write(
            workspace.path().join("AGENTS.md"),
            "- run ./scripts/check.sh\n- avoid adding to vtcode-core\n- use Conventional Commits\n- start with docs/ARCHITECTURE.md\n",
        )
        .expect("write agents");
        std::fs::create_dir_all(workspace.path().join(".vtcode/rules")).expect("rules dir");
        std::fs::write(
            workspace.path().join(".vtcode/rules/rust.md"),
            "---\npaths:\n  - \"**/*.rs\"\n---\n# Rust\n- keep changes surgical\n",
        )
        .expect("write rust rule");

        let mut config = VTCodeConfig::default();
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        let base = compose_system_instruction_text(workspace.path(), Some(&config), None).await;
        let appendix = build_instruction_appendix_with_context(
            &config.agent,
            workspace.path(),
            &[workspace.path().join("src/lib.rs")],
        )
        .await
        .expect("instruction appendix");
        let prompt = format!("{base}\n\n# INSTRUCTIONS\n{appendix}");
        let approx_tokens = prompt.len() / 4;

        assert!(prompt.contains("### Instruction map"));
        assert!(prompt.contains("### On-demand loading"));
        assert!(approx_tokens <= 1100, "got ~{} tokens", approx_tokens);
    }

    #[tokio::test]
    async fn test_generated_prompts_use_task_tracker_not_update_plan() {
        let project_root = PathBuf::from(".");

        for (mode_name, mode) in [
            ("default", SystemPromptMode::Default),
            ("minimal", SystemPromptMode::Minimal),
            ("specialized", SystemPromptMode::Specialized),
        ] {
            let mut config = VTCodeConfig::default();
            config.agent.system_prompt_mode = mode;
            config.agent.include_temporal_context = false;
            config.agent.include_working_directory = false;
            config.agent.instruction_max_bytes = 0;

            let result = compose_system_instruction_text(&project_root, Some(&config), None).await;

            assert!(
                result.contains("task_tracker"),
                "{mode_name} prompt should reference task_tracker"
            );
            assert!(
                !result.contains("update_plan"),
                "{mode_name} prompt should not reference deprecated update_plan"
            );
        }
    }

    #[tokio::test]
    async fn test_default_and_specialized_prompts_drop_rigid_summary_template() {
        let project_root = PathBuf::from(".");

        for (mode_name, mode) in [
            ("default", SystemPromptMode::Default),
            ("specialized", SystemPromptMode::Specialized),
        ] {
            let mut config = VTCodeConfig::default();
            config.agent.system_prompt_mode = mode;
            config.agent.include_temporal_context = false;
            config.agent.include_working_directory = false;
            config.agent.instruction_max_bytes = 0;

            let result = compose_system_instruction_text(&project_root, Some(&config), None).await;

            assert!(
                !result.contains("References\n"),
                "{mode_name} prompt should not force a References section"
            );
            assert!(
                !result.contains("Next action"),
                "{mode_name} prompt should not force a Next action section"
            );
            assert!(
                !result.contains("Scope checkpoint"),
                "{mode_name} prompt should not require the old plan blueprint bullets"
            );
        }
    }

    #[tokio::test]
    async fn test_generated_prompts_keep_sparse_execution_contract() {
        let project_root = PathBuf::from(".");

        for (mode_name, mode) in [
            ("default", SystemPromptMode::Default),
            ("minimal", SystemPromptMode::Minimal),
            ("lightweight", SystemPromptMode::Lightweight),
            ("specialized", SystemPromptMode::Specialized),
        ] {
            let mut config = VTCodeConfig::default();
            config.agent.system_prompt_mode = mode;
            config.agent.include_temporal_context = false;
            config.agent.include_working_directory = false;
            config.agent.instruction_max_bytes = 0;

            let result = compose_system_instruction_text(&project_root, Some(&config), None).await;
            let normalized = result.to_ascii_lowercase();

            assert!(
                normalized.contains("compact") || normalized.contains("concise"),
                "{mode_name} prompt should keep output guidance compact"
            );
            assert!(
                normalized.contains("low-risk") || normalized.contains("reversible"),
                "{mode_name} prompt should include follow-through guidance"
            );
            assert!(
                normalized.contains("verify") || normalized.contains("validation"),
                "{mode_name} prompt should include verification guidance"
            );
            assert!(
                normalized.contains("do not guess"),
                "{mode_name} prompt should gate missing context"
            );
            assert!(
                normalized.contains("unblocked portion")
                    || normalized.contains("unblocked slices")
                    || normalized.contains("answerable without a missing detail"),
                "{mode_name} prompt should require partial progress before clarification"
            );
            assert!(
                normalized.contains("retrieved sources")
                    || normalized.contains("retrieved evidence"),
                "{mode_name} prompt should include grounding/citation guidance"
            );
            assert!(
                !result.contains('ƒ'),
                "{mode_name} prompt should not contain stray prompt characters"
            );
        }
    }

    #[test]
    fn test_prompt_text_avoids_hardcoded_loop_thresholds() {
        let specialized_prompt = specialized_instruction_text();
        assert!(!default_system_prompt().contains("stuck twice"));
        assert!(!minimal_system_prompt().contains("stuck twice"));
        assert!(!specialized_prompt.contains("stuck twice"));
        assert!(!specialized_prompt.contains("10+ calls without progress"));
        assert!(!specialized_prompt.contains("Same tool+params twice"));
    }

    #[test]
    fn test_harness_awareness_in_prompts() {
        assert!(
            default_system_prompt().contains("AGENTS.md"),
            "Default prompt should reference AGENTS.md as map"
        );
        assert!(
            specialized_instruction_text().contains("ARCHITECTURAL_INVARIANTS"),
            "Specialized prompt should reference architectural invariants"
        );
        assert!(
            minimal_system_prompt().contains("AGENTS.md"),
            "Minimal prompt should still reference AGENTS.md"
        );
    }

    #[test]
    fn test_prompts_reject_guessing_when_context_is_missing() {
        assert!(
            default_system_prompt().contains("do not guess"),
            "Default prompt should reject guessing"
        );
        assert!(
            specialized_instruction_text().contains("do not guess"),
            "Specialized prompt should reject guessing"
        );
        assert!(
            minimal_system_prompt().contains("do not guess"),
            "Minimal prompt should still reject guessing"
        );
    }

    #[test]
    fn test_prompts_include_compaction_preservation_contract() {
        assert!(
            default_system_prompt().contains("touched files"),
            "Default prompt should preserve touched files across compaction"
        );
        assert!(
            default_system_prompt().contains("decisions across compaction"),
            "Default prompt should preserve decision rationale across compaction"
        );
        assert!(
            default_system_prompt().contains("tracker state"),
            "Default prompt should preserve tracker state across compaction"
        );
        assert!(
            default_system_prompt().contains("verification status"),
            "Default prompt should preserve verification status across compaction"
        );
        assert!(
            minimal_system_prompt().contains("touched files"),
            "Minimal prompt should preserve touched files across compaction"
        );
    }

    #[test]
    fn test_default_prompt_stays_lean_but_complete() {
        let prompt = default_system_prompt();

        assert!(
            prompt.contains("## Contract"),
            "Default prompt should include the lean contract section"
        );
        assert!(
            prompt.contains("Keep outputs concise"),
            "Default prompt should clamp output shape"
        );
        assert!(
            prompt.contains("Verify changes yourself"),
            "Default prompt should require verification before finalizing"
        );
        assert!(
            prompt.contains("Keep user updates brief and high-signal"),
            "Default prompt should constrain progress updates"
        );
    }

    #[test]
    fn test_all_prompt_modes_treat_completion_as_checkpoint_not_proof() {
        for (mode_name, prompt) in [
            ("default", default_system_prompt()),
            ("minimal", minimal_system_prompt()),
            ("lightweight", default_lightweight_prompt()),
            ("specialized", specialized_instruction_text().as_str()),
        ] {
            assert!(
                prompt.contains("completion language as a checkpoint")
                    || prompt.contains("Verify changes yourself")
                    || prompt.contains("verification"),
                "{mode_name} prompt should include verification guidance"
            );
        }
    }

    #[test]
    fn test_prompts_encode_explicit_delegation_contract() {
        let prompt = default_system_prompt();

        assert!(
            prompt.contains("Keep control on the main thread"),
            "Default prompt should keep control on the main thread"
        );
        assert!(
            prompt.contains("Delegate bounded, independent work"),
            "Default prompt should restrict delegation to bounded independent work"
        );
        assert!(
            minimal_system_prompt().contains("Keep delegation bounded and explicit"),
            "Minimal prompt should preserve the delegation contract"
        );
    }

    #[test]
    fn test_default_prompt_includes_grounding_and_action_bias() {
        let prompt = default_system_prompt();
        assert!(
            prompt.contains("Never speculate about code you have not opened"),
            "Default prompt should include grounding guidance"
        );
        assert!(
            prompt.contains("Make only requested changes"),
            "Default prompt should include anti-overengineering guidance"
        );
        assert!(
            prompt.contains("Implement by default"),
            "Default prompt should include action bias"
        );
    }

    #[test]
    fn test_default_prompt_omits_accuracy_addendum() {
        let runtime = tokio::runtime::Runtime::new().expect("runtime");
        let config = VTCodeConfig::default();
        let prompt = runtime.block_on(compose_system_instruction_text(
            &PathBuf::from("."),
            Some(&config),
            None,
        ));

        assert!(
            !prompt.contains("## Accuracy Optimization"),
            "Runtime prompt should omit the accuracy optimization section"
        );
        assert!(
            prompt.contains("do not guess"),
            "Prompt should still preserve the uncertainty guardrail"
        );
    }

    #[test]
    fn test_openai_gpt55_contract_addendum_is_specific() {
        let addendum = openai_gpt55_contract_addendum();

        assert!(addendum.contains(OPENAI_GPT55_CONTRACT_HEADER));
        assert!(addendum.contains("outcome, constraints, evidence, and output shape"));
        assert!(addendum.contains("smallest missing detail"));
        assert!(addendum.contains("brief progress update"));
        assert!(addendum.contains("minimum evidence sufficient"));
        assert!(!default_system_prompt().contains(OPENAI_GPT55_CONTRACT_HEADER));
    }

    #[tokio::test]
    async fn test_generated_prompts_keep_mode_deltas_bounded() {
        let project_root = PathBuf::from(".");

        for (mode_name, mode) in [
            ("default", SystemPromptMode::Default),
            ("minimal", SystemPromptMode::Minimal),
            ("lightweight", SystemPromptMode::Lightweight),
            ("specialized", SystemPromptMode::Specialized),
        ] {
            let mut config = VTCodeConfig::default();
            config.agent.system_prompt_mode = mode;
            config.agent.include_temporal_context = false;
            config.agent.include_working_directory = false;
            config.agent.instruction_max_bytes = 0;

            let result = compose_system_instruction_text(&project_root, Some(&config), None).await;

            assert!(
                result.contains("## Contract"),
                "{mode_name} prompt should reuse the canonical base prompt"
            );
            assert!(
                result.matches("## Mode").count() == 1,
                "{mode_name} prompt should add only one mode delta"
            );
        }
    }

    #[test]
    fn test_search_guidance_prefers_structural_and_rg() {
        let guidelines = generate_tool_guidelines(
            &["unified_search".to_string(), "unified_exec".to_string()],
            None,
        );
        assert!(
            guidelines.contains("Prefer search over shell"),
            "Tool guidance should prefer search over shell exploration"
        );
        assert!(
            guidelines.contains("git diff -- <path>"),
            "Tool guidance should keep diff guidance explicit"
        );
    }

    // ENHANCEMENT TESTS

    #[tokio::test]
    async fn test_dynamic_guidelines_read_only() {
        use crate::config::types::CapabilityLevel;

        let mut config = VTCodeConfig::default();
        config.agent.system_prompt_mode = SystemPromptMode::Default;

        let mut ctx = PromptContext::default();
        ctx.add_tool("unified_search".to_string());
        ctx.capability_level = Some(CapabilityLevel::FileReading);

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), Some(&ctx)).await;

        assert!(
            result.contains("Mode: read-only"),
            "Should detect read-only mode when no edit/write/exec tools available"
        );
        assert!(
            result.contains("do not modify files"),
            "Should explain read-only constraints"
        );
    }

    #[tokio::test]
    async fn test_dynamic_guidelines_tool_preferences() {
        let config = VTCodeConfig::default();

        let mut ctx = PromptContext::default();
        ctx.add_tool("unified_exec".to_string());
        ctx.add_tool("unified_search".to_string());
        ctx.add_tool("unified_file".to_string());

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), Some(&ctx)).await;

        assert!(
            result.contains("unified_search") || result.contains("unified_file"),
            "Should suggest canonical search/file tools"
        );
    }

    #[tokio::test]
    async fn test_live_prompt_renders_workspace_language_hints() {
        let workspace = tempfile::TempDir::new().expect("workspace tempdir");
        std::fs::create_dir_all(workspace.path().join("src")).expect("create src");
        std::fs::create_dir_all(workspace.path().join("web")).expect("create web");
        std::fs::write(workspace.path().join("src/lib.rs"), "fn alpha() {}\n").expect("write rust");
        std::fs::write(workspace.path().join("web/app.ts"), "const app = 1;\n").expect("write ts");

        let config = VTCodeConfig::default();
        let ctx = PromptContext::from_workspace_tools(workspace.path(), ["unified_search"]);
        let result =
            compose_system_instruction_text(workspace.path(), Some(&config), Some(&ctx)).await;

        assert!(result.contains("## Environment"));
        assert!(result.contains("Rust, TypeScript"));
        assert!(result.contains("structural-search `lang`"));
    }

    #[tokio::test]
    async fn test_live_prompt_omits_workspace_language_hints_without_languages() {
        let workspace = tempfile::TempDir::new().expect("workspace tempdir");
        let config = VTCodeConfig::default();
        let ctx = PromptContext::from_workspace_tools(workspace.path(), ["unified_search"]);
        let result =
            compose_system_instruction_text(workspace.path(), Some(&config), Some(&ctx)).await;

        assert!(!result.contains("Languages:"));
    }

    #[tokio::test]
    async fn test_live_prompt_omits_project_docs_and_user_instructions_from_base_prompt() {
        let workspace = tempfile::TempDir::new().expect("workspace tempdir");
        std::fs::write(
            workspace.path().join("AGENTS.md"),
            "- Root summary\n\nFollow the root guidance.\n",
        )
        .expect("write agents");

        let mut config = VTCodeConfig::default();
        config.agent.user_instructions = Some("keep responses terse".to_string());
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = false;
        config.agent.instruction_max_bytes = 4096;

        let result = compose_system_instruction_text(workspace.path(), Some(&config), None).await;

        assert!(!result.contains("## AGENTS.MD INSTRUCTION HIERARCHY"));
        assert!(!result.contains("### Instruction map"));
        assert!(!result.contains("### Key points"));
        assert!(!result.contains("keep responses terse"));
        assert!(!result.contains("Root summary"));
        assert!(!result.contains("Follow the root guidance."));
    }

    #[tokio::test]
    async fn test_workspace_prompt_resources_override_base_and_keep_dynamic_sections() {
        use crate::skills::model::{SkillMetadata, SkillScope};

        let workspace = tempfile::TempDir::new().expect("workspace tempdir");
        let prompts_dir = workspace.path().join(".vtcode/prompts");
        std::fs::create_dir_all(&prompts_dir).expect("create prompts dir");
        std::fs::write(prompts_dir.join("system.md"), "# Workspace system base").expect("system");
        std::fs::write(
            prompts_dir.join("append-system.md"),
            "Workspace prompt appendix",
        )
        .expect("append");

        let mut config = VTCodeConfig::default();
        config.agent.include_temporal_context = false;
        config.agent.include_working_directory = true;

        let mut ctx = PromptContext::default();
        ctx.add_tool("unified_search".to_string());
        ctx.add_skill_metadata(SkillMetadata {
            name: "skill-creator".to_string(),
            description: "Create skills".to_string(),
            short_description: None,
            path: PathBuf::from("/tmp/skill-creator/SKILL.md"),
            scope: SkillScope::System,
            manifest: None,
        });
        ctx.set_current_directory(workspace.path().to_path_buf());

        let result =
            compose_system_instruction_text(workspace.path(), Some(&config), Some(&ctx)).await;

        assert!(result.starts_with("# Workspace system base"));
        assert!(result.contains("Workspace prompt appendix"));
        assert!(result.contains("## Active Tools"));
        assert!(result.contains("## Skills"));
        assert!(result.contains("## Environment"));

        let appendix_pos = result
            .find("Workspace prompt appendix")
            .expect("append text");
        let tools_pos = result.find("## Active Tools").expect("tools section");
        let skills_pos = result.find("## Skills").expect("skills section");
        let env_pos = result.find("## Environment").expect("environment section");

        assert!(appendix_pos < tools_pos);
        assert!(tools_pos < skills_pos);
        assert!(skills_pos < env_pos);
    }

    #[tokio::test]
    async fn test_temporal_context_inclusion() {
        let mut config = VTCodeConfig::default();
        config.agent.include_temporal_context = true;
        config.prompt_cache.cache_friendly_prompt_shaping = false;
        config.agent.temporal_context_use_utc = false; // Local time

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            result.contains("Time:"),
            "Should include temporal context when enabled"
        );
        let env_pos = result.find("## Environment");
        let temporal_pos = result.find("Time:");
        if let (Some(t), Some(e)) = (temporal_pos, env_pos) {
            assert!(
                t > e,
                "Temporal context should appear inside the environment section"
            );
        }
    }

    #[tokio::test]
    async fn test_temporal_context_utc_format() {
        let mut config = VTCodeConfig::default();
        config.agent.include_temporal_context = true;
        config.prompt_cache.cache_friendly_prompt_shaping = false;
        config.agent.temporal_context_use_utc = true; // UTC format

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            result.contains("UTC"),
            "Should indicate UTC when temporal_context_use_utc is true"
        );
        assert!(
            result.contains("T") && result.contains("Z"),
            "Should use RFC3339 format for UTC (contains T and Z)"
        );
    }

    #[tokio::test]
    async fn test_temporal_context_disabled() {
        let mut config = VTCodeConfig::default();
        config.agent.include_temporal_context = false;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            !result.contains("Time:"),
            "Should not include temporal context when disabled"
        );
    }

    #[tokio::test]
    async fn test_cache_friendly_temporal_context_stays_out_of_base_prompt() {
        let mut config = VTCodeConfig::default();
        config.agent.include_temporal_context = true;
        config.prompt_cache.cache_friendly_prompt_shaping = true;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            !result.contains("Time:"),
            "Stable system prompt should omit temporal context when cache-friendly shaping is enabled"
        );
    }

    #[tokio::test]
    async fn test_configuration_awareness_stays_behavior_focused() {
        let mut config = VTCodeConfig::default();
        config.security.human_in_the_loop = true;
        config.chat.ask_questions.enabled = false;
        config.mcp.enabled = true;
        config.ide_context.enabled = true;
        config.ide_context.inject_into_prompt = true;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(result.contains("## Environment"));
        assert!(result.contains("Interaction: approval may gate sensitive actions"));
        assert!(result.contains("request_user_input"));
        assert!(result.contains("Sources: prefer MCP"));
        assert!(!result.contains("PTY functionality"));
        assert!(!result.contains("Loop guards"));
        assert!(!result.contains(".vtcode/context/tool_outputs/"));
        assert!(!result.contains("IDE context:"));
    }

    #[tokio::test]
    async fn test_configuration_awareness_mentions_reduced_approval_when_disabled() {
        let mut config = VTCodeConfig::default();
        config.security.human_in_the_loop = false;

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(result.contains("Interaction: approval reduced by config"));
    }

    #[tokio::test]
    async fn test_default_environment_omits_default_interaction_guidance() {
        let config = VTCodeConfig::default();

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), None).await;

        assert!(
            !result.contains("Interaction:"),
            "Default-on interaction guidance should stay out of the prompt"
        );
    }

    #[tokio::test]
    async fn test_working_directory_inclusion() {
        let mut config = VTCodeConfig::default();
        config.agent.include_working_directory = true;

        let mut ctx = PromptContext::default();
        ctx.set_current_directory(PathBuf::from("/tmp/test"));

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), Some(&ctx)).await;

        assert!(
            result.contains("Working directory"),
            "Should include working directory label"
        );
        assert!(
            result.contains("/tmp/test"),
            "Should show actual directory path"
        );
        let wd_pos = result.find("Working directory");
        let env_pos = result.find("## Environment");
        if let (Some(w), Some(e)) = (wd_pos, env_pos) {
            assert!(
                w > e,
                "Working directory should appear inside the environment section"
            );
        }
    }

    #[tokio::test]
    async fn test_working_directory_disabled() {
        let mut config = VTCodeConfig::default();
        config.agent.include_working_directory = false;

        let mut ctx = PromptContext::default();
        ctx.set_current_directory(PathBuf::from("/tmp/test"));

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), Some(&ctx)).await;

        assert!(
            !result.contains("Working directory"),
            "Should not include working directory when disabled"
        );
    }

    #[tokio::test]
    async fn test_backward_compatibility() {
        let config = VTCodeConfig::default();

        // Old signature: no prompt context
        let result = compose_system_instruction_text(
            &PathBuf::from("."),
            Some(&config),
            None, // No context - backward compatible
        )
        .await;

        // Should still work without new features
        assert!(result.len() > 600, "Should generate substantial prompt");
        assert!(
            result.contains("VT Code"),
            "Should contain base prompt content"
        );
        // Should not have dynamic guidelines without context
        assert!(
            !result.contains("## Active Tools"),
            "Should not have tool guidelines without prompt context"
        );
    }

    #[tokio::test]
    async fn test_all_enhancements_combined() {
        use crate::skills::model::{SkillMetadata, SkillScope};

        let mut config = VTCodeConfig::default();
        config.agent.include_temporal_context = true;
        config.agent.include_working_directory = true;
        config.prompt_cache.cache_friendly_prompt_shaping = false;

        let mut ctx = PromptContext::default();
        ctx.add_tool("unified_file".to_string());
        ctx.add_tool("unified_search".to_string());
        ctx.infer_capability_level();
        ctx.set_current_directory(PathBuf::from("/workspace"));
        ctx.add_skill_metadata(SkillMetadata {
            name: "rust-skills".to_string(),
            description: "Rust coding guidance".to_string(),
            short_description: None,
            path: PathBuf::from("/tmp/rust-skills/SKILL.md"),
            scope: SkillScope::System,
            manifest: None,
        });

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), Some(&ctx)).await;

        // Verify all enhancements present
        assert!(
            result.contains("## Active Tools"),
            "Should have dynamic guidelines"
        );
        assert!(
            result.contains("## Skills"),
            "Should have lean skills routing"
        );
        assert!(
            result.contains("## Environment"),
            "Should have environment addenda"
        );
        assert!(result.contains("Time:"), "Should have temporal context");
        assert!(
            result.contains("Working directory"),
            "Should have working directory"
        );
        assert!(result.contains("/workspace"), "Should show workspace path");

        // Verify specific guideline for this tool set
        assert!(
            result.contains("Read before edit"),
            "Should have read-before-edit guideline"
        );
    }

    #[tokio::test]
    async fn test_prompt_layers_render_in_stable_order() {
        use crate::skills::model::{SkillMetadata, SkillScope};

        let mut config = VTCodeConfig::default();
        config.agent.include_temporal_context = true;
        config.agent.include_working_directory = true;

        let mut ctx = PromptContext::default();
        ctx.add_tool("unified_search".to_string());
        ctx.add_tool("unified_exec".to_string());
        ctx.add_skill_metadata(SkillMetadata {
            name: "skill-creator".to_string(),
            description: "Create skills".to_string(),
            short_description: None,
            path: PathBuf::from("/tmp/skill-creator/SKILL.md"),
            scope: SkillScope::System,
            manifest: None,
        });
        ctx.add_language("Rust".to_string());
        ctx.set_current_directory(PathBuf::from("/workspace"));

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), Some(&ctx)).await;

        let mode_pos = result.find("## Mode").expect("mode section");
        let tools_pos = result.find("## Active Tools").expect("tools section");
        let skills_pos = result.find("## Skills").expect("skills section");
        let env_pos = result.find("## Environment").expect("environment section");

        assert!(mode_pos < tools_pos, "mode should precede tools");
        assert!(tools_pos < skills_pos, "tools should precede skills");
        assert!(skills_pos < env_pos, "skills should precede environment");
    }

    #[tokio::test]
    async fn test_skills_section_stays_lean_and_routing_focused() {
        use crate::skills::model::SkillScope;
        use crate::skills::types::SkillManifest;

        let config = VTCodeConfig::default();
        let mut ctx = PromptContext::default();
        ctx.available_skill_metadata
            .push(crate::skills::model::SkillMetadata {
                name: "skill-creator".to_string(),
                description: "Create or update skills".to_string(),
                short_description: None,
                path: PathBuf::from("/tmp/skill-creator/SKILL.md"),
                scope: SkillScope::System,
                manifest: Some(
                    SkillManifest {
                        when_to_use: Some("Use when creating or updating a skill.".to_string()),
                        when_not_to_use: Some(
                            "Avoid for unrelated implementation work.".to_string(),
                        ),
                        ..SkillManifest::default()
                    }
                    .into(),
                ),
            });

        let result =
            compose_system_instruction_text(&PathBuf::from("."), Some(&config), Some(&ctx)).await;

        assert!(result.contains("## Skills"));
        assert!(result.contains("skill-creator: Create or update skills"));
        assert!(result.contains("Use a skill only when the user names it"));
        assert!(!result.contains("Discovery: Available skills are listed"));
        assert!(!result.contains("/tmp/skill-creator/SKILL.md"));
        assert!(!result.contains("use: Use when creating or updating a skill."));
        assert!(!result.contains("avoid: Avoid for unrelated implementation work."));
    }

    #[test]
    fn test_static_prompts_have_no_placeholders() {
        let _minimal = generate_minimal_instruction();
        let _lightweight = generate_lightweight_instruction();
        let _specialized = generate_specialized_instruction();

        let minimal_text = minimal_instruction_text();
        let lightweight_text = lightweight_instruction_text();
        let specialized_text = specialized_instruction_text();

        assert!(
            !minimal_text.contains("__UNIFIED_TOOL_GUIDANCE__"),
            "Minimal prompt has uninterpolated placeholder"
        );
        assert!(
            !lightweight_text.contains("__UNIFIED_TOOL_GUIDANCE__"),
            "Lightweight prompt has uninterpolated placeholder"
        );
        assert!(
            !specialized_text.contains("__UNIFIED_TOOL_GUIDANCE__"),
            "Specialized prompt has uninterpolated placeholder"
        );
        assert!(
            !default_system_prompt().contains("__UNIFIED_TOOL_GUIDANCE__"),
            "Default prompt has uninterpolated placeholder"
        );
    }
}
