use hexpr::ast::Hexpr;
use hexpr::parser::HExprParser;

#[test]
fn test_basic_frobenius_join() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("[x x . x]").unwrap();
    let expected = Hexpr::Frobenius {
        sources: vec!["x".parse()?, "x".parse()?],
        targets: vec!["x".parse()?],
    };

    assert_eq!(result, expected);
    Ok(())
}

#[test]
fn test_basic_frobenius_split() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("[x . x x]").unwrap();
    assert_eq!(
        result,
        Hexpr::Frobenius {
            sources: vec!["x".parse()?],
            targets: vec!["x".parse()?, "x".parse()?],
        }
    );
    Ok(())
}

#[test]
fn test_identity_shorthand() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("[x y]").unwrap();
    assert_eq!(
        result,
        Hexpr::Frobenius {
            sources: vec!["x".parse()?, "y".parse()?],
            targets: vec!["x".parse()?, "y".parse()?],
        }
    );
    Ok(())
}

#[test]
fn test_named_identity() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("[a]").unwrap();
    assert_eq!(
        result,
        Hexpr::Frobenius {
            sources: vec!["a".parse()?],
            targets: vec!["a".parse()?],
        }
    );
    Ok(())
}

#[test]
fn test_identity_via_composition() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("([x.][.x])").unwrap();
    assert_eq!(
        result,
        Hexpr::Composition(vec![
            Hexpr::Frobenius {
                sources: vec!["x".parse()?],
                targets: vec![],
            },
            Hexpr::Frobenius {
                sources: vec![],
                targets: vec!["x".parse()?],
            },
        ])
    );
    Ok(())
}

#[test]
fn test_subtraction_pointfree() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("({[a] -} +)").unwrap();
    assert_eq!(
        result,
        Hexpr::Composition(vec![
            Hexpr::Tensor(vec![
                Hexpr::Frobenius {
                    sources: vec!["a".parse()?],
                    targets: vec!["a".parse()?],
                },
                Hexpr::Operation("-".parse()?),
            ]),
            Hexpr::Operation("+".parse()?),
        ])
    );
    Ok(())
}

#[test]
fn test_subtraction_pointed() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("([x y.] ([.y] - [z.]) [.x z] +)").unwrap();
    assert_eq!(
        result,
        Hexpr::Composition(vec![
            Hexpr::Frobenius {
                sources: vec!["x".parse()?, "y".parse()?],
                targets: vec![],
            },
            Hexpr::Composition(vec![
                Hexpr::Frobenius {
                    sources: vec![],
                    targets: vec!["y".parse()?],
                },
                Hexpr::Operation("-".parse()?),
                Hexpr::Frobenius {
                    sources: vec!["z".parse()?],
                    targets: vec![],
                },
            ]),
            Hexpr::Frobenius {
                sources: vec![],
                targets: vec!["x".parse()?, "z".parse()?],
            },
            Hexpr::Operation("+".parse()?),
        ])
    );
    Ok(())
}

#[test]
fn test_explicit_swap_relation() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("[x y . y x]").unwrap();
    assert_eq!(
        result,
        Hexpr::Frobenius {
            sources: vec!["x".parse()?, "y".parse()?],
            targets: vec!["y".parse()?, "x".parse()?],
        }
    );
    Ok(())
}

#[test]
fn test_empty_inputs_outputs() {
    let result = HExprParser::parse_hexpr("[.]").unwrap();
    assert_eq!(
        result,
        Hexpr::Frobenius {
            sources: vec![],
            targets: vec![],
        }
    );
}

#[test]
fn test_discard_variable() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("[x .]").unwrap();
    assert_eq!(
        result,
        Hexpr::Frobenius {
            sources: vec!["x".parse()?],
            targets: vec![],
        }
    );
    Ok(())
}

#[test]
fn test_create_variable() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("[. x]").unwrap();
    assert_eq!(
        result,
        Hexpr::Frobenius {
            sources: vec![],
            targets: vec!["x".parse()?],
        }
    );
    Ok(())
}

#[test]
fn test_dispell_summon_named() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("[a b . c]").unwrap();
    assert_eq!(
        result,
        Hexpr::Frobenius {
            sources: vec!["a".parse()?, "b".parse()?],
            targets: vec!["c".parse()?],
        }
    );
    Ok(())
}

#[test]
fn test_complex_composition() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("(add sub mul)").unwrap();
    assert_eq!(
        result,
        Hexpr::Composition(vec![
            Hexpr::Operation("add".parse()?),
            Hexpr::Operation("sub".parse()?),
            Hexpr::Operation("mul".parse()?),
        ])
    );
    Ok(())
}

#[test]
fn test_complex_tensor() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("{add sub mul}").unwrap();
    assert_eq!(
        result,
        Hexpr::Tensor(vec![
            Hexpr::Operation("add".parse()?),
            Hexpr::Operation("sub".parse()?),
            Hexpr::Operation("mul".parse()?),
        ])
    );
    Ok(())
}

#[test]
fn test_nested_expressions() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("({add} (sub mul))").unwrap();
    assert_eq!(
        result,
        Hexpr::Composition(vec![
            Hexpr::Tensor(vec![Hexpr::Operation("add".parse()?)]),
            Hexpr::Composition(vec![
                Hexpr::Operation("sub".parse()?),
                Hexpr::Operation("mul".parse()?),
            ]),
        ])
    );
    Ok(())
}

#[test]
fn test_names_with_dashes_and_underscores() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("my-operation_2").unwrap();
    assert_eq!(result, Hexpr::Operation("my-operation_2".parse()?));
    Ok(())
}

#[test]
fn test_whitespace_handling() -> anyhow::Result<()> {
    let result = HExprParser::parse_hexpr("  ( add   sub  )  ").unwrap();
    assert_eq!(
        result,
        Hexpr::Composition(vec![
            Hexpr::Operation("add".parse()?),
            Hexpr::Operation("sub".parse()?),
        ])
    );
    Ok(())
}

#[test]
fn test_invalid_syntax() {
    assert!(HExprParser::parse_hexpr("(").is_err());
    assert!(HExprParser::parse_hexpr("[").is_err());
    assert!(HExprParser::parse_hexpr("{").is_err());
    assert!(HExprParser::parse_hexpr("").is_err());
}
