// ── INVARIANT (2026-04-16): DO NOT DELETE THIS FILE ──
// verify.rs was deleted once (01afc5b, "all dead_code") and had to be
// restored (4f704cb) after causing 22-49 turn sessions. The functions
// are called conditionally from mod.rs — #[allow(dead_code)] does NOT
// mean unused. If you think this file is dead, grep for `should_verify`
// and `inject_verify_prompt` in mod.rs before touching it.
// ──────────────────────────────────────────────────────────────────

use super::*;

impl AgentLoop {
    /// Check if the model should verify its changes before finishing.
    /// Returns true if: edits were made AND no bash/build command was run AFTER the last edit.
    #[allow(dead_code)]
    pub(crate) fn should_verify(&self) -> bool {
        if self.files_edited_this_turn.is_empty() {
            return false; // No edits, nothing to verify
        }
        if self.tool_call_count >= 20 {
            return false; // Near step limit, don't waste steps
        }

        // Check the LAST tool call and its result.
        // If it's a SUCCESSFUL bash → already verified. No need for another.
        // If it's a FAILED bash (build error) → need to verify/fix.
        // If it's edit/write/read → hasn't verified yet.
        let mut last_tool_name = String::new();
        let mut last_result_success = true;
        for msg in self.conversation.messages.iter().rev() {
            if let (Some(success), Some(output)) =
                (msg.tool_result_success(), msg.tool_result_output())
            {
                if last_tool_name.is_empty() {
                    last_result_success = success;
                    // Also check output for build failure keywords
                    let out = output.to_lowercase();
                    if out.contains("build failed")
                        || out.contains("error")
                        || out.contains("failed")
                    {
                        last_result_success = false;
                    }
                }
            }
            if let crate::conversation::message::MessageContent::AssistantWithToolCalls {
                tool_calls,
                ..
            } = &msg.content
            {
                if let Some(last_tc) = tool_calls.last() {
                    if last_tool_name.is_empty() {
                        last_tool_name = last_tc.name.clone();
                    }
                    // If last tool was bash AND it succeeded → no verify needed
                    // If last tool was bash AND it failed → verify/fix needed
                    return last_tool_name != "bash" || !last_result_success;
                }
            }
            if matches!(msg.role, crate::conversation::message::Role::User) {
                break;
            }
        }
        false
    }

    // ── INVARIANT (2026-04-16): verify prompt must NOT mention dev server ──
    // "check if the dev server shows errors" caused models to run `npm run dev`
    // for verification → 140-168s blocking waits. Always guide toward build/check
    // commands that exit immediately. Tech-stack neutral: no npm/cargo/mvn.
    // History: "dev server" wording survived 16 commits unnoticed. Fixed today.
    // ────────────────────────────────────────────────────────────────────────
    /// Inject a verification prompt into the conversation as a user message,
    /// forcing the model to check its work before declaring success.
    #[allow(dead_code)]
    pub(crate) fn inject_verify_prompt(&mut self) {
        let files = self.files_edited_this_turn.join(", ");
        let verify_msg = format!(
            "[SYSTEM: You edited {}. Before finishing, verify your changes work. \
             Run the project's build/check/compile command to catch errors. \
             Do NOT start any long-running process that does not exit on its own. \
             If you find errors, fix them now.]",
            files
        );
        // Inject as assistant thought + will trigger another LLM call
        self.conversation.push_delta(&verify_msg);
        self.conversation.finalize_stream();
    }

    /// Legacy auto-compile verification — DISABLED since Phase 4.2.
    /// Replaced by language-agnostic approach: discipline.rs now prompts
    /// the model to verify its own changes (build/test/lint/run).
    /// The model decides the appropriate verification for the project.
    #[allow(dead_code)]
    pub(crate) async fn auto_compile_verify(&mut self) {
        // No-op. Verification is now model-driven via discipline prompt.
        // See discipline.rs apply_post_turn_discipline() for the verification nudge.
    }

    /// Tree-sitter syntax check on recently edited files.
    /// Language-agnostic: works on any file tree-sitter can parse.
    /// Catches bracket mismatches, missing closings, duplicate declarations
    /// that build tools may miss (e.g., Vite doesn't catch Vue SFC syntax errors).
    /// Called for non-compiled projects as an auto-compile equivalent.
    #[allow(dead_code)]
    pub(crate) async fn syntax_check_edited_files(&mut self) {
        let wd = self
            .turn_runner
            .context
            .working_dir
            .try_read()
            .map(|g| g.clone())
            .unwrap_or_default();

        let mut warnings: Vec<String> = Vec::new();
        let mut searcher = self.turn_runner.context.semantic.lock().await;

        for file in &self.files_edited_this_turn {
            // Resolve to full path
            let path = if std::path::Path::new(file).is_absolute() {
                std::path::PathBuf::from(file)
            } else {
                wd.join(file)
            };
            if let Ok(content) = std::fs::read_to_string(&path) {
                let (errors, lines) = searcher.count_syntax_errors(&content, &path);
                if errors > 0 {
                    let lines_str = lines
                        .iter()
                        .map(|l| format!("L{}", l))
                        .collect::<Vec<_>>()
                        .join(", ");
                    warnings.push(format!(
                        "{}: {} syntax error(s) at {}",
                        file, errors, lines_str
                    ));
                }
            }
        }
        drop(searcher);

        if !warnings.is_empty() {
            let msg = format!(
                "[SYNTAX CHECK: {}. Fix these before continuing — the file structure may be broken.]",
                warnings.join("; ")
            );
            self.conversation.add_user_message(&msg);
        }
    }

    /// Snapshot dev server log sizes before an edit, so we can diff after.
    #[allow(dead_code)]
    pub(crate) fn snapshot_devserver_log_sizes(&self) -> std::collections::HashMap<String, u64> {
        let wd = self
            .turn_runner
            .context
            .working_dir
            .try_read()
            .map(|g| g.clone())
            .unwrap_or_default();
        let candidates = [
            "frontend.log",
            "backend.log",
            "server.log",
            "frontend/frontend.log",
            "backend/backend.log",
        ];
        let mut sizes = std::collections::HashMap::new();
        for name in &candidates {
            let path = wd.join(name);
            if let Ok(meta) = std::fs::metadata(&path) {
                sizes.insert(name.to_string(), meta.len());
            }
        }
        sizes
    }

    /// Check dev server logs for NEW errors after editing frontend/backend files.
    /// Only reads lines appended AFTER `pre_sizes` snapshot, ignoring stale errors.
    #[allow(dead_code)]
    pub(crate) async fn check_devserver_logs(
        &mut self,
        pre_sizes: &std::collections::HashMap<String, u64>,
    ) {
        let wd = self
            .turn_runner
            .context
            .working_dir
            .try_read()
            .map(|g| g.clone())
            .unwrap_or_default();

        // Small delay to let HMR process the file change
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;

        for (log_name, &old_size) in pre_sizes {
            let log_path = wd.join(log_name);
            let new_size = match tokio::fs::metadata(&log_path).await {
                Ok(m) => m.len(),
                Err(_) => continue,
            };
            // No new content since edit → skip
            if new_size <= old_size {
                continue;
            }

            // Read only the NEW bytes
            let content = match tokio::fs::read_to_string(&log_path).await {
                Ok(c) => c,
                Err(_) => continue,
            };
            let new_content = if old_size == 0 {
                &content
            } else {
                // Approximate: skip old_size bytes (may split a UTF-8 char, but log lines are mostly ASCII)
                let skip = old_size as usize;
                if skip < content.len() {
                    &content[skip..]
                } else {
                    continue;
                }
            };

            // Look for error patterns in the new content only
            let error_lines: Vec<&str> = new_content
                .lines()
                .filter(|l| {
                    let lower = l.to_lowercase();
                    (lower.contains("error")
                        || lower.contains("failed")
                        || lower.contains("syntaxerror"))
                        && !lower.contains("0 error")
                        && !lower.contains("error overlay")
                })
                .collect();

            if !error_lines.is_empty() {
                let errors: String = error_lines
                    .iter()
                    .take(5)
                    .map(|l| l.to_string())
                    .collect::<Vec<_>>()
                    .join("\n");
                let msg = format!(
                    "[DEV SERVER ERROR in {}:]\n{}\n\nFix these errors before continuing.",
                    log_name, errors
                );
                self.conversation.add_user_message(&msg);
                break; // One log file's errors is enough
            }
        }
    }

    /// No-op: Vue partial edit detection removed. Multi-edit is disabled;
    /// serial edit_file calls with old_string/new_string are the standard approach.
    #[allow(dead_code)]
    pub(crate) async fn check_vue_partial_edit(&mut self) {
        // Intentionally empty. Kept as stub to avoid changing call sites.
    }
}