use super::*;

#[test]
fn lists_setting_is_unstable() {
  Test::new()
    .justfile("set lists")
    .stderr_regex("error: the `lists` setting is currently unstable.*")
    .failure();
}

#[test]
fn quote_quotes_each_element_of_a_list() {
  assert_list("quote(['bar', 'baz bob'])", r#"["'bar'", "'baz bob'"]"#);
}

#[test]
fn quote_of_empty_list_is_empty() {
  assert_list("quote([])", "[]");
}

#[test]
fn quote_of_empty_variadic_is_empty_string_without_lists_setting() {
  Test::new()
    .justfile(
      r#"
        foo *args:
          @echo "bar{{ quote(args) }}baz"
      "#,
    )
    .arg("foo")
    .stdout("bar''baz\n")
    .success();
}

#[test]
fn quote_quotes_single_element_values_whole() {
  assert_list("quote('baz bob')", r#""'baz bob'""#);
}

#[test]
fn absolute_path_resolves_each_element_of_a_list() {
  let test = Test::new()
    .justfile("set lists\n\nx := show(absolute_path(['bar', 'baz bob']))")
    .unstable()
    .args(["--evaluate", "x"]);

  let mut tempdir = test.tempdir.path().to_owned();

  if cfg!(unix) {
    tempdir = tempdir.canonicalize().unwrap();
  }

  test
    .stdout(format!(
      r#"["{}", "{}"]"#,
      tempdir.join("bar").to_str().unwrap().replace('\\', "\\\\"),
      tempdir
        .join("baz bob")
        .to_str()
        .unwrap()
        .replace('\\', "\\\\"),
    ))
    .unindent_stdout(false)
    .success();
}

#[test]
fn absolute_path_of_empty_list_is_empty() {
  assert_list("absolute_path([])", "[]");
}

#[test]
fn append_appends_to_each_element_of_a_list() {
  assert_list(
    "append('.c', ['bar', 'baz bob'])",
    r#"["bar.c", "baz bob.c"]"#,
  );
}

#[test]
fn prepend_prepends_to_each_element_of_a_list() {
  assert_list(
    "prepend('src/', ['bar', 'baz bob'])",
    r#"["src/bar", "src/baz bob"]"#,
  );
}

#[test]
fn append_does_not_split_single_strings_with_lists_setting() {
  assert_list("append('.c', 'foo bar')", r#""foo bar.c""#);
}

#[test]
fn recipe_interpolations_space_join_lists() {
  Test::new()
    .justfile(
      "
        set lists

        foo *args:
          @echo {{ args }}
      ",
    )
    .unstable()
    .args(["foo", "bar", "baz"])
    .stdout("bar baz\n")
    .success();
}

#[test]
fn fstring_interpolations_space_join_lists() {
  assert_list(r#"f"{{['bar', 'baz']}}""#, r#""bar baz""#);
}

#[test]
fn join_list_joins_lists_with_spaces() {
  assert_list("join_list(['bar', 'baz'])", r#""bar baz""#);
}

#[test]
fn join_list_joins_with_separator() {
  assert_list("join_list(['bar', 'baz'], ', ')", r#""bar, baz""#);
}

#[test]
fn join_list_requires_lists_setting() {
  Test::new()
    .justfile(r#"x := join_list("foo")"#)
    .args(["--evaluate", "x"])
    .stderr(
      r#"
        error: the `join_list()` function requires `set lists`
         ——▶ justfile:1:6
          │
        1 │ x := join_list("foo")
          │      ^^^^^^^^^
      "#,
    )
    .failure();
}

#[test]
fn join_list_separator_must_not_be_a_list() {
  Test::new()
    .justfile(
      "
        set lists

        foo:
          @echo {{ join_list(['bar', 'baz'], [',', ';']) }}
      ",
    )
    .unstable()
    .stderr(
      r#"
        error: list value [",", ";"] passed to `join_list()`
        the behavior of lists with many built-in functions is undecided
        see https://github.com/casey/just#lists
         ——▶ justfile:4:12
          │
        4 │   @echo {{ join_list(['bar', 'baz'], [',', ';']) }}
          │            ^^^^^^^^^
      "#,
    )
    .failure();
}

#[test]
fn split_splits_string_on_separator() {
  assert_list("split('foo,bar,baz', ',')", r#"["foo", "bar", "baz"]"#);
}

#[test]
fn split_of_string_not_containing_separator_is_single_element() {
  assert_list("split('foo', ',')", r#""foo""#);
}

#[test]
fn split_keeps_empty_elements_with_explicit_separator() {
  assert_list("split('foo,,bar,', ',')", r#"["foo", "", "bar", ""]"#);
}

#[test]
fn split_on_empty_separator_splits_into_characters() {
  assert_list("split('abc', '')", r#"["a", "b", "c"]"#);
}

#[test]
fn split_without_separator_splits_on_whitespace() {
  assert_list("split('  foo \t bar  baz ')", r#"["foo", "bar", "baz"]"#);
}

#[test]
fn split_without_separator_of_whitespace_is_empty() {
  assert_list("split('  \t ')", "[]");
}

#[test]
fn split_requires_lists_setting() {
  Test::new()
    .justfile(r#"x := split("foo", ",")"#)
    .args(["--evaluate", "x"])
    .stderr(
      r#"
        error: the `split()` function requires `set lists`
         ——▶ justfile:1:6
          │
        1 │ x := split("foo", ",")
          │      ^^^^^
      "#,
    )
    .failure();
}

#[test]
fn split_argument_must_not_be_a_list() {
  Test::new()
    .justfile(
      "
        set lists

        foo:
          @echo {{ split(['bar', 'baz'], ',') }}
      ",
    )
    .unstable()
    .stderr(
      r#"
        error: list value ["bar", "baz"] passed to `split()`
        the behavior of lists with many built-in functions is undecided
        see https://github.com/casey/just#lists
         ——▶ justfile:4:12
          │
        4 │   @echo {{ split(['bar', 'baz'], ',') }}
          │            ^^^^^
      "#,
    )
    .failure();
}

#[test]
fn dependency_arguments_join_lists_without_lists_setting() {
  Test::new()
    .justfile(
      "
        foo *args: (bar args)

        bar first *rest:
          @echo first={{ first }} rest={{ rest }}
      ",
    )
    .args(["foo", "bar", "baz"])
    .stdout("first=bar baz rest=\n")
    .success();
}

#[test]
fn dependency_arguments_forward_lists() {
  Test::new()
    .justfile(
      "
        set lists

        foo *args: (bar args)

        bar *rest:
          @echo '{{ show(rest) }}'
      ",
    )
    .unstable()
    .args(["foo", "bar", "baz bob"])
    .stdout(
      r#"
        ["bar", "baz bob"]
      "#,
    )
    .success();
}

#[test]
fn dependency_arguments_forward_lists_to_positional_arguments() {
  Test::new()
    .justfile(
      r#"
        set lists
        set positional-arguments

        foo *args: (bar args)

        bar *rest:
          @echo "$1-$2"
      "#,
    )
    .unstable()
    .args(["foo", "bar", "baz"])
    .stdout("bar-baz\n")
    .success();
}

#[test]
fn singular_parameters_contribute_one_positional_argument() {
  Test::new()
    .justfile(
      r#"
        set lists
        set positional-arguments

        foo *args: (bar args 'bob')

        bar first second:
          @echo "$1-$2"
      "#,
    )
    .unstable()
    .args(["foo", "bar", "baz"])
    .stdout("bar baz-bob\n")
    .success();
}

#[test]
fn lists_bind_to_singular_parameters() {
  Test::new()
    .justfile(
      "
        set lists

        foo *args: (bar args)

        bar first:
          @echo '{{ show(first) }}'
      ",
    )
    .unstable()
    .args(["foo", "bar", "baz"])
    .stdout(
      r#"
        ["bar", "baz"]
      "#,
    )
    .success();
}

#[test]
fn dependency_arguments_bind_to_one_parameter_each() {
  Test::new()
    .justfile(
      "
        set lists

        foo *args: (bar 'baz' args)

        bar first *rest:
          @echo '{{ show(first) }} {{ show(rest) }}'
      ",
    )
    .unstable()
    .args(["foo", "bar", "bob"])
    .stdout(
      r#"
        "baz" ["bar", "bob"]
      "#,
    )
    .success();
}

#[test]
fn variadic_parameters_accept_at_most_one_dependency_argument() {
  Test::new()
    .justfile(
      "
        set lists

        foo *args:

        bar: (foo 'baz' 'bob')
      ",
    )
    .unstable()
    .arg("bar")
    .stderr(
      "
        error: dependency `foo` got 2 arguments but takes at most 1 argument
         ——▶ justfile:5:7
          │
        5 │ bar: (foo 'baz' 'bob')
          │       ^^^
      ",
    )
    .failure();
}

#[test]
fn empty_list_for_plus_variadic_is_an_error() {
  Test::new()
    .justfile(
      "
        set lists

        foo *args: (bar args)

        bar +rest:
          @echo {{ rest }}
      ",
    )
    .unstable()
    .arg("foo")
    .stderr("error: recipe `bar` parameter `rest` requires at least one element but received empty list\n")
    .failure();
}

#[test]
fn empty_list_for_required_parameter_is_an_error() {
  Test::new()
    .justfile(
      "
        set lists

        foo *args: (bar args)

        bar first:
          @echo {{ first }}
      ",
    )
    .unstable()
    .arg("foo")
    .stderr("error: recipe `bar` parameter `first` requires at least one element but received empty list\n")
    .failure();
}

#[test]
fn empty_list_for_defaulted_parameter_uses_default() {
  Test::new()
    .justfile(
      "
        set lists

        foo *args: (bar args)

        bar first='baz':
          @echo {{ first }}
      ",
    )
    .unstable()
    .arg("foo")
    .stdout("baz\n")
    .success();
}

#[test]
fn omitted_star_variadic_dependency_argument_is_empty_list() {
  Test::new()
    .justfile(
      "
        set lists

        foo: (bar)

        bar *rest:
          @echo '{{ show(rest) }}'
      ",
    )
    .unstable()
    .arg("foo")
    .stdout("[]\n")
    .success();
}

#[test]
fn lists_forwarded_to_module_without_lists_setting_are_joined() {
  Test::new()
    .write(
      "foo.just",
      "baz first *rest:\n @echo first={{ first }} rest={{ rest }}",
    )
    .justfile(
      "
        set lists

        mod foo

        bar *args: (foo::baz args)
      ",
    )
    .unstable()
    .args(["bar", "baz", "bob"])
    .stdout("first=baz bob rest=\n")
    .success();
}

#[test]
fn joined_arguments_forwarded_to_module_with_lists_setting_are_single_elements() {
  Test::new()
    .write(
      "foo.just",
      "set lists\nbaz *rest:\n @echo '{{ show(rest) }}'",
    )
    .justfile(
      "
        mod foo

        bar *args: (foo::baz args)
      ",
    )
    .unstable()
    .args(["bar", "baz", "bob"])
    .stdout(
      r#"
        "baz bob"
      "#,
    )
    .success();
}

#[test]
fn evaluate_prints_lists() {
  Test::new()
    .justfile(
      "
        set lists

        a := 'foo'
        b := ['bar']
        c := ['baz', 'bob']
        d := []
      ",
    )
    .unstable()
    .arg("--evaluate")
    .stdout(
      r#"
        a := "foo"
        b := "bar"
        c := ["baz", "bob"]
        d := []
      "#,
    )
    .success();
}

#[test]
fn concatenation_broadcasts_string_over_list() {
  assert_list("'foo' + ['bar', 'baz']", r#"["foobar", "foobaz"]"#);
  assert_list("['bar', 'baz'] + 'foo'", r#"["barfoo", "bazfoo"]"#);
}

#[test]
fn concatenation_combines_equal_length_lists_pairwise() {
  assert_list("['a', 'b'] + ['c', 'd']", r#"["ac", "bd"]"#);
}

#[test]
fn concatenation_of_strings_is_a_string() {
  assert_list("'foo' + 'bar'", r#""foobar""#);
}

#[test]
fn concatenation_of_empty_lists_is_empty() {
  assert_list("[] + []", "[]");
}

#[test]
fn list_concatenation_appends_lists() {
  assert_list("['foo', 'bar'] ++ ['baz']", r#"["foo", "bar", "baz"]"#);
}

#[test]
fn list_concatenation_requires_lists_setting() {
  Test::new()
    .justfile("x := 'foo' ++ 'bar'")
    .args(["--evaluate", "x"])
    .stderr(
      r"
        error: list concatenation operator `++` requires `set lists`
         ——▶ justfile:1:12
          │
        1 │ x := 'foo' ++ 'bar'
          │            ^^
      ",
    )
    .failure();
}

#[test]
fn join_broadcasts_string_over_list() {
  assert_list("'foo' / ['bar', 'baz']", r#"["foo/bar", "foo/baz"]"#);
  assert_list("['bar', 'baz'] / 'foo'", r#"["bar/foo", "baz/foo"]"#);
}

#[test]
fn join_combines_equal_length_lists_pairwise() {
  assert_list("['a', 'b'] / ['c', 'd']", r#"["a/c", "b/d"]"#);
}

#[test]
fn unary_join_broadcasts_over_list() {
  assert_list("/ ['bar', 'baz']", r#"["/bar", "/baz"]"#);
}

#[test]
fn concatenation_with_empty_list_is_an_error() {
  Test::new()
    .justfile("set lists\n\nx := 'foo' + []")
    .unstable()
    .args(["--evaluate", "x"])
    .stderr(
      r"
        error: operator `+` cannot be applied to empty lists
         ——▶ justfile:3:12
          │
        3 │ x := 'foo' + []
          │            ^
      ",
    )
    .failure();
}

#[test]
fn concatenation_of_different_length_lists_is_an_error() {
  Test::new()
    .justfile("set lists\n\nx := ['a', 'b'] + ['c', 'd', 'e']")
    .unstable()
    .args(["--evaluate", "x"])
    .stderr(
      r#"
        error: operator `+` cannot be applied to lists of different lengths: ["a", "b"] + ["c", "d", "e"]
         ——▶ justfile:3:17
          │
        3 │ x := ['a', 'b'] + ['c', 'd', 'e']
          │                 ^
      "#,
    )
    .failure();
}

#[test]
fn unary_join_with_empty_list_is_an_error() {
  Test::new()
    .justfile("set lists\n\nx := / []")
    .unstable()
    .args(["--evaluate", "x"])
    .stderr(
      r"
        error: operator `/` cannot be applied to empty lists
         ——▶ justfile:3:6
          │
        3 │ x := / []
          │      ^
      ",
    )
    .failure();
}

#[test]
fn assert_message_space_joins_lists() {
  Test::new()
    .justfile(
      "
        set lists

        foo:
          {{ assert('a' != 'a', ['foo', 'bar']) }}
      ",
    )
    .unstable()
    .stderr(
      "
        error: assert failed: foo bar
         ——▶ justfile:4:6
          │
        4 │   {{ assert('a' != 'a', ['foo', 'bar']) }}
          │      ^^^^^^
      ",
    )
    .failure();
}

#[test]
fn confirm_prompt_space_joins_lists() {
  Test::new()
    .justfile(
      "
        set lists

        [confirm(['foo', 'bar'])]
        @foo:
          echo FOO
      ",
    )
    .unstable()
    .stderr("foo bar ")
    .stdout("FOO\n")
    .stdin("y")
    .success();
}

#[test]
fn env_attribute_value_space_joins_lists() {
  Test::new()
    .justfile(
      "
        set lists

        [env('FOO', ['bar', 'baz'])]
        foo:
          @echo $FOO
      ",
    )
    .unstable()
    .stdout("bar baz\n")
    .success();
}

#[test]
fn env_attribute_empty_list_leaves_variable_unset() {
  Test::new()
    .justfile(
      r#"
        set lists

        [env('FOO', [])]
        foo:
          @echo "${FOO-unset}"
      "#,
    )
    .unstable()
    .stdout("unset\n")
    .success();
}

#[test]
fn env_attribute_empty_string_sets_variable() {
  Test::new()
    .justfile(
      r#"
        set lists

        [env('FOO', [''])]
        foo:
          @echo "[${FOO-unset}]"
      "#,
    )
    .unstable()
    .stdout("[]\n")
    .success();
}

#[test]
fn empty_list_export_leaves_variable_unset() {
  Test::new()
    .justfile(
      r#"
        set lists

        export FOO := []

        foo:
          @echo "${FOO-unset}"
      "#,
    )
    .unstable()
    .stdout("unset\n")
    .success();
}

#[test]
fn non_empty_list_export_sets_variable() {
  Test::new()
    .justfile(
      r#"
        set lists

        export FOO := ['bar', 'baz']

        foo:
          @echo "$FOO"
      "#,
    )
    .unstable()
    .stdout("bar baz\n")
    .success();
}

#[test]
fn empty_list_exported_parameter_leaves_variable_unset() {
  Test::new()
    .justfile(
      r#"
        set lists

        foo $bar=[]:
          @echo "${bar-unset}"
      "#,
    )
    .unstable()
    .stdout("unset\n")
    .success();
}

#[test]
fn empty_list_set_export_leaves_variable_unset() {
  Test::new()
    .justfile(
      r#"
        set lists
        set export

        FOO := []

        foo:
          @echo "${FOO-unset}"
      "#,
    )
    .unstable()
    .stdout("unset\n")
    .success();
}

#[test]
fn list_in_env_attribute_name_points_at_attribute_name() {
  Test::new()
    .justfile(
      "
        set lists

        [env(['FOO', 'BAR'], 'baz')]
        foo:
          @echo hi
      ",
    )
    .unstable()
    .stderr(
      r#"
        error: list value ["FOO", "BAR"] used as `env` attribute name
         ——▶ justfile:3:2
          │
        3 │ [env(['FOO', 'BAR'], 'baz')]
          │  ^^^
      "#,
    )
    .failure();
}

#[test]
fn env_returns_first_present_variable() {
  Test::new()
    .justfile("set lists\n\nx := env(['ZADDY', 'BAR'])")
    .unstable()
    .env("BAR", "bar")
    .args(["--evaluate", "x"])
    .stdout("bar")
    .unindent_stdout(false)
    .success();
}

#[test]
fn env_stops_at_first_present_variable_including_empty() {
  Test::new()
    .justfile("set lists\n\nx := show(env(['ZADDY', 'BAR']))")
    .unstable()
    .env("ZADDY", "")
    .env("BAR", "bar")
    .args(["--evaluate", "x"])
    .stdout(r#""""#)
    .unindent_stdout(false)
    .success();
}

#[test]
fn env_returns_default_when_no_variable_present() {
  assert_list("env(['ZADDY', 'XYZ'], 'baz')", r#""baz""#);
}

#[test]
fn env_returns_list_default() {
  assert_list("env(['ZADDY'], ['a', 'b'])", r#"["a", "b"]"#);
}

#[test]
fn env_with_empty_key_list_uses_default() {
  assert_list("env([], 'baz')", r#""baz""#);
}

#[test]
fn env_with_empty_key_list_and_no_default_is_an_error() {
  Test::new()
    .arg("a")
    .justfile(
      "
        set lists

        a:
          echo {{env([])}}
      ",
    )
    .unstable()
    .stderr(
      "
        error: call to function `env` failed: empty environment variable list with no default
         ——▶ justfile:4:10
          │
        4 │   echo {{env([])}}
          │          ^^^
      ",
    )
    .failure();
}

#[test]
fn env_missing_keys_error_names_all_keys() {
  Test::new()
    .arg("a")
    .justfile(
      "
        set lists

        a:
          echo {{env(['ZADDY', 'XYZ'])}}
      ",
    )
    .unstable()
    .stderr(
      "
        error: call to function `env` failed: environment variables `ZADDY` and `XYZ` not present
         ——▶ justfile:4:10
          │
        4 │   echo {{env(['ZADDY', 'XYZ'])}}
          │          ^^^
      ",
    )
    .failure();
}

#[test]
fn env_single_missing_key_keeps_singular_message() {
  Test::new()
    .arg("a")
    .justfile(
      "
        set lists

        a:
          echo {{env('ZADDY')}}
      ",
    )
    .unstable()
    .stderr(
      "
        error: call to function `env` failed: environment variable `ZADDY` not present
         ——▶ justfile:4:10
          │
        4 │   echo {{env('ZADDY')}}
          │          ^^^
      ",
    )
    .failure();
}

#[test]
fn env_var_accepts_list_of_keys() {
  Test::new()
    .justfile("set lists\n\nx := env_var(['ZADDY', 'BAR'])")
    .unstable()
    .env("BAR", "bar")
    .args(["--evaluate", "x"])
    .stdout("bar")
    .unindent_stdout(false)
    .success();
}

#[test]
fn env_var_or_default_accepts_list_of_keys() {
  assert_list("env_var_or_default(['ZADDY', 'XYZ'], [])", "[]");
}

#[test]
fn list_in_working_directory_attribute_points_at_attribute_name() {
  Test::new()
    .justfile(
      "
        set lists

        [working-directory(['foo', 'bar'])]
        baz:
          @echo hi
      ",
    )
    .unstable()
    .stderr(
      r#"
        error: list value ["foo", "bar"] used as a `[working-directory]` attribute
         ——▶ justfile:3:2
          │
        3 │ [working-directory(['foo', 'bar'])]
          │  ^^^^^^^^^^^^^^^^^
      "#,
    )
    .failure();
}

#[test]
fn interpreter_settings_flatten_lists() {
  Test::new()
    .justfile(
      "
        set lists
        set shell := ['echo', ['foo', 'bar']]

        baz:
          hello
      ",
    )
    .unstable()
    .stdout("foo bar hello\n")
    .stderr("hello\n")
    .shell(false)
    .success();
}

#[test]
fn empty_interpreter_setting_is_an_error() {
  Test::new()
    .justfile(
      "
        set lists
        set shell := [[]]

        foo:
          @echo bar
      ",
    )
    .unstable()
    .stderr(
      "
        error: `shell` setting requires at least one element but evaluated to empty list
         ——▶ justfile:2:5
          │
        2 │ set shell := [[]]
          │     ^^^^^
      ",
    )
    .failure();
}

#[test]
fn list_in_function_argument_points_at_function_name() {
  Test::new()
    .justfile(
      "
        set lists

        foo:
          @echo {{ uppercase(['bar', 'baz']) }}
      ",
    )
    .unstable()
    .stderr(
      r#"
        error: list value ["bar", "baz"] passed to `uppercase()`
        the behavior of lists with many built-in functions is undecided
        see https://github.com/casey/just#lists
         ——▶ justfile:4:12
          │
        4 │   @echo {{ uppercase(['bar', 'baz']) }}
          │            ^^^^^^^^^
      "#,
    )
    .failure();
}

#[test]
fn list_in_setting_value_points_at_setting_name() {
  Test::new()
    .justfile(
      "
        set lists
        set tempdir := ['foo', 'bar']

        foo:
          @echo bar
      ",
    )
    .unstable()
    .stderr(
      r#"
        error: list value ["foo", "bar"] assigned to `tempdir` setting
         ——▶ justfile:2:5
          │
        2 │ set tempdir := ['foo', 'bar']
          │     ^^^^^^^
      "#,
    )
    .failure();
}

#[test]
fn len() {
  #[track_caller]
  fn case(expression: &str, expected: &str) {
    Test::new()
      .justfile(format!("set lists\nx := len({expression})"))
      .unstable()
      .args(["--evaluate", "x"])
      .stdout(expected)
      .success();
  }

  case("[]", "0");
  case("''", "1");
  case("'foo'", "1");
  case("['foo']", "1");
  case("['foo', 'bar']", "2");
}
