//! Minimal EDID parsing: vendor code, product and display name — enough to
//! label a monitor in Settings without a libdisplay-info dependency.

pub struct EdidInfo {
    pub make: String,
    pub model: String,
    pub serial: String,
}

impl EdidInfo {
    /// Stable identity of the panel across connector renames: "make model
    /// serial", `None` when the EDID carries neither make nor model.
    pub fn identity(&self) -> Option<String> {
        let make = self.make.trim();
        let model = self.model.trim();
        if make.is_empty() && model.is_empty() {
            return None;
        }
        Some(
            [make, model, self.serial.trim()]
                .iter()
                .filter(|s| !s.is_empty())
                .copied()
                .collect::<Vec<_>>()
                .join(" "),
        )
    }
}

const HEADER: [u8; 8] = [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];

pub fn parse(bytes: &[u8]) -> Option<EdidInfo> {
    if bytes.len() < 128 || bytes[..8] != HEADER {
        return None;
    }

    let id = u16::from_be_bytes([bytes[8], bytes[9]]);
    let make: String = [(id >> 10) & 0x1F, (id >> 5) & 0x1F, id & 0x1F]
        .into_iter()
        .filter(|l| (1..=26).contains(l))
        .map(|l| (b'A' + l as u8 - 1) as char)
        .collect();
    let product = u16::from_le_bytes([bytes[10], bytes[11]]);

    // Four 18-byte descriptor blocks; a display descriptor starts with a zero
    // pixel clock, carries its tag at byte 3, and its text spans bytes 5..18
    // terminated by 0x0A.
    let mut model = String::new();
    let mut serial = String::new();
    for off in [54usize, 72, 90, 108] {
        let d = &bytes[off..off + 18];
        if d[0] != 0 || d[1] != 0 {
            continue;
        }
        let text = d[5..18]
            .iter()
            .take_while(|&&b| b != 0x0A)
            .map(|&b| if b.is_ascii_graphic() || b == b' ' { b as char } else { ' ' })
            .collect::<String>()
            .trim()
            .to_string();
        match d[3] {
            0xFC if model.is_empty() => model = text,
            0xFF if serial.is_empty() => serial = text,
            _ => {}
        }
    }

    if model.is_empty() {
        model = format!("{make} {product:04X}");
    }

    Some(EdidInfo { make, model, serial })
}

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

    fn base_edid() -> Vec<u8> {
        let mut e = vec![0u8; 128];
        e[..8].copy_from_slice(&HEADER);
        // "DEL" = Dell: D=4, E=5, L=12 → 00100 00101 01100
        let id: u16 = (4 << 10) | (5 << 5) | 12;
        e[8..10].copy_from_slice(&id.to_be_bytes());
        e[10..12].copy_from_slice(&0xA0F5u16.to_le_bytes());
        e
    }

    #[test]
    fn parses_vendor_and_product() {
        let info = parse(&base_edid()).unwrap();
        assert_eq!(info.make, "DEL");
        assert_eq!(info.model, "DEL F5A0");
        assert_eq!(info.serial, "");
    }

    #[test]
    fn parses_display_name_descriptor() {
        let mut e = base_edid();
        e[54..59].copy_from_slice(&[0, 0, 0, 0xFC, 0]);
        e[59..72].copy_from_slice(b"U2720Q\n      ");
        let info = parse(&e).unwrap();
        assert_eq!(info.model, "U2720Q");
    }

    #[test]
    fn rejects_bad_header() {
        assert!(parse(&[0u8; 128]).is_none());
    }
}
