use super::*;

#[test]
fn pattern_match() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='BAR')]
        foo bar:
      ",
    )
    .args(["foo", "BAR"])
    .success();
}

#[test]
fn pattern_mismatch() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='BAR')]
        foo bar:
      ",
    )
    .args(["foo", "bar"])
    .stderr(
      "
        error: argument `bar` passed to recipe `foo` parameter `bar` does not match pattern `BAR`
      ",
    )
    .failure();
}

#[test]
fn patterns_are_regular_expressions() {
  Test::new()
    .justfile(
      r"
        [arg('bar', pattern='\d+')]
        foo bar:
      ",
    )
    .args(["foo", r"\d+"])
    .stderr(
      r"
        error: argument `\d+` passed to recipe `foo` parameter `bar` does not match pattern `\d+`
      ",
    )
    .failure();
}

#[test]
fn pattern_must_match_entire_string() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='bar')]
        foo bar:
      ",
    )
    .args(["foo", "xbarx"])
    .stderr(
      "
        error: argument `xbarx` passed to recipe `foo` parameter `bar` does not match pattern `bar`
      ",
    )
    .failure();
}

#[test]
fn pattern_invalid_regex_error() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='{')]
        foo bar:
      ",
    )
    .stderr(
      "
        error: failed to parse argument pattern
         ——▶ justfile:1:13
          │
        1 │ [arg('bar', pattern='{')]
          │             ^^^^^^^
        caused by: regex parse error:
            {
            ^
        error: repetition operator missing expression
      ",
    )
    .failure();
}

#[test]
fn pattern_may_be_expression() {
  Test::new()
    .justfile(
      "
        prefix := 'B'
        [arg('bar', pattern=prefix + 'AR')]
        foo bar:
      ",
    )
    .args(["foo", "bar"])
    .stderr(
      "
        error: argument `bar` passed to recipe `foo` parameter `bar` does not match pattern `BAR`
      ",
    )
    .failure();
}

#[test]
fn pattern_cannot_reference_parameter() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern=bar)]
        foo bar:
      ",
    )
    .stderr(
      "
        error: variable `bar` not defined
         ——▶ justfile:1:21
          │
        1 │ [arg('bar', pattern=bar)]
          │                     ^^^
      ",
    )
    .failure();
}

#[test]
fn pattern_cannot_reference_undefined_variable() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern=undefined)]
        foo bar:
      ",
    )
    .stderr(
      "
        error: variable `undefined` not defined
         ——▶ justfile:1:21
          │
        1 │ [arg('bar', pattern=undefined)]
          │                     ^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn pattern_list_match() {
  Test::new()
    .justfile(
      "
          set lists
          [arg('bar', pattern=['A', 'B'])]
          foo bar:
        ",
    )
    .unstable()
    .args(["foo", "A"])
    .success()
    .test()
    .unstable()
    .args(["foo", "B"])
    .success();
}

#[test]
fn pattern_list_mismatch() {
  Test::new()
    .justfile(
      "
        set lists
        [arg('bar', pattern=['A', 'B'])]
        foo bar:
      ",
    )
    .unstable()
    .args(["foo", "C"])
    .stderr(
      "
        error: argument `C` passed to recipe `foo` parameter `bar` does not match pattern `A` or `B`
      ",
    )
    .failure();
}

#[test]
fn pattern_empty_list_accepts_all_arguments() {
  Test::new()
    .justfile(
      "
        set lists
        [arg('bar', pattern=[])]
        foo bar:
      ",
    )
    .unstable()
    .args(["foo", "anything"])
    .success();
}

#[test]
fn pattern_cannot_reference_non_const_variable() {
  Test::new()
    .justfile(
      "
        bar := `echo BAR`
        [arg('bar', pattern=bar)]
        foo bar:
      ",
    )
    .stderr(
      "
        error: cannot access non-const variable `bar` in const context
         ——▶ justfile:2:21
          │
        2 │ [arg('bar', pattern=bar)]
          │                     ^^^
      ",
    )
    .failure();
}

#[test]
fn dump() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='BAR')]
        foo bar:
      ",
    )
    .arg("--dump")
    .stdout(
      "
        [arg('bar', pattern='BAR')]
        foo bar:
      ",
    )
    .success();
}

#[test]
fn duplicate_attribute_error() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='BAR')]
        [arg('bar', pattern='BAR')]
        foo bar:
      ",
    )
    .args(["foo", "BAR"])
    .stderr(
      "
        error: attribute for argument `bar` first used on line 1 is duplicated on line 2
         ——▶ justfile:2:2
          │
        2 │ [arg('bar', pattern='BAR')]
          │  ^^^
      ",
    )
    .failure();
}

#[test]
fn unknown_argument_error() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='BAR')]
        foo:
      ",
    )
    .arg("foo")
    .stderr(
      "
        error: argument attribute for undefined argument `bar`
         ——▶ justfile:1:6
          │
        1 │ [arg('bar', pattern='BAR')]
          │      ^^^^^
      ",
    )
    .failure();
}

#[test]
fn pattern_mismatches_are_caught_before_running_dependencies() {
  Test::new()
    .justfile(
      "
        baz:
          exit 1

        [arg('bar', pattern='BAR')]
        foo bar: baz
      ",
    )
    .args(["foo", "bar"])
    .stderr(
      "
        error: argument `bar` passed to recipe `foo` parameter `bar` does not match pattern `BAR`
      ",
    )
    .failure();
}

#[test]
fn pattern_mismatches_are_caught_before_running_invocation() {
  Test::new()
    .justfile(
      "
        baz:
          exit 1

        [arg('bar', pattern='BAR')]
        foo bar: baz
      ",
    )
    .args(["baz", "foo", "bar"])
    .stderr(
      "
        error: argument `bar` passed to recipe `foo` parameter `bar` does not match pattern `BAR`
      ",
    )
    .failure();
}

#[test]
fn pattern_mismatches_are_caught_in_evaluated_arguments() {
  Test::new()
    .justfile(
      "
        bar: (foo 'ba' + 'r')

        [arg('bar', pattern='BAR')]
        foo bar:
      ",
    )
    .stderr(
      "
        error: argument `bar` passed to recipe `foo` parameter `bar` does not match pattern `BAR`
      ",
    )
    .failure();
}

#[test]
fn alternates_do_not_bind_to_anchors() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='a|b')]
        foo bar:
      ",
    )
    .args(["foo", "aa"])
    .stderr(
      "
        error: argument `aa` passed to recipe `foo` parameter `bar` does not match pattern `a|b`
      ",
    )
    .failure();
}

#[test]
fn pattern_match_variadic() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='BAR')]
        foo *bar:
      ",
    )
    .args(["foo", "BAR", "BAR"])
    .success();
}

#[test]
fn pattern_mismatch_variadic() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern='BAR BAR')]
        foo *bar:
      ",
    )
    .args(["foo", "BAR", "BAR"])
    .stderr(
      "
        error: argument `BAR` passed to recipe `foo` parameter `bar` does not match pattern `BAR BAR`
      ",
    )
    .failure();
}

#[test]
fn pattern_mismatch_repeatable_option() {
  Test::new()
    .justfile(
      "
        [arg('bar', long='bar', pattern='BAR')]
        foo +bar:
      ",
    )
    .args(["foo", "--bar", "BAR", "--bar", "BAZ"])
    .stderr(
      "
        error: argument `BAZ` passed to recipe `foo` parameter `bar` does not match pattern `BAR`
      ",
    )
    .failure();
}

#[test]
fn pattern_requires_value() {
  Test::new()
    .justfile(
      "
        [arg('bar', pattern)]
        foo bar:
      ",
    )
    .stderr(
      "
        error: attribute key `pattern` requires value
         ——▶ justfile:1:13
          │
        1 │ [arg('bar', pattern)]
          │             ^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn value_requires_value() {
  Test::new()
    .justfile(
      "
        [arg('bar', long, value)]
        foo bar:
      ",
    )
    .stderr(
      "
        error: attribute key `value` requires value
         ——▶ justfile:1:19
          │
        1 │ [arg('bar', long, value)]
          │                   ^^^^^
      ",
    )
    .failure();
}

#[test]
fn help_cannot_reference_parameter() {
  Test::new()
    .justfile(
      "
        [arg('bar', help=bar)]
        foo bar:
      ",
    )
    .stderr(
      "
        error: variable `bar` not defined
         ——▶ justfile:1:18
          │
        1 │ [arg('bar', help=bar)]
          │                  ^^^
      ",
    )
    .failure();
}

#[test]
fn help_cannot_reference_undefined_variable() {
  Test::new()
    .justfile(
      "
        [arg('bar', help=undefined)]
        foo bar:
      ",
    )
    .stderr(
      "
        error: variable `undefined` not defined
         ——▶ justfile:1:18
          │
        1 │ [arg('bar', help=undefined)]
          │                  ^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn help_cannot_reference_non_const_variable() {
  Test::new()
    .justfile(
      "
        bar := `echo BAR`
        [arg('bar', help=bar)]
        foo bar:
      ",
    )
    .stderr(
      "
        error: cannot access non-const variable `bar` in const context
         ——▶ justfile:2:18
          │
        2 │ [arg('bar', help=bar)]
          │                  ^^^
      ",
    )
    .failure();
}

#[test]
fn help_may_be_expression() {
  Test::new()
    .justfile(
      "
        prefix := 'hello '
        [arg('bar', help=prefix + 'world')]
        foo bar:
      ",
    )
    .args(["--usage", "foo"])
    .stdout(
      "
        Usage: just foo bar

        Arguments:
          bar hello world
      ",
    )
    .success();
}

#[test]
fn help_list_is_joined() {
  Test::new()
    .justfile(
      "
        set lists
        [arg('bar', help=['hello', 'world'])]
        foo bar:
      ",
    )
    .unstable()
    .args(["--usage", "foo"])
    .stdout(
      "
        Usage: just foo bar

        Arguments:
          bar hello world
      ",
    )
    .success();
}

#[test]
fn help_empty_list_is_no_help() {
  Test::new()
    .justfile(
      "
        set lists
        [arg('bar', help=[])]
        foo bar:
      ",
    )
    .unstable()
    .args(["--usage", "foo"])
    .stdout(
      "
        Usage: just foo bar

        Arguments:
          bar
      ",
    )
    .success();
}

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

        [arg('bar', max='2')]
        @foo +bar:
          echo bar='{{ show(bar) }}'
      ",
    )
    .unstable()
    .args(["foo", "a", "b"])
    .stdout(
      r#"
        bar=["a", "b"]
      "#,
    )
    .success();
}

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

        [arg('bar', long, multiple, max='2')]
        @foo +bar:
          echo bar='{{ show(bar) }}'
      ",
    )
    .unstable()
    .args(["foo", "--bar=a", "--bar=b"])
    .stdout(
      r#"
        bar=["a", "b"]
      "#,
    )
    .success();
}

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

        [arg('bar', max='2')]
        foo +bar:
      ",
    )
    .unstable()
    .args(["foo", "a", "b", "c"])
    .stderr("error: recipe `foo` parameter `bar` got 3 values but takes at most 2\n")
    .failure();
}

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

        [arg('bar', max='0')]
        foo *bar:
      ",
    )
    .unstable()
    .args(["foo", "a"])
    .stderr("error: recipe `foo` parameter `bar` got 1 value but takes at most 0\n")
    .failure();
}

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

        [arg('bar', max='2')]
        foo bar:
      ",
    )
    .unstable()
    .stderr(
      "
        error: argument attribute `max` only valid with `multiple` or a variadic parameter
         ——▶ justfile:3:13
          │
        3 │ [arg('bar', max='2')]
          │             ^^^
      ",
    )
    .failure();
}

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

        [arg('bar', max)]
        foo +bar:
      ",
    )
    .unstable()
    .stderr(
      "
        error: attribute key `max` requires value
         ——▶ justfile:3:13
          │
        3 │ [arg('bar', max)]
          │             ^^^
      ",
    )
    .failure();
}

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

        [arg('bar', max=('2'))]
        foo +bar:
      ",
    )
    .unstable()
    .stderr(
      "
        error: attribute `arg` arguments must be string literals
         ——▶ justfile:3:13
          │
        3 │ [arg('bar', max=('2'))]
          │             ^^^
      ",
    )
    .failure();
}

#[test]
fn invalid_max_value_error() {
  #[track_caller]
  fn case(value: &str) {
    Test::new()
      .justfile(format!("[arg('bar', max='{value}')]\nfoo +bar:"))
      .stderr(format!(
        "
          error: invalid `max` value `{value}`
           ——▶ justfile:1:17
            │
          1 │ [arg('bar', max='{value}')]
            │                 {caret}
        ",
        caret = "^".repeat(value.len() + 2),
      ))
      .failure();
  }

  case("x");
  case("+1");
  case("01");
  case("1 ");
}

#[test]
fn max_overflow_error() {
  Test::new()
    .justfile(
      "
        [arg('bar', max='18446744073709551616')]
        foo +bar:
      ",
    )
    .stderr(
      "
        error: invalid `max` value `18446744073709551616`: number too large to fit in target type
         ——▶ justfile:1:17
          │
        1 │ [arg('bar', max='18446744073709551616')]
          │                 ^^^^^^^^^^^^^^^^^^^^^^
      ",
    )
    .failure();
}

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

        [arg('bar', max='2')]
        foo +bar:

        baz: (foo ['a', 'b', 'c'])
      ",
    )
    .unstable()
    .args(["baz"])
    .stderr("error: recipe `foo` parameter `bar` got 3 values but takes at most 2\n")
    .failure();
}

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

        [arg('bar', max='2')]
        foo +bar:
      ",
    )
    .unstable()
    .arg("--dump")
    .stdout(
      "
        set lists

        [arg('bar', max='2')]
        foo +bar:
      ",
    )
    .success();
}

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

        [arg('bar', min='2')]
        @foo +bar:
          echo bar='{{ show(bar) }}'
      ",
    )
    .unstable()
    .args(["foo", "a", "b"])
    .stdout(
      r#"
        bar=["a", "b"]
      "#,
    )
    .success();
}

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

        [arg('bar', long, multiple, min='2')]
        @foo +bar:
          echo bar='{{ show(bar) }}'
      ",
    )
    .unstable()
    .args(["foo", "--bar=a", "--bar=b"])
    .stdout(
      r#"
        bar=["a", "b"]
      "#,
    )
    .success();
}

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

        [arg('bar', min='2')]
        foo +bar:
      ",
    )
    .unstable()
    .args(["foo", "a"])
    .stderr("error: recipe `foo` parameter `bar` got 1 value but takes at least 2\n")
    .failure();
}

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

        [arg('bar', min='2')]
        foo *bar:
      ",
    )
    .unstable()
    .arg("foo")
    .stderr("error: recipe `foo` parameter `bar` got 0 values but takes at least 2\n")
    .failure();
}

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

        [arg('bar', min='1')]
        foo *bar:
          @echo {{ bar }}
      ",
    )
    .unstable()
    .stderr(
      "error: recipe `foo` cannot be used as default recipe since it requires at least 1 argument\n",
    )
    .failure();
}

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

        [arg('bar', min='2')]
        @foo *bar=['a', 'b']:
          echo bar='{{ show(bar) }}'
      ",
    )
    .unstable()
    .arg("foo")
    .stdout(
      r#"
        bar=["a", "b"]
      "#,
    )
    .success();
}

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

        [arg('bar', min='2')]
        foo *bar=['a']:
      ",
    )
    .unstable()
    .arg("foo")
    .stderr("error: recipe `foo` parameter `bar` got 1 value but takes at least 2\n")
    .failure();
}

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

        [arg('bar', max='2')]
        foo *bar=['a', 'b', 'c']:
      ",
    )
    .unstable()
    .arg("foo")
    .stderr("error: recipe `foo` parameter `bar` got 3 values but takes at most 2\n")
    .failure();
}

#[test]
fn min_requires_set_lists() {
  Test::new()
    .justfile(
      "
        [arg('bar', min='2')]
        foo +bar:
      ",
    )
    .stderr(
      "
        error: `[arg(min)]` requires `set lists`
         ——▶ justfile:1:13
          │
        1 │ [arg('bar', min='2')]
          │             ^^^
      ",
    )
    .failure();
}

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

        [arg('bar', min='2')]
        foo bar:
      ",
    )
    .unstable()
    .stderr(
      "
        error: argument attribute `min` only valid with `multiple` or a variadic parameter
         ——▶ justfile:3:13
          │
        3 │ [arg('bar', min='2')]
          │             ^^^
      ",
    )
    .failure();
}

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

        [arg('bar', min)]
        foo +bar:
      ",
    )
    .unstable()
    .stderr(
      "
        error: attribute key `min` requires value
         ——▶ justfile:3:13
          │
        3 │ [arg('bar', min)]
          │             ^^^
      ",
    )
    .failure();
}

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

        [arg('bar', min=('2'))]
        foo +bar:
      ",
    )
    .unstable()
    .stderr(
      "
        error: attribute `arg` arguments must be string literals
         ——▶ justfile:3:13
          │
        3 │ [arg('bar', min=('2'))]
          │             ^^^
      ",
    )
    .failure();
}

#[test]
fn invalid_min_value_error() {
  #[track_caller]
  fn case(value: &str) {
    Test::new()
      .justfile(format!("[arg('bar', min='{value}')]\nfoo +bar:"))
      .stderr(format!(
        "
          error: invalid `min` value `{value}`
           ——▶ justfile:1:17
            │
          1 │ [arg('bar', min='{value}')]
            │                 {caret}
        ",
        caret = "^".repeat(value.len() + 2),
      ))
      .failure();
  }

  case("x");
  case("+1");
  case("01");
  case("1 ");
}

#[test]
fn min_overflow_error() {
  Test::new()
    .justfile(
      "
        [arg('bar', min='18446744073709551616')]
        foo +bar:
      ",
    )
    .stderr(
      "
        error: invalid `min` value `18446744073709551616`: number too large to fit in target type
         ——▶ justfile:1:17
          │
        1 │ [arg('bar', min='18446744073709551616')]
          │                 ^^^^^^^^^^^^^^^^^^^^^^
      ",
    )
    .failure();
}

#[test]
fn min_exceeding_max_is_an_error() {
  Test::new()
    .justfile(
      "
        [arg('bar', min='3', max='2')]
        foo +bar:
      ",
    )
    .stderr(
      "
        error: argument attribute `min` `3` exceeds `max` `2`
         ——▶ justfile:1:13
          │
        1 │ [arg('bar', min='3', max='2')]
          │             ^^^
      ",
    )
    .failure();
}

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

        [arg('bar', min='2')]
        foo +bar:

        baz: (foo ['a'])
      ",
    )
    .unstable()
    .args(["baz"])
    .stderr("error: recipe `foo` parameter `bar` got 1 value but takes at least 2\n")
    .failure();
}

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

        [arg('bar', min='1', max='2')]
        foo +bar:
      ",
    )
    .unstable()
    .arg("--dump")
    .stdout(
      "
        set lists

        [arg('bar', min='1', max='2')]
        foo +bar:
      ",
    )
    .success();
}
