///|
pub(all) enum Direction {
  Benefit
  Cost
} derive(@debug.Debug, Eq)

///|
/// Validated immutable matrix; all array accessors return defensive copies.
pub struct DecisionMatrix {
  names : Array[String]
  criteria : Array[String]
  directions : Array[Direction]
  cells : Array[Array[Double]]
}

///|
fn valid_labels(
  labels : Array[String],
  max_count : Int32,
) -> Unit raise ChoiceError {
  if labels.length() == 0 || labels.length() > max_count {
    raise Budget("label count outside supported shape")
  }
  for i, name in labels {
    if name.trim().length() == 0 || name.length() > 128 {
      raise Invalid("labels must contain 1..128 code units and nonblank text")
    }
    for j in 0.. DecisionMatrix raise ChoiceError {
  valid_labels(alternatives, 256)
  valid_labels(criteria, 32)
  if directions.length() != criteria.length() {
    raise Invalid("criterion direction count mismatch")
  }
  if values.length() != alternatives.length() {
    raise Invalid("alternative row count mismatch")
  }
  for row in values {
    if row.length() != criteria.length() {
      raise Invalid("ragged decision matrix")
    }
    for value in row {
      input_number(value)
    }
  }
  {
    names: alternatives.copy(),
    criteria: criteria.copy(),
    directions: directions.copy(),
    cells: values.map(row => row.copy()),
  }
}

///|
pub fn DecisionMatrix::alternatives(self : DecisionMatrix) -> Array[String] {
  self.names.copy()
}

///|
pub fn DecisionMatrix::criterion_names(self : DecisionMatrix) -> Array[String] {
  self.criteria.copy()
}

///|
pub fn DecisionMatrix::criterion_directions(
  self : DecisionMatrix,
) -> Array[Direction] {
  self.directions.copy()
}

///|
pub fn DecisionMatrix::values(self : DecisionMatrix) -> Array[Array[Double]] {
  self.cells.map(row => row.copy())
}

///|
pub fn DecisionMatrix::shape(self : DecisionMatrix) -> (Int32, Int32) {
  (self.names.length(), self.criteria.length())
}

///|
pub fn DecisionMatrix::cell(
  self : DecisionMatrix,
  row : Int32,
  column : Int32,
) -> Double raise ChoiceError {
  if row < 0 ||
    row >= self.names.length() ||
    column < 0 ||
    column >= self.criteria.length() {
    raise Invalid("matrix index out of bounds")
  }
  self.cells[row][column]
}

///|
pub fn DecisionMatrix::column(
  self : DecisionMatrix,
  column : Int32,
) -> Array[Double] raise ChoiceError {
  if column < 0 || column >= self.criteria.length() {
    raise Invalid("criterion index out of bounds")
  }
  self.cells.map(row => row[column])
}

///|
pub extend Direction with @debug.Debug::{to_repr}

///|
pub extend Direction with Eq::{equal, not_equal}