Documentation
use liwe::query::{parse_filter_expression, Filter, KeyOp, ParseError};

#[test]
fn parses_block_style_field_eq() {
    let f = parse_filter_expression("status: draft").unwrap();
    match f {
        Filter::Field { path, op } => {
            assert_eq!(path.segments(), &["status".to_string()]);
            let _ = op;
        }
        other => panic!("expected Field, got {:?}", other),
    }
}

#[test]
fn parses_flow_style_mapping() {
    let f = parse_filter_expression("{status: draft, priority: 5}").unwrap();
    match f {
        Filter::And(parts) => assert_eq!(parts.len(), 2),
        other => panic!("expected And, got {:?}", other),
    }
}

#[test]
fn parses_top_level_dollar_operator() {
    let f = parse_filter_expression("$key: notes/foo").unwrap();
    match f {
        Filter::Key(KeyOp::Eq(k)) => assert_eq!(k.to_string(), "notes/foo"),
        other => panic!("expected Key($eq), got {:?}", other),
    }
}

#[test]
fn parses_field_op_expression() {
    let f = parse_filter_expression("priority: { $gt: 3 }").unwrap();
    match f {
        Filter::Field { path, op: _ } => {
            assert_eq!(path.segments(), &["priority".to_string()]);
        }
        other => panic!("expected Field, got {:?}", other),
    }
}

#[test]
fn empty_input_yields_empty_and() {
    let f = parse_filter_expression("").unwrap();
    match f {
        Filter::And(parts) => assert!(parts.is_empty()),
        other => panic!("expected empty And, got {:?}", other),
    }
}

#[test]
fn whitespace_only_input_yields_empty_and() {
    let f = parse_filter_expression("   \n  ").unwrap();
    match f {
        Filter::And(parts) => assert!(parts.is_empty()),
        other => panic!("expected empty And, got {:?}", other),
    }
}

#[test]
fn parses_top_level_bare_and_or() {
    let f = parse_filter_expression(
        "{type: tracker, $or: [{status: open}, {status: pending}]}",
    )
    .unwrap();
    let parts = match f {
        Filter::And(p) => p,
        other => panic!("expected And, got {:?}", other),
    };
    assert_eq!(parts.len(), 2);
    match &parts[0] {
        Filter::Or(branches) => assert_eq!(branches.len(), 2),
        other => panic!("expected Or first (dollar group), got {:?}", other),
    }
    match &parts[1] {
        Filter::Field { path, op: _ } => {
            assert_eq!(path.segments(), &["type".to_string()]);
        }
        other => panic!("expected Field second (bare group), got {:?}", other),
    }
}

#[test]
fn parses_top_level_bare_and_and() {
    let f = parse_filter_expression("{a: 1, $and: [{b: 2}]}").unwrap();
    let parts = match f {
        Filter::And(p) => p,
        other => panic!("expected And, got {:?}", other),
    };
    assert_eq!(parts.len(), 2);
    match &parts[0] {
        Filter::And(inner) => assert_eq!(inner.len(), 1),
        other => panic!("expected inner And first, got {:?}", other),
    }
    match &parts[1] {
        Filter::Field { path, op: _ } => assert_eq!(path.segments(), &["a".to_string()]),
        other => panic!("expected Field second, got {:?}", other),
    }
}

#[test]
fn parses_top_level_bare_and_nor() {
    let f = parse_filter_expression("{a: 1, $nor: [{b: 2}]}").unwrap();
    let parts = match f {
        Filter::And(p) => p,
        other => panic!("expected And, got {:?}", other),
    };
    assert_eq!(parts.len(), 2);
    match &parts[0] {
        Filter::Nor(inner) => assert_eq!(inner.len(), 1),
        other => panic!("expected Nor first, got {:?}", other),
    }
    match &parts[1] {
        Filter::Field { path, op: _ } => assert_eq!(path.segments(), &["a".to_string()]),
        other => panic!("expected Field second, got {:?}", other),
    }
}

#[test]
fn rejects_top_level_not() {
    let err = parse_filter_expression("{$not: {b: 2}}").unwrap_err();
    match err {
        ParseError::TopLevelNotNotSupported { ref path } => {
            assert!(path.is_empty());
        }
        other => panic!("expected TopLevelNotNotSupported, got {:?}", other),
    }
}

#[test]
fn parses_top_level_bare_and_key() {
    let f = parse_filter_expression("{type: t, $key: notes/foo}").unwrap();
    let parts = match f {
        Filter::And(p) => p,
        other => panic!("expected And, got {:?}", other),
    };
    assert_eq!(parts.len(), 2);
    match &parts[0] {
        Filter::Key(KeyOp::Eq(k)) => assert_eq!(k.to_string(), "notes/foo"),
        other => panic!("expected Key(Eq) first, got {:?}", other),
    }
    match &parts[1] {
        Filter::Field { path, op: _ } => assert_eq!(path.segments(), &["type".to_string()]),
        other => panic!("expected Field second, got {:?}", other),
    }
}

#[test]
fn parses_top_level_multiple_bare_and_multiple_dollar() {
    let f = parse_filter_expression(
        "{a: 1, b: 2, $or: [{c: 3}], $and: [{d: 4}]}",
    )
    .unwrap();
    let parts = match f {
        Filter::And(p) => p,
        other => panic!("expected And, got {:?}", other),
    };
    assert_eq!(parts.len(), 4);
    assert!(matches!(&parts[0], Filter::And(_) | Filter::Or(_)));
    assert!(matches!(&parts[1], Filter::And(_) | Filter::Or(_)));
    assert!(matches!(&parts[2], Filter::Field { .. }));
    assert!(matches!(&parts[3], Filter::Field { .. }));
}

#[test]
fn parses_mix_inside_or_branch() {
    let f = parse_filter_expression(
        "$or: [{a: 1, $nor: [{b: 2}]}, {c: 3}]",
    )
    .unwrap();
    let branches = match f {
        Filter::Or(b) => b,
        other => panic!("expected Or, got {:?}", other),
    };
    assert_eq!(branches.len(), 2);
    let first = match &branches[0] {
        Filter::And(p) => p,
        other => panic!("expected And inside Or branch, got {:?}", other),
    };
    assert_eq!(first.len(), 2);
    match &first[0] {
        Filter::Nor(inner) => assert_eq!(inner.len(), 1),
        other => panic!("expected Nor first, got {:?}", other),
    }
    match &first[1] {
        Filter::Field { path, op: _ } => assert_eq!(path.segments(), &["a".to_string()]),
        other => panic!("expected Field second, got {:?}", other),
    }
}

#[test]
fn parses_mix_inside_and_branch() {
    let f = parse_filter_expression("$and: [{a: 1, $or: [{b: 2}]}]").unwrap();
    let branches = match f {
        Filter::And(b) => b,
        other => panic!("expected And, got {:?}", other),
    };
    assert_eq!(branches.len(), 1);
    let inner = match &branches[0] {
        Filter::And(p) => p,
        other => panic!("expected And inside And branch, got {:?}", other),
    };
    assert_eq!(inner.len(), 2);
    match &inner[0] {
        Filter::Or(_) => {}
        other => panic!("expected Or first, got {:?}", other),
    }
    match &inner[1] {
        Filter::Field { path, op: _ } => assert_eq!(path.segments(), &["a".to_string()]),
        other => panic!("expected Field second, got {:?}", other),
    }
}

#[test]
fn parses_mix_inside_nor_branch() {
    let f = parse_filter_expression("$nor: [{a: 1, $or: [{b: 2}]}]").unwrap();
    let branches = match f {
        Filter::Nor(b) => b,
        other => panic!("expected Nor, got {:?}", other),
    };
    assert_eq!(branches.len(), 1);
    let inner = match &branches[0] {
        Filter::And(p) => p,
        other => panic!("expected And inside Nor branch, got {:?}", other),
    };
    assert_eq!(inner.len(), 2);
    match &inner[0] {
        Filter::Or(_) => {}
        other => panic!("expected Or first, got {:?}", other),
    }
    match &inner[1] {
        Filter::Field { path, op: _ } => assert_eq!(path.segments(), &["a".to_string()]),
        other => panic!("expected Field second, got {:?}", other),
    }
}

#[test]
fn parses_mix_inside_graph_anchor_match() {
    let f = parse_filter_expression(
        "$includedBy: { match: {a: 1, $key: notes/foo}, maxDepth: 3 }",
    )
    .unwrap();
    let anchor = match f {
        Filter::IncludedBy(a) => a,
        other => panic!("expected IncludedBy, got {:?}", other),
    };
    assert_eq!(anchor.max_depth, 3);
    let parts = match &anchor.match_filter {
        Filter::And(p) => p,
        other => panic!("expected And in match, got {:?}", other),
    };
    assert_eq!(parts.len(), 2);
    match &parts[0] {
        Filter::Key(KeyOp::Eq(k)) => assert_eq!(k.to_string(), "notes/foo"),
        other => panic!("expected Key(Eq) first, got {:?}", other),
    }
    match &parts[1] {
        Filter::Field { path, op: _ } => assert_eq!(path.segments(), &["a".to_string()]),
        other => panic!("expected Field second, got {:?}", other),
    }
}

#[test]
fn rejects_mix_inside_field_value_mapping() {
    let err = parse_filter_expression("{author: {$eq: alice, name: alice}}").unwrap_err();
    match err {
        ParseError::MixedDollarAndBare { ref path } => {
            assert_eq!(path, &vec!["author".to_string()]);
        }
        other => panic!("expected MixedDollarAndBare, got {:?}", other),
    }
}

#[test]
fn rejects_mix_inside_field_level_not() {
    let err = parse_filter_expression("{score: {$not: {$gt: 5, extra: 1}}}").unwrap_err();
    match err {
        ParseError::MixedDollarAndBare { ref path } => {
            assert_eq!(path, &vec!["score".to_string()]);
        }
        other => panic!("expected MixedDollarAndBare, got {:?}", other),
    }
}

#[test]
fn parses_or_with_list_of_filters() {
    let expr = "$or: [{ status: draft }, { status: review }]";
    let f = parse_filter_expression(expr).unwrap();
    match f {
        Filter::Or(parts) => assert_eq!(parts.len(), 2),
        other => panic!("expected Or, got {:?}", other),
    }
}

#[test]
fn parses_graph_anchor_with_max_depth() {
    let expr = "$includedBy: { match: { $key: projects/alpha }, maxDepth: 5 }";
    let f = parse_filter_expression(expr).unwrap();
    match f {
        Filter::IncludedBy(anchor) => {
            match &anchor.match_filter {
                Filter::Key(KeyOp::Eq(k)) => assert_eq!(k.to_string(), "projects/alpha"),
                other => panic!("expected Key(Eq), got {:?}", other),
            }
            assert_eq!(anchor.max_depth, 5);
        }
        other => panic!("expected IncludedBy, got {:?}", other),
    }
}

#[test]
fn parses_graph_anchor_scalar_shorthand() {
    let expr = "$includedBy: projects/alpha";
    let f = parse_filter_expression(expr).unwrap();
    match f {
        Filter::IncludedBy(anchor) => {
            match &anchor.match_filter {
                Filter::Key(KeyOp::Eq(k)) => assert_eq!(k.to_string(), "projects/alpha"),
                other => panic!("expected Key(Eq), got {:?}", other),
            }
            assert_eq!(anchor.max_depth, 1);
            assert_eq!(anchor.min_depth, 1);
        }
        other => panic!("expected IncludedBy, got {:?}", other),
    }
}

#[test]
fn malformed_yaml_is_an_error() {
    let err = parse_filter_expression("status: draft, : bad");
    assert!(err.is_err(), "expected parse error");
}