// Path operations (src/path.js Path) over Values, plus the leaf-update core of
// the transactor (updateRootValue).

///|
pub fn Path::new(steps? : Array[Step]) -> Path {
  match steps {
    Some(s) => { steps: s }
    None => { steps: [] }
  }
}

///|
pub fn Path::concat(self : Path, steps : Array[Step]) -> Path {
  let out = self.steps.copy()
  for s in steps {
    out.push(s)
  }
  { steps: out }
}

///|
/// `Path::new().field("rows").index("rows", 2)` — the absolute twin of the
/// relative `PathChanges` builder, which delegates to these so the two spell
/// a step the same way. Each returns a fresh Path, so a base is reusable.
pub fn Path::field(self : Path, name : String) -> Path {
  self.concat([FieldStep(name)])
}

///|
/// A positional item of a sequence FIELD. There is no bare-index step: a Path
/// addresses field-then-key, so a nested `.a[0][1]` is not expressible and the
/// value-level `item`/`index` are what read one.
pub fn Path::index(self : Path, name : String, i : Int) -> Path {
  self.concat([SeqStep(field=name, key=KInt(i))])
}

///|
/// A keyed item of a sequence field.
pub fn Path::key(self : Path, name : String, k : String) -> Path {
  self.concat([SeqStep(field=name, key=KStr(k))])
}

///|
pub fn Path::pop_step(self : Path) -> Path {
  let n = self.steps.length()
  if n == 0 {
    self
  } else {
    { steps: self.steps[0:n - 1].to_owned() }
  }
}

///|
pub fn Path::lookup(self : Path, root : Value) -> Value? {
  let mut cur = root
  for step in self.steps {
    match step_get(cur, step) {
      Some(next) => cur = next
      None => return None
    }
  }
  Some(cur)
}

///|
pub fn Path::set_value(self : Path, root : Value, v : Value) -> Value {
  let n = self.steps.length()
  let intermediates : Array[Value] = []
  let mut cur = root
  for step in self.steps {
    intermediates.push(cur)
    match step_get(cur, step) {
      Some(next) => cur = next
      None => return root
    }
  }
  let mut new_val = v
  for i = n - 1; i >= 0; i = i - 1 {
    match step_put(intermediates[i], self.steps[i], new_val) {
      Some(nv) => new_val = nv
      None => return root
    }
  }
  new_val
}

///|
pub fn Path::resolve_chain(self : Path, root : Value) -> Array[Value] {
  let out : Array[Value] = [root]
  let mut cur = root
  for step in self.steps {
    match step_get(cur, step) {
      Some(next) => {
        cur = next
        out.push(next)
      }
      None => break
    }
  }
  out
}

///|
pub fn Path::pin_keys(self : Path, root : Value) -> Path {
  let mut out : Array[Step]? = None
  let mut cur : Value? = Some(root)
  for i in 0.. s
        None => {
          let s = self.steps.copy()
          out = Some(s)
          s
        }
      }
      steps[i] = pinned
    }
    cur = step_get(node, step)
  }
  match out {
    Some(steps) => { steps, }
    None => self
  }
}

///|
pub fn Path::to_keys(self : Path) -> Array[StepKey] {
  let out : Array[StepKey] = []
  for step in self.steps {
    match step_to_key(step) {
      Some(k) => out.push(k)
      None => ()
    }
  }
  out
}

///|
/// A key as its addressing text: `2` or `title`.
pub fn PathKey::to_label(self : PathKey) -> String {
  match self {
    KInt(i) => i.to_string()
    KStr(s) => s
  }
}

///|
/// A key as a Value — what `@key` binds to inside a loop, and what a
/// seq-access read compares against.
pub fn PathKey::to_value(self : PathKey) -> Value {
  match self {
    KInt(i) => Num(i.to_double())
    KStr(s) => Str(s)
  }
}

///|
/// Addressing steps as readable text: `value.rows[1].title`.
///
/// Over `StepKey` rather than `Step`, because that is the projection a
/// transaction is RECORDED as (`ObserveRecord.path_keys`) — so a log line and
/// a `Path` print the same way without the log having to keep the Path.
pub fn step_keys_label(keys : Array[StepKey]) -> String {
  keys
  .map(k => {
    match k.key {
      Some(pk) => "\{k.field}[\{pk.to_label()}]"
      None => k.field
    }
  })
  .join(".")
}

///|
/// The addressing steps as readable text.
///
/// Over `to_keys`, so the frame-only steps (which address nothing) are absent
/// and a seq-access step shows as its field.
pub fn Path::to_label(self : Path) -> String {
  step_keys_label(self.to_keys())
}

///|
pub impl Show for Path with fn output(self, logger) {
  logger.write_string(self.to_label())
}

///|
pub fn RequestOpts::new(
  on_ok_name? : String,
  on_error_name? : String,
  on_res_name? : String,
  live_path? : Bool = false,
) -> RequestOpts {
  { on_ok_name, on_error_name, on_res_name, live_path }
}

///|
/// The handlers a value carries: component instances answer through the `Obj`
/// trait, pure data has none.
pub fn Value::handler(
  self : Value,
  bucket : HandlerBucket,
  name : String,
) -> Handler? {
  match self {
    Obj(o) => o.obj_handler(bucket, name)
    _ => None
  }
}

///|
pub fn Path::update(
  self : Path,
  root : Value,
  bucket : HandlerBucket,
  name : String,
  args : Array[Value],
) -> Value {
  guard self.lookup(root) is Some(leaf) else {
    // The two guards here are the whole reason the refusal channel exists: from
    // outside, they are the same `root` a handler that ran and changed nothing
    // hands back. They report OUTSIDE the dispatch scope opened below, because
    // each one already is where the chain ends — there is no handler under them
    // to have decided anything more specific.
    if refusing() {
      refuse({
        code: PathUnresolved,
        asked: name,
        rule: "",
        sentence: "",
        state: root,
        path: self,
      })
    }
    return root
  }
  // One lookup. There used to be a second, for the literal name `"$unknown"`,
  // as a catch-all a component could register — a JS-era sentinel that cost a
  // string lookup on every miss. A typed `update` is one pattern match whose
  // `_ => None` already IS the catch-all, and the generated `Msg` hands the
  // unmatched name to an `Unknown(name, args)` arm.
  guard leaf.handler(bucket, name) is Some(Handler(f)) else {
    if refusing() {
      refuse({
        code: NoHandler,
        asked: name,
        rule: "",
        sentence: "",
        state: leaf,
        path: self,
      })
    }
    return root
  }
  // From here down a handler exists and whatever happens is its answer, so this
  // is the dispatch a record belongs to: a contract inside the body knows the
  // rule it broke and not where it ran, and the scope is what supplies the rest.
  // A handler that dispatches again nests into this same scope — one dispatch,
  // one record, however many layers it walked.
  let opened = begin_dispatch(self)
  let out = match f(args, NullCtx::{  }) {
    Some(new_leaf) =>
      // JS `newLeaf !== curLeaf` parity: a handler that hands back the value
      // it was given skips the rebuild, whether it says so by answering None
      // or by handing the same object back. Neither is a refusal: an arm that
      // answers `None` DECLINED, and the mutator behind it is the design.
      if physical_equal(new_leaf, leaf) {
        root
      } else {
        self.set_value(root, new_leaf)
      }
    None => root
  }
  end_dispatch(opened)
  out
}