use crate::error::{Result, ResultWithContext, VegaFusionError};
use crate::expression::lexer::{tokenize, Token};
use crate::expression::ops::{binary_op_from_token, logical_op_from_token, unary_op_from_token};
use crate::proto::gen::expression::expression::Expr;
use crate::proto::gen::expression::{
    ArrayExpression, BinaryExpression, BinaryOperator, CallExpression, ConditionalExpression,
    Expression, Identifier, Literal, LogicalExpression, LogicalOperator, MemberExpression,
    ObjectExpression, Property, Span, UnaryExpression, UnaryOperator,
};
pub fn parse(expr: &str) -> Result<Expression> {
    let mut tokens = tokenize(expr)?;
    let result = perform_parse(&mut tokens, 0.0, expr)?;
    if !tokens.is_empty() {
        let (token, start, _) = &tokens[0];
        return Err(VegaFusionError::parse(format!(
            "Unexpected token {token} at position {start} in expression: {expr}"
        )));
    }
    Ok(result)
}
fn perform_parse(
    tokens: &mut Vec<(Token, usize, usize)>,
    min_bp: f64,
    full_expr: &str,
) -> Result<Expression> {
    if tokens.is_empty() {
        return Err(VegaFusionError::parse("Unexpected end of expression"));
    }
    let (lhs_token, start, end) = tokens[0].clone();
    tokens.remove(0);
    let lhs_result = if is_atom(&lhs_token) {
        parse_atom(&lhs_token, start, end)
    } else if let Ok(op) = unary_op_from_token(&lhs_token) {
        parse_unary(tokens, op, start, full_expr)
    } else if lhs_token == Token::OpenParen {
        parse_paren_grouping(tokens, full_expr)
    } else if lhs_token == Token::OpenSquare {
        parse_array(tokens, start, full_expr)
    } else if lhs_token == Token::OpenCurly {
        parse_object(tokens, start, full_expr)
    } else {
        Err(VegaFusionError::parse(format!(
            "Unexpected token: {lhs_token}"
        )))
    };
    let mut lhs = lhs_result.with_context(|| {
        format!("Failed to parse form starting at position {start} in expression: {full_expr}")
    })?;
    while !tokens.is_empty() {
        let (token, start, _) = &tokens[0];
        let start = *start;
        match token {
            Token::CloseParen
            | Token::CloseCurly
            | Token::CloseSquare
            | Token::Comma
            | Token::Colon => break,
            _ => {}
        }
        let expr_result: Result<Expression> = if let Ok(op) = binary_op_from_token(token) {
            if let Some(new_lhs_result) = parse_binary(tokens, op, &lhs, min_bp, start, full_expr) {
                new_lhs_result
            } else {
                break;
            }
        } else if let Ok(op) = logical_op_from_token(token) {
            if let Some(new_lhs_result) = parse_logical(tokens, op, &lhs, min_bp, start, full_expr)
            {
                new_lhs_result
            } else {
                break;
            }
        } else if token == &Token::OpenParen {
            if let Some(new_lhs_result) = parse_call(tokens, &lhs, min_bp, start, full_expr) {
                new_lhs_result
            } else {
                break;
            }
        } else if token == &Token::OpenSquare {
            if let Some(new_lhs_result) =
                parse_computed_member(tokens, &lhs, min_bp, start, full_expr)
            {
                new_lhs_result
            } else {
                break;
            }
        } else if token == &Token::Dot {
            if let Some(new_lhs_result) =
                parse_static_member(tokens, &lhs, min_bp, start, full_expr)
            {
                new_lhs_result
            } else {
                break;
            }
        } else if token == &Token::Question {
            if let Some(new_lhs_result) = parse_ternary(tokens, &lhs, min_bp, start, full_expr) {
                new_lhs_result
            } else {
                break;
            }
        } else {
            Err(VegaFusionError::parse(format!(
                "Unexpected token '{token}'"
            )))
        };
        lhs = expr_result.with_context(|| {
            format!("Failed to parse form starting at position {start} in expression: {full_expr}")
        })?;
    }
    Ok(lhs)
}
pub fn expect_token(
    tokens: &mut Vec<(Token, usize, usize)>,
    expected: Token,
) -> Result<(Token, usize, usize)> {
    if tokens.is_empty() {
        return Err(VegaFusionError::parse(format!(
            "Expected {expected}, reached end of expression"
        )));
    }
    let (token, start, end) = tokens[0].clone();
    if token != expected {
        return Err(VegaFusionError::parse(format!(
            "Expected {expected}, received {token}"
        )));
    }
    tokens.remove(0);
    Ok((token, start, end))
}
pub fn is_atom(token: &Token) -> bool {
    matches!(
        token,
        Token::Null
            | Token::Number { .. }
            | Token::Identifier { .. }
            | Token::String { .. }
            | Token::Bool { .. }
    )
}
pub fn parse_atom(token: &Token, start: usize, end: usize) -> Result<Expression> {
    let span = Span {
        start: start as i32,
        end: end as i32,
    };
    let expr = match token {
        Token::Null => Expr::from(Literal::null()),
        Token::Bool { value, raw } => Expr::from(Literal::new(*value, raw)),
        Token::Number { value, raw } => Expr::from(Literal::new(*value, raw)),
        Token::String { value, raw } => Expr::from(Literal::new(value.clone(), raw)),
        Token::Identifier { value } => Expr::from(Identifier::new(value)),
        _ => {
            return Err(VegaFusionError::parse(format!(
                "Token not an atom: {token}"
            )))
        }
    };
    Ok(Expression {
        expr: Some(expr),
        span: Some(span),
    })
}
pub fn parse_unary(
    tokens: &mut Vec<(Token, usize, usize)>,
    op: UnaryOperator,
    start: usize,
    full_expr: &str,
) -> Result<Expression> {
    let unary_bp = op.unary_binding_power();
    let rhs = perform_parse(tokens, unary_bp, full_expr)?;
    let new_span = Span {
        start: start as i32,
        end: rhs.span.clone().unwrap().end,
    };
    let expr = Expr::from(UnaryExpression::new(&op, rhs));
    Ok(Expression::new(expr, Some(new_span)))
}
pub fn parse_binary(
    tokens: &mut Vec<(Token, usize, usize)>,
    op: BinaryOperator,
    lhs: &Expression,
    min_bp: f64,
    start: usize,
    full_expr: &str,
) -> Option<Result<Expression>> {
    let (left_bp, right_bp) = op.infix_binding_power();
    if left_bp < min_bp {
        return None;
    }
    tokens.remove(0);
    Some(match perform_parse(tokens, right_bp, full_expr) {
        Ok(rhs) => {
            let new_span = Span {
                start: start as i32,
                end: rhs.span.clone().unwrap().end,
            };
            let expr = Expr::from(BinaryExpression::new(lhs.clone(), &op, rhs));
            Ok(Expression::new(expr, Some(new_span)))
        }
        Err(err) => Err(err),
    })
}
pub fn parse_logical(
    tokens: &mut Vec<(Token, usize, usize)>,
    op: LogicalOperator,
    lhs: &Expression,
    min_bp: f64,
    start: usize,
    full_expr: &str,
) -> Option<Result<Expression>> {
    let (left_bp, right_bp) = op.infix_binding_power();
    if left_bp < min_bp {
        return None;
    }
    tokens.remove(0);
    Some(match perform_parse(tokens, right_bp, full_expr) {
        Ok(rhs) => {
            let new_span = Span {
                start: start as i32,
                end: rhs.span.clone().unwrap().end,
            };
            let expr = Expr::from(LogicalExpression::new(lhs.clone(), &op, rhs));
            Ok(Expression::new(expr, Some(new_span)))
        }
        Err(err) => Err(err),
    })
}
pub fn parse_call(
    tokens: &mut Vec<(Token, usize, usize)>,
    lhs: &Expression,
    min_bp: f64,
    start: usize,
    full_expr: &str,
) -> Option<Result<Expression>> {
    let lhs = match lhs
        .as_identifier()
        .with_context(|| "Only global functions are callable")
    {
        Ok(identifier) => identifier,
        Err(err) => return Some(Err(err)),
    };
    let computed_member_bp = 20.0;
    if min_bp >= computed_member_bp {
        return None;
    }
    expect_token(tokens, Token::OpenParen).unwrap();
    let mut arguments: Vec<Expression> = Vec::new();
    while !tokens.is_empty() && tokens[0].0 != Token::CloseParen {
        let parsed_arg = perform_parse(tokens, 1.0, full_expr);
        match parsed_arg {
            Ok(parsed_arg) => {
                arguments.push(parsed_arg);
                expect_token(tokens, Token::Comma).ok();
            }
            Err(err) => return Some(Err(err)),
        }
    }
    let (_, _, end) = expect_token(tokens, Token::CloseParen).unwrap();
    let new_span = Span {
        start: start as i32,
        end: end as i32,
    };
    let expr = Expr::from(CallExpression::new(&lhs.name, arguments));
    Some(Ok(Expression::new(expr, Some(new_span))))
}
pub fn parse_computed_member(
    tokens: &mut Vec<(Token, usize, usize)>,
    lhs: &Expression,
    min_bp: f64,
    start: usize,
    full_expr: &str,
) -> Option<Result<Expression>> {
    let computed_member_bp = 20.0;
    if min_bp >= computed_member_bp {
        return None;
    }
    expect_token(tokens, Token::OpenSquare).unwrap();
    Some(match perform_parse(tokens, 1.0, full_expr) {
        Ok(property) => {
            let (_, _, end) = expect_token(tokens, Token::CloseSquare).unwrap();
            let new_span = Span {
                start: start as i32,
                end: end as i32,
            };
            let expr = Expr::from(MemberExpression::new_computed(lhs.clone(), property));
            Ok(Expression::new(expr, Some(new_span)))
        }
        Err(err) => Err(err),
    })
}
pub fn parse_static_member(
    tokens: &mut Vec<(Token, usize, usize)>,
    lhs: &Expression,
    min_bp: f64,
    start: usize,
    full_expr: &str,
) -> Option<Result<Expression>> {
    let computed_member_bp = 20.0;
    if min_bp >= computed_member_bp {
        return None;
    }
    expect_token(tokens, Token::Dot).unwrap();
    Some(match perform_parse(tokens, 1000.0, full_expr) {
        Ok(property) => {
            let new_span = Span {
                start: start as i32,
                end: property.span.clone().unwrap().end,
            };
            let expr = match MemberExpression::new_static(lhs.clone(), property) {
                Ok(member) => Expr::from(member),
                Err(err) => return Some(Err(err)),
            };
            Ok(Expression::new(expr, Some(new_span)))
        }
        Err(err) => Err(err),
    })
}
pub fn parse_ternary(
    tokens: &mut Vec<(Token, usize, usize)>,
    lhs: &Expression,
    min_bp: f64,
    start: usize,
    full_expr: &str,
) -> Option<Result<Expression>> {
    let (left_bp, middle_bp, right_bp) = ConditionalExpression::ternary_binding_power();
    if min_bp >= left_bp {
        return None;
    }
    expect_token(tokens, Token::Question).unwrap();
    let consequent = if let Ok(consequent) = perform_parse(tokens, middle_bp, full_expr) {
        consequent
    } else {
        return Some(Err(VegaFusionError::parse(
            "Failed to parse consequent of ternary operator",
        )));
    };
    expect_token(tokens, Token::Colon).unwrap();
    let alternate = if let Ok(alternate) = perform_parse(tokens, right_bp, full_expr) {
        alternate
    } else {
        return Some(Err(VegaFusionError::parse(
            "Failed to parse alternate of ternary operator",
        )));
    };
    let new_span = Span {
        start: start as i32,
        end: alternate.span.clone().unwrap().end,
    };
    let expr = Expr::from(ConditionalExpression::new(
        lhs.clone(),
        consequent,
        alternate,
    ));
    Some(Ok(Expression::new(expr, Some(new_span))))
}
pub fn parse_paren_grouping(
    tokens: &mut Vec<(Token, usize, usize)>,
    full_expr: &str,
) -> Result<Expression> {
    perform_parse(tokens, 0.0, full_expr).and_then(|new_lhs| {
        expect_token(tokens, Token::CloseParen)?;
        Ok(new_lhs)
    })
}
pub fn parse_array(
    tokens: &mut Vec<(Token, usize, usize)>,
    start: usize,
    full_expr: &str,
) -> Result<Expression> {
    let mut elements: Vec<Expression> = Vec::new();
    while !tokens.is_empty() && tokens[0].0 != Token::CloseSquare {
        elements.push(perform_parse(tokens, 1.0, full_expr)?);
        expect_token(tokens, Token::Comma).ok();
    }
    let (_, _, end) = expect_token(tokens, Token::CloseSquare).unwrap();
    let new_span = Span {
        start: start as i32,
        end: end as i32,
    };
    let expr = Expr::from(ArrayExpression::new(elements));
    Ok(Expression::new(expr, Some(new_span)))
}
pub fn parse_object(
    tokens: &mut Vec<(Token, usize, usize)>,
    start: usize,
    full_expr: &str,
) -> Result<Expression> {
    let mut properties: Vec<Property> = Vec::new();
    while !tokens.is_empty() && tokens[0].0 != Token::CloseCurly {
        let key = match perform_parse(tokens, 1.0, full_expr) {
            Ok(key) => key,
            Err(err) => return Err(err.with_context(|| "Failed to parse object key".to_string())),
        };
        expect_token(tokens, Token::Colon)?;
        let value = match perform_parse(tokens, 1.0, full_expr) {
            Ok(key) => key,
            Err(err) => {
                return Err(err.with_context(|| "Failed to parse object property value".to_string()))
            }
        };
        expect_token(tokens, Token::Comma).ok();
        let property = Property::try_new(key, value)?;
        properties.push(property);
    }
    let (_, _, end) = expect_token(tokens, Token::CloseCurly).unwrap();
    let new_span = Span {
        start: start as i32,
        end: end as i32,
    };
    let expr = Expr::from(ObjectExpression::new(properties));
    Ok(Expression::new(expr, Some(new_span)))
}
#[cfg(test)]
mod test_parse {
    use crate::expression::parser::parse;
    #[test]
    fn test_parse_atom() {
        let node = parse("23.500000").unwrap();
        assert_eq!(format!("{node}"), "23.5");
        let node = parse("\"hello\"").unwrap();
        assert_eq!(format!("{node}"), "\"hello\"");
    }
    #[test]
    fn test_parse_binary() {
        let node = parse("23.50 + foo * 87").unwrap();
        assert_eq!(node.to_string(), "23.5 + foo * 87");
    }
    #[test]
    fn test_parse_logical() {
        let node = parse("false || (foo && bar)").unwrap();
        assert_eq!(node.to_string(), "false || foo && bar");
    }
    #[test]
    fn test_parse_prefix() {
        let node = parse("-23.50 + +foo").unwrap();
        assert_eq!(node.to_string(), "-23.5 + +foo");
    }
    #[test]
    fn test_paren_grouping() {
        let node = parse("-(23.50 + foo)").unwrap();
        assert_eq!(node.to_string(), "-(23.5 + foo)");
    }
    #[test]
    fn test_call() {
        let node = parse("foo(19.0)").unwrap();
        assert_eq!(node.to_string(), "foo(19)");
        let node = parse("foo()").unwrap();
        assert_eq!(node.to_string(), "foo()");
        let node = parse("foo('a', 21)").unwrap();
        assert_eq!(node.to_string(), "foo(\"a\", 21)");
        let node = parse("foo('a', 21,)").unwrap();
        assert_eq!(node.to_string(), "foo(\"a\", 21)");
    }
    #[test]
    fn test_computed_membership() {
        let node = parse("foo[19.0]").unwrap();
        assert_eq!(node.to_string(), "foo[19]");
        let node = parse("foo['bar']").unwrap();
        assert_eq!(node.to_string(), "foo[\"bar\"]");
    }
    #[test]
    fn test_static_membership() {
        let node = parse("foo.bar").unwrap();
        assert_eq!(node.to_string(), "foo.bar");
        let node = parse("foo.bar[2]").unwrap();
        assert_eq!(node.to_string(), "foo.bar[2]");
    }
    #[test]
    fn test_ternary() {
        let node = parse("foo ? 2 + 3: 27").unwrap();
        assert_eq!(node.to_string(), "foo ? 2 + 3: 27");
        let node = parse("foo ? 2 + 3: 27 || 17").unwrap();
        assert_eq!(node.to_string(), "foo ? 2 + 3: 27 || 17");
        let node = parse("foo ? 2 + 3: (27 || 17)").unwrap();
        assert_eq!(node.to_string(), "foo ? 2 + 3: 27 || 17");
        let node = parse("(foo ? 2 + 3: 27) || 17").unwrap();
        assert_eq!(node.to_string(), "(foo ? 2 + 3: 27) || 17");
        let node = parse("c1 ? v1: c2 ? v2: c3 ? v3: v4").unwrap();
        assert_eq!(node.to_string(), "c1 ? v1: c2 ? v2: c3 ? v3: v4");
        let node = parse("c1 ? v1: (c2 ? v2: (c3 ? v3: v4))").unwrap();
        assert_eq!(node.to_string(), "c1 ? v1: c2 ? v2: c3 ? v3: v4");
        let node = parse("((c1 ? v1: c2) ? v2: c3)? v3: v4").unwrap();
        assert_eq!(node.to_string(), "((c1 ? v1: c2) ? v2: c3) ? v3: v4");
    }
    #[test]
    fn test_array() {
        let node = parse("[19.0]").unwrap();
        assert_eq!(node.to_string(), "[19]");
        let node = parse("['bar', 23]").unwrap();
        assert_eq!(node.to_string(), "[\"bar\", 23]");
        let node = parse("[]").unwrap();
        assert_eq!(node.to_string(), "[]");
    }
    #[test]
    fn test_object() {
        let node = parse("{a: 2, 'b': 2 + 2}").unwrap();
        assert_eq!(node.to_string(), r#"{a: 2, "b": 2 + 2}"#);
    }
}