/// Position of a chunk within the chunk grid.
///
/// Each element is the chunk index along that dimension. For example,
/// `ChunkCoord(vec![3, 7])` refers to the chunk at grid position (3, 7)
/// in a 2-D array.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ChunkCoord(pub Vec<i64>);

impl ChunkCoord {
    /// Creates a new chunk coordinate from a slice.
    pub fn new(coords: &[i64]) -> Self {
        Self(coords.to_vec())
    }

    /// Number of dimensions.
    pub fn ndim(&self) -> usize {
        self.0.len()
    }

    /// Returns the coordinate along the given axis.
    ///
    /// # Panics
    ///
    /// Panics if `axis >= self.ndim()`.
    pub fn axis(&self, axis: usize) -> i64 {
        self.0[axis]
    }

    /// Converts cell-space indices to a chunk coordinate given chunk sizes.
    ///
    /// For each dimension: `chunk_index = cell_index / chunk_size` (floor division).
    pub fn from_cell_indices(cell_indices: &[i64], chunk_sizes: &[u64]) -> Self {
        assert_eq!(
            cell_indices.len(),
            chunk_sizes.len(),
            "cell_indices and chunk_sizes must have the same length"
        );
        let coords = cell_indices
            .iter()
            .zip(chunk_sizes.iter())
            .map(|(&idx, &cs)| idx.div_euclid(cs as i64))
            .collect();
        Self(coords)
    }
}

impl std::fmt::Display for ChunkCoord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "(")?;
        for (i, c) in self.0.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }
            write!(f, "{c}")?;
        }
        write!(f, ")")
    }
}

impl std::ops::Index<usize> for ChunkCoord {
    type Output = i64;
    fn index(&self, index: usize) -> &Self::Output {
        &self.0[index]
    }
}

impl From<Vec<i64>> for ChunkCoord {
    fn from(v: Vec<i64>) -> Self {
        Self(v)
    }
}

impl From<&[i64]> for ChunkCoord {
    fn from(s: &[i64]) -> Self {
        Self(s.to_vec())
    }
}

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

    #[cfg(feature = "serde")]
    #[test]
    fn chunk_coord_serde_roundtrip() {
        let c = ChunkCoord::new(&[3, 7, 0]);
        let json = serde_json::to_string(&c).unwrap();
        let back: ChunkCoord = serde_json::from_str(&json).unwrap();
        assert_eq!(back, c);
    }

    #[test]
    fn chunk_coord_basics() {
        let c = ChunkCoord::new(&[3, 7, 0]);
        assert_eq!(c.ndim(), 3);
        assert_eq!(c.axis(0), 3);
        assert_eq!(c[1], 7);
        assert_eq!(c.to_string(), "(3, 7, 0)");
    }

    #[test]
    fn from_cell_indices() {
        // cell (500, 700) with chunk size (256, 256) → chunk (1, 2)
        let c = ChunkCoord::from_cell_indices(&[500, 700], &[256, 256]);
        assert_eq!(c, ChunkCoord::new(&[1, 2]));
    }

    #[test]
    fn from_cell_indices_exact_boundary() {
        // cell (256, 0) → chunk (1, 0)
        let c = ChunkCoord::from_cell_indices(&[256, 0], &[256, 256]);
        assert_eq!(c, ChunkCoord::new(&[1, 0]));
    }

    #[test]
    fn from_vec() {
        let c: ChunkCoord = vec![1i64, 2, 3].into();
        assert_eq!(c.ndim(), 3);
    }
}
