///|
pub(all) enum SnapshotDeltaKind {
  SnapshotSetSection
  SnapshotAppendItem
  SnapshotRemoveSection
  SnapshotReplaceAll
} derive(Debug, Eq)

///|
pub struct SnapshotDelta {
  kind : SnapshotDeltaKind
  scope : String
  section : String
  item_id : String
  value_json : String
  patch : @core.Patch
} derive(Debug, Eq)

///|
pub struct SnapshotDeltaPlan {
  deltas : Array[SnapshotDelta]
  patch_plan : @core.PatchPlan
  section_update_count : Int
  append_item_count : Int
  full_snapshot_count : Int
  diagnostics : Array[String]
  summary : String
} derive(Debug, Eq)

///|
pub fn set_snapshot_section_delta(
  scope : String,
  section : String,
  value_json : String,
) -> SnapshotDelta {
  snapshot_delta(
    kind=SnapshotSetSection,
    scope~,
    section~,
    value_json~,
    patch=@core.set_json(snapshot_section_path(scope, section), value_json),
  )
}

///|
pub fn append_snapshot_item_delta(
  scope : String,
  section : String,
  item_id : String,
  value_json : String,
) -> SnapshotDelta {
  snapshot_delta(
    kind=SnapshotAppendItem,
    scope~,
    section~,
    item_id~,
    value_json~,
    patch=@core.append_json(snapshot_section_path(scope, section), value_json),
  )
}

///|
pub fn remove_snapshot_section_delta(
  scope : String,
  section : String,
) -> SnapshotDelta {
  snapshot_delta(
    kind=SnapshotRemoveSection,
    scope~,
    section~,
    patch=@core.remove_path(snapshot_section_path(scope, section)),
  )
}

///|
pub fn replace_snapshot_delta(
  scope : String,
  value_json : String,
) -> SnapshotDelta {
  snapshot_delta(
    kind=SnapshotReplaceAll,
    scope~,
    value_json~,
    patch=@core.set_json(snapshot_scope_path(scope), value_json),
  )
}

///|
pub fn plan_snapshot_deltas(deltas : Array[SnapshotDelta]) -> SnapshotDeltaPlan {
  plan_snapshot_deltas_with_budget(deltas, @core.default_patch_budget())
}

///|
pub fn plan_snapshot_deltas_with_budget(
  deltas : Array[SnapshotDelta],
  budget : @core.PatchBudget,
) -> SnapshotDeltaPlan {
  let patches : Array[@core.Patch] = []
  let diagnostics : Array[String] = []
  let mut section_update_count = 0
  let mut append_item_count = 0
  let mut full_snapshot_count = 0
  for delta in deltas {
    patches.push(delta.patch)
    append_delta_diagnostics(delta, diagnostics)
    match delta.kind {
      SnapshotSetSection | SnapshotRemoveSection => section_update_count += 1
      SnapshotAppendItem => append_item_count += 1
      SnapshotReplaceAll => full_snapshot_count += 1
    }
  }
  append_duplicate_item_diagnostics(deltas, diagnostics)
  let patch_plan = @core.plan_patches_with_budget(patches, budget)
  for item in patch_plan.diagnostics {
    diagnostics.push(item)
  }
  {
    deltas,
    patch_plan,
    section_update_count,
    append_item_count,
    full_snapshot_count,
    diagnostics,
    summary: "Bunnia snapshot delta plan: deltas=\{deltas.length()} patches=\{patch_plan.patches.length()} sections=\{section_update_count} appends=\{append_item_count} full_snapshots=\{full_snapshot_count} bytes=\{patch_plan.total_estimated_bytes} diagnostics=\{diagnostics.length()}",
  }
}

///|
pub fn snapshot_delta_kind_id(kind : SnapshotDeltaKind) -> String {
  match kind {
    SnapshotSetSection => "set-section"
    SnapshotAppendItem => "append-item"
    SnapshotRemoveSection => "remove-section"
    SnapshotReplaceAll => "replace-all"
  }
}

///|
fn snapshot_delta(
  kind~ : SnapshotDeltaKind,
  scope~ : String,
  section? : String = "",
  item_id? : String = "",
  value_json? : String = "null",
  patch~ : @core.Patch,
) -> SnapshotDelta {
  { kind, scope, section, item_id, value_json, patch }
}

///|
fn snapshot_scope_path(scope : String) -> String {
  "snapshots.\{scope}"
}

///|
fn snapshot_section_path(scope : String, section : String) -> String {
  "\{snapshot_scope_path(scope)}.\{section}"
}

///|
fn append_delta_diagnostics(
  delta : SnapshotDelta,
  diagnostics : Array[String],
) -> Unit {
  if delta.scope == "" {
    diagnostics.push("snapshot-delta-missing-scope")
  }
  match delta.kind {
    SnapshotSetSection | SnapshotAppendItem | SnapshotRemoveSection =>
      if delta.section == "" {
        diagnostics.push(
          "snapshot-delta-missing-section:\{diagnostic_scope(delta.scope)}",
        )
      }
    SnapshotReplaceAll =>
      diagnostics.push(
        "snapshot-delta-full-replace:\{diagnostic_scope(delta.scope)}",
      )
  }
  if delta.kind is SnapshotAppendItem && delta.item_id == "" {
    diagnostics.push(
      "snapshot-delta-missing-item-id:\{diagnostic_section(delta.scope, delta.section)}",
    )
  }
}

///|
fn append_duplicate_item_diagnostics(
  deltas : Array[SnapshotDelta],
  diagnostics : Array[String],
) -> Unit {
  for i in 0.. String {
  if scope == "" {
    ""
  } else {
    scope
  }
}

///|
fn diagnostic_section(scope : String, section : String) -> String {
  "\{diagnostic_scope(scope)}.\{if section == "" { "
" } else { section }}" }