// Copyright 2026 Thomas Zuyev

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::ops::Bound;

/// Represents a length range measured in Unicode scalar values (`char` count),
/// not bytes and not grapheme clusters.
#[derive(Debug, Copy, Clone)]
pub struct LengthRange {
    min: usize,
    max: Option<usize>,
}

impl LengthRange {
    pub fn new<R>(range: R) -> Self
    where
        R: std::ops::RangeBounds<usize>,
    {
        let min = match range.start_bound() {
            Bound::Included(&n) => n,
            Bound::Excluded(&n) => n.saturating_add(1),
            Bound::Unbounded => 0,
        };
        let max = match range.end_bound() {
            Bound::Included(&n) => Some(n),
            Bound::Excluded(&n) => Some(n.saturating_sub(1)),
            Bound::Unbounded => None,
        };

        Self { min, max }
    }

    pub fn within(&self, value: usize) -> bool {
        value >= self.min && self.max.map_or(true, |m| value <= m)
    }

    pub fn min(&self) -> usize {
        self.min
    }

    pub fn max(&self) -> Option<usize> {
        self.max
    }
}

impl std::fmt::Display for LengthRange {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.max() {
            Some(max) => write!(f, "({}; {})", self.min(), max),
            None => write!(f, "({}; unbounded)", self.min()),
        }
    }
}

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

    #[test]
    fn test_new_inclusive_ranges() {
        let r = LengthRange::new(3..=5);
        assert_eq!(r.min(), 3);
        assert_eq!(r.max(), Some(5));
        assert!(r.within(3));
        assert!(r.within(5));
        assert!(!r.within(2));
        assert!(!r.within(6));
    }

    #[test]
    fn test_new_exclusive_ranges() {
        let r = LengthRange::new(3..5);
        assert_eq!(r.min(), 3);
        assert_eq!(r.max(), Some(4));
        assert!(r.within(3));
        assert!(r.within(4));
        assert!(!r.within(5));
    }

    #[test]
    fn test_unbounded_start() {
        let r = LengthRange::new(..5);
        assert_eq!(r.min(), 0);
        assert_eq!(r.max(), Some(4));
        assert!(r.within(0));
        assert!(r.within(4));
        assert!(!r.within(5));
    }

    #[test]
    fn test_unbounded_end() {
        let r = LengthRange::new(3..);
        assert_eq!(r.min(), 3);
        assert_eq!(r.max(), None);
        assert!(r.within(3));
        assert!(r.within(1000));
        assert!(!r.within(2));
    }

    #[test]
    fn test_fully_unbounded() {
        let r = LengthRange::new(..);
        assert_eq!(r.min(), 0);
        assert_eq!(r.max(), None);
        assert!(r.within(0));
        assert!(r.within(9999));
    }

    #[test]
    fn test_start_excluded_edge() {
        let r = LengthRange::new((Bound::Excluded(0), Bound::Included(5)));
        assert_eq!(r.min(), 1);
        assert_eq!(r.max(), Some(5));
        assert!(!r.within(0));
        assert!(r.within(1));
    }

    #[test]
    fn test_end_excluded_edge() {
        let r = LengthRange::new((Bound::Included(0), Bound::Excluded(1)));
        assert_eq!(r.min(), 0);
        assert_eq!(r.max(), Some(0));
        assert!(r.within(0));
        assert!(!r.within(1));
    }

    #[test]
    fn test_display() {
        let r = LengthRange::new(1..=3);
        assert_eq!(format!("{}", r), "(1; 3)");

        let r = LengthRange::new(5..);
        assert_eq!(format!("{}", r), "(5; unbounded)");
    }

    #[test]
    fn test_empty() {
        let r = LengthRange::new(10..2);
        // No value should be within
        assert!(!r.within(5));
        assert!(!r.within(10));
        assert!(!r.within(2));
    }
}
