use {super::*, pretty_assertions::assert_eq};

pub(crate) fn compile(src: &str) -> Justfile {
  Compiler::test_compile(src).expect("expected successful compilation")
}

pub(crate) fn config(args: &[&str]) -> Config {
  let mut args = Vec::from(args);
  args.insert(0, "just");

  let arguments = Arguments::try_parse_from(args).unwrap();

  Config::from_arguments(arguments).unwrap()
}

pub(crate) fn search(config: &Config) -> Search {
  let working_directory = config.invocation_directory.clone();
  let justfile = working_directory.join("justfile");

  Search {
    justfile,
    tempdir: None,
    working_directory,
  }
}

pub(crate) fn tempdir() -> tempfile::TempDir {
  tempfile::Builder::new()
    .prefix("just-test-tempdir")
    .tempdir()
    .expect("failed to create temporary directory")
}

macro_rules! analysis_error {
  (
      name:   $name:ident,
      input:  $input:expr,
      offset: $offset:expr,
      line:   $line:expr,
      column: $column:expr,
      width:  $width:expr,
      kind:   $kind:expr,
    ) => {
    #[test]
    fn $name() {
      $crate::testing::analysis_error($input, $offset, $line, $column, $width, $kind);
    }
  };
}

pub(crate) fn analysis_error(
  src: &str,
  offset: usize,
  line: usize,
  column: usize,
  length: usize,
  kind: CompileErrorKind,
) {
  let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");

  let ast = Parser::parse_tokens(&mut Numerator::new(), &tokens)
    .expect("Parsing failed in analysis test...");

  let root = PathBuf::from("justfile");
  let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
  asts.insert(root.clone(), ast);

  let mut paths: HashMap<PathBuf, PathBuf> = HashMap::new();
  paths.insert("justfile".into(), "justfile".into());

  match Analyzer::analyze(
    &asts,
    &Config::new().unwrap(),
    None,
    &[],
    &[],
    None,
    &mut HashMap::new(),
    &paths,
    false,
    &root,
  ) {
    Ok(_) => panic!("Analysis unexpectedly succeeded"),
    Err(have) => {
      let Error::Compile { compile_error } = have else {
        panic!(
          "unexpected non-compile analysis error: {}",
          have.color_display(Color::never()),
        );
      };

      let want = CompileError {
        token: Token {
          kind: compile_error.token.kind,
          src,
          offset,
          line,
          column,
          length,
          path: "justfile".as_ref(),
        },
        kind: kind.into(),
      };
      assert_eq!(compile_error, want);
    }
  }
}

macro_rules! run_error {
  {
    name: $name:ident,
    src:  $src:expr,
    args: $args:expr,
    error: $error:pat,
    check: $check:block $(,)?
  } => {
    #[test]
    fn $name() {
      let config = $crate::testing::config(&$args);
      let search = $crate::testing::search(&config);

      if let Subcommand::Run{ arguments } = &config.subcommand {
        match $crate::testing::compile(&$crate::unindent::unindent($src))
          .run(
            &config,
            &search,
            &arguments,
            &HashMap::new(),
          ).expect_err("Expected runtime error") {
            $error => $check
            other => {
              panic!("Unexpected run error: {other:?}");
            }
          }
      } else {
          panic!("Unexpected subcommand: {:?}", config.subcommand);
      }
    }
  };
}

macro_rules! assert_matches {
  ($expression:expr, $( $pattern:pat_param )|+ $( if $guard:expr )? $(,)?) => {
    match $expression {
      $( $pattern )|+ $( if $guard )? => {}
      left => panic!(
        "assertion failed: (left ~= right)\n  left: `{:?}`\n right: `{}`",
        left,
        stringify!($($pattern)|+ $(if $guard)?)
      ),
    }
  }
}
