///|
/// A deterministic, review-friendly change plan derived from two documents.
/// It is descriptive rather than an in-place mutator, so callers can review
/// or approve changes before applying them in their own storage layer.
pub enum ChangeOperation {
  Add
  Remove
  Replace
}

///|
pub(all) struct ChangePlan {
  path : String
  operation : ChangeOperation
  before : String?
  after : String?
}

///|
pub fn ChangeOperation::to_string(self : ChangeOperation) -> String {
  match self {
    Add => "add"
    Remove => "remove"
    Replace => "replace"
  }
}

///|
pub fn ChangeOperation::to_json(self : ChangeOperation) -> String {
  canonical_string(self.to_string())
}

///|
/// Convert semantic differences into a stable operation list.
pub fn change_plan(left : JsonValue, right : JsonValue) -> Array[ChangePlan] {
  let differences = diff_json(left, right)
  let plan : Array[ChangePlan] = []
  for difference in differences {
    let operation = match difference.kind {
      Added => ChangeOperation::Add
      Removed => ChangeOperation::Remove
      Changed | TypeChanged => ChangeOperation::Replace
    }
    plan.push({
      path: difference.path,
      operation,
      before: difference.left,
      after: difference.right,
    })
  }
  plan
}

///|
pub fn change_plan_documents(
  left : String,
  right : String,
) -> Result[Array[ChangePlan], String] {
  let left_value = match parse(left) {
    Ok(value) => value
    Err(error) => return Err("left document: " + error.message())
  }
  let right_value = match parse(right) {
    Ok(value) => value
    Err(error) => return Err("right document: " + error.message())
  }
  Ok(change_plan(left_value, right_value))
}

///|
pub fn ChangePlan::to_json(self : ChangePlan) -> String {
  let before = match self.before {
    Some(value) => value
    None => "null"
  }
  let after = match self.after {
    Some(value) => value
    None => "null"
  }
  "{\"op\":" +
  canonical_string(self.operation.to_string()) +
  ",\"path\":" +
  canonical_string(self.path) +
  ",\"before\":" +
  before +
  ",\"after\":" +
  after +
  "}"
}

///|
pub fn change_plan_text(plan : Array[ChangePlan]) -> String {
  let mut output = ""
  for change in plan {
    if output != "" {
      output = output + "\n"
    }
    output = output +
      change.operation.to_string() +
      " " +
      change.path +
      " : " +
      (match change.before {
        Some(value) => value
        None => "-"
      }) +
      " -> " +
      (match change.after {
        Some(value) => value
        None => "-"
      })
  }
  output
}