use serde::{Deserialize, Serialize};

use super::Grid;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Direction {
    Across,
    Down,
}

#[derive(Debug, Clone)]
pub struct Intersection {
    pub other_slot: usize,
    pub self_pos: usize,
    pub other_pos: usize,
}

#[derive(Debug, Clone)]
pub struct Slot {
    pub index: usize,
    pub row: usize,
    pub col: usize,
    pub direction: Direction,
    pub length: usize,
    pub intersections: Vec<Intersection>,
}

impl Slot {
    pub fn cells(&self) -> Vec<(usize, usize)> {
        (0..self.length)
            .map(|i| match self.direction {
                Direction::Across => (self.row, self.col + i),
                Direction::Down => (self.row + i, self.col),
            })
            .collect()
    }
}

pub fn extract_slots(grid: &Grid, min_length: usize) -> Vec<Slot> {
    let mut slots = Vec::new();

    // across slots
    for r in 0..grid.height {
        let mut c = 0;
        while c < grid.width {
            if grid.get(r, c).is_white() {
                let start = c;
                while c < grid.width && grid.get(r, c).is_white() {
                    c += 1;
                }
                let len = c - start;
                if len >= min_length {
                    slots.push(Slot {
                        index: slots.len(),
                        row: r,
                        col: start,
                        direction: Direction::Across,
                        length: len,
                        intersections: Vec::new(),
                    });
                }
            } else {
                c += 1;
            }
        }
    }

    // down slots
    for c in 0..grid.width {
        let mut r = 0;
        while r < grid.height {
            if grid.get(r, c).is_white() {
                let start = r;
                while r < grid.height && grid.get(r, c).is_white() {
                    r += 1;
                }
                let len = r - start;
                if len >= min_length {
                    slots.push(Slot {
                        index: slots.len(),
                        row: start,
                        col: c,
                        direction: Direction::Down,
                        length: len,
                        intersections: Vec::new(),
                    });
                }
            } else {
                r += 1;
            }
        }
    }

    compute_intersections(&mut slots);
    slots
}

fn compute_intersections(slots: &mut Vec<Slot>) {
    let n = slots.len();
    // build a map of (row, col) -> (slot_index, position_in_slot)
    let mut cell_map: std::collections::HashMap<(usize, usize), Vec<(usize, usize)>> =
        std::collections::HashMap::new();

    for slot in slots.iter() {
        for (pos, (r, c)) in slot.cells().iter().enumerate() {
            cell_map.entry((*r, *c)).or_default().push((slot.index, pos));
        }
    }

    let mut intersections: Vec<Vec<Intersection>> = vec![Vec::new(); n];

    for entries in cell_map.values() {
        if entries.len() == 2 {
            let (s1, p1) = entries[0];
            let (s2, p2) = entries[1];
            intersections[s1].push(Intersection {
                other_slot: s2,
                self_pos: p1,
                other_pos: p2,
            });
            intersections[s2].push(Intersection {
                other_slot: s1,
                self_pos: p2,
                other_pos: p1,
            });
        }
    }

    for (i, slot) in slots.iter_mut().enumerate() {
        slot.intersections = std::mem::take(&mut intersections[i]);
    }
}

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

    fn make_mini_grid() -> Grid {
        // 3x3 grid with center black:
        // . . .
        // . # .
        // . . .
        let mut g = Grid::new(3, 3);
        g.set(1, 1, Cell::Black);
        g
    }

    #[test]
    fn extract_across_slots() {
        let g = make_mini_grid();
        let slots = extract_slots(&g, 2);
        let across: Vec<_> = slots
            .iter()
            .filter(|s| s.direction == Direction::Across)
            .collect();
        assert_eq!(across.len(), 2); // top row (3) and no middle across (1+1 too short)
        assert!(across.iter().any(|s| s.row == 0 && s.length == 3));
        assert!(across.iter().any(|s| s.row == 2 && s.length == 3));
    }

    #[test]
    fn extract_down_slots() {
        let g = make_mini_grid();
        let slots = extract_slots(&g, 2);
        let down: Vec<_> = slots
            .iter()
            .filter(|s| s.direction == Direction::Down)
            .collect();
        assert_eq!(down.len(), 2); // left col (3) and right col (3)
        assert!(down.iter().any(|s| s.col == 0 && s.length == 3));
        assert!(down.iter().any(|s| s.col == 2 && s.length == 3));
    }

    #[test]
    fn intersections_found() {
        let g = Grid::new(3, 3); // fully white
        let slots = extract_slots(&g, 3);
        // 3 across + 3 down = 6 slots, but only rows/cols of length 3
        // actually: 3 across (one per row) + 3 down (one per col)
        for slot in &slots {
            assert!(!slot.intersections.is_empty());
        }
    }
}
