use crate::parser::token::Keyword;
use std::fmt;

#[derive(Debug, Clone)]
pub enum Expected {
    Keyword(Keyword),
    Description(&'static str),
}

#[derive(Debug, Clone)]
pub struct ParseError {
    pub position: usize,
    pub expected: Vec<Expected>,
    pub found: Option<String>,
}

impl ParseError {
    pub fn new(position: usize) -> Self {
        Self {
            position,
            expected: Vec::new(),
            found: None,
        }
    }

    pub fn expected(mut self, exp: Expected) -> Self {
        self.expected.push(exp);
        self
    }

    pub fn expected_keyword(mut self, kw: Keyword) -> Self {
        self.expected.push(Expected::Keyword(kw));
        self
    }

    pub fn expected_desc(mut self, desc: &'static str) -> Self {
        self.expected.push(Expected::Description(desc));
        self
    }

    pub fn found(mut self, found: String) -> Self {
        self.found = Some(found);
        self
    }
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "parse error at token {}", self.position)?;
        if !self.expected.is_empty() {
            let expected_strs: Vec<String> = self
                .expected
                .iter()
                .map(|e| match e {
                    Expected::Keyword(kw) => format!("keyword {}", kw),
                    Expected::Description(d) => d.to_string(),
                })
                .collect();
            write!(f, ", expected: {}", expected_strs.join(", "))?;
        }
        if let Some(ref found) = self.found {
            write!(f, ", found: '{}'", found)?;
        }
        Ok(())
    }
}

impl std::error::Error for ParseError {}

pub type ParseResult<T> = Result<T, ParseError>;
