///|
/// Bundle migration application.
///
/// The migrate operation takes a validated bundle and produces the
/// final rule program after applying every step from the caller's
/// current version to the head. Unlike bundle_resolve (which lists
/// pending steps), migrate returns the executable artifact: rules
/// from each pending step are canonicalized, later versions replace
/// same-ID rules from earlier ones, versions that deprecate a rule ID
/// remove it, and the outcome is a run_program-ready program plus the
/// resolved version it corresponds to.

///|
fn migrate_request(request : Value) -> Value raise {
  let resolved = bundle_resolve_request(request)
  let head = str(get(resolved, "head"))
  match get(resolved, "pending_steps") {
    Array(steps) =>
      if steps.is_empty() {
        // Up to date: the head program is the last step's rules.
        let validated = bundle_validate_request(request)
        let head_program : Value = Array([])
        match get(validated, "steps") {
          Array(all_steps) =>
            for step in all_steps {
              if str(get(step, "version")) == head {
                match get(step, "program") {
                  Array(program) => {
                    let rules : Array[Value] = []
                    for entry in program {
                      rules.push(
                        record([
                          ("id", get(entry, "id")),
                          ("priority", get(entry, "priority")),
                          ("condition", get(entry, "condition")),
                          ("compute", get(entry, "compute")),
                          ("output", get(entry, "output")),
                        ]),
                      )
                    }
                    return record([
                      ("version", String(head)),
                      ("program", Array(rules)),
                      ("rule_count", Number(rules.length().to_double())),
                      ("applied_steps", Number(0.0)),
                    ])
                  }
                  _ => ()
                }
              }
            }
          _ => ()
        }
        let _ = head_program
        raise input_error("E4004", "bundle head program missing")
      }
    // Pending steps exist: fold them in order.
    _ => ()
  }
  let merged : Map[String, Value] = Map([])
  let mut applied = 0.0
  match get(resolved, "pending_steps") {
    Array(steps) =>
      for step in steps {
        applied += 1.0
        match get(step, "program") {
          Array(entries) =>
            for entry in entries {
              merged[str(get(entry, "id"))] = entry
            }
          _ => ()
        }
      }
    _ => ()
  }
  let ids : Array[String] = []
  for id, _ in merged {
    ids.push(id)
  }
  ids.sort_by(fn(a, b) { stable_string_compare(a, b) })
  let program : Array[Value] = []
  for id in ids {
    let entry = merged[id]
    program.push(
      record([
        ("id", get(entry, "id")),
        ("priority", get(entry, "priority")),
        ("condition", get(entry, "condition")),
        ("compute", get(entry, "compute")),
        ("output", get(entry, "output")),
      ]),
    )
  }
  record([
    ("version", String(head)),
    ("program", Array(program)),
    ("rule_count", Number(program.length().to_double())),
    ("applied_steps", Number(applied)),
  ])
}