use crate::app;
use crate::domain::{MemoryScope, OutputFormat, RouteInput, TargetTool};
use crate::lifecycle_format;
use crate::lifecycle_service::{LifecycleAction, LifecycleService, available_actions};
use crate::lifecycle_store::{
    LedgerEntry, ProposeMemoryRequest, RecordMemoryRequest, TransitionMetadata,
    lifecycle_root_from_config,
};
use eframe::egui;
use std::fs;
use std::path::{Path, PathBuf};

const DEFAULT_CONFIG_PATH: &str = "spool.example.toml";
const SAMPLE_TASK: &str =
    "请基于当前仓库，帮我理解 wakeup packet 的生成链路，以及 GUI 覆盖入口应该优先看哪些文件。";
const SAMPLE_FILES: &str = "src/gui/app_shell.rs\nsrc/app.rs\nsrc/wakeup.rs";

pub struct GuiApp {
    config_path: String,
    vault_root_override: String,
    cwd: String,
    task: String,
    files: String,
    target: TargetTool,
    format: OutputFormat,
    context_output: String,
    explain_output: String,
    review_output: String,
    wakeup_ready_output: String,
    review_entries: Vec<LedgerEntry>,
    wakeup_ready_entries: Vec<LedgerEntry>,
    history_output: String,
    selected_record_id: Option<String>,
    draft_mode: MemoryDraftMode,
    draft_title: String,
    draft_summary: String,
    draft_memory_type: String,
    draft_scope: MemoryScope,
    draft_source_ref: String,
    draft_project_id: String,
    draft_user_id: String,
    draft_sensitivity: String,
    draft_actor: String,
    draft_reason: String,
    draft_evidence_refs: String,
    status: String,
    status_detail: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MemoryDraftMode {
    Manual,
    Proposal,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FailureKind {
    Input,
    Config,
    Routing,
    Scan,
    Runtime,
}

struct FailurePresentation {
    status: String,
    detail: String,
    explain: String,
}

impl Default for GuiApp {
    fn default() -> Self {
        Self::new("GUI 使用系统默认字体。".to_string())
    }
}

impl GuiApp {
    pub fn new(font_status: String) -> Self {
        Self {
            config_path: DEFAULT_CONFIG_PATH.to_string(),
            vault_root_override: String::new(),
            cwd: current_dir_display(),
            task: String::new(),
            files: String::new(),
            target: TargetTool::Claude,
            format: OutputFormat::Prompt,
            context_output: String::new(),
            explain_output: String::new(),
            review_output: String::new(),
            wakeup_ready_output: String::new(),
            review_entries: Vec::new(),
            wakeup_ready_entries: Vec::new(),
            history_output: String::new(),
            selected_record_id: None,
            draft_mode: MemoryDraftMode::Manual,
            draft_title: String::new(),
            draft_summary: String::new(),
            draft_memory_type: "preference".to_string(),
            draft_scope: MemoryScope::User,
            draft_source_ref: String::new(),
            draft_project_id: String::new(),
            draft_user_id: String::new(),
            draft_sensitivity: String::new(),
            draft_actor: String::new(),
            draft_reason: String::new(),
            draft_evidence_refs: String::new(),
            status: "就绪：请填写输入后运行。".to_string(),
            status_detail: format!(
                "默认使用配置文件中的 vault.root；如需临时切换，可填写下方覆盖路径。{}",
                font_status
            ),
        }
    }

    fn apply_sample_inputs(&mut self) {
        self.config_path = DEFAULT_CONFIG_PATH.to_string();
        self.vault_root_override.clear();
        self.cwd = current_dir_display();
        self.task = SAMPLE_TASK.to_string();
        self.files = SAMPLE_FILES.to_string();
        self.draft_title = "简洁输出".to_string();
        self.draft_summary = "用户偏好简洁直接的回复。".to_string();
        self.draft_memory_type = "preference".to_string();
        self.draft_scope = MemoryScope::User;
        self.draft_source_ref = "manual:gui".to_string();
        self.draft_user_id = "long".to_string();
        self.draft_actor = "codex".to_string();
        self.draft_reason = "captured from GUI example flow".to_string();
        self.draft_evidence_refs = "session:example,obsidian://preferences".to_string();
        self.status = "已填入示例。".to_string();
        self.status_detail =
            "现在可以直接点“生成上下文”，或先按你的场景微调 Task / Files。".to_string();
    }

    fn apply_failure(&mut self, failure: FailurePresentation) {
        self.status = failure.status;
        self.status_detail = failure.detail;
        self.explain_output = failure.explain;
    }

    fn refresh_lifecycle_views(&mut self, config_path: &Path) {
        match LifecycleService::new().load_workbench(config_path) {
            Ok(snapshot) => {
                self.review_entries = snapshot.pending_review;
                self.wakeup_ready_entries = snapshot.wakeup_ready;
                self.review_output =
                    render_lifecycle_entries("Pending review", &self.review_entries);
                self.wakeup_ready_output =
                    render_lifecycle_entries("Wakeup-ready", &self.wakeup_ready_entries);
                if self.selected_entry().is_none() {
                    self.selected_record_id = None;
                }
                self.refresh_selected_history(config_path);
            }
            Err(error) => {
                self.review_entries.clear();
                self.wakeup_ready_entries.clear();
                self.history_output = format!("无法加载 history：{error}");
                self.selected_record_id = None;
                self.review_output = format!("无法加载 review 队列：{error}");
                self.wakeup_ready_output = format!("无法加载 wakeup-ready 列表：{error}");
            }
        }
    }

    fn selected_entry(&self) -> Option<&LedgerEntry> {
        let record_id = self.selected_record_id.as_deref()?;
        self.review_entries
            .iter()
            .chain(self.wakeup_ready_entries.iter())
            .find(|entry| entry.record_id == record_id)
    }

    fn refresh_selected_history(&mut self, config_path: &Path) {
        let Some(record_id) = self.selected_record_id.clone() else {
            self.history_output = "未选择记忆。".to_string();
            return;
        };

        match LifecycleService::new().get_history(config_path, &record_id) {
            Ok(entries) => {
                self.history_output = render_lifecycle_history(&record_id, &entries);
            }
            Err(error) => {
                self.history_output = format!("无法加载 history：{error}");
            }
        }
    }

    fn apply_lifecycle_action(&mut self, action: LifecycleAction) {
        let Some(record_id) = self.selected_record_id.clone() else {
            self.status = "未选择记忆。".to_string();
            self.status_detail =
                "请先在 Pending review 或 Wakeup-ready 中选择一条记忆。".to_string();
            return;
        };

        let config_path = PathBuf::from(self.config_path.trim());
        match LifecycleService::new().apply_action(config_path.as_path(), &record_id, action) {
            Ok(result) => {
                self.review_entries = result.snapshot.pending_review;
                self.wakeup_ready_entries = result.snapshot.wakeup_ready;
                self.review_output =
                    render_lifecycle_entries("Pending review", &self.review_entries);
                self.wakeup_ready_output =
                    render_lifecycle_entries("Wakeup-ready", &self.wakeup_ready_entries);
                if self
                    .review_entries
                    .iter()
                    .chain(self.wakeup_ready_entries.iter())
                    .any(|entry| entry.record_id == record_id)
                {
                    self.selected_record_id = Some(record_id.clone());
                } else {
                    self.selected_record_id = None;
                }
                self.refresh_selected_history(config_path.as_path());
                self.status = format!("已执行 {}。", action_button_label(action));
                self.status_detail = format!(
                    "record_id={}；当前状态={:?}。",
                    result.entry.record_id, result.entry.record.state
                );
            }
            Err(error) => {
                self.status = format!("{} 失败。", action_button_label(action));
                self.status_detail = error.to_string();
            }
        }
    }

    fn submit_memory_draft(&mut self) {
        let config_path = PathBuf::from(self.config_path.trim());
        match validate_memory_form(
            self.draft_title.trim(),
            self.draft_summary.trim(),
            self.draft_memory_type.trim(),
            self.draft_source_ref.trim(),
        ) {
            Err(message) => {
                self.status = "记忆写入未通过。".to_string();
                self.status_detail = message;
            }
            Ok(()) => {
                let service = LifecycleService::new();
                let metadata = TransitionMetadata {
                    actor: optional_trimmed(&self.draft_actor),
                    reason: optional_trimmed(&self.draft_reason),
                    evidence_refs: parse_evidence_refs(&self.draft_evidence_refs),
                };
                let result = match self.draft_mode {
                    MemoryDraftMode::Manual => service.record_manual(
                        config_path.as_path(),
                        RecordMemoryRequest {
                            title: self.draft_title.trim().to_string(),
                            summary: self.draft_summary.trim().to_string(),
                            memory_type: self.draft_memory_type.trim().to_string(),
                            scope: self.draft_scope,
                            source_ref: self.draft_source_ref.trim().to_string(),
                            project_id: optional_trimmed(&self.draft_project_id),
                            user_id: optional_trimmed(&self.draft_user_id),
                            sensitivity: optional_trimmed(&self.draft_sensitivity),
                            metadata,
                            entities: Vec::new(),
                            tags: Vec::new(),
                            triggers: Vec::new(),
                            related_files: Vec::new(),
                            related_records: Vec::new(),
                            supersedes: None,
                            applies_to: Vec::new(),
                            valid_until: None,
                        },
                    ),
                    MemoryDraftMode::Proposal => service.propose_ai(
                        config_path.as_path(),
                        ProposeMemoryRequest {
                            title: self.draft_title.trim().to_string(),
                            summary: self.draft_summary.trim().to_string(),
                            memory_type: self.draft_memory_type.trim().to_string(),
                            scope: self.draft_scope,
                            source_ref: self.draft_source_ref.trim().to_string(),
                            project_id: optional_trimmed(&self.draft_project_id),
                            user_id: optional_trimmed(&self.draft_user_id),
                            sensitivity: optional_trimmed(&self.draft_sensitivity),
                            metadata,
                            entities: Vec::new(),
                            tags: Vec::new(),
                            triggers: Vec::new(),
                            related_files: Vec::new(),
                            related_records: Vec::new(),
                            supersedes: None,
                            applies_to: Vec::new(),
                            valid_until: None,
                        },
                    ),
                };

                match result {
                    Ok(result) => {
                        self.review_entries = result.snapshot.pending_review;
                        self.wakeup_ready_entries = result.snapshot.wakeup_ready;
                        self.review_output =
                            render_lifecycle_entries("Pending review", &self.review_entries);
                        self.wakeup_ready_output =
                            render_lifecycle_entries("Wakeup-ready", &self.wakeup_ready_entries);
                        self.selected_record_id = Some(result.entry.record_id.clone());
                        self.refresh_selected_history(config_path.as_path());
                        self.status = format!("已写入 {}。", draft_mode_label(self.draft_mode));
                        self.status_detail = format!(
                            "record_id={}；state={:?}。",
                            result.entry.record_id, result.entry.record.state
                        );
                    }
                    Err(error) => {
                        self.status = format!("{} 失败。", draft_mode_label(self.draft_mode));
                        self.status_detail = error.to_string();
                    }
                }
            }
        }
    }
}

fn render_lifecycle_entries(title: &str, entries: &[LedgerEntry]) -> String {
    crate::lifecycle_summary::render_queue_text(title, entries, false, false)
}

fn render_lifecycle_detail(entry: &LedgerEntry) -> String {
    crate::lifecycle_summary::render_record_text(entry, false, false)
}

fn render_lifecycle_history(record_id: &str, entries: &[LedgerEntry]) -> String {
    crate::lifecycle_summary::render_history_text(record_id, entries, false)
}

fn lifecycle_list_label(entry: &LedgerEntry) -> String {
    lifecycle_format::render_gui_list_label(entry)
}

fn action_button_label(action: LifecycleAction) -> &'static str {
    lifecycle_format::action_button_label(action)
}

fn draft_mode_label(mode: MemoryDraftMode) -> &'static str {
    match mode {
        MemoryDraftMode::Manual => "Manual memory",
        MemoryDraftMode::Proposal => "AI proposal",
    }
}

fn optional_trimmed(value: &str) -> Option<String> {
    let trimmed = value.trim();
    (!trimmed.is_empty()).then(|| trimmed.to_string())
}

fn parse_evidence_refs(value: &str) -> Vec<String> {
    value
        .split(',')
        .map(str::trim)
        .filter(|item| !item.is_empty())
        .map(ToString::to_string)
        .collect()
}

fn validate_memory_form(
    title: &str,
    summary: &str,
    memory_type: &str,
    source_ref: &str,
) -> Result<(), String> {
    if title.is_empty() {
        return Err("请先填写记忆标题。".to_string());
    }
    if summary.is_empty() {
        return Err("请先填写记忆摘要。".to_string());
    }
    if memory_type.is_empty() {
        return Err("请先填写 memory_type。".to_string());
    }
    if source_ref.is_empty() {
        return Err("请先填写 source_ref。".to_string());
    }
    Ok(())
}

fn lifecycle_root_hint(config_path: &Path) -> String {
    lifecycle_root_from_config(config_path.parent().unwrap_or_else(|| Path::new(".")))
        .display()
        .to_string()
}

fn update_success_status(app: &mut GuiApp, result: &crate::app::AppResult, config_path: &Path) {
    let candidate_count = result.bundle.route.candidates.len();
    let note_count = result.bundle.route.debug.note_count;
    let scan_roots = if result.bundle.route.debug.scan_roots.is_empty() {
        "vault 根目录".to_string()
    } else {
        result.bundle.route.debug.scan_roots.join(", ")
    };
    app.context_output = result.rendered.clone();
    app.explain_output = result.explain.clone();
    app.refresh_lifecycle_views(config_path);
    app.status = format!(
        "完成：format={}，候选 {} 条，扫描 {} 条笔记。",
        result.used_format.as_str(),
        candidate_count,
        note_count
    );
    app.status_detail = format!(
        "vault root={}；scan roots={}；lifecycle root={}。",
        result.used_vault_root.display(),
        scan_roots,
        lifecycle_root_hint(config_path)
    );
}

fn clear_outputs(app: &mut GuiApp) {
    app.context_output.clear();
    app.explain_output.clear();
    app.review_output.clear();
    app.wakeup_ready_output.clear();
    app.history_output.clear();
    app.review_entries.clear();
    app.wakeup_ready_entries.clear();
    app.selected_record_id = None;
    app.status = "已清空输出。".to_string();
    app.status_detail = "输入内容会保留，方便继续调整后重新运行。".to_string();
}

fn copy_with_status(ui: &mut egui::Ui, text: &str, status: &str, detail: &str, app: &mut GuiApp) {
    ui.ctx().copy_text(text.to_string());
    app.status = status.to_string();
    app.status_detail = detail.to_string();
}

impl eframe::App for GuiApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::TopBottomPanel::top("top_bar").show(ctx, |ui| {
            ui.vertical(|ui| {
                ui.horizontal_wrapped(|ui| {
                    ui.heading("spool");
                    ui.separator();
                    ui.label("实验 GUI");
                    ui.separator();
                    ui.strong(&self.status);
                });
                ui.label(&self.status_detail);
            });
        });

        egui::SidePanel::left("controls")
            .resizable(true)
            .default_width(380.0)
            .show(ctx, |ui| {
                ui.heading("输入");
                ui.group(|ui| {
                    ui.label("快速上手");
                    ui.small("1. 确认 Config 路径；2. CWD 填当前仓库；3. 输入任务；4. 需要时填写 vault root 覆盖；5. 点击“生成上下文”。");
                    ui.add_space(6.0);
                    if ui.button("填入示例").clicked() {
                        self.apply_sample_inputs();
                    }
                });
                ui.add_space(8.0);
                ui.label("Config 路径");
                ui.text_edit_singleline(&mut self.config_path);
                ui.small("支持相对路径；相对路径按当前启动目录解析。默认值是仓库内的 spool.example.toml。 ");

                ui.add_space(8.0);
                ui.label("Obsidian vault root（可选覆盖）");
                ui.text_edit_singleline(&mut self.vault_root_override);
                ui.small("留空则使用配置文件里的 vault.root。填写后仅影响本次 GUI 运行，不会改写配置文件。相对路径按 config 文件所在目录解析。 ");

                ui.add_space(8.0);
                ui.label("CWD");
                ui.text_edit_singleline(&mut self.cwd);
                ui.small("用于匹配 project.repo_paths。通常填当前仓库根目录。 ");

                ui.add_space(8.0);
                ui.label("Task");
                ui.add(
                    egui::TextEdit::multiline(&mut self.task)
                        .desired_rows(4)
                        .hint_text("例如：为 wakeup packet 增加 GUI 配置覆盖入口"),
                );

                ui.add_space(8.0);
                ui.label("Files（逗号或换行分隔）");
                ui.add(
                    egui::TextEdit::multiline(&mut self.files)
                        .desired_rows(4)
                        .hint_text("例如：src/gui/app_shell.rs, src/app.rs"),
                );

                ui.add_space(8.0);
                ui.horizontal(|ui| {
                    ui.label("Target");
                    egui::ComboBox::from_id_salt("target")
                        .selected_text(match self.target {
                            TargetTool::Claude => "Claude",
                            TargetTool::Codex => "Codex",
                            TargetTool::Opencode => "OpenCode",
                        })
                        .show_ui(ui, |ui| {
                            ui.selectable_value(&mut self.target, TargetTool::Claude, "Claude");
                            ui.selectable_value(&mut self.target, TargetTool::Codex, "Codex");
                            ui.selectable_value(&mut self.target, TargetTool::Opencode, "OpenCode");
                        });
                });

                ui.horizontal(|ui| {
                    ui.label("Format");
                    egui::ComboBox::from_id_salt("format")
                        .selected_text(match self.format {
                            OutputFormat::Prompt => "prompt",
                            OutputFormat::Markdown => "markdown",
                            OutputFormat::Json => "json",
                        })
                        .show_ui(ui, |ui| {
                            ui.selectable_value(&mut self.format, OutputFormat::Prompt, "prompt");
                            ui.selectable_value(&mut self.format, OutputFormat::Markdown, "markdown");
                            ui.selectable_value(&mut self.format, OutputFormat::Json, "json");
                        });
                });

                ui.add_space(12.0);
                ui.separator();
                ui.add_space(8.0);
                ui.heading("Memory write");
                ui.small("用最小表单写入 manual memory 或 AI proposal。");
                ui.horizontal(|ui| {
                    ui.label("Mode");
                    egui::ComboBox::from_id_salt("draft_mode")
                        .selected_text(draft_mode_label(self.draft_mode))
                        .show_ui(ui, |ui| {
                            ui.selectable_value(
                                &mut self.draft_mode,
                                MemoryDraftMode::Manual,
                                "Manual memory",
                            );
                            ui.selectable_value(
                                &mut self.draft_mode,
                                MemoryDraftMode::Proposal,
                                "AI proposal",
                            );
                        });
                });
                ui.label("Title");
                ui.text_edit_singleline(&mut self.draft_title);
                ui.label("Summary");
                ui.add(
                    egui::TextEdit::multiline(&mut self.draft_summary)
                        .desired_rows(3)
                        .hint_text("例如：用户偏好简洁直接的回复"),
                );
                ui.label("memory_type");
                ui.text_edit_singleline(&mut self.draft_memory_type);
                ui.horizontal(|ui| {
                    ui.label("Scope");
                    egui::ComboBox::from_id_salt("draft_scope")
                        .selected_text(format!("{:?}", self.draft_scope))
                        .show_ui(ui, |ui| {
                            ui.selectable_value(&mut self.draft_scope, MemoryScope::User, "User");
                            ui.selectable_value(
                                &mut self.draft_scope,
                                MemoryScope::Project,
                                "Project",
                            );
                            ui.selectable_value(
                                &mut self.draft_scope,
                                MemoryScope::Workspace,
                                "Workspace",
                            );
                            ui.selectable_value(&mut self.draft_scope, MemoryScope::Team, "Team");
                            ui.selectable_value(&mut self.draft_scope, MemoryScope::Agent, "Agent");
                        });
                });
                ui.label("source_ref");
                ui.text_edit_singleline(&mut self.draft_source_ref);
                ui.label("project_id（可选）");
                ui.text_edit_singleline(&mut self.draft_project_id);
                ui.label("user_id（可选）");
                ui.text_edit_singleline(&mut self.draft_user_id);
                ui.label("sensitivity（可选）");
                ui.text_edit_singleline(&mut self.draft_sensitivity);
                ui.label("actor（可选）");
                ui.text_edit_singleline(&mut self.draft_actor);
                ui.label("reason（可选）");
                ui.text_edit_singleline(&mut self.draft_reason);
                ui.label("evidence_refs（可选，逗号分隔）");
                ui.text_edit_singleline(&mut self.draft_evidence_refs);

                ui.add_space(10.0);
                if ui.button("生成上下文").clicked() {
                    self.run_context();
                }
                if ui
                    .button(match self.draft_mode {
                        MemoryDraftMode::Manual => "写入 Manual memory",
                        MemoryDraftMode::Proposal => "写入 AI proposal",
                    })
                    .clicked()
                {
                    self.submit_memory_draft();
                }
                if ui.button("清空输出").clicked() {
                    clear_outputs(self);
                }
            });

        egui::TopBottomPanel::bottom("lifecycle_panel")
            .resizable(true)
            .default_height(320.0)
            .show(ctx, |ui| {
                ui.columns(3, |columns| {
                    columns[0].horizontal(|ui| {
                        ui.heading("Pending review");
                        if ui.button("复制").clicked() {
                            let text = self.review_output.clone();
                            copy_with_status(
                                ui,
                                &text,
                                "已复制 Pending review。",
                                "可用于人工审核候选记忆。",
                                self,
                            );
                        }
                    });
                    columns[0].label("当前仍需人工审核的候选记忆。 ");
                    let mut clicked_review: Option<String> = None;
                    egui::ScrollArea::vertical().show(&mut columns[0], |ui| {
                        if self.review_entries.is_empty() {
                            ui.label("- none");
                        }
                        for entry in &self.review_entries {
                            let selected = self.selected_record_id.as_deref()
                                == Some(entry.record_id.as_str());
                            if ui
                                .selectable_label(selected, lifecycle_list_label(entry))
                                .clicked()
                            {
                                clicked_review = Some(entry.record_id.clone());
                            }
                        }
                    });
                    if let Some(record_id) = clicked_review {
                        self.selected_record_id = Some(record_id);
                        let config_path = PathBuf::from(self.config_path.trim());
                        self.refresh_selected_history(config_path.as_path());
                    }

                    columns[1].horizontal(|ui| {
                        ui.heading("Wakeup-ready");
                        if ui.button("复制").clicked() {
                            let text = self.wakeup_ready_output.clone();
                            copy_with_status(
                                ui,
                                &text,
                                "已复制 Wakeup-ready。",
                                "可用于查看已稳定、可进入 wakeup 的记忆。",
                                self,
                            );
                        }
                    });
                    columns[1].label("当前已稳定、可被 wakeup 使用的记忆。 ");
                    let mut clicked_ready: Option<String> = None;
                    egui::ScrollArea::vertical().show(&mut columns[1], |ui| {
                        if self.wakeup_ready_entries.is_empty() {
                            ui.label("- none");
                        }
                        for entry in &self.wakeup_ready_entries {
                            let selected = self.selected_record_id.as_deref()
                                == Some(entry.record_id.as_str());
                            if ui
                                .selectable_label(selected, lifecycle_list_label(entry))
                                .clicked()
                            {
                                clicked_ready = Some(entry.record_id.clone());
                            }
                        }
                    });
                    if let Some(record_id) = clicked_ready {
                        self.selected_record_id = Some(record_id);
                        let config_path = PathBuf::from(self.config_path.trim());
                        self.refresh_selected_history(config_path.as_path());
                    }

                    columns[2].horizontal(|ui| {
                        ui.heading("Detail");
                        if ui.button("复制").clicked() {
                            let text = self
                                .selected_entry()
                                .map(render_lifecycle_detail)
                                .unwrap_or_else(|| "未选择记忆。".to_string());
                            copy_with_status(
                                ui,
                                &text,
                                "已复制 Detail。",
                                "可用于查看当前选中记忆的完整字段。",
                                self,
                            );
                        }
                    });
                    columns[2].label("选择一条记忆后，可查看详情并执行最小动作流。 ");
                    if let Some(detail) = self.selected_entry().cloned() {
                        columns[2].monospace(render_lifecycle_detail(&detail));
                        columns[2].add_space(8.0);
                        columns[2].horizontal_wrapped(|ui| {
                            for action in available_actions(&detail.record) {
                                if ui.button(action_button_label(*action)).clicked() {
                                    self.apply_lifecycle_action(*action);
                                }
                            }
                            if available_actions(&detail.record).is_empty() {
                                ui.small("当前状态没有可执行动作。");
                            }
                        });
                        columns[2].add_space(10.0);
                        columns[2].separator();
                        columns[2].horizontal(|ui| {
                            ui.heading("History");
                            if ui.button("复制").clicked() {
                                let text = self.history_output.clone();
                                copy_with_status(
                                    ui,
                                    &text,
                                    "已复制 History。",
                                    "可用于查看该记忆的完整事件历史。",
                                    self,
                                );
                            }
                        });
                        egui::ScrollArea::vertical().max_height(150.0).show(
                            &mut columns[2],
                            |ui| {
                                ui.monospace(&self.history_output);
                            },
                        );
                    } else {
                        columns[2].small("未选择记忆。");
                    }
                });
            });

        egui::CentralPanel::default().show(ctx, |ui| {
            ui.columns(2, |columns| {
                columns[0].horizontal(|ui| {
                    ui.heading("Context");
                    if ui.button("复制").clicked() {
                        ui.ctx().copy_text(self.context_output.clone());
                        self.status = "已复制 Context。".to_string();
                        self.status_detail = "可以直接粘贴到终端或对话输入框。".to_string();
                    }
                });
                columns[0].label("给模型的主输出。可直接复制到终端或对话中。 ");
                columns[0].add(
                    egui::TextEdit::multiline(&mut self.context_output)
                        .desired_rows(28)
                        .desired_width(f32::INFINITY),
                );

                columns[1].horizontal(|ui| {
                    ui.heading("Explain");
                    if ui.button("复制").clicked() {
                        ui.ctx().copy_text(self.explain_output.clone());
                        self.status = "已复制 Explain。".to_string();
                        self.status_detail = "可用于排查路由命中原因。".to_string();
                    }
                });
                columns[1].label("路由解释与候选来源，便于排查为什么命中了这些笔记。 ");
                columns[1].add(
                    egui::TextEdit::multiline(&mut self.explain_output)
                        .desired_rows(28)
                        .desired_width(f32::INFINITY),
                );
            });
        });
    }
}

impl GuiApp {
    fn run_context(&mut self) {
        let config_path = PathBuf::from(self.config_path.trim());
        let cwd = PathBuf::from(self.cwd.trim());
        let files = parse_files(&self.files);
        let vault_root_override = self.normalized_override_path();

        if let Err(message) = validate_inputs(
            config_path.as_path(),
            cwd.as_path(),
            self.task.trim(),
            vault_root_override.as_deref(),
        ) {
            self.apply_failure(present_validation_failure(&message));
            return;
        }

        self.status = "运行中…".to_string();
        self.status_detail = "正在加载配置、匹配项目并扫描笔记。".to_string();

        let input = RouteInput {
            task: self.task.clone(),
            cwd,
            files,
            target: self.target,
            format: self.format,
        };

        match app::run_with_overrides(
            config_path.as_path(),
            input,
            Some(self.format),
            vault_root_override.as_deref(),
        ) {
            Ok(result) => {
                update_success_status(self, &result, config_path.as_path());
            }
            Err(error) => {
                self.apply_failure(present_runtime_failure(&error.to_string()));
            }
        }
    }

    fn normalized_override_path(&self) -> Option<PathBuf> {
        let trimmed = self.vault_root_override.trim();
        if trimmed.is_empty() {
            return None;
        }

        let override_path = Path::new(trimmed);
        let config_path = Path::new(self.config_path.trim());
        Some(
            app::resolve_override_path(override_path, config_path)
                .unwrap_or_else(|_| override_path.to_path_buf()),
        )
    }
}

fn current_dir_display() -> String {
    std::env::current_dir()
        .unwrap_or_else(|_| PathBuf::from("."))
        .display()
        .to_string()
}

fn parse_files(raw: &str) -> Vec<String> {
    raw.split(|ch| ch == ',' || ch == '\n')
        .map(str::trim)
        .filter(|item| !item.is_empty())
        .map(ToString::to_string)
        .collect()
}

fn validate_inputs(
    config_path: &Path,
    cwd: &Path,
    task: &str,
    vault_root_override: Option<&Path>,
) -> Result<(), String> {
    if config_path.as_os_str().is_empty() {
        return Err("请先填写 Config 路径。".to_string());
    }
    if !config_path.exists() {
        return Err(format!("Config 不存在：{}", config_path.display()));
    }
    if !config_path.is_file() {
        return Err(format!("Config 不是文件：{}", config_path.display()));
    }
    if cwd.as_os_str().is_empty() {
        return Err("请先填写 CWD。".to_string());
    }
    if !cwd.exists() {
        return Err(format!("CWD 不存在：{}", cwd.display()));
    }
    if !cwd.is_dir() {
        return Err(format!("CWD 不是目录：{}", cwd.display()));
    }
    if task.is_empty() {
        return Err("请先填写 Task，至少用一句话说明你要什么上下文。".to_string());
    }
    if let Some(vault_root_override) = vault_root_override {
        let resolved_override = app::resolve_override_path(vault_root_override, config_path)
            .map_err(|error| format!("vault root 覆盖路径解析失败：{}", error))?;
        match fs::metadata(&resolved_override) {
            Ok(metadata) if metadata.is_dir() => {}
            Ok(_) => {
                return Err(format!(
                    "vault root 覆盖路径不是目录：{}",
                    resolved_override.display()
                ));
            }
            Err(_) => {
                return Err(format!(
                    "vault root 覆盖路径不存在：{}",
                    resolved_override.display()
                ));
            }
        }
    }
    Ok(())
}

fn classify_runtime_failure(message: &str) -> FailureKind {
    let lowered = message.to_ascii_lowercase();
    if message.contains("no project matched cwd") {
        FailureKind::Routing
    } else if message.contains("configured note_root")
        || message.contains("vault scan exceeded")
        || message.contains("markdown file exceeds")
        || message.contains("failed to read markdown file")
        || message.contains("failed to stat markdown file")
        || message.contains("failed to canonicalize vault root")
    {
        FailureKind::Scan
    } else if lowered.contains("toml")
        || lowered.contains("config")
        || lowered.contains("missing field")
        || lowered.contains("unknown field")
        || lowered.contains("invalid type")
    {
        FailureKind::Config
    } else {
        FailureKind::Runtime
    }
}

fn present_validation_failure(message: &str) -> FailurePresentation {
    present_failure(
        FailureKind::Input,
        message,
        "请先修正输入字段或路径，再重新运行。",
    )
}

fn present_runtime_failure(message: &str) -> FailurePresentation {
    let kind = classify_runtime_failure(message);
    let hint = match kind {
        FailureKind::Input => "请先修正输入字段或路径，再重新运行。",
        FailureKind::Config => "请检查 config 路径、TOML 内容以及 vault.root / note_roots 配置。",
        FailureKind::Routing => {
            "请确认 CWD 位于某个 project.repo_paths 下，且该项目已配置 note_roots。"
        }
        FailureKind::Scan => {
            "请检查 vault root、note_roots、Markdown 文件体积限制，以及相关目录是否可读。"
        }
        FailureKind::Runtime => "请查看下方 Explain 中的原始错误，并结合输入与配置继续排查。",
    };
    present_failure(kind, message, hint)
}

fn present_failure(kind: FailureKind, message: &str, hint: &str) -> FailurePresentation {
    let (status, heading) = match kind {
        FailureKind::Input => ("输入未通过。", "输入校验失败"),
        FailureKind::Config => ("配置有误。", "配置错误"),
        FailureKind::Routing => ("项目未命中。", "项目路由失败"),
        FailureKind::Scan => ("笔记扫描失败。", "Vault 扫描失败"),
        FailureKind::Runtime => ("运行失败。", "运行时错误"),
    };

    FailurePresentation {
        status: status.to_string(),
        detail: format!("{} 原因：{}", hint, message),
        explain: format!("{}\n\n建议：{}\n\n原始错误：{}", heading, hint, message),
    }
}

#[cfg(test)]
mod tests {
    use super::{
        DEFAULT_CONFIG_PATH, FailureKind, GuiApp, SAMPLE_FILES, action_button_label,
        classify_runtime_failure, clear_outputs, parse_evidence_refs, parse_files,
        present_runtime_failure, render_lifecycle_detail, render_lifecycle_history,
        validate_inputs, validate_memory_form,
    };
    use crate::domain::{MemoryScope, MemorySourceKind};
    use crate::lifecycle_service::{LifecycleAction, available_actions};
    use crate::lifecycle_store::LedgerEntry;
    use std::fs;
    use std::path::PathBuf;
    use tempfile::tempdir;

    #[test]
    fn parse_files_accepts_commas_and_newlines() {
        assert_eq!(
            parse_files("a.rs, b.rs\n c.rs\n\n,d.rs"),
            vec!["a.rs", "b.rs", "c.rs", "d.rs"]
        );
    }

    #[test]
    fn validate_inputs_rejects_empty_task() {
        let temp = tempdir().unwrap();
        let config_path = temp.path().join("spool.toml");
        fs::write(&config_path, "[vault]\nroot='/tmp'\n").unwrap();

        let error = validate_inputs(config_path.as_path(), temp.path(), "", None).unwrap_err();

        assert!(error.contains("请先填写 Task"));
    }

    #[test]
    fn sample_button_fills_useful_defaults() {
        let mut app = GuiApp::default();

        app.apply_sample_inputs();

        assert_eq!(app.config_path, DEFAULT_CONFIG_PATH);
        assert!(app.vault_root_override.is_empty());
        assert_eq!(app.files, SAMPLE_FILES);
        assert!(!app.task.trim().is_empty());
        assert_eq!(app.draft_actor, "codex");
        assert!(app.draft_reason.contains("GUI example flow"));
        assert_eq!(app.status, "已填入示例。");
    }

    #[test]
    fn normalized_override_path_should_resolve_relative_to_config_dir() {
        let mut app = GuiApp::default();
        app.config_path = "/tmp/demo/configs/spool.toml".to_string();
        app.vault_root_override = "../vault".to_string();

        assert_eq!(
            app.normalized_override_path(),
            Some(PathBuf::from("/tmp/demo/vault"))
        );
    }

    #[test]
    fn validate_inputs_resolves_relative_override_against_config_dir() {
        let temp = tempdir().unwrap();
        let config_dir = temp.path().join("config");
        let vault_dir = temp.path().join("vault");
        fs::create_dir_all(&config_dir).unwrap();
        fs::create_dir_all(&vault_dir).unwrap();
        let config_path = config_dir.join("spool.toml");
        fs::write(&config_path, "[vault]\nroot='../vault'\n").unwrap();

        validate_inputs(
            config_path.as_path(),
            temp.path(),
            "resume spool",
            Some(std::path::Path::new("../vault")),
        )
        .unwrap();
    }

    #[test]
    fn clear_outputs_should_reset_all_panels() {
        let mut app = GuiApp::default();
        app.context_output = "context".to_string();
        app.explain_output = "explain".to_string();
        app.review_output = "review".to_string();
        app.wakeup_ready_output = "ready".to_string();
        app.history_output = "history".to_string();
        app.selected_record_id = Some("record-1".to_string());
        app.review_entries.push(sample_entry());

        clear_outputs(&mut app);

        assert!(app.context_output.is_empty());
        assert!(app.explain_output.is_empty());
        assert!(app.review_output.is_empty());
        assert!(app.wakeup_ready_output.is_empty());
        assert!(app.history_output.is_empty());
        assert!(app.review_entries.is_empty());
        assert!(app.selected_record_id.is_none());
    }

    #[test]
    fn render_lifecycle_entries_shows_none_for_empty_list() {
        let rendered = super::render_lifecycle_entries("Pending review", &[]);
        assert!(rendered.contains("- none"));
    }

    #[test]
    fn classify_runtime_failure_detects_routing_errors() {
        assert_eq!(
            classify_runtime_failure("no project matched cwd: /tmp/demo"),
            FailureKind::Routing
        );
    }

    #[test]
    fn classify_runtime_failure_detects_scan_errors() {
        assert_eq!(
            classify_runtime_failure("configured note_root does not exist: /tmp/vault/10-Projects"),
            FailureKind::Scan
        );
    }

    #[test]
    fn runtime_failure_presentation_includes_hint_and_raw_error() {
        let failure = present_runtime_failure("no project matched cwd: /tmp/demo");

        assert_eq!(failure.status, "项目未命中。");
        assert!(failure.detail.contains("project.repo_paths"));
        assert!(
            failure
                .explain
                .contains("原始错误：no project matched cwd: /tmp/demo")
        );
    }

    #[test]
    fn selected_record_actions_follow_current_state() {
        let entry = sample_entry();
        assert_eq!(
            available_actions(&entry.record),
            &[LifecycleAction::Accept, LifecycleAction::Archive]
        );
        assert_eq!(
            action_button_label(LifecycleAction::PromoteToCanonical),
            "Promote"
        );
    }

    #[test]
    fn render_lifecycle_detail_includes_record_id_and_summary() {
        let rendered = render_lifecycle_detail(&sample_entry());
        assert!(rendered.contains("record_id: record-1"));
        assert!(rendered.contains("summary: 先 smoke 再收口"));
        assert!(rendered.contains("source_kind: AiProposal"));
    }

    #[test]
    fn render_lifecycle_history_includes_event_count() {
        let rendered = render_lifecycle_history("record-1", &[sample_entry()]);
        assert!(rendered.contains("events: 1"));
        assert!(rendered.contains("record_id: record-1"));
    }

    #[test]
    fn validate_memory_form_rejects_missing_title() {
        let error = validate_memory_form("", "summary", "preference", "manual:gui").unwrap_err();
        assert!(error.contains("记忆标题"));
    }

    #[test]
    fn parse_evidence_refs_should_split_and_trim() {
        assert_eq!(
            parse_evidence_refs("session:1, obsidian://preferences ,, session:2"),
            vec!["session:1", "obsidian://preferences", "session:2"]
        );
    }

    fn sample_entry() -> LedgerEntry {
        LedgerEntry {
            schema_version: "memory-ledger.v1".to_string(),
            recorded_at: "unix:1".to_string(),
            record_id: "record-1".to_string(),
            scope_key: "long".to_string(),
            action: crate::domain::MemoryLedgerAction::ProposeAi,
            source_kind: MemorySourceKind::AiProposal,
            metadata: crate::lifecycle_store::TransitionMetadata::default(),
            record: crate::domain::MemoryRecord::new_ai_proposal(
                "测试偏好",
                "先 smoke 再收口",
                "workflow",
                MemoryScope::User,
                "session:1",
            )
            .with_user_id("long"),
        }
    }
}
