///|
/// A single recorded operation step.
pub struct LdapStep {
  op : String
  result_code : Int
  diagnostic : String
} derive(Eq, @debug.Debug)

///|
pub fn LdapStep::new(
  op : String,
  result_code : Int,
  diagnostic : String,
) -> LdapStep {
  { op, result_code, diagnostic }
}

///|
/// A running diagnostic report. It is recorded by `LdapClient` as operations
/// execute; passwords and SASL payloads never appear in it.
pub struct LdapTrace {
  steps : Array[LdapStep]
  mut result_code : Int
  mut matched_dn : String?
  mut diagnostic : String?
  mut entries : Int
  referrals : Array[String]
} derive(@debug.Debug)

///|
pub fn LdapTrace::new() -> LdapTrace {
  {
    steps: [],
    result_code: 0,
    matched_dn: None,
    diagnostic: None,
    entries: 0,
    referrals: [],
  }
}

///|
pub fn LdapTrace::steps(self : LdapTrace) -> Array[LdapStep] {
  self.steps
}

///|
pub fn LdapTrace::result_code(self : LdapTrace) -> Int {
  self.result_code
}

///|
pub fn LdapTrace::matched_dn(self : LdapTrace) -> String? {
  self.matched_dn
}

///|
pub fn LdapTrace::diagnostic(self : LdapTrace) -> String? {
  self.diagnostic
}

///|
pub fn LdapTrace::entries(self : LdapTrace) -> Int {
  self.entries
}

///|
pub fn LdapTrace::referrals(self : LdapTrace) -> Array[String] {
  self.referrals
}

///|
fn LdapTrace::record_step(self : LdapTrace, step : LdapStep) -> Unit {
  self.steps.push(step)
}

///|
fn LdapTrace::record_result(self : LdapTrace, result : LdapResult) -> Unit {
  self.result_code = result.result_code.to_int()
  self.matched_dn = Some(result.matched_dn)
  self.diagnostic = Some(result.diagnostic_message)
  match result.referral {
    Some(refs) =>
      for uri in refs {
        self.referrals.push(uri)
      }
    None => ()
  }
}

///|
/// Render the trace as a compact multi-line string for CLI output.
pub fn LdapTrace::to_string(self : LdapTrace) -> String {
  let sb = StringBuilder()
  sb.write_string("steps: \{self.steps.length()}\n")
  for step in self.steps {
    let code = ResultCode::from_int(step.result_code).to_string()
    sb.write_string(
      "  - \{step.op}: resultCode=\{step.result_code} (\{code})\n",
    )
  }
  let final_code = ResultCode::from_int(self.result_code).to_string()
  sb.write_string("final resultCode: \{self.result_code} (\{final_code})\n")
  match self.matched_dn {
    Some(dn) => sb.write_string("matchedDN: \{dn}\n")
    None => ()
  }
  match self.diagnostic {
    Some(d) => sb.write_string("diagnostic: \{d}\n")
    None => ()
  }
  sb.write_string("entries: \{self.entries}\n")
  sb.write_string("referrals: \{self.referrals.length()}\n")
  for uri in self.referrals {
    sb.write_string("  - \{uri}\n")
  }
  sb.to_string()
}