///|
fn data_mode_text(mode : DataSegmentMode) -> String {
  match mode {
    Active => "active"
    Passive => "passive"
  }
}

///|
fn data_scope() -> String {
  "File offsets are not memory addresses; file bytes are not runtime memory usage. " +
  "Active offset expressions are not_evaluated; passive offsets are not_applicable. " +
  "No source attribution or deletion savings are inferred. This is structural inspection, not full type, constant-expression, memory-index or instantiation validation."
}

///|
/// Payload descending, then original index. Sorting and Top affect display only.
pub fn render_data_segments(report : DataSegmentsReport, top : Int) -> String {
  let ordered = report.entries.copy()
  ordered.sort_by(fn(a, b) {
    if a.payload.size != b.payload.size {
      b.payload.size - a.payload.size
    } else {
      a.index - b.index
    }
  })
  let shown = if top < 0 {
    0
  } else if top < ordered.length() {
    top
  } else {
    ordered.length()
  }
  let mut out = "DATA SEGMENT ENCODINGS\n"
  out += "  data section: " +
    (if report.section is Some(_) { "present" } else { "absent" }) +
    "; total " +
    report.section_size.to_string() +
    " B\n"
  out += "  payload " +
    report.payload_size.to_string() +
    " B; entry metadata " +
    report.entry_metadata_size.to_string() +
    " B; section overhead " +
    (report.header_size + report.count_size).to_string() +
    " B\n"
  out += "  section header " +
    report.header_size.to_string() +
    " B; count field " +
    report.count_size.to_string() +
    " B; datacount " +
    (match report.datacount {
      Some(count) => count.to_string()
      None => "absent"
    }) +
    " (separate section)\n"
  out += "  Showing " +
    shown.to_string() +
    " of " +
    ordered.length().to_string() +
    " entries (payload descending, index ascending)\n\n"
  out += "  INDEX  MODE     MEMORY  FILE OFFSET  ENCODED B  PAYLOAD B\n"
  for i = 0; i < shown; i = i + 1 {
    let entry = ordered[i]
    out += "  " +
      pad_left(entry.index.to_string(), 5) +
      "  " +
      pad_right(data_mode_text(entry.mode), 7) +
      "  " +
      pad_left(
        match entry.memory_index {
          Some(index) => index.to_string()
          None => "n/a"
        },
        6,
      ) +
      "  " +
      pad_left(entry.encoding.offset.to_string(), 11) +
      "  " +
      pad_left(entry.encoding.size.to_string(), 9) +
      "  " +
      pad_left(entry.payload.size.to_string(), 9) +
      "\n"
    out += "         payload file offset " + entry.payload.offset.to_string()
    out += match entry.offset_expression {
      Some(range) =>
        "; expression file offset " +
        range.offset.to_string() +
        ", " +
        range.size.to_string() +
        " B (not_evaluated, includes end)\n"
      None => "; expression not_applicable\n"
    }
  }
  out + "\n  " + data_scope()
}

///|
fn data_range_json(range : DataByteRange) -> Json {
  let fields : Map[String, Json] = Map([])
  fields.set("file_offset", int_json(range.offset))
  fields.set("size", int_json(range.size))
  Json::object(fields)
}

///|
/// Independent schema. All entries in original order; no payload contents.
fn data_segments_json(report : DataSegmentsReport) -> Json {
  let root : Map[String, Json] = Map([])
  root.set("schema_version", int_json(1))
  root.set("format", Json::string("moonsize-data-segments"))
  root.set("scope", Json::string(data_scope()))
  root.set("file_size", int_json(report.file_size))
  let summary : Map[String, Json] = Map([])
  summary.set("present", Json::boolean(report.section is Some(_)))
  summary.set(
    "file_offset",
    optional_index_json(report.section.map(fn(section) { section.offset })),
  )
  summary.set("total_size", int_json(report.section_size))
  summary.set("header_size", int_json(report.header_size))
  summary.set("count_size", int_json(report.count_size))
  summary.set("entry_count", int_json(report.entries.length()))
  summary.set("payload_size", int_json(report.payload_size))
  summary.set("entry_metadata_size", int_json(report.entry_metadata_size))
  summary.set(
    "section_overhead_size",
    int_json(report.header_size + report.count_size),
  )
  root.set("summary", Json::object(summary))
  root.set("datacount", optional_index_json(report.datacount))
  root.set(
    "entries",
    Json::array(
      report.entries.map(fn(entry) {
        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)
      }),
    ),
  )
  Json::object(root)
}

///|
/// The original single-file format and signature are unchanged.
pub fn render_data_segments_json(report : DataSegmentsReport) -> String {
  data_segments_json(report).stringify(indent=2) + "\n"
}