use super::lex::{Spanned, Tok};
use crate::query::{ComparisonOp, FieldPath, Literal, Predicate, Projection, Query, QueryError};

struct Parser<'a> {
    toks: &'a [Spanned],
    pos: usize,
}

pub(crate) fn parse_query(toks: &[Spanned]) -> Result<Query, QueryError> {
    let mut p = Parser { toks, pos: 0 };
    let projection = if p.peek_is(&Tok::Select) {
        p.bump();
        p.parse_projection()?
    } else {
        Projection::All
    };
    let predicate = if p.peek_is(&Tok::Where) {
        p.bump();
        p.parse_or()?
    } else {
        Predicate::True
    };
    if p.pos < p.toks.len() {
        return Err(p.err("unexpected trailing tokens"));
    }
    Ok(Query {
        projection,
        predicate,
    })
}

impl<'a> Parser<'a> {
    fn peek(&self) -> Option<&Tok> {
        self.toks.get(self.pos).map(|s| &s.tok)
    }

    fn peek_is(&self, t: &Tok) -> bool {
        match (self.peek(), t) {
            (Some(a), b) => std::mem::discriminant(a) == std::mem::discriminant(b),
            _ => false,
        }
    }

    fn bump(&mut self) -> Option<&Spanned> {
        let v = self.toks.get(self.pos)?;
        self.pos += 1;
        Some(v)
    }

    fn err(&self, msg: &str) -> QueryError {
        let offset = self.toks.get(self.pos).map(|s| s.offset).unwrap_or(0);
        QueryError::Parse {
            offset,
            message: msg.into(),
        }
    }

    fn parse_projection(&mut self) -> Result<Projection, QueryError> {
        if matches!(self.peek(), Some(Tok::Star)) {
            self.bump();
            return Ok(Projection::All);
        }
        let mut paths = Vec::new();
        paths.push(self.parse_path()?);
        while matches!(self.peek(), Some(Tok::Comma)) {
            self.bump();
            paths.push(self.parse_path()?);
        }
        Ok(Projection::Fields(paths))
    }

    fn parse_path(&mut self) -> Result<FieldPath, QueryError> {
        let mut segs = Vec::new();
        let first = match self.bump() {
            Some(s) => match &s.tok {
                Tok::Ident(id) => id.clone(),
                _ => return Err(self.err("expected identifier")),
            },
            None => return Err(self.err("expected identifier, got end of input")),
        };
        segs.push(first);
        while matches!(self.peek(), Some(Tok::Dot)) {
            self.bump();
            let next = match self.bump() {
                Some(s) => match &s.tok {
                    Tok::Ident(id) => id.clone(),
                    _ => return Err(self.err("expected identifier after `.`")),
                },
                None => return Err(self.err("expected identifier after `.`")),
            };
            segs.push(next);
        }
        Ok(FieldPath::from_segments(segs))
    }

    fn parse_or(&mut self) -> Result<Predicate, QueryError> {
        let mut lhs = self.parse_and()?;
        while matches!(self.peek(), Some(Tok::Or)) {
            self.bump();
            let rhs = self.parse_and()?;
            lhs = Predicate::Or(vec![lhs, rhs]);
        }
        Ok(lhs)
    }

    fn parse_and(&mut self) -> Result<Predicate, QueryError> {
        let mut lhs = self.parse_not()?;
        while matches!(self.peek(), Some(Tok::And)) {
            self.bump();
            let rhs = self.parse_not()?;
            lhs = Predicate::And(vec![lhs, rhs]);
        }
        Ok(lhs)
    }

    fn parse_not(&mut self) -> Result<Predicate, QueryError> {
        if matches!(self.peek(), Some(Tok::Not)) {
            self.bump();
            let inner = self.parse_not()?;
            return Ok(Predicate::not(inner));
        }
        if matches!(self.peek(), Some(Tok::Exists)) {
            self.bump();
            let path = self.parse_path()?;
            return Ok(Predicate::exists(path));
        }
        self.parse_cmp()
    }

    fn parse_cmp(&mut self) -> Result<Predicate, QueryError> {
        if matches!(self.peek(), Some(Tok::LParen)) {
            self.bump();
            let inner = self.parse_or()?;
            match self.bump() {
                Some(Spanned {
                    tok: Tok::RParen, ..
                }) => {}
                _ => return Err(self.err("expected `)`")),
            }
            return Ok(inner);
        }
        let left = self.parse_path()?;
        // `path contains value` lowers to Predicate::Contains.
        if matches!(self.peek(), Some(Tok::Contains)) {
            self.bump();
            let right = self.parse_value()?;
            if matches!(right, Literal::Array(_)) {
                return Err(self.err("`contains` rhs must be a single literal, not an array"));
            }
            return Ok(Predicate::contains(left, right));
        }
        let op = match self.bump() {
            Some(s) => match &s.tok {
                Tok::Eq => ComparisonOp::Eq,
                Tok::Ne => ComparisonOp::Ne,
                Tok::Lt => ComparisonOp::Lt,
                Tok::Le => ComparisonOp::Le,
                Tok::Gt => ComparisonOp::Gt,
                Tok::Ge => ComparisonOp::Ge,
                Tok::Like => ComparisonOp::Like,
                Tok::In => ComparisonOp::InSet,
                _ => return Err(self.err("expected comparison operator")),
            },
            None => return Err(self.err("expected comparison operator")),
        };
        let right = self.parse_value()?;
        Ok(Predicate::Compare { left, op, right })
    }

    fn parse_value(&mut self) -> Result<Literal, QueryError> {
        match self.bump() {
            Some(s) => match &s.tok {
                Tok::String(v) => Ok(Literal::String(v.clone())),
                Tok::Number(n) => Ok(Literal::Number(*n)),
                Tok::True => Ok(Literal::Bool(true)),
                Tok::False => Ok(Literal::Bool(false)),
                Tok::Null => Ok(Literal::Null),
                Tok::LBracket => {
                    let mut items = Vec::new();
                    if !matches!(self.peek(), Some(Tok::RBracket)) {
                        items.push(self.parse_value()?);
                        while matches!(self.peek(), Some(Tok::Comma)) {
                            self.bump();
                            items.push(self.parse_value()?);
                        }
                    }
                    match self.bump() {
                        Some(Spanned {
                            tok: Tok::RBracket, ..
                        }) => Ok(Literal::Array(items)),
                        _ => Err(self.err("expected `]`")),
                    }
                }
                _ => Err(self.err("expected literal value")),
            },
            None => Err(self.err("expected literal value")),
        }
    }
}
