#[macro_use]
extern crate lazy_static;

mod util;

use util::check::check_transform_evaluation;
use util::datasets::vega_json_dataset;
use util::equality::TablesEqualConfig;
use vegafusion_core::spec::transform::formula::FormulaTransformSpec;

use rstest::rstest;
use vegafusion_core::spec::transform::stack::StackTransformSpec;
use vegafusion_core::spec::transform::TransformSpec;
use vegafusion_core::spec::values::{
    CompareSpec, Field, SortOrderOrList, SortOrderSpec, StringOrStringList,
};

#[cfg(test)]
mod test_stack_no_group {
    use super::*;
    use vegafusion_core::spec::transform::stack::StackOffsetSpec;

    #[rstest(
        offset,
        case(None),
        case(Some(StackOffsetSpec::Zero)),
        case(Some(StackOffsetSpec::Normalize)),
        case(Some(StackOffsetSpec::Center))
    )]
    fn test(offset: Option<StackOffsetSpec>) {
        let dataset = vega_json_dataset("penguins");

        let stack_spec = StackTransformSpec {
            field: Field::String("Body Mass (g)".to_string()),
            groupby: None,
            sort: None,
            as_: None,
            offset,
            extra: Default::default(),
        };

        println!("{}", serde_json::to_string_pretty(&stack_spec).unwrap());

        let transform_specs = vec![TransformSpec::Stack(stack_spec)];

        let comp_config = Default::default();
        let eq_config = TablesEqualConfig {
            row_order: true,
            ..Default::default()
        };

        check_transform_evaluation(
            &dataset,
            transform_specs.as_slice(),
            &comp_config,
            &eq_config,
        );
    }

    #[test]
    fn test_marker() {} // Help IDE detect test module
}

#[cfg(test)]
mod test_stack_with_group {
    use super::*;
    use vegafusion_core::spec::transform::stack::StackOffsetSpec;

    #[rstest(
        offset,
        case(None),
        case(Some(StackOffsetSpec::Zero)),
        case(Some(StackOffsetSpec::Normalize)),
        case(Some(StackOffsetSpec::Center))
    )]
    fn test(offset: Option<StackOffsetSpec>) {
        let dataset = vega_json_dataset("penguins");

        let stack_spec = StackTransformSpec {
            field: Field::String("Body Mass (g)".to_string()),
            groupby: Some(vec![Field::String("Species".to_string())]),
            sort: None,
            as_: None,
            offset,
            extra: Default::default(),
        };

        let transform_specs = vec![TransformSpec::Stack(stack_spec)];

        let comp_config = Default::default();
        let eq_config = TablesEqualConfig {
            row_order: true,
            ..Default::default()
        };

        check_transform_evaluation(
            &dataset,
            transform_specs.as_slice(),
            &comp_config,
            &eq_config,
        );
    }

    #[test]
    fn test_marker() {} // Help IDE detect test module
}

#[cfg(test)]
mod test_stack_with_group_sort {
    use super::*;
    use vegafusion_core::spec::transform::stack::StackOffsetSpec;

    #[rstest(
        offset,
        case(None),
        case(Some(StackOffsetSpec::Zero)),
        case(Some(StackOffsetSpec::Normalize)),
        case(Some(StackOffsetSpec::Center))
    )]
    fn test(offset: Option<StackOffsetSpec>) {
        let dataset = vega_json_dataset("penguins");

        let stack_spec = StackTransformSpec {
            field: Field::String("Body Mass (g)".to_string()),
            groupby: Some(vec![Field::String("Species".to_string())]),
            sort: Some(CompareSpec {
                field: StringOrStringList::StringList(vec![
                    "Sex".to_string(),
                    "Flipper Length (mm)".to_string(),
                ]),
                order: Some(SortOrderOrList::SortOrderList(vec![
                    SortOrderSpec::Ascending,
                    SortOrderSpec::Descending,
                ])),
            }),
            as_: Some(vec!["foo".to_string(), "bar".to_string()]),
            offset,
            extra: Default::default(),
        };

        let transform_specs = vec![TransformSpec::Stack(stack_spec)];

        let comp_config = Default::default();
        let eq_config = TablesEqualConfig {
            row_order: true,
            ..Default::default()
        };

        check_transform_evaluation(
            &dataset,
            transform_specs.as_slice(),
            &comp_config,
            &eq_config,
        );
    }

    #[test]
    fn test_marker() {} // Help IDE detect test module
}

#[cfg(test)]
mod test_stack_with_group_sort_negative {
    use super::*;
    use vegafusion_core::spec::transform::stack::StackOffsetSpec;

    #[rstest(
        offset,
        case(None),
        case(Some(StackOffsetSpec::Zero)),
        case(Some(StackOffsetSpec::Normalize)),
        case(Some(StackOffsetSpec::Center))
    )]
    fn test(offset: Option<StackOffsetSpec>) {
        let dataset = vega_json_dataset("penguins");

        let formula_spec = FormulaTransformSpec {
            expr: "(datum['Body Mass (g)'] || 0) - 4000".to_string(),
            as_: "Body Mass (g)".to_string(),
            extra: Default::default(),
        };

        let stack_spec = StackTransformSpec {
            field: Field::String("Body Mass (g)".to_string()),
            groupby: Some(vec![Field::String("Species".to_string())]),
            sort: Some(CompareSpec {
                field: StringOrStringList::StringList(vec![
                    "Sex".to_string(),
                    "Flipper Length (mm)".to_string(),
                ]),
                order: Some(SortOrderOrList::SortOrderList(vec![
                    SortOrderSpec::Ascending,
                    SortOrderSpec::Descending,
                ])),
            }),
            as_: Some(vec!["foo".to_string(), "bar".to_string()]),
            offset,
            extra: Default::default(),
        };

        let transform_specs = vec![
            TransformSpec::Formula(formula_spec),
            TransformSpec::Stack(stack_spec),
        ];

        let comp_config = Default::default();
        let eq_config = TablesEqualConfig {
            row_order: true,
            ..Default::default()
        };

        check_transform_evaluation(
            &dataset,
            transform_specs.as_slice(),
            &comp_config,
            &eq_config,
        );
    }

    #[test]
    fn test_marker() {} // Help IDE detect test module
}

#[cfg(test)]
mod test_stack_no_divide_by_zero {
    use super::*;
    use serde_json::json;
    use vegafusion_core::spec::transform::collect::CollectTransformSpec;
    use vegafusion_core::spec::transform::timeunit::TimeUnitTransformSpec;

    #[test]
    fn test() {
        let dataset = vega_json_dataset("movies");
        let formula_spec: FormulaTransformSpec = serde_json::from_value(json!(
            {
                "expr": "toDate(datum['Release Date'])",
                "as": "Release Date"
            }
        ))
        .unwrap();

        let timeunit_spec: TimeUnitTransformSpec = serde_json::from_value(json!(
            {
              "field": "Release Date",
              "type": "timeunit",
              "units": ["year"],
              "as": ["year_Release Date", "year_Release Date_end"]
            }
        ))
        .unwrap();

        let stack_spec: StackTransformSpec = serde_json::from_value(json!(
            {
              "type": "stack",
              "groupby": ["year_Release Date"],
              "field": "US Gross",
              "sort": {"field": ["Release Date", "Title"], "order": ["descending", "descending"]},
              "as": ["US Gross_start", "US Gross_end"],
              "offset": "normalize"
            }
        ))
        .unwrap();

        let collect_spec: CollectTransformSpec = serde_json::from_value(json!(
            {
              "type": "collect",
              "sort": {"field": ["Title", "Release Date"]}
            }
        ))
        .unwrap();

        // Vega sometimes produces NULL or NaN when a stack group has zero sum.
        // Replace these with 0 to match VegaFusion's behavior
        let start_formula_spec: FormulaTransformSpec = serde_json::from_value(json!(
            {
                "expr": "if(isValid(datum['US Gross_start']) && isFinite(datum['US Gross_start']), datum['US Gross_start'], 0)",
                "as": "US Gross_start"
            }
        )).unwrap();

        let end_formula_spec: FormulaTransformSpec = serde_json::from_value(json!(
            {
                "expr": "if(isValid(datum['US Gross_end']) && isFinite(datum['US Gross_end']), datum['US Gross_end'], 0)",
                "as": "US Gross_end"
            }
        )).unwrap();

        let transform_specs = vec![
            TransformSpec::Formula(formula_spec),
            TransformSpec::Timeunit(timeunit_spec),
            TransformSpec::Stack(stack_spec),
            TransformSpec::Collect(collect_spec),
            TransformSpec::Formula(start_formula_spec),
            TransformSpec::Formula(end_formula_spec),
        ];

        let comp_config = Default::default();

        let eq_config = TablesEqualConfig {
            row_order: true,
            ..Default::default()
        };

        check_transform_evaluation(
            &dataset,
            transform_specs.as_slice(),
            &comp_config,
            &eq_config,
        );
    }

    #[test]
    fn test_marker() {} // Help IDE detect test module
}
