///|
/// A validated rectangular table. Constructors and accessors copy their arrays.
pub struct Table {
  priv columns : Array[String]
  priv records : Array[Array[String]]
} derive(Eq, Debug)

///|
/// A compound row identity. Components are never joined with a user delimiter.
pub struct RowKey {
  priv parts : Array[String]
} derive(Eq, Debug)

///|
/// Conflict coordinates are independent of input row and column ordering.
pub(all) enum Target {
  Column(String)
  Row(RowKey)
  Cell(RowKey, String)
} derive(Eq, Debug)

///|
pub(all) enum ConflictKind {
  ColumnDeleteEdit
  RowDeleteEdit
  ConcurrentInsert
  CellEdit
  MissingCell
} derive(Eq, Debug)

///|
/// A source value, including its row identity and column name.
pub(all) struct CellValue {
  key : RowKey
  column : String
  value : String
} derive(Eq, Debug)

///|
/// Empty source arrays mean absence; an empty string is an actual stored value.
pub(all) struct Conflict {
  target : Target
  kind : ConflictKind
  base : Array[CellValue]
  ours : Array[CellValue]
  theirs : Array[CellValue]
} derive(Eq, Debug)

///|
/// Keep/Drop resolve deletion conflicts; Ours/Theirs select inserted rows.
/// Base/Ours/Theirs/Value resolve cells. Unsupported combinations are errors.
pub(all) enum Choice {
  Keep
  Drop
  Base
  Ours
  Theirs
  Value(String)
} derive(Eq, Debug)

///|
pub(all) struct Decision {
  target : Target
  choice : Choice
} derive(Eq, Debug)

///|
/// Evidence for an explicit resolution, including original source values.
pub(all) struct Resolution {
  conflict : Conflict
  choice : Choice
} derive(Eq, Debug)

///|
/// Owns immutable input snapshots and cumulative decisions.
pub struct MergePlan {
  priv base : Table
  priv ours : Table
  priv theirs : Table
  priv key_columns : Array[String]
  priv ordering : RowOrder
  priv lineage_data : Array[ColumnLineage]
  priv row_lineage_data : Array[RowLineage]
  priv evolution_data : Array[ColumnEvolution]
  priv decisions_data : Array[Decision]
  priv pending : Array[Conflict]
  priv evidence : Array[Resolution]
  priv output : Table?
}

///|
/// Available only after all conflicts have been resolved.
pub struct MergedTable {
  priv result : Table
  priv evidence : Array[Resolution]
}

///|
/// Diagnostics include locations and corrective actions, not partial output.
pub(all) suberror MergeError {
  InvalidTable(String)
  InvalidKey(String)
  InvalidCsv(record~ : Int, column~ : Int, reason~ : String)
  LimitExceeded(String)
  InvalidDecision(String)
  Unresolved(Int)
  InvalidReview(String)
  InvalidOrder(String)
  InvalidLineage(String)
} derive(Debug)

///|
pub fn RowKey::RowKey(parts : Array[String]) -> RowKey {
  { parts: parts.copy(), }
}

///|
pub fn RowKey::components(self : RowKey) -> Array[String] {
  self.parts.copy()
}

///|
pub fn MergeError::diagnostic(self : MergeError) -> String {
  match self {
    InvalidTable(reason) => "InvalidTable: \{reason}"
    InvalidKey(reason) => "InvalidKey: \{reason}"
    InvalidCsv(record~, column~, reason~) =>
      "InvalidCsv: record \{record}, field \{column}: \{reason}"
    LimitExceeded(reason) => "LimitExceeded: \{reason}; reduce the table size"
    InvalidDecision(reason) => "InvalidDecision: \{reason}"
    Unresolved(count) =>
      "Unresolved: \{count} conflict(s); inspect the report and supply explicit decisions"
    InvalidReview(reason) => "InvalidReview: \{reason}"
    InvalidOrder(reason) => "InvalidOrder: \{reason}"
    InvalidLineage(reason) => "InvalidLineage: \{reason}"
  }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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