use texform_argspec::{ContentMode, ValueKind};
use texform_knowledge::builtin::{ALL_PACKAGES, MANAGED_PACKAGE_IMPORT_ORDER, PackageName};
use texform_knowledge::specs::{AllowedMode, load_package_specs_from_str};

#[test]
fn package_name_import_order_matches_transform_order() {
    assert_eq!(
        MANAGED_PACKAGE_IMPORT_ORDER,
        &[
            PackageName::Base,
            PackageName::Ams,
            PackageName::Braket,
            PackageName::Physics,
            PackageName::Textmacros,
            PackageName::Bboldx,
            PackageName::Boldsymbol,
        ]
    );
    assert_eq!(
        MANAGED_PACKAGE_IMPORT_ORDER
            .iter()
            .map(|package| package.as_str())
            .collect::<Vec<_>>(),
        vec![
            "base",
            "ams",
            "braket",
            "physics",
            "textmacros",
            "bboldx",
            "boldsymbol",
        ]
    );
    assert_eq!(
        texform_knowledge::builtin::managed_package_import_order(),
        MANAGED_PACKAGE_IMPORT_ORDER
    );
    assert_eq!(
        texform_knowledge::packages::MANAGED_PACKAGE_IMPORT_ORDER,
        MANAGED_PACKAGE_IMPORT_ORDER
    );
}

#[test]
fn package_name_lookup_and_package_access_use_generated_registry() {
    assert_eq!(PackageName::from_str("physics"), Some(PackageName::Physics));
    assert_eq!(PackageName::from_str("missing"), None);
    assert_eq!(PackageName::Physics.package().name, "physics");
    assert_eq!(
        texform_knowledge::packages::PackageName::Physics.as_str(),
        "physics"
    );
}

#[test]
fn all_resource_specs_are_registered() {
    let names: Vec<&str> = ALL_PACKAGES.iter().map(|pkg| pkg.name).collect();
    assert_eq!(texform_knowledge::packages::all_package_names(), names);
    assert_eq!(
        names,
        vec![
            "ams",
            "base",
            "bboldx",
            "boldsymbol",
            "braket",
            "physics",
            "textmacros"
        ]
    );
}

#[test]
fn registered_packages_expose_builtin_records() {
    for package in ALL_PACKAGES {
        let is_empty = package.characters.is_empty()
            && package.delimiters.is_empty()
            && package.commands.is_empty()
            && package.environments.is_empty();
        assert!(!is_empty, "package {} should not be empty", package.name);
    }
}

#[test]
fn non_alpha_commands_expose_representative_facades() {
    use texform_knowledge::builtin::{base, braket, textmacros};

    assert_eq!(base::cmd::_CONTROL_SPACE.name, " ");
    assert_eq!(base::cmd::_BACKSLASH.name, "\\");
    assert_eq!(braket::cmd::_VERTICAL_BAR.name, "|");
    assert_eq!(textmacros::cmd::_PERIOD.name, ".");
}

#[test]
fn test_load_package_specs_from_str() {
    let yaml = r#"
characters:
  - name: alpha
    allowed_mode: math
    unicode_value: α
    attributes:
      mathvariant: italic
  - name: beta
    allowed_mode: text
    unicode_value: β
    attributes: {}
commands:
  - name: frac
    kind: prefix
    allowed_mode: math
    tags: [discouraged]
    argspec: "m m:D"
  - name: text
    kind: prefix
    allowed_mode: both
    argspec: "m:T"
environments:
  - name: matrix
    allowed_mode: math
    body_mode: math
    tags: [matrix]
    argspec: "m:C"
delimiters:
  - name: langle
    is_control_sequence: true
    allowed_mode: math
    unicode_value: ⟨
    attributes:
      tex_class: OPEN
  - name: "|"
    is_control_sequence: false
    allowed_mode: math
    unicode_value: "|"
    attributes: {}
"#;

    let specs = load_package_specs_from_str(yaml, "test");
    assert_eq!(specs.characters.len(), 2);
    assert_eq!(specs.characters[0].name, "alpha");
    assert_eq!(specs.characters[0].allowed_mode, AllowedMode::Math);
    assert_eq!(specs.characters[0].unicode_value, "α");
    assert_eq!(
        specs.characters[0].attributes.mathvariant.as_deref(),
        Some("italic")
    );
    assert_eq!(specs.characters[1].name, "beta");
    assert_eq!(specs.characters[1].allowed_mode, AllowedMode::Text);
    assert_eq!(specs.characters[1].unicode_value, "β");
    assert_eq!(specs.characters[1].attributes.mathvariant, None);

    assert_eq!(specs.commands.len(), 2);
    assert_eq!(specs.commands[0].name, "frac");
    assert_eq!(specs.commands[0].allowed_mode, AllowedMode::Math);
    assert_eq!(specs.commands[0].argspec.args.len(), 2);
    assert_eq!(specs.commands[0].tags, vec!["discouraged"]);
    assert!(specs.commands[0].argspec.args[0].required);
    assert_eq!(
        specs.commands[0].argspec.args[0].kind,
        ValueKind::Content {
            mode: ContentMode::Math
        }
    );
    assert_eq!(specs.commands[0].argspec.args[1].kind, ValueKind::Delimiter);

    assert_eq!(specs.commands[1].name, "text");
    assert_eq!(specs.commands[1].allowed_mode, AllowedMode::Both);
    assert_eq!(specs.commands[1].argspec.args.len(), 1);
    assert!(specs.commands[1].tags.is_empty());
    assert_eq!(
        specs.commands[1].argspec.args[0].kind,
        ValueKind::Content {
            mode: ContentMode::Text
        }
    );

    assert_eq!(specs.environments.len(), 1);
    assert_eq!(specs.environments[0].name, "matrix");
    assert_eq!(specs.environments[0].allowed_mode, AllowedMode::Math);
    assert_eq!(specs.environments[0].argspec.args.len(), 1);
    assert_eq!(specs.environments[0].tags, vec!["matrix"]);
    assert_eq!(specs.delimiters.len(), 2);
    assert_eq!(specs.delimiters[0].name, "langle");
    assert!(specs.delimiters[0].is_control_sequence);
    assert_eq!(specs.delimiters[0].allowed_mode, AllowedMode::Math);
    assert_eq!(specs.delimiters[0].unicode_value, "⟨");
    assert_eq!(
        specs.delimiters[0].attributes.tex_class.as_deref(),
        Some("OPEN")
    );
    assert_eq!(specs.delimiters[1].name, "|");
    assert!(!specs.delimiters[1].is_control_sequence);
    assert_eq!(specs.delimiters[1].unicode_value, "|");
}

#[test]
fn test_parse_column_kind_from_yaml() {
    let yaml = r#"
commands:
  - name: colspec
    kind: prefix
    argspec: "m:C"
"#;

    let specs = load_package_specs_from_str(yaml, "column-kind");
    assert_eq!(specs.commands.len(), 1);
    assert_eq!(specs.commands[0].argspec.args.len(), 1);
    assert_eq!(specs.commands[0].argspec.args[0].kind, ValueKind::Column);
}

#[test]
fn test_command_allowed_mode_defaults_to_both() {
    let yaml = r#"
commands:
  - name: foo
    kind: prefix
"#;

    let specs = load_package_specs_from_str(yaml, "default-allowed-mode");
    assert_eq!(specs.commands.len(), 1);
    assert_eq!(specs.commands[0].allowed_mode, AllowedMode::Both);
    assert!(specs.commands[0].argspec.args.is_empty());
}

#[test]
#[should_panic(expected = "missing field `allowed_mode`")]
fn test_character_allowed_mode_is_required() {
    let yaml = r#"
characters:
  - name: alpha
    unicode_value: α
    attributes: {}
"#;

    let _ = load_package_specs_from_str(yaml, "character-allowed-mode-required");
}

#[test]
fn test_environment_body_mode_can_be_text() {
    let yaml = r#"
environments:
  - name: textenv
    allowed_mode: math
    body_mode: text
"#;

    let specs = load_package_specs_from_str(yaml, "test");
    assert_eq!(specs.environments.len(), 1);
    assert_eq!(specs.environments[0].name, "textenv");
    assert_eq!(specs.environments[0].body_mode, ContentMode::Text);
}

#[test]
#[should_panic(expected = "missing field `allowed_mode`")]
fn test_environment_allowed_mode_is_required() {
    let yaml = r#"
environments:
  - name: matrix
    body_mode: math
"#;

    let _ = load_package_specs_from_str(yaml, "environment-allowed-mode-required");
}

#[test]
fn test_allowed_mode_helpers() {
    assert!(AllowedMode::Math.allows(ContentMode::Math));
    assert!(!AllowedMode::Math.allows(ContentMode::Text));
    assert!(AllowedMode::Text.allows(ContentMode::Text));
    assert!(!AllowedMode::Text.allows(ContentMode::Math));
    assert!(AllowedMode::Both.allows(ContentMode::Math));
    assert!(AllowedMode::Both.allows(ContentMode::Text));

    assert_eq!(
        AllowedMode::Math.union(AllowedMode::Math),
        AllowedMode::Math
    );
    assert_eq!(
        AllowedMode::Text.union(AllowedMode::Text),
        AllowedMode::Text
    );
    assert_eq!(
        AllowedMode::Math.union(AllowedMode::Text),
        AllowedMode::Both
    );
    assert_eq!(
        AllowedMode::Text.union(AllowedMode::Math),
        AllowedMode::Both
    );
    assert_eq!(
        AllowedMode::Both.union(AllowedMode::Math),
        AllowedMode::Both
    );
}
