///|
fn data_preview_hex(bytes : BytesView) -> String {
  let out = StringBuilder()
  for byte in bytes {
    out.write_string(byte.to_hex())
  }
  out.to_string()
}

///|
pub fn render_data_preview(report : DataPreviewReport) -> String {
  let out = StringBuilder()
  let entry = report.segment
  let memory = match entry.memory_index {
    Some(index) => index.to_string()
    None => "n/a"
  }
  out.write_string("DATA PAYLOAD BYTE PREVIEW\n\n")
  out.write_string(
    "Input: \{report.file_size} B\nInput SHA-256: \{report.sha256}\n",
  )
  out.write_string(
    "Segment: \{entry.index} of \{report.segment_count} entries (zero-based); mode: " +
    data_mode_text(entry.mode) +
    "; memory index: " +
    memory +
    "\n",
  )
  out.write_string(
    "Payload file range: offset \{entry.payload.offset}, size \{entry.payload.size} bytes\n",
  )
  out.write_string(
    "Requested payload offset: \{report.payload_offset}; limit: \{report.requested_limit} bytes\n",
  )
  out.write_string(
    "Window file range: offset \{report.window.offset}, size \{report.window.size} bytes\nReturned: \{report.returned_size} bytes; remaining: \{report.remaining_bytes} bytes\n",
  )
  out.write_string(
    "Payload byte ledger: \{entry.payload.size} = offset \{report.payload_offset} + returned \{report.returned_size} + remaining \{report.remaining_bytes}\n",
  )
  out.write_string(
    "\nOffsets are decimal bytes. ASCII shows only 0x20..0x7e; other bytes are dots.\nPAYLOAD OFFSET  FILE OFFSET  HEX BYTES                                        ASCII\n",
  )
  if report.returned_size == 0 {
    out.write_string("Empty window (at payload end).\n")
  }
  let mut pos = 0
  while pos < report.bytes.length() {
    let count = (report.bytes.length() - pos).min(16)
    let hex = StringBuilder()
    let ascii = StringBuilder()
    for i in 0.. 0 {
        hex.write_string(" ")
      }
      hex.write_string(byte.to_hex())
      ascii.write_string(
        if byte >= 0x20 && byte <= 0x7e {
          byte.to_char().to_string()
        } else {
          "."
        },
      )
    }
    out.write_string(
      pad_left((report.payload_offset + pos).to_string(), 14) +
      "  " +
      pad_left((report.window.offset + pos).to_string(), 11) +
      "  " +
      pad_right(hex.to_string(), 47) +
      "  " +
      ascii.to_string() +
      "\n",
    )
    pos += count
  }
  out.write_string("\n" + report.scope + "\n")
  out.to_string()
}

///|
/// Contains the selected entry only, plus the entire returned bounded window.
fn data_preview_json_value(report : DataPreviewReport) -> Json {
  let root : Map[String, Json] = Map([])
  root.set("format", Json::string("moonsize-data-preview"))
  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("segment_count", int_json(report.segment_count))
  let entry = report.segment
  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))
  root.set("segment", Json::object(row))
  root.set("payload_offset", int_json(report.payload_offset))
  root.set("requested_limit", int_json(report.requested_limit))
  root.set("window", data_range_json(report.window))
  root.set("returned_size", int_json(report.returned_size))
  root.set("remaining_bytes", int_json(report.remaining_bytes))
  root.set("hex", Json::string(data_preview_hex(report.bytes[:])))
  root.set("scope", Json::string(report.scope))
  Json::object(root)
}

///|
pub fn render_data_preview_json(report : DataPreviewReport) -> String {
  data_preview_json_value(report).stringify(indent=2) + "\n"
}