use std::collections::HashSet;
use std::fs;
use std::path::Path;

/// The structural difference between a real file tree and a set of expected relative paths:
/// `missing` = expected but absent, `extra` = present but unexpected. An empty diff = a match.
/// Takes a plain path list (NOT a Codex blueprint) so Glyph stays independent of the write side.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructureDiff {
    pub missing: Vec<String>,
    pub extra: Vec<String>,
}

impl StructureDiff {
    /// True when the tree contains exactly the expected paths — no more, no less.
    pub fn matches(&self) -> bool {
        self.missing.is_empty() && self.extra.is_empty()
    }
}

/// Compare the files under `root` against `expected` relative paths (`/`-separated). Used to verify
/// a scaffolded tree matches its blueprint: `structure_diff(target, &paths).matches()`.
pub fn structure_diff(root: &Path, expected: &[String]) -> StructureDiff {
    let mut found = Vec::new();
    collect_files(root, root, &mut found);
    let actual: HashSet<String> = found.into_iter().collect();
    let want: HashSet<String> = expected.iter().cloned().collect();
    let mut missing: Vec<String> = want.difference(&actual).cloned().collect();
    let mut extra: Vec<String> = actual.difference(&want).cloned().collect();
    missing.sort();
    extra.sort();
    StructureDiff { missing, extra }
}

/// Collect every file under `dir` as a `/`-separated path relative to `root`.
fn collect_files(dir: &Path, root: &Path, out: &mut Vec<String>) {
    let Ok(entries) = fs::read_dir(dir) else {
        return;
    };
    for entry in entries.flatten() {
        let path = entry.path();
        if path.is_dir() {
            collect_files(&path, root, out);
        } else if let Ok(rel) = path.strip_prefix(root) {
            out.push(rel.to_string_lossy().replace('\\', "/"));
        }
    }
}
