use std::fmt;

use miette::Diagnostic;

use knus::{Decode, span::Span};

#[derive(Debug, Decode, PartialEq)]
struct Unit;

#[derive(Debug, Decode, PartialEq)]
struct Arg(#[knus(argument)] u32);

#[derive(Debug, Decode, PartialEq)]
struct Opt(Option<Arg>);

#[derive(Debug, Decode, PartialEq)]
struct Extra(#[knus(argument)] Option<String>, u32);

#[derive(Debug, Decode, PartialEq)]
enum Enum {
    Unit,
    Arg(#[knus(argument)] u32),
    Opt(Option<Arg>),
    Extra(#[knus(argument)] Option<String>, u32),
}

fn parse<T: Decode<Span>>(text: &str) -> T {
    let mut nodes: Vec<T> = knus::parse("<test>", text).unwrap();
    assert_eq!(nodes.len(), 1);
    nodes.remove(0)
}

fn parse_err<T: Decode<Span> + fmt::Debug>(text: &str) -> String {
    let err = knus::parse::<Vec<T>>("<test>", text).unwrap_err();
    err.related()
        .unwrap()
        .map(|e| e.to_string())
        .collect::<Vec<_>>()
        .join("\n")
}

#[test]
fn parse_unit() {
    assert_eq!(parse::<Unit>(r#"node"#), Unit);
    assert_eq!(
        parse_err::<Unit>(r#"node something="world""#),
        "unexpected property `something`"
    );
}

#[test]
fn parse_arg() {
    assert_eq!(parse::<Arg>(r#"node 123"#), Arg(123));
    assert_eq!(
        parse_err::<Arg>(r#"node something="world""#),
        "additional argument is required"
    );
}

#[test]
fn parse_extra() {
    assert_eq!(
        parse::<Extra>(r#"node "123""#),
        Extra(Some("123".into()), 0)
    );
    assert_eq!(parse::<Extra>(r#"node"#), Extra(None, 0));
    assert_eq!(
        parse_err::<Extra>(r#"node "123" 456"#),
        "unexpected argument"
    );
}

#[test]
fn parse_opt() {
    assert_eq!(parse::<Opt>(r#"node 123"#), Opt(Some(Arg(123))));
    assert_eq!(parse::<Opt>(r#"node"#), Opt(None));
    assert_eq!(
        parse_err::<Opt>(r#"node something="world""#),
        "additional argument is required"
    );
}

#[test]
fn parse_enum() {
    assert_eq!(parse::<Enum>(r#"unit"#), Enum::Unit);
    assert_eq!(parse::<Enum>(r#"arg 123"#), Enum::Arg(123));
    assert_eq!(parse::<Enum>(r#"opt 123"#), Enum::Opt(Some(Arg(123))));
    assert_eq!(parse::<Enum>(r#"opt"#), Enum::Opt(None));
    assert_eq!(parse::<Enum>(r#"extra"#), Enum::Extra(None, 0));
    assert_eq!(
        parse_err::<Enum>(r#"unit something="world""#),
        "unexpected property `something`"
    );
    assert_eq!(
        parse_err::<Enum>(r#"other something="world""#),
        "expected `unit`, `arg`, or one of 2 others"
    );
    assert_eq!(
        parse_err::<Enum>(r#"extra "hello" "world""#),
        "unexpected argument"
    );
}
