///|
pub struct ReceiverState {
  guard_state : GuardState
  admission_state : AdmissionState
  audit_records : Array[AuditRecord]
} derive(Eq, Debug)

///|
pub fn ReceiverState::empty() -> ReceiverState {
  {
    guard_state: GuardState::empty(),
    admission_state: AdmissionState::empty(),
    audit_records: [],
  }
}

///|
pub struct ReceiverConfig {
  guard_config : GuardConfig
  admission_rule : AdmissionRule
  validate_payload : Bool
} derive(Eq, Debug)

///|
pub fn ReceiverConfig::default() -> ReceiverConfig {
  {
    guard_config: GuardConfig::default(),
    admission_rule: admission_rule_for("github"),
    validate_payload: true,
  }
}

///|
pub struct ReceiverOutput {
  pipeline : GuardPipelineOutput
  plan : HandlingPlan
  payload_errors : Array[PayloadValidationError]
  admission : AdmissionDecision
  state : ReceiverState
} derive(Eq, Debug)

///|
fn append_audit(
  records : Array[AuditRecord],
  output : GuardPipelineOutput,
) -> Array[AuditRecord] {
  let previous = if records.length() == 0 {
    "root"
  } else {
    records[records.length() - 1].digest
  }
  let record = audit_record(records.length(), previous, output)
  Array::makei(records.length() + 1, i => {
    if i < records.length() {
      records[i]
    } else {
      record
    }
  })
}

///|
pub fn receive_webhook(
  request : WebhookRequest,
  config : ReceiverConfig,
  state : ReceiverState,
) -> ReceiverOutput {
  let admission = check_admission(
    request.provider,
    config.guard_config.policy.now,
    config.admission_rule,
    state.admission_state,
  )
  if !admission.allowed {
    let rejected = VerificationResult::rejected(
      config.guard_config.policy.provider.name,
      provider_event_type(request, config.guard_config.policy.provider),
      provider_event_id(request, config.guard_config.policy.provider),
      [GuardDiagnostic::new(ReplayDetected, "admission blocked request")],
    )
    let pipeline = {
      verification: rejected,
      route: RouteDecision::miss("admission blocked"),
      risk: score_result(rejected),
      state: state.guard_state,
    }
    let plan = HandlingPlan::new(
      [HandlerAction::new(RetryLater, "", "admission blocked")],
      retry_response("admission blocked"),
    )
    {
      pipeline,
      plan,
      payload_errors: [],
      admission,
      state: {
        guard_state: state.guard_state,
        admission_state: admission.state,
        audit_records: append_audit(state.audit_records, pipeline),
      },
    }
  } else {
    let guard_config = config.guard_config.with_state(state.guard_state)
    let pipeline = run_guard_pipeline(request, guard_config)
    let payload_errors = if config.validate_payload &&
      pipeline.verification.is_ok() {
      validate_request_payload(request, config.guard_config.policy.provider)
    } else {
      []
    }
    let plan = if payload_errors.length() > 0 {
      HandlingPlan::new(
        [HandlerAction::new(Reject, "", "payload schema failed")],
        rejected_response("payload_schema"),
      )
    } else {
      plan_for_output(pipeline)
    }
    {
      pipeline,
      plan,
      payload_errors,
      admission,
      state: {
        guard_state: pipeline.state,
        admission_state: admission.state,
        audit_records: append_audit(state.audit_records, pipeline),
      },
    }
  }
}

///|
pub fn receiver_output_text(output : ReceiverOutput) -> String {
  let builder = StringBuilder::new()
  builder.write_string(pipeline_output_text(output.pipeline))
  if output.payload_errors.length() > 0 {
    builder.write_string("\npayload_errors=\n")
    builder.write_string(payload_error_text(output.payload_errors))
  }
  builder.write_string("\nplan=\n")
  builder.write_string(plan_text(output.plan))
  builder.to_string()
}

///|
pub fn receive_batch(
  requests : Array[WebhookRequest],
  config : ReceiverConfig,
) -> Array[ReceiverOutput] {
  let mut state = ReceiverState::empty()
  Array::makei(requests.length(), i => {
    let output = receive_webhook(requests[i], config, state)
    state = output.state
    output
  })
}

///|
pub fn receiver_batch_report(outputs : Array[ReceiverOutput]) -> String {
  let builder = StringBuilder::new()
  builder.write_string("| index | status | route | response |\n")
  builder.write_string("| ---: | --- | --- | ---: |\n")
  for i in 0..