///|
fn file_offset_kind_text(kind : FileOffsetKind) -> String {
  match kind {
    ModuleHeader => "module_header"
    SectionHeader => "section_header"
    SectionPayload => "section_payload"
    CodeCount => "code_count"
    FunctionSizePrefix => "function_size_prefix"
    FunctionBodyBytes => "function_body"
    DataEntryCount => "data_count"
    DataEntryMetadata => "data_entry_metadata"
    DataPayload => "data_payload"
  }
}

///|
pub fn render_file_offset(report : FileOffsetReport) -> String {
  let out = StringBuilder()
  out.write_string(
    "WASM FILE OFFSET LOCATION\n\nQuery file offset: \{report.offset} bytes\nInput: \{report.file_size} B\nInput SHA-256: \{report.sha256}\n",
  )
  match report.section {
    None => out.write_string("Physical section: n/a (module header)\n")
    Some(section) => {
      out.write_string(
        "Physical section index: " +
        report.section_index.unwrap().to_string() +
        "; kind: " +
        section.kind.name() +
        "\n",
      )
      out.write_string(
        "Section file range: offset \{section.offset}, size \{section.total_size}; header \{section.total_size - section.payload_size}, payload \{section.payload_size} bytes\n",
      )
      if section.custom_name is Some(name) {
        out.write_string("Custom name: " + search_terminal_string(name) + "\n")
      }
    }
  }
  let region = report.region
  out.write_string(
    "Region: " +
    file_offset_kind_text(region.kind) +
    "\nRegion file range: offset \{region.file_offset}, size \{region.size} bytes (half-open)\nRelative offset: \{region.relative_offset} bytes\n",
  )
  if report.function is Some(stat) {
    out.write_string(
      "Function index: \{stat.index}; name: " +
      search_terminal_string(stat.name) +
      "\nRaw name: " +
      (match stat.raw_name {
        Some(name) => search_terminal_string(name)
        None => "n/a"
      }) +
      "\n",
    )
    out.write_string(
      "Function encoding: offset \{stat.offset}, size \{stat.total_size}; size prefix \{stat.total_size - stat.body_size}, body \{stat.body_size} bytes\n",
    )
  }
  if report.data_segment is Some(entry) {
    out.write_string(
      "Data segment index: \{entry.index}; mode: " +
      data_mode_text(entry.mode) +
      "; memory index: " +
      (match entry.memory_index {
        Some(index) => index.to_string()
        None => "n/a"
      }) +
      "\n",
    )
    out.write_string(
      "Entry file range: offset \{entry.encoding.offset}, size \{entry.encoding.size}; payload file range: offset \{entry.payload.offset}, size \{entry.payload.size} bytes\n",
    )
  }
  out.write_string("\n" + report.scope + "\n")
  out.to_string()
}

///|
fn file_offset_function_json(stat : FunctionStat) -> Json {
  let row : Map[String, Json] = Map([])
  row.set("index", int_json(stat.index))
  row.set("name", Json::string(stat.name))
  row.set("raw_name", path_optional_string(stat.raw_name))
  row.set("module_name", Json::string(stat.module_name))
  let prefix = stat.total_size - stat.body_size
  row.set(
    "encoding",
    data_range_json({ offset: stat.offset, size: stat.total_size, }),
  )
  row.set(
    "size_prefix",
    data_range_json({ offset: stat.offset, size: prefix, }),
  )
  row.set(
    "body",
    data_range_json({ offset: stat.offset + prefix, size: stat.body_size, }),
  )
  Json::object(row)
}

///|
fn file_offset_segment_json(section : Section) -> Json {
  let row : Map[String, Json] = Map([])
  row.set("id", int_json(section.id))
  row.set("kind", Json::string(section.kind.name()))
  row.set("custom_name", path_optional_string(section.custom_name))
  let header_size = section.total_size - section.payload_size
  row.set(
    "encoding",
    data_range_json({ offset: section.offset, size: section.total_size, }),
  )
  row.set(
    "header",
    data_range_json({ offset: section.offset, size: header_size, }),
  )
  row.set(
    "payload",
    data_range_json({
      offset: section.offset + header_size,
      size: section.payload_size,
    }),
  )
  Json::object(row)
}

///|
fn file_offset_data_segment_json(entry : DataSegment) -> Json {
  let row : Map[String, Json] = Map([])
  row.set("index", int_json(entry.index))
  row.set("mode", Json::string(data_mode_text(entry.mode)))
  row.set("memory_index", optional_index_json(entry.memory_index))
  row.set("encoding", data_range_json(entry.encoding))
  row.set("payload", data_range_json(entry.payload))
  row.set(
    "offset_expression",
    match entry.offset_expression {
      Some(range) => data_range_json(range)
      None => Json::null()
    },
  )
  row.set(
    "offset_status",
    Json::string(
      if entry.mode == Active {
        "not_evaluated"
      } else {
        "not_applicable"
      },
    ),
  )
  row.set("mode_size", int_json(entry.mode_size))
  row.set("memory_index_size", int_json(entry.memory_index_size))
  row.set("payload_length_size", int_json(entry.payload_length_size))
  Json::object(row)
}

///|
/// New independent format, rendering only the already located ownership.
fn file_offset_json_value(report : FileOffsetReport) -> Json {
  let root : Map[String, Json] = Map([])
  root.set("format", Json::string("moonsize-file-offset"))
  root.set("schema_version", int_json(1))
  root.set("file_size", int_json(report.file_size))
  root.set("sha256", Json::string(report.sha256))
  root.set("offset", int_json(report.offset))
  root.set("section_index", optional_index_json(report.section_index))
  root.set(
    "section",
    match report.section {
      Some(section) => file_offset_segment_json(section)
      None => Json::null()
    },
  )
  root.set(
    "function",
    match report.function {
      Some(stat) => file_offset_function_json(stat)
      None => Json::null()
    },
  )
  root.set(
    "data_segment",
    match report.data_segment {
      Some(entry) => file_offset_data_segment_json(entry)
      None => Json::null()
    },
  )
  let region : Map[String, Json] = Map([])
  region.set("kind", Json::string(file_offset_kind_text(report.region.kind)))
  region.set("file_offset", int_json(report.region.file_offset))
  region.set("size", int_json(report.region.size))
  region.set("relative_offset", int_json(report.region.relative_offset))
  root.set("region", Json::object(region))
  root.set("scope", Json::string(report.scope))
  Json::object(root)
}

///|
/// Preserve the original standalone format; refinement nests the same object.
pub fn render_file_offset_json(report : FileOffsetReport) -> String {
  file_offset_json_value(report).stringify(indent=2) + "\n"
}