use anyhow::{Context, Result};
use serde::de::DeserializeOwned;
use vtcode_config::{SubagentSource, SubagentSpec, builtin_subagents};
use vtcode_core::commands::init::{
    GuidedInitAnswer, GuidedInitAnswers, GuidedInitGrounding, GuidedInitOverwriteState,
    GuidedInitPlan, GuidedInitQuestion, GuidedInitQuestionKey, prepare_guided_init,
    render_agents_md, write_agents_file,
};
use vtcode_core::llm::provider::{AssistantPhase, MessageRole};
use vtcode_core::subagents::SpawnAgentRequest;
use vtcode_core::subagents::SubagentStatusEntry;
use vtcode_core::utils::ansi::MessageStyle;
use vtcode_core::{llm::provider::Message, persistent_memory::scaffold_persistent_memory};
use vtcode_tui::app::{InlineListItem, InlineListSelection, WizardModalMode, WizardStep};

use crate::agent::runloop::unified::turn::workspace::{
    bootstrap_config_files, build_workspace_index,
};
use crate::agent::runloop::unified::wizard_modal::{
    WizardModalOutcome, show_wizard_modal_and_wait,
};

use super::{SlashCommandContext, SlashCommandControl, ui};

const OVERWRITE_PROMPT_ID: &str = "overwrite_agents";
const INIT_GROUNDING_TIMEOUT_MS: u64 = 120_000;
const INIT_GROUNDING_AGENT_NAME: &str = "init-grounding-explorer";
const INIT_GROUNDING_AGENT_PROMPT: &str = r#"You are the VT Code `/init` project grounding explorer.

Inspect the repository directly and extract only the agent-facing facts needed to ground `AGENTS.md`.
Stay read-only, prefer direct repository evidence, and return concise structured results."#;
const INIT_GROUNDING_TASK: &str = r#"Inspect the current repository and ground `/init` setup.

Return JSON only with this shape:
{
  "project_summary": string | null,
  "verification_command": string | null,
  "orientation_doc": string | null,
  "critical_instruction": string | null
}

Requirements:
- Base every field on repository evidence you can inspect directly.
- `project_summary`: one concise sentence describing what this repository is for.
- `verification_command`: the best default command agents should run before claiming work complete, or null.
- `orientation_doc`: the best file path to read first for orientation, or null.
- `critical_instruction`: one repo-wide always-follow instruction, or null.
- Use null for anything that is not well supported by the repository.
- Do not include explanations, markdown fences, or extra keys."#;

pub(crate) async fn handle_initialize_workspace(
    ctx: SlashCommandContext<'_>,
    force: bool,
) -> Result<SlashCommandControl> {
    let mut ctx = ctx;
    let workspace_path = ctx.config.workspace.clone();
    let workspace_label = workspace_path.display().to_string();

    ctx.renderer.line(
        MessageStyle::Info,
        &format!(
            "Initializing VT Code configuration in {}...",
            workspace_label
        ),
    )?;

    let created_files = match bootstrap_config_files(workspace_path.clone(), force).await {
        Ok(files) => files,
        Err(err) => {
            ctx.renderer.line(
                MessageStyle::Error,
                &format!("Failed to initialize configuration: {}", err),
            )?;
            return Ok(SlashCommandControl::Continue);
        }
    };

    if created_files.is_empty() {
        ctx.renderer.line(
            MessageStyle::Info,
            "Existing configuration detected; no files were changed.",
        )?;
    } else {
        ctx.renderer.line(
            MessageStyle::Info,
            &format!(
                "Created {}: {}",
                if created_files.len() == 1 {
                    "file"
                } else {
                    "files"
                },
                created_files.join(", "),
            ),
        )?;
    }

    match prepare_guided_init(workspace_path.as_path(), force) {
        Ok(plan) => {
            let plan = match maybe_ground_project_context(&mut ctx).await {
                Ok(Some(grounding)) => plan.with_grounding(grounding),
                Ok(None) => plan,
                Err(err) => {
                    ctx.renderer.line(
                        MessageStyle::Info,
                        &format!("Skipped VT Code explorer grounding for `/init`: {}", err),
                    )?;
                    plan
                }
            };
            run_guided_agents_generation(&mut ctx, &plan).await?;
        }
        Err(err) => {
            ctx.renderer.line(
                MessageStyle::Error,
                &format!("Failed to analyze workspace for AGENTS.md: {}", err),
            )?;
        }
    }

    let persistent_memory_config = ctx
        .vt_cfg
        .as_ref()
        .map(|cfg| cfg.agent.persistent_memory.clone())
        .unwrap_or_default();
    match scaffold_persistent_memory(&persistent_memory_config, workspace_path.as_path()).await {
        Ok(Some(status)) => {
            ctx.renderer.line(
                MessageStyle::Info,
                &format!("Persistent memory: {}", status.directory.display()),
            )?;
        }
        Ok(None) => {}
        Err(err) => {
            ctx.renderer.line(
                MessageStyle::Error,
                &format!("Failed to scaffold persistent memory: {}", err),
            )?;
        }
    }

    ctx.renderer.line(
        MessageStyle::Info,
        "Indexing workspace context (this may take a moment)...",
    )?;
    match build_workspace_index(workspace_path).await {
        Ok(()) => {
            ctx.renderer.line(
                MessageStyle::Info,
                "Workspace indexing complete. Stored under .vtcode/index.",
            )?;
        }
        Err(err) => {
            ctx.renderer.line(
                MessageStyle::Error,
                &format!("Failed to index workspace: {}", err),
            )?;
        }
    }

    Ok(SlashCommandControl::Continue)
}

async fn maybe_ground_project_context(
    ctx: &mut SlashCommandContext<'_>,
) -> Result<Option<GuidedInitGrounding>> {
    let Some(controller) = ctx.tool_registry.subagent_controller() else {
        return Ok(None);
    };

    ctx.renderer.line(
        MessageStyle::Info,
        "Grounding project context with VT Code explorer subagent...",
    )?;
    let Some(spec) =
        build_init_grounding_subagent_spec(controller.effective_specs().await.as_slice())
    else {
        ctx.renderer.line(
            MessageStyle::Info,
            "VT Code explorer grounding unavailable: no read-only explorer subagent definition found.",
        )?;
        return Ok(None);
    };
    let spawned = controller
        .spawn_custom(
            spec,
            SpawnAgentRequest {
                message: Some(INIT_GROUNDING_TASK.to_owned()),
                max_turns: Some(4),
                ..SpawnAgentRequest::default()
            },
        )
        .await;

    let spawned = match spawned {
        Ok(spawned) => spawned,
        Err(err) => {
            ctx.renderer.line(
                MessageStyle::Info,
                &format!("VT Code explorer grounding unavailable: {}", err),
            )?;
            return Ok(None);
        }
    };

    let grounding = wait_for_init_grounding(ctx, &controller, &spawned).await;
    let _ = controller.close(&spawned.id).await;
    let grounding = grounding.with_context(|| format!("grounding subagent {}", spawned.id))?;
    Ok(grounding)
}

async fn wait_for_init_grounding(
    ctx: &mut SlashCommandContext<'_>,
    controller: &std::sync::Arc<vtcode_core::subagents::SubagentController>,
    spawned: &SubagentStatusEntry,
) -> Result<Option<GuidedInitGrounding>> {
    let Some(status) = controller
        .wait(
            std::slice::from_ref(&spawned.id),
            Some(INIT_GROUNDING_TIMEOUT_MS),
        )
        .await?
    else {
        ctx.renderer.line(
            MessageStyle::Info,
            "VT Code explorer grounding timed out; continuing with repository heuristics.",
        )?;
        return Ok(None);
    };

    let grounding = parse_grounding_from_status(&status)
        .or_else(|| status.error.as_deref().and_then(parse_grounding_from_text));
    let grounding = match grounding {
        Some(grounding) => Some(grounding),
        None => parse_grounding_from_snapshot(controller, &spawned.id).await,
    };
    if let Some(grounding) = grounding {
        ctx.renderer.line(
            MessageStyle::Info,
            "VT Code explorer grounding added repo context to `/init` suggestions.",
        )?;
        return Ok(Some(grounding));
    }

    let status_message = status
        .error
        .as_deref()
        .unwrap_or_else(|| status.status.as_str());
    ctx.renderer.line(
        MessageStyle::Info,
        &format!(
            "VT Code explorer grounding finished without usable structured output ({}).",
            status_message
        ),
    )?;
    Ok(None)
}

fn build_init_grounding_subagent_spec(effective_specs: &[SubagentSpec]) -> Option<SubagentSpec> {
    let mut spec = effective_specs
        .iter()
        .find(|candidate| candidate.matches_name("explorer") && candidate.is_read_only())
        .cloned()
        .or_else(|| {
            builtin_subagents()
                .into_iter()
                .find(|candidate| candidate.name == "explorer")
        })?;
    spec.name = INIT_GROUNDING_AGENT_NAME.to_string();
    spec.description =
        "VT Code explorer specialized for grounding `/init` AGENTS.md suggestions.".to_string();
    spec.prompt = if spec.prompt.trim().is_empty() {
        INIT_GROUNDING_AGENT_PROMPT.to_string()
    } else {
        format!("{}\n\n{}", spec.prompt.trim(), INIT_GROUNDING_AGENT_PROMPT)
    };
    spec.background = false;
    spec.initial_prompt = None;
    spec.nickname_candidates = vec!["init-grounding".to_string()];
    spec.aliases.clear();
    spec.source = SubagentSource::ProjectVtcode;
    spec.file_path = None;
    spec.warnings.clear();
    Some(spec)
}

fn parse_grounding_from_status(status: &SubagentStatusEntry) -> Option<GuidedInitGrounding> {
    status
        .summary
        .as_deref()
        .and_then(parse_grounding_from_text)
}

async fn parse_grounding_from_snapshot(
    controller: &std::sync::Arc<vtcode_core::subagents::SubagentController>,
    target: &str,
) -> Option<GuidedInitGrounding> {
    let snapshot = controller.snapshot_for_thread(target).await.ok()?;
    extract_grounding_from_messages(&snapshot.snapshot.messages)
}

fn extract_grounding_from_messages(messages: &[Message]) -> Option<GuidedInitGrounding> {
    messages
        .iter()
        .rev()
        .filter(|message| message.role == MessageRole::Assistant)
        .find_map(|message| {
            if message.phase == Some(AssistantPhase::FinalAnswer) || message.phase.is_none() {
                parse_grounding_from_text(message.get_text_content().as_ref())
            } else {
                None
            }
        })
}

fn parse_grounding_from_text(text: &str) -> Option<GuidedInitGrounding> {
    let grounding = parse_json_like::<GuidedInitGrounding>(text).ok()?;
    grounding.has_any().then_some(grounding)
}

fn parse_json_like<T>(text: &str) -> Result<T>
where
    T: DeserializeOwned,
{
    let trimmed = text.trim();
    if trimmed.is_empty() {
        anyhow::bail!("empty grounding payload");
    }

    if let Ok(parsed) = serde_json::from_str::<T>(trimmed) {
        return Ok(parsed);
    }

    if let Some(json_block) = extract_first_json_block(trimmed) {
        return serde_json::from_str::<T>(json_block).context("decode grounded json block");
    }

    serde_json::from_str::<T>(trimmed).context("decode grounding payload")
}

fn extract_first_json_block(text: &str) -> Option<&str> {
    let (start, opening) = text
        .char_indices()
        .find(|(_, ch)| matches!(ch, '{' | '['))?;
    let mut stack = vec![opening];
    let mut in_string = false;
    let mut escaped = false;

    for (offset, ch) in text[start + opening.len_utf8()..].char_indices() {
        if in_string {
            if escaped {
                escaped = false;
            } else if ch == '\\' {
                escaped = true;
            } else if ch == '"' {
                in_string = false;
            }
            continue;
        }

        match ch {
            '"' => in_string = true,
            '{' | '[' => stack.push(ch),
            '}' => {
                if stack.pop() != Some('{') {
                    return None;
                }
            }
            ']' => {
                if stack.pop() != Some('[') {
                    return None;
                }
            }
            _ => {}
        }

        if stack.is_empty() {
            return Some(&text[start..=start + opening.len_utf8() + offset]);
        }
    }

    None
}

async fn run_guided_agents_generation(
    ctx: &mut SlashCommandContext<'_>,
    plan: &GuidedInitPlan,
) -> Result<()> {
    let should_write = match plan.overwrite_state {
        GuidedInitOverwriteState::Skip | GuidedInitOverwriteState::Force => true,
        GuidedInitOverwriteState::Confirm => {
            if !can_use_inline_wizard(ctx, "confirming AGENTS.md overwrite")? {
                ctx.renderer.line(
                    MessageStyle::Info,
                    "AGENTS.md already exists. Re-run `/init --force` to overwrite without an interactive confirmation.",
                )?;
                false
            } else {
                match prompt_overwrite_confirmation(ctx, &plan.path).await? {
                    Some(result) => result,
                    None => {
                        ctx.renderer
                            .line(MessageStyle::Info, "AGENTS.md generation cancelled.")?;
                        false
                    }
                }
            }
        }
    };

    if !should_write {
        return Ok(());
    }

    let answers = if plan.questions.is_empty() {
        GuidedInitAnswers::default()
    } else if can_use_inline_wizard(ctx, "answering guided `/init` questions")? {
        match prompt_guided_answers(ctx, &plan.questions).await? {
            Some(answers) => answers,
            None => {
                ctx.renderer
                    .line(MessageStyle::Info, "AGENTS.md generation cancelled.")?;
                return Ok(());
            }
        }
    } else {
        ctx.renderer.line(
            MessageStyle::Info,
            "Guided `/init` questions require inline UI. Using inferred defaults for AGENTS.md.",
        )?;
        GuidedInitAnswers::default()
    };

    let content = render_agents_md(plan, &answers)?;
    let overwrite_existing = matches!(
        plan.overwrite_state,
        GuidedInitOverwriteState::Confirm | GuidedInitOverwriteState::Force
    );
    match write_agents_file(ctx.config.workspace.as_path(), &content, overwrite_existing) {
        Ok(report) => {
            ctx.renderer.line(
                MessageStyle::Info,
                &format!("AGENTS.md: {}", report.path.display()),
            )?;
        }
        Err(err) => {
            ctx.renderer.line(
                MessageStyle::Error,
                &format!("Failed to scaffold AGENTS.md: {}", err),
            )?;
        }
    }

    Ok(())
}

fn can_use_inline_wizard(ctx: &mut SlashCommandContext<'_>, activity: &str) -> Result<bool> {
    if !ctx.renderer.supports_inline_ui() {
        return Ok(false);
    }
    ui::ensure_selection_ui_available(ctx, activity)
}

async fn prompt_overwrite_confirmation(
    ctx: &mut SlashCommandContext<'_>,
    path: &std::path::Path,
) -> Result<Option<bool>> {
    let step = WizardStep {
        title: "Overwrite".to_string(),
        question: format!(
            "AGENTS.md already exists at {}. Overwrite it?",
            path.display()
        ),
        items: vec![
            InlineListItem {
                title: "1. Overwrite existing AGENTS.md".to_string(),
                subtitle: Some("Replace the file with the newly generated guidance.".to_string()),
                badge: None,
                indent: 0,
                selection: Some(InlineListSelection::RequestUserInputAnswer {
                    question_id: OVERWRITE_PROMPT_ID.to_string(),
                    selected: vec!["overwrite".to_string()],
                    other: None,
                }),
                search_value: Some("overwrite replace yes".to_string()),
            },
            InlineListItem {
                title: "2. Keep current AGENTS.md".to_string(),
                subtitle: Some(
                    "Skip AGENTS.md generation and leave the existing file untouched.".to_string(),
                ),
                badge: None,
                indent: 0,
                selection: Some(InlineListSelection::RequestUserInputAnswer {
                    question_id: OVERWRITE_PROMPT_ID.to_string(),
                    selected: vec!["keep".to_string()],
                    other: None,
                }),
                search_value: Some("keep skip no".to_string()),
            },
        ],
        completed: false,
        answer: None,
        allow_freeform: false,
        freeform_label: None,
        freeform_placeholder: None,
        freeform_default: None,
    };

    let outcome = show_wizard_modal_and_wait(
        ctx.handle,
        ctx.session,
        "AGENTS.md".to_string(),
        vec![step],
        0,
        None,
        WizardModalMode::MultiStep,
        ctx.ctrl_c_state,
        ctx.ctrl_c_notify,
    )
    .await?;

    Ok(match outcome {
        WizardModalOutcome::Submitted(selections) => {
            selections
                .into_iter()
                .find_map(|selection| match selection {
                    InlineListSelection::RequestUserInputAnswer {
                        question_id,
                        selected,
                        ..
                    } if question_id == OVERWRITE_PROMPT_ID => {
                        selected.first().map(|value| value == "overwrite")
                    }
                    _ => None,
                })
        }
        WizardModalOutcome::Cancelled { .. } => None,
    })
}

async fn prompt_guided_answers(
    ctx: &mut SlashCommandContext<'_>,
    questions: &[GuidedInitQuestion],
) -> Result<Option<GuidedInitAnswers>> {
    let steps = questions
        .iter()
        .map(build_question_step)
        .collect::<Vec<_>>();

    let outcome = show_wizard_modal_and_wait(
        ctx.handle,
        ctx.session,
        "Guided /init".to_string(),
        steps,
        0,
        None,
        WizardModalMode::MultiStep,
        ctx.ctrl_c_state,
        ctx.ctrl_c_notify,
    )
    .await?;

    Ok(match outcome {
        WizardModalOutcome::Submitted(selections) => {
            let mut answers = GuidedInitAnswers::default();
            for selection in selections {
                if let Some(answer) = selection_to_answer(selection) {
                    answers.insert(answer);
                }
            }
            Some(answers)
        }
        WizardModalOutcome::Cancelled { .. } => None,
    })
}

fn build_question_step(question: &GuidedInitQuestion) -> WizardStep {
    let mut items = question
        .options
        .iter()
        .enumerate()
        .map(|(index, option)| InlineListItem {
            title: format!(
                "{}. {}{}",
                index + 1,
                option.label,
                if option.recommended {
                    " (Recommended)"
                } else {
                    ""
                }
            ),
            subtitle: Some(option.description.clone()),
            badge: None,
            indent: 0,
            selection: Some(InlineListSelection::RequestUserInputAnswer {
                question_id: question.key.as_str().to_string(),
                selected: vec![option.value.clone()],
                other: None,
            }),
            search_value: Some(format!("{} {}", option.label, option.description)),
        })
        .collect::<Vec<_>>();

    if question.allow_custom {
        items.push(InlineListItem {
            title: format!("{}. Custom", items.len() + 1),
            subtitle: Some("Type a custom answer inline, then press Enter.".to_string()),
            badge: None,
            indent: 0,
            selection: Some(InlineListSelection::RequestUserInputAnswer {
                question_id: question.key.as_str().to_string(),
                selected: vec![],
                other: Some(String::new()),
            }),
            search_value: Some("custom freeform".to_string()),
        });
    }

    WizardStep {
        title: question.header.clone(),
        question: question.prompt.clone(),
        items,
        completed: false,
        answer: None,
        allow_freeform: question.allow_custom,
        freeform_label: question
            .allow_custom
            .then(|| question.key.custom_label().to_string()),
        freeform_placeholder: question
            .allow_custom
            .then(|| question.key.custom_placeholder().to_string()),
        freeform_default: None,
    }
}

fn selection_to_answer(selection: InlineListSelection) -> Option<GuidedInitAnswer> {
    match selection {
        InlineListSelection::RequestUserInputAnswer {
            question_id,
            selected,
            other,
        } => {
            let key = question_id.parse::<GuidedInitQuestionKey>().ok()?;
            GuidedInitAnswer::from_input(
                key,
                selected.first().map(String::as_str),
                other.as_deref(),
            )
        }
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use vtcode_core::llm::provider::{AssistantPhase, Message};

    #[test]
    fn init_grounding_subagent_spec_is_vtcode_native_and_read_only() {
        let spec = build_init_grounding_subagent_spec(&[]).expect("init grounding spec");

        assert_eq!(spec.name, INIT_GROUNDING_AGENT_NAME);
        assert_eq!(spec.source, SubagentSource::ProjectVtcode);
        assert!(spec.is_read_only());
        assert_eq!(
            spec.nickname_candidates.as_slice(),
            ["init-grounding".to_string()]
        );
        assert!(spec.prompt.contains("`/init`"));
    }

    #[test]
    fn init_grounding_subagent_spec_uses_read_only_explorer_as_base() {
        let spec = build_init_grounding_subagent_spec(&[SubagentSpec {
            name: "explorer".to_string(),
            description: "Project explorer".to_string(),
            prompt: "Use project-specific search guidance.".to_string(),
            tools: Some(vec!["unified_search".to_string()]),
            disallowed_tools: vec!["unified_file".to_string()],
            model: Some("inherit".to_string()),
            color: Some("green".to_string()),
            reasoning_effort: Some("medium".to_string()),
            permission_mode: Some(vtcode_core::config::PermissionMode::Plan),
            skills: vec!["repo-skill".to_string()],
            mcp_servers: Vec::new(),
            hooks: None,
            background: true,
            max_turns: Some(9),
            nickname_candidates: vec!["repo".to_string()],
            initial_prompt: Some("ignored".to_string()),
            memory: None,
            isolation: None,
            aliases: vec!["explore".to_string()],
            source: SubagentSource::ProjectVtcode,
            file_path: None,
            warnings: vec!["warning".to_string()],
        }])
        .expect("init grounding spec");

        assert_eq!(spec.model.as_deref(), Some("inherit"));
        assert_eq!(spec.skills.as_slice(), ["repo-skill".to_string()]);
        assert!(!spec.background);
        assert_eq!(spec.initial_prompt, None);
        assert!(
            spec.prompt
                .contains("Use project-specific search guidance.")
        );
        assert!(spec.prompt.contains("`/init`"));
    }

    #[test]
    fn parse_grounding_from_text_accepts_fenced_json() {
        let grounding = parse_grounding_from_text(
            r#"
Before the payload:
```json
{
  "project_summary": "Terminal-first coding agent for repository work.",
  "verification_command": "cargo nextest run",
  "orientation_doc": "docs/ARCHITECTURE.md",
  "critical_instruction": null
}
```
"#,
        )
        .expect("grounding");

        assert_eq!(
            grounding.project_summary.as_deref(),
            Some("Terminal-first coding agent for repository work.")
        );
        assert_eq!(
            grounding.verification_command.as_deref(),
            Some("cargo nextest run")
        );
    }

    #[test]
    fn extract_grounding_from_messages_prefers_final_assistant_answer() {
        let messages = vec![
            Message::assistant("working".to_owned()).with_phase(Some(AssistantPhase::Commentary)),
            Message::assistant(
                r#"{"project_summary":"Grounded summary","verification_command":null,"orientation_doc":null,"critical_instruction":null}"#
                    .to_owned(),
            )
            .with_phase(Some(AssistantPhase::FinalAnswer)),
        ];

        let grounding = extract_grounding_from_messages(&messages).expect("grounding");
        assert_eq!(
            grounding.project_summary.as_deref(),
            Some("Grounded summary")
        );
    }
}
