#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum EditSafetyMode {
    StructuralEditSafe,
    TextEditSafe,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum EditSourceAuthority {
    DiskRefreshed,
    CurrentIndex,
    /// The edit base was re-read and re-parsed from the rerouted worktree
    /// TARGET because it had diverged from the indexed copy (a prior routed
    /// edit). Splicing into index content here would silently discard those
    /// earlier routed edits (review finding 5, post-v7.19.0).
    WorktreeTarget,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum EditWriteSemantics {
    DryRunNoWrites,
    AtomicWriteAndReindex,
    TransactionalWriteRollbackAndReindex,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum MatchType {
    Exact,
    Constrained,
}

fn safety_mode_label(mode: EditSafetyMode) -> &'static str {
    match mode {
        EditSafetyMode::StructuralEditSafe => "structural-edit-safe",
        EditSafetyMode::TextEditSafe => "text-edit-safe",
    }
}

fn source_authority_label(authority: EditSourceAuthority) -> &'static str {
    match authority {
        EditSourceAuthority::DiskRefreshed => "disk-refreshed",
        EditSourceAuthority::CurrentIndex => "current index",
        EditSourceAuthority::WorktreeTarget => "worktree target (rebased)",
    }
}

fn write_semantics_label(semantics: EditWriteSemantics) -> &'static str {
    match semantics {
        EditWriteSemantics::DryRunNoWrites => "dry run (no writes)",
        EditWriteSemantics::AtomicWriteAndReindex => "atomic write + reindex",
        EditWriteSemantics::TransactionalWriteRollbackAndReindex => {
            "transactional write + rollback + reindex"
        }
    }
}

fn match_type_label(match_type: MatchType) -> &'static str {
    match match_type {
        MatchType::Exact => "exact",
        MatchType::Constrained => "constrained",
    }
}

pub(crate) fn format_edit_envelope(
    safety_mode: EditSafetyMode,
    source_authority: EditSourceAuthority,
    write_semantics: EditWriteSemantics,
    evidence_anchor: &str,
) -> String {
    format!(
        "Edit safety: {}\nPath authority: repository-bound\nSource authority: {}\nWrite semantics: {}\nEvidence: symbol anchor `{}`",
        safety_mode_label(safety_mode),
        source_authority_label(source_authority),
        write_semantics_label(write_semantics),
        evidence_anchor
    )
}

pub(crate) fn format_batch_envelope(
    safety_mode: EditSafetyMode,
    match_type: MatchType,
    source_authority: EditSourceAuthority,
    write_semantics: EditWriteSemantics,
    evidence: &str,
) -> String {
    format!(
        "Edit safety: {}\nMatch type: {}\nPath authority: repository-bound\nSource authority: {}\nWrite semantics: {}\nEvidence: {}",
        safety_mode_label(safety_mode),
        match_type_label(match_type),
        source_authority_label(source_authority),
        write_semantics_label(write_semantics),
        evidence
    )
}

pub(crate) fn format_capability_warning(
    tool_name: &str,
    language: &str,
    required_safety: &str,
    available_safety: &str,
    suggestion: &str,
) -> String {
    format!(
        "{tool_name}: edit safety blocked\nRequired safety: {required_safety}\nAvailable safety: {available_safety}\nLanguage: {language}\nSuggested next step: {suggestion}"
    )
}

/// Format the result of a replace_symbol_body operation.
pub(crate) fn format_replace(
    path: &str,
    name: &str,
    kind: &str,
    old_bytes: usize,
    new_bytes: usize,
) -> String {
    format!("{path} — replaced {kind} `{name}` ({old_bytes} → {new_bytes} bytes)")
}

/// Format the result of an insert operation.
pub(crate) fn format_insert(
    path: &str,
    name: &str,
    position: &str,
    inserted_bytes: usize,
) -> String {
    format!("{path} — inserted {position} `{name}` ({inserted_bytes} bytes)")
}

/// Format the result of a delete operation.
pub(crate) fn format_delete(path: &str, name: &str, kind: &str, deleted_bytes: usize) -> String {
    format!("{path} — deleted {kind} `{name}` ({deleted_bytes} bytes)")
}

/// Format the result of an edit-within-symbol operation.
pub(crate) fn format_edit_within(
    path: &str,
    name: &str,
    replacements: usize,
    old_bytes: usize,
    new_bytes: usize,
) -> String {
    format!(
        "{path} — edited within `{name}` ({replacements} replacement(s), {old_bytes} → {new_bytes} bytes)"
    )
}

/// Format stale reference warnings after a signature-changing edit.
pub(crate) fn format_stale_warnings(
    _path: &str,
    name: &str,
    refs: &[(String, u32, Option<String>)],
) -> String {
    if refs.is_empty() {
        return String::new();
    }
    let mut out = format!(
        "\n[!] Signature of `{name}` may have changed — {} reference(s) to check:\n",
        refs.len()
    );
    for (ref_path, line, enclosing) in refs {
        out.push_str(&format!("  {ref_path}:{line}"));
        if let Some(enc) = enclosing {
            out.push_str(&format!(" (in {enc})"));
        }
        out.push('\n');
    }
    out
}

/// Format the worktree-routing suffix appended to edit responses when the caller
/// supplied `working_directory`. Produces concise target evidence so agents can
/// verify the requested workspace, actual write target, indexed path, and
/// reroute state. Returns an empty string when `working_directory` was omitted,
/// preserving byte-identical output for today's callers.
pub(crate) fn format_reroute_suffix(
    working_directory: Option<&std::path::Path>,
    resolved: &crate::worktree::ResolvedTarget,
) -> String {
    let Some(working_directory) = working_directory else {
        return String::new();
    };
    format!(
        "\nworking_directory: {}\nrerouted: {}\nwrote_to: {}\nindexed_path: {}",
        working_directory.display(),
        resolved.rerouted,
        resolved.target_path.display(),
        resolved.indexed_path.display(),
    )
}

/// Format a batch edit summary.
pub(crate) fn format_batch_summary(results: &[String], file_count: usize) -> String {
    let mut out = format!("{} edit(s) across {} file(s):\n", results.len(), file_count);
    for r in results {
        out.push_str("  ");
        out.push_str(r);
        out.push('\n');
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_format_edit_envelope() {
        let result = format_edit_envelope(
            EditSafetyMode::StructuralEditSafe,
            EditSourceAuthority::DiskRefreshed,
            EditWriteSemantics::AtomicWriteAndReindex,
            "src/lib.rs:12",
        );
        assert!(result.contains("Edit safety: structural-edit-safe"));
        assert!(result.contains("Path authority: repository-bound"));
        assert!(result.contains("Source authority: disk-refreshed"));
        assert!(result.contains("Write semantics: atomic write + reindex"));
        assert!(result.contains("src/lib.rs:12"));
    }

    #[test]
    fn test_format_capability_warning() {
        let result = format_capability_warning(
            "replace_symbol_body",
            "html",
            "structural-edit-safe",
            "text-edit-safe",
            "use edit_within_symbol",
        );
        assert!(result.contains("edit safety blocked"));
        assert!(result.contains("Required safety: structural-edit-safe"));
        assert!(result.contains("Available safety: text-edit-safe"));
        assert!(result.contains("Language: html"));
    }

    #[test]
    fn test_format_batch_envelope() {
        let result = format_batch_envelope(
            EditSafetyMode::StructuralEditSafe,
            MatchType::Constrained,
            EditSourceAuthority::CurrentIndex,
            EditWriteSemantics::TransactionalWriteRollbackAndReindex,
            "definition `src/lib.rs` + 2 target file(s)",
        );
        assert!(result.contains("Edit safety: structural-edit-safe"));
        assert!(result.contains("Match type: constrained"));
        assert!(result.contains("Source authority: current index"));
        assert!(result.contains("Write semantics: transactional write + rollback + reindex"));
        assert!(result.contains("definition `src/lib.rs` + 2 target file(s)"));
    }

    #[test]
    fn test_format_replace() {
        let result = format_replace("src/lib.rs", "process", "fn", 342, 287);
        assert!(result.contains("src/lib.rs"));
        assert!(result.contains("process"));
        assert!(result.contains("342"));
        assert!(result.contains("287"));
    }

    #[test]
    fn test_format_insert() {
        let result = format_insert("src/lib.rs", "handler", "after", 120);
        assert!(result.contains("src/lib.rs"));
        assert!(result.contains("after"));
        assert!(result.contains("handler"));
        assert!(result.contains("120"));
    }

    #[test]
    fn test_format_delete() {
        let result = format_delete("src/lib.rs", "old_fn", "fn", 200);
        assert!(result.contains("src/lib.rs"));
        assert!(result.contains("old_fn"));
        assert!(result.contains("200"));
    }

    #[test]
    fn test_format_edit_within() {
        let result = format_edit_within("src/lib.rs", "process", 2, 500, 480);
        assert!(result.contains("src/lib.rs"));
        assert!(result.contains("process"));
        assert!(result.contains("2"));
    }

    #[test]
    fn test_format_stale_warnings_empty() {
        let result = format_stale_warnings("src/lib.rs", "foo", &[]);
        assert!(result.is_empty());
    }

    #[test]
    fn test_format_stale_warnings_with_refs() {
        let refs = vec![
            ("src/main.rs".to_string(), 45, Some("fn main".to_string())),
            ("src/handler.rs".to_string(), 23, None),
        ];
        let result = format_stale_warnings("src/lib.rs", "process", &refs);
        assert!(result.contains("src/main.rs:45"));
        assert!(result.contains("fn main"));
        assert!(result.contains("src/handler.rs:23"));
        assert!(result.contains("2 reference(s)"));
    }

    #[test]
    fn test_format_batch_summary() {
        let results = vec![
            "src/a.rs — replaced `foo`".to_string(),
            "src/b.rs — deleted `bar`".to_string(),
        ];
        let result = format_batch_summary(&results, 2);
        assert!(result.contains("2 edit(s)"));
        assert!(result.contains("2 file(s)"));
    }
}
