use std::env;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use chrono::Utc;
use serde_json::Value;
use serde_json::json;

use crate::agent::ConversationTurnControlChannels;
use crate::app;
use crate::mentat_policy::{
    MentatDecision, MentatSelfReflectionContext, build_self_reflection_prompt,
    parse_self_reflection_output,
};
use crate::model::{
    SessionAgentRole, SessionFile, SessionImprovementRecord, SessionImprovementStatus,
    SessionMentatPhase, SessionMentatState, SessionMentatStatus, SessionMentatTarget,
    SessionReflectionRecord, SessionTranscriptEntry, SessionTranscriptEntryKind,
};
use crate::storage::{AppResult, Storage, timestamp};

#[derive(Debug, Clone)]
pub struct SessionTurnOutcome {
    pub answer: Option<String>,
    pub mentat: Option<SessionMentatState>,
    pub completed_agent_role: Option<SessionAgentRole>,
    pub cancelled: bool,
}

#[derive(Debug, Clone)]
pub struct SessionController {
    storage: Storage,
}

impl SessionController {
    pub fn new(storage: Storage) -> Self {
        Self { storage }
    }

    pub fn create_session(&self, cwd: PathBuf) -> AppResult<SessionFile> {
        self.storage.create_session(cwd)
    }

    pub fn submit_input(&self, session_id: &str, input: &str) -> AppResult<SessionTurnOutcome> {
        self.submit_input_with_cancel(session_id, input, None)
    }

    pub fn submit_input_with_cancel(
        &self,
        session_id: &str,
        input: &str,
        cancel_requested: Option<Arc<AtomicBool>>,
    ) -> AppResult<SessionTurnOutcome> {
        self.submit_input_with_cancel_and_raw_event_path(session_id, input, cancel_requested, None)
    }

    pub fn submit_input_with_cancel_and_raw_event_path(
        &self,
        session_id: &str,
        input: &str,
        cancel_requested: Option<Arc<AtomicBool>>,
        raw_event_path: Option<&Path>,
    ) -> AppResult<SessionTurnOutcome> {
        self.submit_input_with_cancel_and_raw_event_path_and_control(
            session_id,
            input,
            cancel_requested,
            raw_event_path,
            None,
        )
    }

    pub fn submit_input_with_cancel_and_raw_event_path_and_control(
        &self,
        session_id: &str,
        input: &str,
        cancel_requested: Option<Arc<AtomicBool>>,
        raw_event_path: Option<&Path>,
        control: Option<ConversationTurnControlChannels>,
    ) -> AppResult<SessionTurnOutcome> {
        let mut session = self.storage.load_session(session_id)?;
        self.storage.append_user_message(session_id, input)?;

        if looks_like_explicit_mentat_request(input) {
            let mentat = SessionMentatState {
                status: SessionMentatStatus::Intervening,
                phase: Some(SessionMentatPhase::Planning),
                target: Some(mentat_target_from_input(&session, input)),
                source: "explicit".to_string(),
            };
            session.foreground_turn_id = None;
            session.mentat = Some(mentat.clone());
            session.updated_at = timestamp();
            self.storage.save_session(&session)?;
            self.storage.append_agent_message(
                session_id,
                SessionAgentRole::Mentat,
                "Mentat planning...",
            )?;
            return Ok(SessionTurnOutcome {
                answer: None,
                mentat: Some(mentat),
                completed_agent_role: None,
                cancelled: false,
            });
        }

        let turn_id = format!("turn-{}", Utc::now().format("%Y%m%dT%H%M%S%3fZ"));
        session.foreground_turn_id = Some(turn_id);
        session.updated_at = timestamp();
        self.storage.save_session(&session)?;

        let raw_event_path = raw_event_path.map(Path::to_path_buf).unwrap_or_else(|| {
            self.storage.session_dir(session_id).join(format!(
                "conversation-{}.ndjson",
                Utc::now().format("%Y%m%dT%H%M%S%3fZ")
            ))
        });
        let answer = match app::direct_answer_with_control(
            &self.storage,
            &session.cwd,
            input,
            &raw_event_path,
            cancel_requested.clone(),
            control,
        ) {
            Ok(answer) => Some(answer),
            Err(error)
                if is_canceled_error(error.as_ref())
                    && is_cancellation_requested(&cancel_requested) =>
            {
                None
            }
            Err(error) => return Err(error),
        };
        let canceled = answer.is_none() && is_cancellation_requested(&cancel_requested);

        for entry in persisted_tool_transcript_entries(&raw_event_path, SessionAgentRole::Worker)? {
            self.storage.append_session_transcript_entry(
                session_id,
                entry.kind,
                entry.agent_role,
                &entry.content,
            )?;
        }
        let persisted_answer = match answer.clone() {
            Some(answer) => Some(answer),
            None => persisted_assistant_message_text(&raw_event_path)?,
        };
        if let Some(answer) = persisted_answer.as_deref()
            && !answer.trim().is_empty()
        {
            self.storage
                .append_agent_message(session_id, SessionAgentRole::Worker, answer)?;
        }
        let mut updated_session = self.storage.load_session(session_id)?;
        updated_session.foreground_turn_id = None;
        updated_session.updated_at = timestamp();
        self.storage.save_session(&updated_session)?;
        Ok(SessionTurnOutcome {
            answer: persisted_answer.clone(),
            mentat: updated_session.mentat.clone(),
            completed_agent_role: persisted_answer
                .as_ref()
                .filter(|value| !value.trim().is_empty())
                .map(|_| SessionAgentRole::Worker),
            cancelled: canceled,
        })
    }

    pub fn cancel_mentat_intervention(&self, session_id: &str) -> AppResult<()> {
        let mut session = self.storage.load_session(session_id)?;
        session.mentat = None;
        session.updated_at = timestamp();
        self.storage.save_session(&session)
    }

    pub fn begin_auto_self_reflection(
        &self,
        session_id: &str,
        input: &str,
    ) -> AppResult<Option<SessionMentatState>> {
        let mut session = self.storage.load_session(session_id)?;
        if session.mentat.is_some() {
            return Ok(None);
        }

        let trimmed = input.trim();
        if trimmed.is_empty() {
            return Ok(None);
        }

        let mentat = SessionMentatState {
            status: SessionMentatStatus::Intervening,
            phase: Some(SessionMentatPhase::SelfReflection),
            target: Some(mentat_target_from_input(&session, trimmed)),
            source: "auto".to_string(),
        };
        session.mentat = Some(mentat.clone());
        session.updated_at = timestamp();
        self.storage.save_session(&session)?;
        self.storage.append_agent_message(
            session_id,
            SessionAgentRole::Mentat,
            "Mentat self reflection...",
        )?;
        Ok(Some(mentat))
    }

    pub fn reject_session_improvement(
        &self,
        session_id: &str,
        improvement_id: &str,
    ) -> AppResult<()> {
        let mut improvement = self
            .storage
            .load_session_improvement(session_id, improvement_id)?;
        if improvement.status != SessionImprovementStatus::Proposed {
            return Err(io::Error::other(format!(
                "improvement {improvement_id} is not actionable (current status: {})",
                improvement.status.as_str()
            ))
            .into());
        }
        improvement.status = SessionImprovementStatus::Rejected;
        self.storage
            .update_session_improvement(session_id, &improvement)
    }

    pub fn run_mentat_reflection_with_cancel_and_raw_event_path(
        &self,
        session_id: &str,
        worker_raw_event_path: Option<&Path>,
        cancel_requested: Option<Arc<AtomicBool>>,
        raw_event_path: &Path,
    ) -> AppResult<String> {
        let mut session = self.storage.load_session(session_id)?;
        let target = session
            .mentat_target()
            .cloned()
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no Mentat target"))?;

        if let Some(mentat) = session.mentat.as_mut() {
            mentat.phase = Some(SessionMentatPhase::Planning);
        }
        session.updated_at = timestamp();
        self.storage.save_session(&session)?;

        let prompt = build_mentat_reflection_prompt(
            &self.storage,
            session_id,
            &session,
            &target,
            worker_raw_event_path,
        )?;
        let answer = match app::direct_reflection(
            &self.storage,
            &session.cwd,
            &prompt,
            raw_event_path,
            cancel_requested.clone(),
        ) {
            Ok(answer) => Some(answer),
            Err(error)
                if is_canceled_error(error.as_ref())
                    && is_cancellation_requested(&cancel_requested) =>
            {
                None
            }
            Err(error) => {
                let _ = persist_session_runtime_output(
                    &self.storage,
                    session_id,
                    raw_event_path,
                    SessionAgentRole::Mentat,
                );
                let mut failed_session = self.storage.load_session(session_id)?;
                failed_session.mentat = None;
                failed_session.updated_at = timestamp();
                self.storage.save_session(&failed_session)?;
                return Err(error);
            }
        };

        let persisted_answer = persist_session_runtime_output(
            &self.storage,
            session_id,
            raw_event_path,
            SessionAgentRole::Mentat,
        )?
        .or(answer);

        let cancelled = persisted_answer.is_none() && is_cancellation_requested(&cancel_requested);
        if !cancelled
            && let Some(content) = persisted_answer.as_ref().map(|value| value.trim())
            && !content.is_empty()
        {
            let reflection_id = format!("reflection-{}", Utc::now().format("%Y%m%dT%H%M%S%3fZ"));
            let reflection = SessionReflectionRecord {
                reflection_id: reflection_id.clone(),
                session_id: session_id.to_string(),
                created_at: timestamp(),
                reason: session
                    .mentat
                    .as_ref()
                    .map(|mentat| mentat.source.clone())
                    .unwrap_or_else(|| "explicit".to_string()),
                goal: target.clone(),
                content: content.to_string(),
            };
            self.storage
                .save_session_reflection(session_id, &reflection)?;
            if let Some(improvement) = parse_session_improvement(content) {
                self.storage.save_session_improvement(
                    session_id,
                    &SessionImprovementRecord {
                        improvement_id: format!(
                            "improvement-{}",
                            Utc::now().format("%Y%m%dT%H%M%S%3fZ")
                        ),
                        reflection_id,
                        session_id: session_id.to_string(),
                        created_at: timestamp(),
                        goal: target.clone(),
                        proposed_change: improvement.proposed_change,
                        expected_benefit: improvement.expected_benefit,
                        adoption_hint: improvement.adoption_hint,
                        status: SessionImprovementStatus::Proposed,
                    },
                )?;
            }
        }
        let mut updated_session = self.storage.load_session(session_id)?;
        updated_session.mentat = None;
        updated_session.updated_at = timestamp();
        self.storage.save_session(&updated_session)?;

        if cancelled {
            Ok("Mentat reflection interrupted".to_string())
        } else {
            Ok("Mentat reflection complete".to_string())
        }
    }

    pub fn run_mentat_self_reflection_with_cancel_and_raw_event_path(
        &self,
        session_id: &str,
        cancel_requested: Option<Arc<AtomicBool>>,
        raw_event_path: &Path,
    ) -> AppResult<String> {
        let mut session = self.storage.load_session(session_id)?;
        let target = session
            .mentat_target()
            .cloned()
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no Mentat target"))?;

        if let Some(mentat) = session.mentat.as_mut() {
            mentat.phase = Some(SessionMentatPhase::SelfReflection);
        }
        session.updated_at = timestamp();
        self.storage.save_session(&session)?;

        let prompt = build_self_reflection_prompt(&MentatSelfReflectionContext {
            raw_user_request: target.raw_user_request.clone(),
            working_dir: session.cwd.clone(),
            recent_transcript: load_recent_session_transcript(&self.storage, session_id)?,
            worker_final_reply: latest_worker_reply(&self.storage, session_id)?,
        })?;
        let answer = match app::direct_self_reflection(
            &self.storage,
            &session.cwd,
            &prompt,
            raw_event_path,
            cancel_requested.clone(),
        ) {
            Ok(answer) => Some(answer),
            Err(error)
                if is_canceled_error(error.as_ref())
                    && is_cancellation_requested(&cancel_requested) =>
            {
                None
            }
            Err(error) => {
                let _ = persist_filtered_session_runtime_output(
                    &self.storage,
                    session_id,
                    raw_event_path,
                    SessionAgentRole::Mentat,
                    |content| {
                        parse_self_reflection_output(content)
                            .map(|parsed| parsed.display_text)
                            .unwrap_or_else(|_| content.trim().to_string())
                    },
                );
                let mut failed_session = self.storage.load_session(session_id)?;
                failed_session.mentat = None;
                failed_session.updated_at = timestamp();
                self.storage.save_session(&failed_session)?;
                return Err(error);
            }
        };

        let raw_answer = answer.clone();
        let persisted_raw_answer = persisted_assistant_message_text(raw_event_path)?;
        let persisted_answer = persist_filtered_session_runtime_output(
            &self.storage,
            session_id,
            raw_event_path,
            SessionAgentRole::Mentat,
            |content| {
                parse_self_reflection_output(content)
                    .map(|parsed| parsed.display_text)
                    .unwrap_or_else(|_| content.trim().to_string())
            },
        )?
        .or(raw_answer.clone());
        let cancelled = persisted_answer.is_none() && is_cancellation_requested(&cancel_requested);
        let parsed_decision = raw_answer
            .as_deref()
            .or(persisted_raw_answer.as_deref())
            .map(parse_self_reflection_output)
            .transpose()?;

        let mut updated_session = self.storage.load_session(session_id)?;
        if cancelled {
            updated_session.mentat = None;
        } else if let Some(parsed) = parsed_decision {
            match parsed.decision {
                MentatDecision::Ignore => {
                    updated_session.mentat = None;
                }
                MentatDecision::Review => {
                    if let Some(mentat) = updated_session.mentat.as_mut() {
                        mentat.phase = Some(SessionMentatPhase::Planning);
                    }
                }
            }
        } else {
            updated_session.mentat = None;
        }
        updated_session.updated_at = timestamp();
        self.storage.save_session(&updated_session)?;

        if cancelled {
            Ok("Mentat self reflection interrupted".to_string())
        } else {
            Ok("Mentat self reflection complete".to_string())
        }
    }

    pub fn adopt_session_improvement_with_cancel_and_raw_event_path(
        &self,
        session_id: &str,
        improvement_id: &str,
        cancel_requested: Option<Arc<AtomicBool>>,
        raw_event_path: &Path,
    ) -> AppResult<String> {
        let session = self.storage.load_session(session_id)?;
        let mut improvement = self
            .storage
            .load_session_improvement(session_id, improvement_id)?;
        if improvement.status != SessionImprovementStatus::Proposed {
            return Err(io::Error::other(format!(
                "improvement {improvement_id} is not actionable (current status: {})",
                improvement.status.as_str()
            ))
            .into());
        }

        improvement.status = SessionImprovementStatus::Adopting;
        self.storage
            .update_session_improvement(session_id, &improvement)?;

        let prompt =
            build_mentat_adoption_prompt(&self.storage, session_id, &session, &improvement)?;
        let answer = match app::direct_improvement_adoption(
            &self.storage,
            &session.cwd,
            &prompt,
            raw_event_path,
            cancel_requested.clone(),
        ) {
            Ok(answer) => Some(answer),
            Err(error)
                if is_canceled_error(error.as_ref())
                    && is_cancellation_requested(&cancel_requested) =>
            {
                None
            }
            Err(error) => {
                let _ = persist_session_runtime_output(
                    &self.storage,
                    session_id,
                    raw_event_path,
                    SessionAgentRole::Mentat,
                );
                improvement.status = SessionImprovementStatus::Failed;
                self.storage
                    .update_session_improvement(session_id, &improvement)?;
                return Err(error);
            }
        };

        let persisted_answer = persist_session_runtime_output(
            &self.storage,
            session_id,
            raw_event_path,
            SessionAgentRole::Mentat,
        )?
        .or(answer);
        if persisted_answer.is_none() && is_cancellation_requested(&cancel_requested) {
            improvement.status = SessionImprovementStatus::Proposed;
            self.storage
                .update_session_improvement(session_id, &improvement)?;
            return Ok("Mentat improvement adoption interrupted".to_string());
        }

        improvement.status = SessionImprovementStatus::Adopted;
        self.storage
            .update_session_improvement(session_id, &improvement)?;
        Ok("Mentat improvement adoption complete".to_string())
    }
}

pub fn run_session(storage: Storage, session_id: Option<&str>) -> AppResult<String> {
    let controller = SessionController::new(storage.clone());
    let session = match session_id {
        Some(existing_id) => storage.load_session(existing_id)?,
        None => controller.create_session(env::current_dir()?)?,
    };
    Ok(format!("session\t{}\n", session.session_id))
}

#[derive(Debug, Clone)]
struct PersistedToolCall {
    item_id: String,
    title: String,
    preview: Option<String>,
    status_line: String,
    output_preview: Option<String>,
    finished: bool,
}

#[derive(Debug, Clone)]
enum PersistedToolEvent {
    ToolUpdated(PersistedToolCall),
    ToolOutputDelta { item_id: String, delta: String },
}

#[derive(Debug, Clone)]
enum PersistedAssistantEvent {
    Delta { item_id: String, delta: String },
    Completed { item_id: String, text: String },
}

fn persisted_tool_transcript_entries(
    raw_event_path: &Path,
    agent_role: SessionAgentRole,
) -> AppResult<Vec<SessionTranscriptEntry>> {
    if !raw_event_path.exists() {
        return Ok(Vec::new());
    }

    let content = fs::read_to_string(raw_event_path)?;
    let mut tools = Vec::<PersistedToolCall>::new();
    for line in content.lines() {
        let Some(event) = parse_persisted_tool_event(line) else {
            continue;
        };
        match event {
            PersistedToolEvent::ToolUpdated(tool) => {
                if let Some(existing) = tools
                    .iter_mut()
                    .find(|existing| existing.item_id == tool.item_id)
                {
                    *existing = tool;
                } else {
                    tools.push(tool);
                }
            }
            PersistedToolEvent::ToolOutputDelta { item_id, delta } => {
                let preview = compact_output_preview(&delta);
                if preview.is_empty() {
                    continue;
                }
                if let Some(tool) = tools
                    .iter_mut()
                    .find(|existing| existing.item_id == item_id)
                {
                    tool.output_preview = Some(match &tool.output_preview {
                        Some(existing) if !existing.is_empty() => {
                            format!("{existing} {preview}").trim().to_string()
                        }
                        _ => preview,
                    });
                }
            }
        }
    }

    let mut entries = Vec::new();
    for tool in tools {
        let mut body_lines = vec![format!("Tool: {}", tool.title)];
        if let Some(preview) = tool.preview {
            body_lines.push(preview);
        }
        body_lines.push(tool.status_line);
        if let Some(output_preview) = tool.output_preview {
            body_lines.push(format!("Output: {output_preview}"));
        }
        entries.push(SessionTranscriptEntry {
            timestamp: timestamp(),
            kind: SessionTranscriptEntryKind::ToolEvent,
            agent_role: Some(agent_role),
            content: body_lines.join("\n"),
        });
        if tool.finished {
            entries.push(SessionTranscriptEntry {
                timestamp: timestamp(),
                kind: SessionTranscriptEntryKind::Divider,
                agent_role: None,
                content: TOOL_DIVIDER.to_string(),
            });
        }
    }
    Ok(entries)
}

fn persisted_assistant_message_text(raw_event_path: &Path) -> AppResult<Option<String>> {
    if !raw_event_path.exists() {
        return Ok(None);
    }

    let content = fs::read_to_string(raw_event_path)?;
    let mut items = Vec::<(String, String)>::new();
    for line in content.lines() {
        let Some(event) = parse_persisted_assistant_event(line) else {
            continue;
        };
        match event {
            PersistedAssistantEvent::Delta { item_id, delta } => {
                append_assistant_item_text(&mut items, &item_id, &delta, false);
            }
            PersistedAssistantEvent::Completed { item_id, text } => {
                append_assistant_item_text(&mut items, &item_id, &text, true);
            }
        }
    }

    let combined = items
        .into_iter()
        .map(|(_, text)| text.trim().to_string())
        .filter(|text| !text.is_empty())
        .collect::<Vec<_>>()
        .join("\n\n");
    if combined.trim().is_empty() {
        Ok(None)
    } else {
        Ok(Some(combined))
    }
}

fn parse_persisted_tool_event(line: &str) -> Option<PersistedToolEvent> {
    let message: Value = serde_json::from_str(line).ok()?;
    let method = message.get("method")?.as_str()?;
    let params = message.get("params")?;

    match method {
        "item/commandExecution/outputDelta" => Some(PersistedToolEvent::ToolOutputDelta {
            item_id: params.get("itemId")?.as_str()?.to_string(),
            delta: params.get("delta")?.as_str()?.to_string(),
        }),
        "item/started" | "item/updated" => parse_persisted_tool_item(params.get("item")?, false)
            .map(PersistedToolEvent::ToolUpdated),
        "item/completed" => parse_persisted_tool_item(params.get("item")?, true)
            .map(PersistedToolEvent::ToolUpdated),
        _ => None,
    }
}

fn parse_persisted_assistant_event(line: &str) -> Option<PersistedAssistantEvent> {
    let message: Value = serde_json::from_str(line).ok()?;
    let method = message.get("method")?.as_str()?;
    let params = message.get("params")?;

    match method {
        "item/agentMessage/delta" => Some(PersistedAssistantEvent::Delta {
            item_id: params.get("itemId")?.as_str()?.to_string(),
            delta: params.get("delta")?.as_str()?.to_string(),
        }),
        "item/completed" => {
            let item = params.get("item")?;
            if item.get("type").and_then(Value::as_str) != Some("agentMessage") {
                return None;
            }
            Some(PersistedAssistantEvent::Completed {
                item_id: item.get("id")?.as_str()?.to_string(),
                text: item.get("text")?.as_str()?.to_string(),
            })
        }
        _ => None,
    }
}

fn append_assistant_item_text(
    items: &mut Vec<(String, String)>,
    item_id: &str,
    text: &str,
    replace: bool,
) {
    if let Some((_, existing)) = items
        .iter_mut()
        .find(|(existing_id, _)| existing_id == item_id)
    {
        if replace {
            *existing = text.to_string();
        } else {
            existing.push_str(text);
        }
        return;
    }

    items.push((item_id.to_string(), text.to_string()));
}

fn parse_persisted_tool_item(item: &Value, completed: bool) -> Option<PersistedToolCall> {
    let item_id = item.get("id")?.as_str()?.to_string();
    let item_type = item.get("type")?.as_str()?;
    if item_type == "agentMessage" {
        return None;
    }
    match item_type {
        "commandExecution" => {
            let command = item.get("command")?.as_str()?.to_string();
            let status = item
                .get("status")
                .and_then(Value::as_str)
                .unwrap_or(if completed { "completed" } else { "inProgress" });
            let exit_code = item.get("exitCode").and_then(Value::as_i64);
            let duration_ms = item.get("durationMs").and_then(Value::as_i64);
            Some(PersistedToolCall {
                item_id,
                title: "exec_command".to_string(),
                preview: Some(command),
                status_line: format_tool_status(status, exit_code, duration_ms),
                output_preview: None,
                finished: completed || status != "inProgress",
            })
        }
        "fileChange" => {
            let change_count = item
                .get("changes")
                .and_then(Value::as_array)
                .map(Vec::len)
                .unwrap_or(0);
            let status = item
                .get("status")
                .and_then(Value::as_str)
                .unwrap_or(if completed { "completed" } else { "inProgress" });
            Some(PersistedToolCall {
                item_id,
                title: "apply_patch".to_string(),
                preview: Some(format!("{change_count} change(s)")),
                status_line: format_simple_tool_status(status, None),
                output_preview: None,
                finished: completed || status != "inProgress",
            })
        }
        "mcpToolCall" => {
            let server = item.get("server").and_then(Value::as_str).unwrap_or("mcp");
            let tool = item.get("tool").and_then(Value::as_str).unwrap_or("tool");
            let args = item.get("arguments").map(compact_json_value);
            let status = item
                .get("status")
                .and_then(Value::as_str)
                .unwrap_or(if completed { "completed" } else { "inProgress" });
            Some(PersistedToolCall {
                item_id,
                title: format!("{server}.{tool}"),
                preview: args,
                status_line: format_simple_tool_status(
                    status,
                    item.get("durationMs").and_then(Value::as_i64),
                ),
                output_preview: item.get("result").map(compact_json_value),
                finished: completed || status != "inProgress",
            })
        }
        "dynamicToolCall" => {
            let tool = item.get("tool").and_then(Value::as_str).unwrap_or("tool");
            let args = item.get("arguments").map(compact_json_value);
            let status = item
                .get("status")
                .and_then(Value::as_str)
                .unwrap_or(if completed { "completed" } else { "inProgress" });
            Some(PersistedToolCall {
                item_id,
                title: tool.to_string(),
                preview: args,
                status_line: format_simple_tool_status(
                    status,
                    item.get("durationMs").and_then(Value::as_i64),
                ),
                output_preview: item.get("contentItems").map(compact_json_value),
                finished: completed || status != "inProgress",
            })
        }
        "collabAgentToolCall" => {
            let tool = item
                .get("tool")
                .and_then(Value::as_str)
                .unwrap_or("spawnAgent");
            Some(PersistedToolCall {
                item_id,
                title: tool.to_string(),
                preview: item
                    .get("prompt")
                    .and_then(Value::as_str)
                    .map(|value| value.to_string()),
                status_line: format_simple_tool_status(
                    item.get("status")
                        .and_then(Value::as_str)
                        .unwrap_or(if completed { "completed" } else { "inProgress" }),
                    None,
                ),
                output_preview: None,
                finished: completed
                    || item.get("status").and_then(Value::as_str) != Some("inProgress"),
            })
        }
        "webSearch" => Some(PersistedToolCall {
            item_id,
            title: "web_search".to_string(),
            preview: item
                .get("query")
                .and_then(Value::as_str)
                .map(|value| value.to_string()),
            status_line: if completed {
                "Completed".to_string()
            } else {
                "Running".to_string()
            },
            output_preview: None,
            finished: completed,
        }),
        "imageView" => Some(PersistedToolCall {
            item_id,
            title: "view_image".to_string(),
            preview: item
                .get("path")
                .and_then(Value::as_str)
                .map(|value| value.to_string()),
            status_line: if completed {
                "Completed".to_string()
            } else {
                "Running".to_string()
            },
            output_preview: None,
            finished: completed,
        }),
        _ => {
            let status = item
                .get("status")
                .and_then(Value::as_str)
                .unwrap_or(if completed { "completed" } else { "inProgress" });
            Some(PersistedToolCall {
                item_id,
                title: generic_tool_title(item, item_type),
                preview: generic_tool_preview(item),
                status_line: format_simple_tool_status(
                    status,
                    item.get("durationMs").and_then(Value::as_i64),
                ),
                output_preview: generic_tool_output_preview(item),
                finished: completed || status != "inProgress",
            })
        }
    }
}

fn is_cancellation_requested(cancel_requested: &Option<Arc<AtomicBool>>) -> bool {
    cancel_requested
        .as_ref()
        .is_some_and(|flag| flag.load(Ordering::Relaxed))
}

fn is_canceled_error(error: &(dyn std::error::Error + 'static)) -> bool {
    error
        .downcast_ref::<io::Error>()
        .is_some_and(|io_error| io_error.kind() == io::ErrorKind::Interrupted)
}

fn generic_tool_title(item: &Value, fallback_type: &str) -> String {
    item.get("title")
        .and_then(Value::as_str)
        .or_else(|| item.get("label").and_then(Value::as_str))
        .or_else(|| item.get("tool").and_then(Value::as_str))
        .map(|value| value.to_string())
        .unwrap_or_else(|| humanize_item_type(fallback_type))
}

fn generic_tool_preview(item: &Value) -> Option<String> {
    first_string_field(
        item,
        &["command", "query", "path", "prompt", "input", "url"],
    )
    .or_else(|| first_json_field(item, &["arguments", "pattern", "targets", "commandActions"]))
}

fn generic_tool_output_preview(item: &Value) -> Option<String> {
    first_json_field(item, &["result", "contentItems", "output", "matches"])
}

fn first_string_field(item: &Value, keys: &[&str]) -> Option<String> {
    keys.iter().find_map(|key| {
        item.get(*key)
            .and_then(Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(|value| value.to_string())
    })
}

fn first_json_field(item: &Value, keys: &[&str]) -> Option<String> {
    keys.iter().find_map(|key| {
        item.get(*key).and_then(|value| {
            let rendered = compact_json_value(value);
            (!rendered.is_empty() && rendered != "null").then_some(rendered)
        })
    })
}

fn humanize_item_type(value: &str) -> String {
    let mut rendered = String::new();
    let mut previous_is_lower = false;
    for ch in value.chars() {
        if ch == '_' || ch == '-' {
            if !rendered.ends_with(' ') {
                rendered.push(' ');
            }
            previous_is_lower = false;
            continue;
        }
        if ch.is_ascii_uppercase() && previous_is_lower {
            rendered.push(' ');
        }
        if rendered.is_empty() {
            rendered.push(ch.to_ascii_uppercase());
        } else {
            rendered.push(ch);
        }
        previous_is_lower = ch.is_ascii_lowercase() || ch.is_ascii_digit();
    }
    rendered
}

fn format_tool_status(status: &str, exit_code: Option<i64>, duration_ms: Option<i64>) -> String {
    let mut parts = vec![humanize_tool_status(status).to_string()];
    if let Some(exit_code) = exit_code {
        parts.push(format!("exit {exit_code}"));
    }
    if let Some(duration_ms) = duration_ms {
        parts.push(format!("{duration_ms}ms"));
    }
    parts.join(" · ")
}

fn format_simple_tool_status(status: &str, duration_ms: Option<i64>) -> String {
    let mut parts = vec![humanize_tool_status(status).to_string()];
    if let Some(duration_ms) = duration_ms {
        parts.push(format!("{duration_ms}ms"));
    }
    parts.join(" · ")
}

fn humanize_tool_status(status: &str) -> &str {
    match status {
        "inProgress" => "Running",
        "completed" => "Completed",
        "failed" => "Failed",
        "declined" => "Declined",
        _ => status,
    }
}

fn compact_json_value(value: &Value) -> String {
    serde_json::to_string(value).unwrap_or_else(|_| String::new())
}

fn compact_output_preview(delta: &str) -> String {
    delta
        .lines()
        .map(str::trim)
        .filter(|line| !line.is_empty())
        .last()
        .unwrap_or("")
        .to_string()
}

const TOOL_DIVIDER: &str = "--------------------------------";

pub fn looks_like_explicit_mentat_request(input: &str) -> bool {
    let trimmed = input.trim();
    if trimmed.is_empty() {
        return false;
    }

    let lower = trimmed.to_ascii_lowercase();
    if lower.contains("mentat") {
        const ENGLISH_SIGNALS: &[&str] = &[
            "review",
            "reflect",
            "reflection",
            "supervise",
            "supervision",
            "intervene",
            "self-reflection",
            "self reflection",
        ];
        if lower.starts_with("mentat")
            || ENGLISH_SIGNALS.iter().any(|signal| lower.contains(signal))
        {
            return true;
        }
    }

    const EXPLICIT_CJK_SIGNALS: &[&str] = &["自省", "反思", "审查", "介入"];
    EXPLICIT_CJK_SIGNALS
        .iter()
        .any(|signal| trimmed.contains(signal))
}

fn mentat_target_from_input(session: &SessionFile, input: &str) -> SessionMentatTarget {
    SessionMentatTarget {
        raw_user_request: input.trim().to_string(),
        goal: infer_goal_from_user_message(input),
        spec_path: default_spec_path(&session.cwd),
    }
}

fn build_mentat_reflection_prompt(
    storage: &Storage,
    session_id: &str,
    session: &SessionFile,
    target: &SessionMentatTarget,
    worker_raw_event_path: Option<&Path>,
) -> AppResult<String> {
    let recent_transcript = load_recent_session_transcript(storage, session_id)?;
    let worker_live_response = worker_raw_event_path
        .map(persisted_assistant_message_text)
        .transpose()?
        .flatten();
    let worker_live_tools = worker_raw_event_path
        .map(|path| persisted_tool_transcript_entries(path, SessionAgentRole::Worker))
        .transpose()?
        .unwrap_or_default()
        .into_iter()
        .filter(|entry| entry.kind == SessionTranscriptEntryKind::ToolEvent)
        .map(|entry| format!("worker_tool: {}", entry.content))
        .collect::<Vec<_>>();

    let request = json!({
        "session_id": session_id,
        "reason": session
            .mentat
            .as_ref()
            .map(|mentat| mentat.source.clone())
            .unwrap_or_else(|| "explicit".to_string()),
        "working_dir": session.cwd,
        "goal": {
            "raw_user_request": target.raw_user_request,
            "goal": target.goal,
            "spec_path": target.spec_path,
        },
        "recent_transcript": recent_transcript,
        "worker_live_response": worker_live_response,
        "worker_live_tools": worker_live_tools,
    });

    Ok(format!(
        concat!(
            "You are Mentat, the reflective agent for this session.\n",
            "Review the current goal and visible trajectory.\n",
            "Focus on process quality, verification quality, tooling quality, and goal framing.\n",
            "Do not restate the worker output unless it is necessary evidence.\n",
            "If no intervention is warranted, say so briefly and explain why.\n",
            "If an improvement is warranted, end with a markdown section exactly in this form:\n",
            "## Improvement\n",
            "- Change: <one concrete change>\n",
            "- Benefit: <expected benefit>\n",
            "- Adoption: <smallest practical adoption hint>\n",
            "Return concise markdown.\n\n",
            "MENTAT_REFLECTION_REQUEST_JSON:\n{}\n"
        ),
        serde_json::to_string_pretty(&request)?
    ))
}

fn build_mentat_adoption_prompt(
    storage: &Storage,
    session_id: &str,
    session: &SessionFile,
    improvement: &SessionImprovementRecord,
) -> AppResult<String> {
    let recent_transcript = load_recent_session_transcript(storage, session_id)?;
    let reflection = storage
        .load_session_reflection(session_id, &improvement.reflection_id)
        .ok()
        .map(|record| record.content);
    let request = json!({
        "session_id": session_id,
        "working_dir": session.cwd,
        "goal": {
            "raw_user_request": improvement.goal.raw_user_request,
            "goal": improvement.goal.goal,
            "spec_path": improvement.goal.spec_path,
        },
        "improvement": {
            "improvement_id": improvement.improvement_id,
            "proposed_change": improvement.proposed_change,
            "expected_benefit": improvement.expected_benefit,
            "adoption_hint": improvement.adoption_hint,
        },
        "reflection_content": reflection,
        "recent_transcript": recent_transcript,
    });

    Ok(format!(
        concat!(
            "You are Mentat adopting an approved improvement for this session.\n",
            "Make the smallest practical real change in the current workspace that materially implements the improvement.\n",
            "Prefer editing canonical project files such as code, docs, and SPEC when relevant.\n",
            "Do not edit Mentat session state files.\n",
            "If no safe material change is possible, say so plainly instead of inventing work.\n",
            "Return concise markdown summarizing what changed.\n\n",
            "MENTAT_ADOPTION_REQUEST_JSON:\n{}\n"
        ),
        serde_json::to_string_pretty(&request)?
    ))
}

fn load_recent_session_transcript(storage: &Storage, session_id: &str) -> AppResult<Vec<String>> {
    let mut lines = Vec::new();
    for entry in storage.load_session_transcript_entries(session_id)? {
        let content = entry.content.trim();
        if content.is_empty() {
            continue;
        }
        if entry.kind == SessionTranscriptEntryKind::AgentMessage
            && entry.agent_role == Some(SessionAgentRole::Mentat)
            && matches!(content, "Mentat planning..." | "Mentat self reflection...")
        {
            continue;
        }
        let speaker = match (entry.kind, entry.agent_role) {
            (SessionTranscriptEntryKind::UserMessage, _) => "user",
            (SessionTranscriptEntryKind::AgentMessage, Some(SessionAgentRole::Worker)) => "worker",
            (SessionTranscriptEntryKind::AgentMessage, Some(SessionAgentRole::Mentat)) => "mentat",
            (SessionTranscriptEntryKind::ToolEvent, Some(SessionAgentRole::Worker)) => {
                "worker_tool"
            }
            (SessionTranscriptEntryKind::ToolEvent, Some(SessionAgentRole::Mentat)) => {
                "mentat_tool"
            }
            (SessionTranscriptEntryKind::Divider, _) => "divider",
            _ => "system",
        };
        lines.push(format!("{speaker}: {content}"));
    }

    if lines.len() > 24 {
        Ok(lines.split_off(lines.len() - 24))
    } else {
        Ok(lines)
    }
}

fn latest_worker_reply(storage: &Storage, session_id: &str) -> AppResult<String> {
    let entries = storage.load_session_transcript_entries(session_id)?;
    for entry in entries.into_iter().rev() {
        if entry.kind != SessionTranscriptEntryKind::AgentMessage
            || entry.agent_role != Some(SessionAgentRole::Worker)
        {
            continue;
        }
        let content = entry.content.trim();
        if !content.is_empty() {
            return Ok(content.to_string());
        }
    }

    Ok(String::new())
}

fn persist_session_runtime_output(
    storage: &Storage,
    session_id: &str,
    raw_event_path: &Path,
    agent_role: SessionAgentRole,
) -> AppResult<Option<String>> {
    for entry in persisted_tool_transcript_entries(raw_event_path, agent_role)? {
        storage.append_session_transcript_entry(
            session_id,
            entry.kind,
            entry.agent_role,
            &entry.content,
        )?;
    }
    let answer = persisted_assistant_message_text(raw_event_path)?;
    if let Some(answer) = answer.as_deref()
        && !answer.trim().is_empty()
    {
        storage.append_agent_message(session_id, agent_role, answer)?;
    }
    Ok(answer)
}

fn persist_filtered_session_runtime_output(
    storage: &Storage,
    session_id: &str,
    raw_event_path: &Path,
    agent_role: SessionAgentRole,
    transform: impl FnOnce(&str) -> String,
) -> AppResult<Option<String>> {
    for entry in persisted_tool_transcript_entries(raw_event_path, agent_role)? {
        storage.append_session_transcript_entry(
            session_id,
            entry.kind,
            entry.agent_role,
            &entry.content,
        )?;
    }
    let answer = persisted_assistant_message_text(raw_event_path)?;
    if let Some(answer) = answer.as_deref() {
        let rendered = transform(answer);
        if !rendered.trim().is_empty() {
            storage.append_agent_message(session_id, agent_role, &rendered)?;
        }
    }
    Ok(answer)
}

fn default_spec_path(cwd: &std::path::Path) -> Option<PathBuf> {
    let spec_path = cwd.join("SPEC.md");
    spec_path.exists().then_some(spec_path)
}

fn infer_goal_from_user_message(input: &str) -> String {
    let trimmed = input.trim();
    let lower = trimmed.to_ascii_lowercase();
    let without_prefix = if lower.starts_with("mentat,") {
        &trimmed["Mentat,".len()..]
    } else if lower.starts_with("mentat:") {
        &trimmed["Mentat:".len()..]
    } else if lower.starts_with("mentat ") {
        &trimmed["Mentat".len()..]
    } else {
        trimmed
    };
    without_prefix
        .trim()
        .trim_end_matches(['.', '。'])
        .to_string()
}

#[derive(Debug)]
struct ParsedSessionImprovement {
    proposed_change: String,
    expected_benefit: String,
    adoption_hint: String,
}

fn parse_session_improvement(content: &str) -> Option<ParsedSessionImprovement> {
    let mut in_section = false;
    let mut proposed_change = None;
    let mut expected_benefit = None;
    let mut adoption_hint = None;

    for line in content.lines() {
        let trimmed = line.trim();
        if trimmed.eq_ignore_ascii_case("## improvement") {
            in_section = true;
            continue;
        }
        if !in_section {
            continue;
        }
        if trimmed.starts_with('#') {
            break;
        }
        let Some(bullet) = trimmed.strip_prefix("- ") else {
            continue;
        };
        if let Some(value) = bullet.strip_prefix("Change:") {
            proposed_change = Some(value.trim().to_string());
            continue;
        }
        if let Some(value) = bullet.strip_prefix("Benefit:") {
            expected_benefit = Some(value.trim().to_string());
            continue;
        }
        if let Some(value) = bullet.strip_prefix("Adoption:") {
            adoption_hint = Some(value.trim().to_string());
        }
    }

    Some(ParsedSessionImprovement {
        proposed_change: proposed_change?,
        expected_benefit: expected_benefit?,
        adoption_hint: adoption_hint?,
    })
}
