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

use diwe::config::Configuration;
use diwe::fs::new_for_path;
use diwe::graph_from_path;
use serde::Serialize;

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
pub struct Churn {
    pub changed: usize,
    pub total: usize,
}

impl Churn {
    pub fn render(&self) -> String {
        if self.total == 0 {
            return "no files".to_string();
        }
        format!("{}/{} files", self.changed, self.total)
    }
}

fn library_path(root: &Path, config: &Configuration) -> PathBuf {
    if config.library.path.is_empty() {
        root.to_path_buf()
    } else {
        root.join(&config.library.path)
    }
}

pub fn measure(root: &Path, config: &Configuration) -> Churn {
    let library = library_path(root, config);
    if !library.exists() {
        return Churn::default();
    }

    let before = new_for_path(&library, config.format);
    if before.is_empty() {
        return Churn::default();
    }

    let graph = graph_from_path(
        &library,
        false,
        config.format_options(),
        config.library.frontmatter_document_title.clone(),
    );
    let after = graph.export();

    let changed = before
        .iter()
        .filter(|(key, content)| {
            after
                .get(*key)
                .is_some_and(|normalized| normalized != *content)
        })
        .count();

    Churn {
        changed,
        total: before.len(),
    }
}

pub fn sample_diff(root: &Path, config: &Configuration) -> Option<String> {
    let library = library_path(root, config);
    if !library.exists() {
        return None;
    }

    let before = new_for_path(&library, config.format);
    let graph = graph_from_path(
        &library,
        false,
        config.format_options(),
        config.library.frontmatter_document_title.clone(),
    );
    let after = graph.export();

    let mut keys: Vec<&String> = before
        .keys()
        .filter(|key| {
            after
                .get(*key)
                .is_some_and(|normalized| normalized != &before[*key])
        })
        .collect();
    keys.sort();

    let key = keys.first()?;
    let original = &before[*key];
    let normalized = after.get(*key)?;

    let mut output = format!("--- {} (on disk)\n+++ {} (after normalize)\n", key, key);
    for line in line_diff(original, normalized) {
        output.push_str(&line);
        output.push('\n');
    }
    Some(output)
}

fn line_diff(before: &str, after: &str) -> Vec<String> {
    let before_lines: Vec<&str> = before.lines().collect();
    let after_lines: Vec<&str> = after.lines().collect();

    let mut output = Vec::new();
    let mut shown = 0;
    let length = before_lines.len().max(after_lines.len());

    for index in 0..length {
        let original = before_lines.get(index);
        let normalized = after_lines.get(index);
        if original == normalized {
            continue;
        }
        if shown >= 12 {
            output.push("… diff truncated".to_string());
            break;
        }
        if let Some(line) = original {
            output.push(format!("-{}", line));
        }
        if let Some(line) = normalized {
            output.push(format!("+{}", line));
        }
        shown += 1;
    }

    output
}