///|
pub(all) struct UnattendedDriver {
  adapter_id : String
  executable : String
  arguments : Array[String]
  draft_output_artifacts : Array[String]
  expected_output_artifacts : Array[String]
  attestor_id : String
  attestor_executable : String
  attestor_arguments : Array[String]
  reviewer_id : String
  reviewer_executable : String
  reviewer_arguments : Array[String]
  review_authority_id : String
  budget_request : @pipeline.AutonomyBudgetUsage
} derive(Debug, Eq)

///|
pub(all) struct UnattendedManifest {
  contract_id : String
  gate_executable : String
  gate_arguments : Array[String]
  max_cycles : Int
  drivers : Array[UnattendedDriver]
  observer_executable : String
  observer_arguments : Array[String]
} derive(Debug, Eq)

///|
fn decode_unattended_driver(value : Json) -> UnattendedDriver raise {
  let fields = wire_object(value, "MoonFlow unattended driver")
  guard fields.get("budget_request") is Some(budget_request) else {
    fail("MoonFlow unattended driver requires budget_request")
  }
  {
    adapter_id: wire_string(fields, "adapter_id"),
    executable: wire_string(fields, "executable"),
    arguments: wire_strings(fields, "arguments", required=false),
    draft_output_artifacts: wire_strings(fields, "draft_output_artifacts"),
    expected_output_artifacts: wire_strings(fields, "expected_output_artifacts"),
    attestor_id: wire_string(fields, "attestor_id"),
    attestor_executable: wire_string(fields, "attestor_executable"),
    attestor_arguments: wire_strings(
      fields,
      "attestor_arguments",
      required=false,
    ),
    reviewer_id: wire_string(fields, "reviewer_id"),
    reviewer_executable: wire_string(fields, "reviewer_executable"),
    reviewer_arguments: wire_strings(
      fields,
      "reviewer_arguments",
      required=false,
    ),
    review_authority_id: wire_string(fields, "review_authority_id"),
    budget_request: @pipeline.decode_autonomy_budget_usage(budget_request),
  }
}

///|
pub fn decode_unattended_manifest(value : Json) -> UnattendedManifest raise {
  let fields = wire_object(value, "MoonFlow unattended manifest")
  guard fields.get("drivers") is Some(Array(drivers)) else {
    fail("MoonFlow unattended manifest requires drivers")
  }
  let manifest = {
    contract_id: wire_string(fields, "contract_id"),
    gate_executable: wire_string(fields, "gate_executable"),
    gate_arguments: wire_strings(fields, "gate_arguments", required=false),
    max_cycles: wire_int(fields, "max_cycles"),
    drivers: drivers.map(decode_unattended_driver),
    observer_executable: match fields.get("observer_executable") {
      Some(String(value)) => value
      None => ""
      _ => fail("observer_executable must be a string")
    },
    observer_arguments: wire_strings(
      fields,
      "observer_arguments",
      required=false,
    ),
  }
  let issues = manifest.quality_issues()
  if !issues.is_empty() {
    fail("invalid MoonFlow unattended manifest: \{issues.join("; ")}")
  }
  manifest
}

///|
pub fn UnattendedManifest::quality_issues(
  self : UnattendedManifest,
) -> Array[String] {
  let issues : Array[String] = []
  if self.contract_id != "moonflow.unattended-manifest.v3" {
    issues.push("unsupported unattended manifest contract")
  }
  if self.gate_executable.trim().is_empty() {
    issues.push("gate_executable is required")
  }
  if self.max_cycles < 1 || self.max_cycles > 10000 {
    issues.push("max_cycles must be between 1 and 10000")
  }
  if self.drivers.is_empty() {
    issues.push("at least one unattended driver is required")
  }
  if self.observer_executable.trim().is_empty() !=
    self.observer_arguments.is_empty() {
    issues.push("observer executable and arguments must be declared together")
  }
  let ids : Array[String] = []
  for driver in self.drivers {
    if ids.contains(driver.adapter_id) {
      issues.push("duplicate unattended driver: \{driver.adapter_id}")
    } else {
      ids.push(driver.adapter_id)
    }
    if driver.executable.trim().is_empty() {
      issues.push("driver executable is required: \{driver.adapter_id}")
    }
    if driver.attestor_id.trim().is_empty() {
      issues.push("product attestor identity is required: \{driver.adapter_id}")
    }
    if driver.attestor_id == driver.adapter_id ||
      driver.attestor_id == driver.reviewer_id {
      issues.push(
        "product attestor identity must be distinct: \{driver.adapter_id}",
      )
    }
    if driver.attestor_executable.trim().is_empty() {
      issues.push(
        "product attestor executable is required: \{driver.adapter_id}",
      )
    }
    if driver.reviewer_id == driver.adapter_id {
      issues.push("reviewer identity must be independent: \{driver.adapter_id}")
    }
    if driver.reviewer_executable.trim().is_empty() {
      issues.push("reviewer executable is required: \{driver.adapter_id}")
    }
    if driver.review_authority_id.trim().is_empty() {
      issues.push("review authority is required: \{driver.adapter_id}")
    }
    if driver.expected_output_artifacts.is_empty() {
      issues.push(
        "expected output artifacts are required: \{driver.adapter_id}",
      )
    }
    if driver.draft_output_artifacts.is_empty() {
      issues.push("draft output artifacts are required: \{driver.adapter_id}")
    }
    if driver.draft_output_artifacts.length() != 1 ||
      driver.expected_output_artifacts.length() != 1 {
      issues.push(
        "v3 drivers require exactly one draft and one product-owned final artifact: \{driver.adapter_id}",
      )
    }
    for artifact in driver.draft_output_artifacts {
      if !artifact_ref_is_workspace_relative(artifact) {
        issues.push("invalid draft output artifact: \{artifact}")
      }
      if driver.expected_output_artifacts.contains(artifact) {
        issues.push(
          "draft and product-owned final artifact must differ: \{artifact}",
        )
      }
    }
    for artifact in driver.expected_output_artifacts {
      if !artifact_ref_is_workspace_relative(artifact) {
        issues.push("invalid expected output artifact: \{artifact}")
      }
    }
    for issue in driver.budget_request.quality_issues() {
      issues.push("driver \{driver.adapter_id}: \{issue}")
    }
  }
  issues
}

///|
pub fn render_unattended_observer_arguments(
  arguments : Array[String],
  workspace : String,
  run_id : String,
  book_id : String,
  projection_path : String,
  recorded_at : String,
) -> Array[String] {
  arguments.map(argument => {
    argument
    .replace(old="{workspace}", new=workspace)
    .replace(old="{run_id}", new=run_id)
    .replace(old="{book_id}", new=book_id)
    .replace(old="{projection}", new=projection_path)
    .replace(old="{recorded_at}", new=recorded_at)
  })
}

///|
pub fn UnattendedManifest::find_driver(
  self : UnattendedManifest,
  adapter_id : String,
) -> UnattendedDriver? {
  for driver in self.drivers {
    if driver.adapter_id == adapter_id {
      return Some(driver)
    }
  }
  None
}

///|
pub fn render_unattended_arguments(
  arguments : Array[String],
  workspace : String,
  request_artifact : String,
  result_artifact : String,
  receipt_artifact : String,
  review_artifact : String,
) -> Array[String] {
  arguments.map(argument => {
    argument
    .replace(old="{workspace}", new=workspace)
    .replace(old="{request}", new=request_artifact)
    .replace(old="{result}", new=result_artifact)
    .replace(old="{receipt}", new=receipt_artifact)
    .replace(old="{review}", new=review_artifact)
  })
}

///|
pub fn render_unattended_attestor_arguments(
  arguments : Array[String],
  workspace : String,
  request_artifact : String,
  result_artifact : String,
  attestation_artifact : String,
  draft_artifact : String,
  final_artifact : String,
) -> Array[String] {
  arguments.map(argument => {
    argument
    .replace(old="{workspace}", new=workspace)
    .replace(old="{request}", new=request_artifact)
    .replace(old="{result}", new=result_artifact)
    .replace(old="{attestation}", new=attestation_artifact)
    .replace(old="{draft}", new=draft_artifact)
    .replace(old="{final}", new=final_artifact)
  })
}

///|
pub fn product_attestation_issues(
  value : Json,
  driver : UnattendedDriver,
  request : AdapterRequest,
  result : AdapterResult,
) -> Array[String] {
  let issues : Array[String] = []
  let fields = wire_object(value, "MoonFlow product attestation") catch {
    _ => {
      issues.push("product attestation must be an object")
      return issues
    }
  }
  fn check(
    fields : Map[String, Json],
    name : String,
    expected : String,
    issues : Array[String],
  ) {
    match fields.get(name) {
      Some(String(actual)) if actual == expected => ()
      _ => issues.push("product attestation \{name} mismatch")
    }
  }
  check(fields, "contract_id", "moonflow.product-attestation.v1", issues)
  check(fields, "attestor_id", driver.attestor_id, issues)
  check(fields, "product_id", request.product_id, issues)
  check(fields, "request_id", request.request_id, issues)
  check(fields, "result_id", result.result_id, issues)
  check(fields, "output_digest", result.output_digest, issues)
  match fields.get("accepted") {
    Some(True) => ()
    _ => issues.push("product attestation did not accept the artifact")
  }
  issues
}