///|
/// A stronger shard card that can carry multiple parity rows.
///
/// `index < data_count` is a data card. `index >= data_count` is a parity
/// card, and `index - data_count` is the parity row number.
pub(all) struct ResilientShardCard {
  deck : String
  index : Int
  data_count : Int
  parity_count : Int
  width : Int
  original_len : Int
  checksum : Int
  cells : Array[Int]
} derive(Eq, @debug.Debug)

///|
/// Recovery result for the multi-parity card format.
pub(all) enum ResilientRecovery {
  ResilientRestored(Array[Int])
  ResilientNeedCards(Array[Int])
  ResilientDamaged(String)
} derive(Eq, @debug.Debug)

///|
/// Parse result for one rendered `RSN1|...` card line.
pub(all) enum CardParse {
  ParsedCard(ResilientShardCard)
  InvalidCard(String)
} derive(Eq, @debug.Debug)

///|
/// Parse result for a sheet that may contain comments, blank lines, and cards.
pub(all) struct SheetParseReport {
  cards : Array[ResilientShardCard]
  ignored_lines : Int
  errors : Array[String]
} derive(Eq, @debug.Debug)

///|
/// A deterministic plan used by the planner and documentation examples.
pub(all) struct ResilientPlan {
  deck : String
  payload_len : Int
  data_count : Int
  parity_count : Int
  width : Int
  total_cards : Int
  recoverable_missing_data_cards : Int
  estimated_symbols : Int
} derive(Eq, @debug.Debug)

///|
/// Convert an ASCII string into byte cells. Non-ASCII characters are replaced
/// with the byte value for `?`, so the helper stays deterministic everywhere.
pub fn ascii_payload(text : String) -> Array[Int] {
  let out : Array[Int] = []
  for ch in text.iter() {
    let code = ch.to_int()
    if code >= 0 && code <= 127 {
      out.push(code)
    } else {
      out.push(63)
    }
  }
  out
}

///|
/// Convert byte cells back to a printable ASCII string.
pub fn payload_to_ascii(payload : Array[Int]) -> String {
  let chars : Array[Char] = []
  for value in payload {
    let byte = normalize_byte(value)
    let printable = if byte >= 32 && byte <= 126 { byte } else { 46 }
    match printable.to_char() {
      Some(ch) => chars.push(ch)
      None => chars.push('?')
    }
  }
  String::from_array(chars)
}

///|
/// Build a practical plan from a payload length and target card width.
pub fn plan_resilient_deck(
  deck : String,
  payload_len : Int,
  target_width : Int,
  max_data_cards : Int,
  parity_count : Int,
) -> ResilientPlan {
  let clean_payload_len = max_int(0, payload_len)
  let clean_target_width = clamp(target_width, 1, 96)
  let clean_max_data_cards = clamp(max_data_cards, 2, 32)
  let clean_parity_count = clamp(parity_count, 1, 8)
  let needed_data_cards = max_int(
    2,
    ceil_div(clean_payload_len, clean_target_width),
  )
  let data_count = min_int(clean_max_data_cards, needed_data_cards)
  let width = max_int(1, ceil_div(clean_payload_len, data_count))
  {
    deck,
    payload_len: clean_payload_len,
    data_count,
    parity_count: clean_parity_count,
    width,
    total_cards: data_count + clean_parity_count,
    recoverable_missing_data_cards: clean_parity_count,
    estimated_symbols: (data_count + clean_parity_count) * width,
  }
}

///|
/// Build data cards plus `parity_count` parity cards.
///
/// Parity rows use a small systematic Reed-Solomon-style construction over
/// GF(256). The first parity row is compatible with XOR parity, while later
/// rows provide independent equations for multi-card recovery.
pub fn weave_resilient(
  deck : String,
  payload : Array[Int],
  data_count : Int,
  parity_count : Int,
) -> Array[ResilientShardCard] {
  let clean_data_count = clamp(data_count, 2, 32)
  let clean_parity_count = clamp(parity_count, 1, 8)
  let width = max_int(1, ceil_div(payload.length(), clean_data_count))
  let rows : Array[Array[Int]] = []
  for i in 0.. ResilientRecovery {
  guard cards.length() > 0 else { return ResilientNeedCards([0]) }
  let profile = cards[0]
  let total = profile.data_count + profile.parity_count
  guard profile.data_count >= 2 &&
    profile.data_count <= 32 &&
    profile.parity_count >= 1 &&
    profile.parity_count <= 8 &&
    profile.width > 0 &&
    profile.original_len >= 0 else {
    return ResilientDamaged("profile values are outside supported bounds")
  }
  let buckets : Array[ResilientShardCard?] = []
  for _ in 0..= 0 && current.index < total else {
      return ResilientDamaged("card index is outside the resilient deck")
    }
    guard current.cells.length() == current.width else {
      return ResilientDamaged(
        "card width mismatch at card " + current.index.to_string(),
      )
    }
    guard validate_resilient_card(current) else {
      return ResilientDamaged(
        "checksum mismatch at card " + current.index.to_string(),
      )
    }
    match buckets[current.index] {
      Some(previous) =>
        if !cells_equal(previous.cells, current.cells) {
          return ResilientDamaged(
            "conflicting duplicate card " + current.index.to_string(),
          )
        }
      None => buckets[current.index] = Some(current)
    }
  }
  let missing_data : Array[Int] = []
  for i in 0.. parity_indices.length() {
    return ResilientNeedCards(
      recovery_needed_indices(
        missing_data,
        profile.data_count,
        profile.parity_count,
        parity_indices.length(),
      ),
    )
  }
  match
    recover_missing_rows(
      buckets,
      missing_data,
      parity_indices,
      profile.data_count,
      profile.width,
    ) {
    Some(recovered_rows) => {
      for i in 0.. ResilientDamaged("available parity rows are not independent")
  }
}

///|
/// Check the checksum carried by one resilient card.
pub fn validate_resilient_card(card : ResilientShardCard) -> Bool {
  card.checksum == resilient_checksum(card)
}

///|
/// Return one compact, copy-friendly line for a resilient card.
pub fn render_resilient_card(card : ResilientShardCard) -> String {
  "RSN1|" +
  card.deck +
  "|" +
  resilient_role_name(card) +
  "|" +
  "i=" +
  card.index.to_string() +
  "|" +
  "k=" +
  card.data_count.to_string() +
  "|" +
  "p=" +
  card.parity_count.to_string() +
  "|" +
  "w=" +
  card.width.to_string() +
  "|" +
  "n=" +
  card.original_len.to_string() +
  "|" +
  "c=" +
  card.checksum.to_string() +
  "|" +
  "x=" +
  hex_cells(card.cells)
}

///|
/// Render a printable resilient note sheet.
pub fn render_resilient_sheet(cards : Array[ResilientShardCard]) -> String {
  let lines : Array[String] = ["# resilient-shard-note"]
  for current in cards {
    lines.push(render_resilient_card(current))
  }
  lines.join("\n")
}

///|
/// Parse one `RSN1|...` line emitted by `render_resilient_card`.
pub fn parse_resilient_card(line : String) -> CardParse {
  let clean = line.trim().to_owned()
  let parts = split_owned(clean, "|")
  guard parts.length() == 10 else {
    return InvalidCard("expected 10 pipe-separated fields")
  }
  guard parts[0] == "RSN1" else { return InvalidCard("expected RSN1 prefix") }
  let deck = parts[1]
  guard parts[2] == "data" || parts[2] == "parity" else {
    return InvalidCard("expected data or parity role")
  }
  let index = parse_prefixed_decimal(parts[3], "i=")
  let data_count = parse_prefixed_decimal(parts[4], "k=")
  let parity_count = parse_prefixed_decimal(parts[5], "p=")
  let width = parse_prefixed_decimal(parts[6], "w=")
  let original_len = parse_prefixed_decimal(parts[7], "n=")
  let checksum = parse_prefixed_decimal(parts[8], "c=")
  let cells = parse_prefixed_hex_cells(parts[9], "x=")
  match
    (index, data_count, parity_count, width, original_len, checksum, cells) {
    (Some(i), Some(k), Some(p), Some(w), Some(n), Some(c), Some(x)) => {
      let card : ResilientShardCard = {
        deck,
        index: i,
        data_count: k,
        parity_count: p,
        width: w,
        original_len: n,
        checksum: c,
        cells: x,
      }
      guard card.cells.length() == card.width else {
        return InvalidCard("hex payload does not match declared width")
      }
      guard validate_resilient_card(card) else {
        return InvalidCard("checksum mismatch")
      }
      ParsedCard(card)
    }
    _ => InvalidCard("one or more numeric fields are malformed")
  }
}

///|
/// Parse a multi-line sheet. Blank lines and Markdown heading lines are ignored.
pub fn parse_resilient_sheet(text : String) -> SheetParseReport {
  let cards : Array[ResilientShardCard] = []
  let errors : Array[String] = []
  let mut ignored_lines = 0
  let mut line_number = 1
  for view in text.split("\n") {
    let line = view.trim().to_owned()
    if line == "" || line.has_prefix("#") {
      ignored_lines = ignored_lines + 1
    } else if line.has_prefix("RSN1|") {
      match parse_resilient_card(line) {
        ParsedCard(card) => cards.push(card)
        InvalidCard(message) =>
          errors.push("line " + line_number.to_string() + ": " + message)
      }
    } else {
      ignored_lines = ignored_lines + 1
    }
    line_number = line_number + 1
  }
  { cards, ignored_lines, errors }
}

///|
/// Parse a sheet and immediately try to recover it.
pub fn recover_resilient_sheet(text : String) -> ResilientRecovery {
  let parsed = parse_resilient_sheet(text)
  if parsed.errors.length() > 0 {
    return ResilientDamaged(parsed.errors.join("; "))
  }
  recover_resilient(parsed.cards)
}

///|
/// Explain how many data cards can be missing for the current evidence.
pub fn resilient_capacity_summary(cards : Array[ResilientShardCard]) -> String {
  guard cards.length() > 0 else { return "empty deck: no cards to inspect" }
  let profile = cards[0]
  let missing_data = missing_resilient_data_indices(cards)
  let parity_seen = count_present_resilient_parity(cards)
  "deck=" +
  profile.deck +
  " data=" +
  profile.data_count.to_string() +
  " parity=" +
  profile.parity_count.to_string() +
  " present-parity=" +
  parity_seen.to_string() +
  " missing-data=" +
  render_ints_inline(missing_data) +
  " recoverable-now=" +
  (missing_data.length() <= parity_seen).to_string()
}

///|
/// Return missing data indices after validating only visible indices.
pub fn missing_resilient_data_indices(
  cards : Array[ResilientShardCard],
) -> Array[Int] {
  guard cards.length() > 0 else { return [0] }
  let profile = cards[0]
  let seen : Array[Bool] = []
  for _ in 0..= 0 && current.index < profile.data_count {
      seen[current.index] = true
    }
  }
  let missing : Array[Int] = []
  for i in 0.. Int {
  guard cards.length() > 0 else { return 0 }
  let profile = cards[0]
  let seen : Array[Bool] = []
  for _ in 0..= profile.data_count &&
      current.index < profile.data_count + profile.parity_count {
      seen[current.index - profile.data_count] = true
    }
  }
  let mut count = 0
  for value in seen {
    if value {
      count = count + 1
    }
  }
  count
}

///|
/// GF(256) addition. In characteristic two this is XOR.
pub fn gf256_add(left : Int, right : Int) -> Int {
  xor_byte(left, right)
}

///|
/// GF(256) subtraction. It is identical to addition in characteristic two.
pub fn gf256_sub(left : Int, right : Int) -> Int {
  xor_byte(left, right)
}

///|
/// GF(256) multiplication using polynomial 0x11D.
pub fn gf256_mul(left : Int, right : Int) -> Int {
  let mut a = normalize_byte(left)
  let mut b = normalize_byte(right)
  let mut out = 0
  for _ in 0..<8 {
    if b % 2 == 1 {
      out = xor_byte(out, a)
    }
    let carry = a >= 128
    a = a * 2
    if a >= 256 {
      a = a - 256
    }
    if carry {
      a = xor_byte(a, 29)
    }
    b = b / 2
  }
  normalize_byte(out)
}

///|
/// GF(256) exponentiation.
pub fn gf256_pow(base : Int, exponent : Int) -> Int {
  if exponent <= 0 {
    return 1
  }
  let mut out = 1
  let clean_base = normalize_byte(base)
  for _ in 0.. Int? {
  let clean = normalize_byte(value)
  if clean == 0 {
    None
  } else {
    Some(gf256_pow(clean, 254))
  }
}

///|
/// GF(256) division.
pub fn gf256_div(left : Int, right : Int) -> Int? {
  match gf256_inv(right) {
    Some(inv) => Some(gf256_mul(left, inv))
    None => None
  }
}

///|
fn resilient_card(
  deck : String,
  index : Int,
  data_count : Int,
  parity_count : Int,
  width : Int,
  original_len : Int,
  cells : Array[Int],
) -> ResilientShardCard {
  let normalized = normalize_cells(cells)
  let check = resilient_checksum_values(
    deck, index, data_count, parity_count, width, original_len, normalized,
  )
  {
    deck,
    index,
    data_count,
    parity_count,
    width,
    original_len,
    checksum: check,
    cells: normalized,
  }
}

///|
fn same_resilient_profile(
  left : ResilientShardCard,
  right : ResilientShardCard,
) -> Bool {
  left.deck == right.deck &&
  left.data_count == right.data_count &&
  left.parity_count == right.parity_count &&
  left.width == right.width &&
  left.original_len == right.original_len
}

///|
fn resilient_checksum(card : ResilientShardCard) -> Int {
  resilient_checksum_values(
    card.deck,
    card.index,
    card.data_count,
    card.parity_count,
    card.width,
    card.original_len,
    card.cells,
  )
}

///|
fn resilient_checksum_values(
  deck : String,
  index : Int,
  data_count : Int,
  parity_count : Int,
  width : Int,
  original_len : Int,
  cells : Array[Int],
) -> Int {
  let mut value = 4147
  for ch in deck.iter() {
    value = checksum_step(value, ch.to_int())
  }
  value = checksum_step(value, index)
  value = checksum_step(value, data_count)
  value = checksum_step(value, parity_count)
  value = checksum_step(value, width)
  value = checksum_step(value, original_len)
  for cell in cells {
    value = checksum_step(value, cell)
  }
  value % 65521
}

///|
fn resilient_role_name(card : ResilientShardCard) -> String {
  if card.index < card.data_count {
    "data"
  } else {
    "parity"
  }
}

///|
fn parity_row(
  rows : Array[Array[Int]],
  data_count : Int,
  width : Int,
  parity_index : Int,
) -> Array[Int] {
  let out : Array[Int] = []
  for offset in 0.. Int {
  gf256_pow(data_index + 1, parity_index)
}

///|
fn available_parity_indices(
  buckets : Array[ResilientShardCard?],
  data_count : Int,
  parity_count : Int,
) -> Array[Int] {
  let out : Array[Int] = []
  for parity in 0.. Array[Int] {
  let out : Array[Int] = []
  for item in missing_data {
    out.push(item)
  }
  let mut need = missing_data.length() - present_parity_count
  let mut parity = 0
  while need > 0 && parity < parity_count {
    let candidate = data_count + parity
    if !out.contains(candidate) {
      out.push(candidate)
      need = need - 1
    }
    parity = parity + 1
  }
  out
}

///|
fn recover_missing_rows(
  buckets : Array[ResilientShardCard?],
  missing_data : Array[Int],
  parity_indices : Array[Int],
  data_count : Int,
  width : Int,
) -> Array[Array[Int]]? {
  let missing_count = missing_data.length()
  guard missing_count > 0 else { return Some([]) }
  let chosen = first_n(parity_indices, missing_count)
  let matrix = coefficient_matrix(missing_data, chosen, data_count)
  let recovered_rows : Array[Array[Int]] = []
  for _ in 0..
        for row in 0.. return None
    }
  }
  Some(recovered_rows)
}

///|
fn coefficient_matrix(
  missing_data : Array[Int],
  parity_indices : Array[Int],
  data_count : Int,
) -> Array[Array[Int]] {
  let matrix : Array[Array[Int]] = []
  for parity_card_index in parity_indices {
    let parity_index = parity_card_index - data_count
    let row : Array[Int] = []
    for data_index in missing_data {
      row.push(parity_coefficient(data_index, parity_index))
    }
    matrix.push(row)
  }
  matrix
}

///|
fn recovery_rhs_for_offset(
  buckets : Array[ResilientShardCard?],
  parity_indices : Array[Int],
  missing_data : Array[Int],
  data_count : Int,
  offset : Int,
) -> Array[Int] {
  let rhs : Array[Int] = []
  for parity_card_index in parity_indices {
    let parity_index = parity_card_index - data_count
    let mut value = buckets[parity_card_index].unwrap().cells[offset]
    for data_index in 0.. Array[Int]? {
  let n = rhs.length()
  guard matrix.length() == n else { return None }
  let aug : Array[Array[Int]] = []
  for row_index in 0..= 0 else { return None }
    if pivot != col {
      let temp = aug[pivot]
      aug[pivot] = aug[col]
      aug[col] = temp
    }
    match gf256_inv(aug[col][col]) {
      Some(inv) =>
        for j in col..<(n + 1) {
          aug[col][j] = gf256_mul(aug[col][j], inv)
        }
      None => return None
    }
    for row in 0.. Int {
  let mut row = column
  while row < size {
    if matrix[row][column] != 0 {
      return row
    }
    row = row + 1
  }
  -1
}

///|
fn join_resilient_data(
  buckets : Array[ResilientShardCard?],
  data_count : Int,
  width : Int,
) -> Array[Int] {
  let out : Array[Int] = []
  for i in 0.. Array[Int] {
  let out : Array[Int] = []
  let take = min_int(values.length(), count)
  for i in 0.. Bool {
  if left.length() != right.length() {
    return false
  }
  for i in 0.. Array[String] {
  let out : Array[String] = []
  for part in text.split(sep) {
    out.push(part.to_owned())
  }
  out
}

///|
fn parse_prefixed_decimal(field : String, prefix : String) -> Int? {
  match field.strip_prefix(prefix) {
    Some(rest) => parse_decimal(rest.to_owned())
    None => None
  }
}

///|
fn parse_prefixed_hex_cells(field : String, prefix : String) -> Array[Int]? {
  match field.strip_prefix(prefix) {
    Some(rest) => parse_hex_cells(rest.to_owned())
    None => None
  }
}

///|
fn parse_decimal(text : String) -> Int? {
  guard text.length() > 0 else { return None }
  let mut value = 0
  for ch in text.iter() {
    let code = ch.to_int()
    if code < 48 || code > 57 {
      return None
    }
    value = value * 10 + code - 48
  }
  Some(value)
}

///|
fn parse_hex_cells(hex : String) -> Array[Int]? {
  guard hex.length() % 2 == 0 else { return None }
  let out : Array[Int] = []
  let mut index = 0
  while index < hex.length() {
    let high = hex_digit_value(hex.get_char(index).unwrap())
    let low = hex_digit_value(hex.get_char(index + 1).unwrap())
    match (high, low) {
      (Some(h), Some(l)) => out.push(h * 16 + l)
      _ => return None
    }
    index = index + 2
  }
  Some(out)
}

///|
fn hex_digit_value(ch : Char) -> Int? {
  let code = ch.to_int()
  if code >= 48 && code <= 57 {
    Some(code - 48)
  } else if code >= 65 && code <= 70 {
    Some(code - 55)
  } else if code >= 97 && code <= 102 {
    Some(code - 87)
  } else {
    None
  }
}

///|
fn render_ints_inline(values : Array[Int]) -> String {
  let parts : Array[String] = []
  for value in values {
    parts.push(value.to_string())
  }
  "[" + parts.join(",") + "]"
}