///|
/// Greedy discrimination over already-computed exact-size fingerprints.
pub(all) struct FingerprintPanel {
  selected_columns : Array[Int]
  unresolved_pairs : Array[(Int, Int)]
  complete : Bool
} derive(Eq, Debug, ToJson)

///|
/// Rows are samples and columns are synthetic-model fingerprints.
/// Deterministic greedy set cover; not a minimum-panel or laboratory-success claim.
pub fn select_panel(
  fingerprints : Array[Array[String]],
  max_columns : Int,
) -> FingerprintPanel raise RestrictError {
  if fingerprints.length() < 2 || fingerprints.length() > 32 {
    raise InvalidInput("PANEL_SAMPLES")
  }
  let columns = fingerprints[0].length()
  if columns < 1 || columns > 32 || max_columns < 0 || max_columns > columns {
    raise InvalidInput("PANEL_COLUMNS")
  }
  let mut bytes = 0
  for row in fingerprints {
    if row.length() != columns {
      raise InvalidInput("PANEL_RAGGED")
    }
    for value in row {
      bytes = bytes + value.length()
      if bytes > 1000000 {
        raise LimitExceeded("PANEL_BYTES")
      }
    }
  }
  let mut remaining : Array[(Int, Int)] = []
  for i in 0.. best_gain {
        best = c
        best_gain = gain
      }
    }
    if best < 0 {
      break
    }
    used[best] = true
    selected.push(best)
    remaining = remaining.filter(pair => {
      fingerprints[pair.0][best] == fingerprints[pair.1][best]
    })
  }
  {
    selected_columns: selected,
    unresolved_pairs: remaining,
    complete: remaining.is_empty(),
  }
}