//! Side-by-side diff between two runs.
//!
//! Usage:
//!   diff_runs <run-dir-A> <run-dir-B>
//!
//! Prints a unified diff of `document.md` and a summary of `==` markers
//! per run, with variant identities pulled from `meta.json`.

use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use clap::Parser;
use similar::{ChangeTag, TextDiff};

#[derive(Parser, Debug)]
#[command(name = "diff_runs")]
struct Args {
    run_a: PathBuf,
    run_b: PathBuf,
}

fn main() -> Result<()> {
    let args = Args::parse();

    println!("=== A: {}", args.run_a.display());
    print_meta_summary(&args.run_a)?;
    println!("=== B: {}", args.run_b.display());
    print_meta_summary(&args.run_b)?;

    let doc_a = read_or_empty(&args.run_a.join("document.md"))?;
    let doc_b = read_or_empty(&args.run_b.join("document.md"))?;

    println!("\n--- document.md diff (A → B) ---");
    print_diff(&doc_a, &doc_b);

    println!(
        "\n=={{}}== markers   A: {}   B: {}",
        count_markers(&doc_a),
        count_markers(&doc_b)
    );

    diff_transcripts(&args.run_a, &args.run_b)?;

    Ok(())
}

fn print_meta_summary(run: &Path) -> Result<()> {
    let meta_path = run.join("meta.json");
    let raw = match std::fs::read_to_string(&meta_path) {
        Ok(s) => s,
        Err(_) => {
            println!("  (no meta.json)");
            return Ok(());
        }
    };
    let v: serde_json::Value = serde_json::from_str(&raw).context("parsing meta.json")?;
    let l = v.get("llm_variant").and_then(|x| x.as_str()).unwrap_or("?");
    let model = v.get("llm_model").and_then(|x| x.as_str()).unwrap_or("?");
    let wallclock = v
        .pointer("/timings_ms/wallclock_ms")
        .and_then(|x| x.as_u64())
        .unwrap_or(0);
    println!("  llm={l}  model={model}  wallclock={wallclock}ms");
    Ok(())
}

fn read_or_empty(p: &Path) -> Result<String> {
    Ok(std::fs::read_to_string(p).unwrap_or_default())
}

fn print_diff(a: &str, b: &str) {
    let diff = TextDiff::from_lines(a, b);
    for change in diff.iter_all_changes() {
        let sign = match change.tag() {
            ChangeTag::Equal => "  ",
            ChangeTag::Insert => "+ ",
            ChangeTag::Delete => "- ",
        };
        print!("{sign}{change}");
    }
}

fn count_markers(text: &str) -> usize {
    text.matches("==").count() / 2
}

fn diff_transcripts(a: &Path, b: &Path) -> Result<()> {
    let ta = a.join("transcripts");
    let tb = b.join("transcripts");
    if !ta.exists() || !tb.exists() {
        return Ok(());
    }
    let mut names: Vec<String> = std::fs::read_dir(&ta)?
        .filter_map(|e| e.ok())
        .filter_map(|e| {
            let p = e.path();
            if p.extension().and_then(|s| s.to_str()) == Some("txt") {
                p.file_name().map(|s| s.to_string_lossy().into_owned())
            } else {
                None
            }
        })
        .collect();
    names.sort();

    for name in names {
        let txt_a = read_or_empty(&ta.join(&name))?;
        let txt_b = read_or_empty(&tb.join(&name))?;
        if txt_a == txt_b {
            continue;
        }
        println!("\n--- transcripts/{name} (A → B) ---");
        print_diff(&txt_a, &txt_b);
    }
    Ok(())
}
