///|
/// ID generator for test planning.
pub(all) struct IdGenerator {
  priv mut counter : Int32
}

///|
pub fn IdGenerator::new() -> IdGenerator {
  { counter: 0, }
}

///|
pub fn IdGenerator::next(self : IdGenerator, prefix : String) -> String {
  self.counter += 1
  prefix + "-" + self.counter.to_string()
}

///|
pub fn IdGenerator::next_param_type_id(self : IdGenerator) -> String {
  self.next("pt")
}

///|
pub fn IdGenerator::next_hook_id(self : IdGenerator) -> String {
  self.next("hook")
}

///|
/// Build TestCase envelopes by matching pickle steps against a shared registry.
pub fn build_test_cases(
  registry : @core.StepRegistry,
  hook_registry : @core.HookRegistry,
  pickles : Array[@cucumber_messages.Pickle],
  id_gen : IdGenerator,
) -> Array[@cucumber_messages.Envelope] {
  let before_case_hooks = hook_registry.by_type(@core.HookType::BeforeTestCase)
  let after_case_hooks = hook_registry.by_type(@core.HookType::AfterTestCase)
  let envelopes : Array[@cucumber_messages.Envelope] = []
  for pickle in pickles {
    let test_steps : Array[Json] = []
    // Prepend before_test_case hook steps
    for hook in before_case_hooks {
      let step_id = id_gen.next("ts")
      test_steps.push({ "id": Json(step_id), "hookId": Json(hook.id) })
    }
    for step in pickle.steps {
      let keyword = match step.type_ {
        Some(@cucumber_messages.PickleStepType::Context) => "Given "
        Some(@cucumber_messages.PickleStepType::Action) => "When "
        Some(@cucumber_messages.PickleStepType::Outcome) => "Then "
        _ => "* "
      }
      let step_def_ids : Array[Json] = try {
        match registry.find_match(step.text, keyword~) {
          Matched(def, _) =>
            match def.id {
              Some(id) => [Json(id.to_string())]
              None => []
            }
          Undefined(..) => []
        }
      } catch {
        // Conversion failures are reported by the executor as failed steps.
        _ => []
      }
      let step_id = id_gen.next("ts")
      test_steps.push({
        "id": Json(step_id),
        "pickleStepId": Json(step.id),
        "stepDefinitionIds": Json(step_def_ids),
      })
    }
    // Append after_test_case hook steps
    for hook in after_case_hooks {
      let step_id = id_gen.next("ts")
      test_steps.push({ "id": Json(step_id), "hookId": Json(hook.id) })
    }
    let tc_id = id_gen.next("tc")
    let json : Json = {
      "testCase": {
        "id": Json(tc_id),
        "pickleId": Json(pickle.id),
        "testSteps": Json(test_steps),
      },
    }
    let envelope : @cucumber_messages.Envelope = @json.from_json(json) catch {
      _ => continue
    }
    envelopes.push(envelope)
  }
  envelopes
}

///|
/// Build StepDefinition envelopes from the shared registry.
pub fn build_step_definition_envelopes(
  registry : @core.StepRegistry,
) -> Array[@cucumber_messages.Envelope] {
  let envelopes : Array[@cucumber_messages.Envelope] = []
  for step_def in registry.step_defs() {
    let id = match step_def.id {
      Some(id) => id.to_string()
      None => continue
    }
    let source_ref : Map[String, Json] = Map([])
    match step_def.source {
      Some(src) => {
        match src.uri {
          Some(uri) => source_ref["uri"] = Json(uri)
          None => ()
        }
        match src.line {
          Some(line) => source_ref["location"] = { "line": Json(line) }
          None => ()
        }
      }
      None => ()
    }
    let json : Json = {
      "stepDefinition": {
        "id": Json(id),
        "pattern": {
          "source": Json(step_def.pattern),
          "type": Json("CUCUMBER_EXPRESSION"),
        },
        "sourceReference": Json(source_ref),
      },
    }
    let envelope : @cucumber_messages.Envelope = @json.from_json(json) catch {
      _ => continue
    }
    envelopes.push(envelope)
  }
  envelopes
}