///|
fn record_accessors(
  project : Project,
  type_ : TypePlan,
) -> Array[Accessor] raise ConfigError {
  let name = match type_.kind {
    Record(name) => name
    _ => raise Invalid("generation: expected record")
  }
  let record = project.record_def(name)
  let prefix = "type_" + type_.index.to_string()
  let accessors : Array[Accessor] = [
    {
      name: prefix + "_new",
      params: record.fields,
      result: type_.kind,
      expression: "{" + record.fields.map(f => f.name).join(", ") + ",}",
    },
  ]
  for i, field in record.fields {
    accessors.push({
      name: prefix + "_field_" + i.to_string(),
      params: [{ name: "v", kind: type_.kind, }],
      result: field.kind,
      expression: "v." + field.name,
    })
  }
  accessors
}

///|
fn planned_record(
  project : Project,
  type_ : TypePlan,
) -> RecordDef raise ConfigError {
  match type_.kind {
    Record(name) => project.record_def(name)
    _ => raise Invalid("generation: expected record type")
  }
}

///|
fn record_reader(
  type_ : TypePlan,
  project : Project,
) -> String raise ConfigError {
  let record = planned_record(project, type_)
  let count = record.fields.length().to_string()
  let mut source = "  if (!Ordinary(env, value, false, path) || !ancestors.enter(value, path)) return false;\n  AncestorScope scope(ancestors);\n  budget.add(" +
    count +
    ", 0, depth + 1, path);\n  MOONOHOS_BUFFER_ALLOCATE();\n  out.children.resize(" +
    count +
    ");\n"
  for i, field in record.fields {
    source += "  { napi_value child; auto child_path = path + \"." +
      field.name +
      "\";\n    if (!Own(env, value, \"" +
      field.name +
      "\", child, child_path)) return false;\n    if (!Read_" +
      type_.dependencies[i].to_string() +
      "(env, child, out.children[" +
      i.to_string() +
      "], budget, ancestors, depth + 1, child_path)) return false;\n  }\n"
  }
  source
}

///|
fn record_maker(type_ : TypePlan, types : Array[TypePlan]) -> String {
  let mut source = ""
  let args : Array[String] = []
  for i, child_index in type_.dependencies {
    let child = types[child_index]
    let variable = "child" + i.to_string()
    let expression = "Make_" +
      child_index.to_string() +
      "(value.children[" +
      i.to_string() +
      "])"
    if child.kind.is_reference() {
      source += "  MoonOwner<" +
        child.kind.c_name() +
        "> " +
        variable +
        "{" +
        expression +
        "};\n"
      args.push(variable + ".ptr")
    } else {
      args.push(expression)
    }
  }
  source + "  return " + type_.symbol + "_new(" + args.join(", ") + ");\n"
}

///|
fn record_copier(
  type_ : TypePlan,
  project : Project,
  types : Array[TypePlan],
) -> String raise ConfigError {
  let record = planned_record(project, type_)
  let count = record.fields.length().to_string()
  let mut source = "  budget.add(" +
    count +
    ", 0, depth + 1, path);\n  MOONOHOS_BUFFER_ALLOCATE();\n  out.children.reserve(" +
    count +
    ");\n"
  for i, field in record.fields {
    let child = types[type_.dependencies[i]]
    let expression = type_.symbol + "_field_" + i.to_string() + "(value)"
    if child.kind.is_reference() {
      source += "  { MoonOwner<" +
        child.kind.c_name() +
        "> child{" +
        expression +
        "};\n    out.children.push_back(Copy_" +
        child.index.to_string() +
        "(child.ptr, budget, depth + 1, path + \"." +
        field.name +
        "\"));\n  }\n"
    } else {
      source += "  out.children.push_back(Copy_" +
        child.index.to_string() +
        "(" +
        expression +
        ", budget, depth + 1, path + \"." +
        field.name +
        "\"));\n"
    }
  }
  source
}

///|
fn record_creator(
  type_ : TypePlan,
  project : Project,
) -> String raise ConfigError {
  let mut source = "  if (!Check(env, napi_create_object(env, &result))) return false;\n"
  for i, field in planned_record(project, type_).fields {
    source += "  { napi_value child;\n    if (!Create_" +
      type_.dependencies[i].to_string() +
      "(env, value.children[" +
      i.to_string() +
      "], child)) return false;\n    if (!Check(env, napi_set_named_property(env, result, \"" +
      field.name +
      "\", child))) return false;\n  }\n"
  }
  source + "  return true;\n"
}

///|
fn record_declarations(project : Project) -> String raise ConfigError {
  let mut source = ""
  for record in project.record_order() {
    source += "export interface " + record.name + " {\n"
    for field in record.fields {
      source += "  " + field.name + ": " + field.kind.ark_name() + ";\n"
    }
    source += "}\n"
  }
  source
}