// One interpreter for addressing steps over the value accessors, so step
// semantics (especially SeqAccessStep's live key resolution) are written once
// instead of at every call site that walks a path.

///|
/// Resolve a field-referenced key against a node NOW: read key_field, ask the
/// scalar for its key form.
fn live_key(node : Value, key_field : String) -> PathKey? {
  node.field(key_field).as_key()
}

///|
pub fn step_get(node : Value, step : Step) -> Value? {
  match step {
    FieldStep(name) => node.field_opt(name)
    SeqStep(field~, key~) | EachRenderItStep(field~, key~) =>
      node.field(field).item_opt(key)
    SeqAccessStep(seq_field~, key_field~) =>
      match live_key(node, key_field) {
        Some(key) => node.field(seq_field).item_opt(key)
        None => None
      }
    BindStep(..) | ScopeBindStep(..) | EachBindStep(..) => Some(node) // frame-only: addresses nothing
  }
}

///|
/// Rebuild one seq level: new seq with `key` replaced, then a new node with
/// the seq field replaced (both COW per the accessors' identity contract).
fn put_seq(
  node : Value,
  field : String,
  key : PathKey,
  child : Value,
) -> Value? {
  match node.field_opt(field) {
    Some(seq) =>
      match seq.with_item_opt(key, child) {
        Some(new_seq) => node.with_field_opt(field, new_seq)
        None => None
      }
    None => None
  }
}

///|
fn step_put(node : Value, step : Step, child : Value) -> Value? {
  match step {
    FieldStep(name) => node.with_field_opt(name, child)
    SeqStep(field~, key~) | EachRenderItStep(field~, key~) =>
      put_seq(node, field, key, child)
    SeqAccessStep(seq_field~, key_field~) =>
      match live_key(node, key_field) {
        Some(key) => put_seq(node, seq_field, key, child)
        None => None
      }
    BindStep(..) | ScopeBindStep(..) | EachBindStep(..) => Some(child) // frame-only: the new value passes through
  }
}

///|
/// Freeze a field-resolved key against `node` as it is now (JS Step.pinKey):
/// SeqAccessStep becomes a literal-key SeqStep; every other step (and an
/// unresolvable key) pins to itself.
fn step_pin(step : Step, node : Value) -> Step {
  match step {
    SeqAccessStep(seq_field~, key_field~) =>
      match live_key(node, key_field) {
        Some(key) => SeqStep(field=seq_field, key~)
        None => step
      }
    _ => step
  }
}

///|
/// The compact/dispatch projection of one step (JS toAbstractPathStep):
/// EachRenderItStep abstracts to a plain SeqStep; None drops the step — no
/// current variant is dropped, the hook exists for future frame-only steps.
fn step_to_abstract(step : Step) -> Step? {
  match step {
    EachRenderItStep(field~, key~) => Some(SeqStep(field~, key~))
    BindStep(..) | ScopeBindStep(..) | EachBindStep(..) => None // frame-only steps are dropped from the dispatch path
    _ => Some(step)
  }
}

///|
/// Generic {field, key?} descriptor (JS Step.toKey). SeqAccessStep reports
/// its seq field with no key — the key is live and unknown without a value;
/// dropping the step instead would shift the indices of later keys.
fn step_to_key(step : Step) -> StepKey? {
  match step {
    FieldStep(name) => Some({ field: name, key: None })
    SeqStep(field~, key~) | EachRenderItStep(field~, key~) =>
      Some({ field, key: Some(key) })
    SeqAccessStep(seq_field~, key_field=_) =>
      Some({ field: seq_field, key: None })
    BindStep(..) | ScopeBindStep(..) | EachBindStep(..) => None // frame-only: no field to report
  }
}

///|
/// Key a step with the si/sk an `§Each§` meta carried (JS Step.withIndex /
/// withKey, used by event reconstruction's applyKey): a plain field access
/// under a keyed meta addresses the keyed item. Steps that carry their own
/// key (or no field) are returned unchanged.
pub fn step_with_key(step : Step, key : PathKey) -> Step {
  match step {
    FieldStep(f) => SeqStep(field=f, key~)
    _ => step
  }
}