use super::*;

fn parse_program(source: &str) -> Vec<crate::SNode> {
    let mut lexer = Lexer::new(source);
    let tokens = lexer.tokenize().unwrap();
    let mut parser = Parser::new(tokens);
    parser.parse().unwrap()
}

#[test]
fn imported_type_aliases_are_registered_into_scope() {
    let imported = parse_program(
        r#"
type SignatureStatus = { state: "verified" } | { state: "failed", reason: string }

type GitHubEventPayload = {
  provider: "github",
  action: string | nil,
  raw: dict,
}

type SlackEventPayload = {
  provider: "slack",
  subtype: string | nil,
  raw: dict,
}

type ProviderPayload = GitHubEventPayload | SlackEventPayload

type TriggerEvent = {
  provider_payload: ProviderPayload,
  signature_status: SignatureStatus,
}
"#,
    );
    let program = parse_program(
        r#"
pipeline t(task) {
  let event: TriggerEvent = {
    provider_payload: {
      provider: "github",
      action: "opened",
      raw: {},
    },
    signature_status: {
      state: "failed",
      reason: "bad signature",
    },
  }

  let payload = event.provider_payload
  if payload.provider == "github" {
    let action: string | nil = payload.action
  } else {
    let subtype: string | nil = payload.subtype
  }

  let status = event.signature_status
  if status.state == "failed" {
    let reason: string = status.reason
  }
}
"#,
    );

    let diagnostics = TypeChecker::new()
        .with_imported_type_decls(imported)
        .check(&program);
    let errors: Vec<String> = diagnostics
        .into_iter()
        .filter(|diag| diag.severity == DiagnosticSeverity::Error)
        .map(|diag| diag.message)
        .collect();
    assert!(errors.is_empty(), "got imported-type errors: {errors:?}");
}

#[test]
fn imported_structs_allow_field_access() {
    let imported = parse_program(
        r"
struct HeaderRecord {
  name: string,
  value: string,
}
",
    );
    let program = parse_program(
        r"
fn use_header(header: HeaderRecord) {
  let value: string = header.value
}

pipeline t(task) {
  return
}
",
    );

    let diagnostics = TypeChecker::new()
        .with_imported_type_decls(imported)
        .check(&program);
    let errors: Vec<String> = diagnostics
        .into_iter()
        .filter(|diag| diag.severity == DiagnosticSeverity::Error)
        .map(|diag| diag.message)
        .collect();
    assert!(errors.is_empty(), "got imported-struct errors: {errors:?}");
}

#[test]
fn imported_callable_signatures_check_arguments() {
    let imported = parse_program(
        r"
type PickOptions = {drop_nil?: bool}

pub fn pick(options: PickOptions = {}) -> nil {
  return nil
}
",
    );
    let program = parse_program(
        r"
pipeline t(task) {
  pick({dropnil: true})
}
",
    );

    let diagnostics = TypeChecker::new()
        .with_imported_type_decls(imported.clone())
        .with_imported_callable_decls(imported)
        .check(&program);
    let errors: Vec<String> = diagnostics
        .into_iter()
        .filter(|diag| diag.severity == DiagnosticSeverity::Error)
        .map(|diag| diag.message)
        .collect();
    assert!(
        errors
            .iter()
            .any(|error| error.contains("argument 1 `options`: unknown option `dropnil`")),
        "expected imported argument error, got: {errors:?}"
    );
}
