use std::{
    cmp::Ordering,
    fmt::{Display, Formatter, Result as FmtResult},
    str::FromStr,
};

use serde::{Serialize, Serializer};

use crate::error::ParseIdError;

const SEPARATORS: [char; 4] = ['.', ':', '/', '|'];

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Segment {
    Number(u64),
    Letter(String),
    Separator(char),
}

impl Ord for Segment {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (Self::Number(left), Self::Number(right)) => left.cmp(right),
            (Self::Letter(left), Self::Letter(right)) => left.cmp(right),
            (Self::Separator(left), Self::Separator(right)) => left.cmp(right),
            _ => {
                let rank = |segment: &Segment| match segment {
                    Segment::Separator(_) => 0,
                    Segment::Number(_) => 1,
                    Segment::Letter(_) => 2,
                };

                rank(self).cmp(&rank(other))
            }
        }
    }
}

impl PartialOrd for Segment {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Display for Segment {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        match self {
            Segment::Number(value) => write!(formatter, "{value}"),
            Segment::Letter(value) => formatter.write_str(value),
            Segment::Separator(value) => write!(formatter, "{value}"),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FolgezettelId {
    segments: Vec<Segment>,
}

#[derive(Clone, Copy)]
enum Kind {
    Number,
    Letter,
}

impl Kind {
    fn toggle(self) -> Self {
        match self {
            Kind::Number => Kind::Letter,
            Kind::Letter => Kind::Number,
        }
    }
}

impl FolgezettelId {
    pub fn parse(input: impl AsRef<str>) -> Result<Self, ParseIdError> {
        let input = input.as_ref();

        if input.is_empty() {
            return Err(ParseIdError::Empty);
        }

        let mut segments = Vec::new();
        let mut position = 0;
        let mut next_kind = Kind::Number;

        while position < input.len() {
            let next = input[position..].chars().next();

            if let Some(separator) = next.filter(|c| !segments.is_empty() && SEPARATORS.contains(c))
            {
                position += separator.len_utf8();
                segments.push(Segment::Separator(separator));
                next_kind = next_kind.toggle();
                continue;
            }

            let rest = &input[position..];
            segments.push(match next_kind {
                Kind::Number => {
                    let digits = leading_run(rest, |c| c.is_ascii_digit());

                    if digits.is_empty() {
                        return Err(ParseIdError::ExpectedNumber { position });
                    }

                    let number = digits
                        .parse()
                        .map_err(|_| ParseIdError::NumberOverflow { position })?;
                    position += digits.len();
                    Segment::Number(number)
                }
                Kind::Letter => {
                    let letters = leading_run(rest, |c| c.is_ascii_alphabetic());
                    if letters.is_empty() {
                        return Err(ParseIdError::ExpectedLetter { position });
                    }
                    position += letters.len();
                    Segment::Letter(letters.to_ascii_lowercase())
                }
            });
            next_kind = next_kind.toggle();
        }
        Ok(Self { segments })
    }

    pub fn segments(&self) -> &[Segment] {
        &self.segments
    }

    pub fn parent(&self) -> Option<Self> {
        let body = self.segments.split_last()?.1;
        let body = match body.split_last() {
            Some((Segment::Separator(_), head)) => head,
            _ => body,
        };

        (!body.is_empty()).then(|| Self {
            segments: body.to_vec(),
        })
    }
}

fn leading_run(input: &str, matches: impl Fn(char) -> bool) -> &str {
    let end = input
        .find(|character| !matches(character))
        .unwrap_or(input.len());
    &input[..end]
}

impl FromStr for FolgezettelId {
    type Err = ParseIdError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        Self::parse(input)
    }
}

impl From<&FolgezettelId> for String {
    fn from(id: &FolgezettelId) -> Self {
        id.to_string()
    }
}

impl Display for FolgezettelId {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        for segment in &self.segments {
            write!(formatter, "{segment}")?;
        }
        Ok(())
    }
}

impl Serialize for FolgezettelId {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_str(self)
    }
}

#[test]
fn reject_malformed_ids() {
    assert_eq!(FolgezettelId::parse("").unwrap_err(), ParseIdError::Empty);
    assert_eq!(
        FolgezettelId::parse("a1").unwrap_err(),
        ParseIdError::ExpectedNumber { position: 0 }
    );
    assert_eq!(
        FolgezettelId::parse("1$").unwrap_err(),
        ParseIdError::ExpectedLetter { position: 1 }
    );
    assert_eq!(
        FolgezettelId::parse("99999999999999999999").unwrap_err(),
        ParseIdError::NumberOverflow { position: 0 }
    );
}

#[test]
fn normalizes_uppercase_letters() {
    assert_eq!(
        FolgezettelId::parse("1ABCD").unwrap(),
        FolgezettelId::parse("1abcd").unwrap()
    );
}

#[test]
fn handles_separators() {
    assert_eq!(
        FolgezettelId::parse("1.1"),
        Ok(FolgezettelId {
            segments: vec![
                Segment::Number(1),
                Segment::Separator('.'),
                Segment::Number(1)
            ]
        })
    );
}
