///|
pub(all) enum ConflictPolicy {
  Reject
  PreferFirst
  PreferLast
}

///|
pub struct CellSource {
  run : Int
  interpolated : Bool
}

///|
pub struct Conflict {
  row : Int
  curve : String
  depth : Double
  kept_run : Int
  incoming_run : Int
  kept_value : Double
  incoming_value : Double
}

///|
pub struct Assembly {
  log : Log
  sources : Array[Array[CellSource?]]
  conflicts : Array[Conflict]
  run_names : Array[String]
}

///|
fn exact_depth(log : Log, depth : Double) -> Bool {
  let mut lo = 0
  let mut hi = log.rows.length()
  while lo < hi {
    let mid = lo + (hi - lo) / 2
    if log.rows[mid][0].unwrap() < depth {
      lo = mid + 1
    } else {
      hi = mid
    }
  }
  lo < log.rows.length() && log.rows[lo][0].unwrap() == depth
}

///|
/// Assemble named runs on an explicit common grid. The first run sets target units.
/// Case-insensitive curve identity, no alias guessing, source retained per non-null cell.
pub fn assemble(
  runs : Array[Log],
  names : Array[String],
  grid : Array[Double],
  max_gap : Double,
  policy : ConflictPolicy,
  tolerance? : Double = 0.0,
) -> Assembly raise WellError {
  if runs.length() == 0 || runs.length() > 64 || names.length() != runs.length() {
    fail("assembly needs 1..64 uniquely named runs")
  }
  if !finite(tolerance) || tolerance < 0.0 {
    fail("conflict tolerance must be finite and nonnegative")
  }
  for i in 0.. 512 ||
    grid.length().to_double() *
    curves.length().to_double() *
    runs.length().to_double() >
    20000000.0 {
    fail("assembly cell-work budget exceeded")
  }
  // Validate the grid and max_gap even for runs with no non-depth curves.
  ignore(resample(select(runs[0], []), grid, max_gap))
  let rows : Array[Array[Double?]] = []
  let sources : Array[Array[CellSource?]] = []
  for depth in grid {
    let row : Array[Double?] = [Some(depth)]
    let provenance : Array[CellSource?] = [None]
    for _ in 1.. row.copy())
    for c in 1.. {
              rows[i][output] = incoming
              sources[i][output] = Some(provenance)
            }
            Some(old) =>
              if (old - value).abs() > tolerance {
                let kept = sources[i][output].unwrap().run
                conflicts.push({
                  row: i,
                  curve: curves[output].name,
                  depth: grid[i],
                  kept_run: kept,
                  incoming_run: run,
                  kept_value: old,
                  incoming_value: value,
                })
                match policy {
                  Reject =>
                    fail(
                      "conflicting run samples at depth " +
                      grid[i].to_string() +
                      " curve " +
                      curves[output].name,
                    )
                  PreferFirst => ()
                  PreferLast => {
                    rows[i][output] = incoming
                    sources[i][output] = Some(provenance)
                  }
                }
              }
          }
        }
      }
    }
  }
  let log = rebuild(runs[0], curves, rows)
  Assembly::{ log, sources, conflicts, run_names: names.copy(), }
}