use crate::hash::Hash64;
use serde::Serialize;
use std::path::PathBuf;

/// Stable identifier for an image: its index in the ingested record list.
pub type ImageId = usize;

/// Lightweight per-image metadata. Holds the hash and dimensions but NOT
/// pixels — pixels are re-fetched on demand via a `PixelStore`.
#[derive(Clone, Debug)]
pub struct ImageRecord {
    pub id: ImageId,
    pub path: PathBuf,
    pub hash: Hash64,
    pub width: u32,
    pub height: u32,
}

/// A path that could not be ingested, with the reason.
#[derive(Clone, Debug)]
pub struct Skipped {
    pub path: PathBuf,
    pub reason: String,
}

/// One output record per image. `match_` is `None` for a root (no match).
/// `pixel_distance` is the normalized fraction of differing pixels.
#[derive(Clone, Debug, Serialize)]
pub struct Edge {
    pub image: PathBuf,
    #[serde(rename = "match")]
    pub match_: Option<PathBuf>,
    pub pixel_distance: Option<f64>,
    pub hamming: Option<u32>,
}

/// Which matching algorithm to run.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MatchMode {
    Nearest,
    Forest,
}

/// Tuning knobs for matching.
#[derive(Clone, Debug)]
pub struct MatchOptions {
    pub mode: MatchMode,
    /// BK-tree candidate radius (Hamming).
    pub hamming_radius: u32,
    /// Optional normalized differing-pixel threshold in `[0.0, 1.0]`.
    pub pixel_threshold: Option<f64>,
}

impl Default for MatchOptions {
    fn default() -> Self {
        MatchOptions {
            mode: MatchMode::Nearest,
            hamming_radius: 10,
            pixel_threshold: None,
        }
    }
}

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

    #[test]
    fn edge_serializes_match_key_and_null_root() {
        let matched = Edge {
            image: PathBuf::from("a.png"),
            match_: Some(PathBuf::from("b.png")),
            pixel_distance: Some(0.5),
            hamming: Some(2),
        };
        let json = serde_json::to_string(&matched).unwrap();
        assert!(json.contains("\"match\":\"b.png\""));
        assert!(!json.contains("match_"));

        let root = Edge {
            image: PathBuf::from("c.png"),
            match_: None,
            pixel_distance: None,
            hamming: None,
        };
        let json = serde_json::to_string(&root).unwrap();
        assert!(json.contains("\"match\":null"));
    }

    #[test]
    fn default_match_options_are_nearest_radius_10() {
        let opts = MatchOptions::default();
        assert_eq!(opts.mode, MatchMode::Nearest);
        assert_eq!(opts.hamming_radius, 10);
        assert_eq!(opts.pixel_threshold, None);
    }
}
