///|
/// Source range in Unicode code points, zero-based and half-open.
pub(all) struct Span {
  start : Int32
  end : Int32
} derive(Debug)

///|
/// A stable error code and the relevant source range.
pub(all) struct TemplateError {
  code : String
  message : String
  span : Span
} derive(Debug)

///|
/// An ordered, unique-key associative value. Construct with `from_entries`.
struct OrderedEntries {
  entries : Array[(String, String)]
} derive(Debug)

///|
/// Values are strings; callers choose numeric and boolean formatting explicitly.
pub(all) enum Value {
  Scalar(String)
  List(Array[String])
  Assoc(OrderedEntries)
} derive(Debug)

///|
/// Mutable bindings. Collection values are copied when inserted.
struct Variables {
  values : Map[String, Value]
} derive(Debug)

///|
priv enum Modifier {
  NoModifier
  Prefix(Int32)
  Explode
} derive(Debug)

///|
priv struct VarSpec {
  name : String
  modifier : Modifier
  span : Span
} derive(Debug)

///|
priv enum Segment {
  Literal(String, Span)
  Expression(Char, Array[VarSpec])
} derive(Debug)

///|
/// A validated template with a hidden, immutable representation.
struct Template {
  segments : Array[Segment]
  source_text : String
} derive(Debug)

///|
pub fn OrderedEntries::from_entries(
  entries : Array[(String, String)],
) -> Result[OrderedEntries, TemplateError] {
  let seen : Map[String, Bool] = Map([])
  for (key, _) in entries {
    if seen.contains(key) {
      return Err(
        problem("DuplicateKey", "duplicate associative key: \{key}", 0, 0),
      )
    }
    seen[key] = true
  }
  Ok({ entries: entries.copy(), })
}

///|
pub fn Variables::new() -> Variables {
  { values: Map([]), }
}

///|
/// Normalize sparse entries before binding; null list members are omitted here.
/// JSON adapter remains strict for list nulls to catch accidental input errors.
pub fn sparse_list(items : Array[String?]) -> Value {
  let result : Array[String] = []
  for item in items {
    if item is Some(text) {
      result.push(text)
    }
  }
  List(result)
}

///|
pub fn sparse_assoc(
  items : Array[(String, String?)],
) -> Result[Value, TemplateError] {
  let seen : Map[String, Bool] = Map([])
  let result : Array[(String, String)] = []
  for (key, value) in items {
    if seen.contains(key) {
      return Err(problem("DuplicateKey", "duplicate associative key", 0, 0))
    }
    seen[key] = true
    if value is Some(text) {
      result.push((key, text))
    }
  }
  Ok(Assoc({ entries: result, }))
}

///|
pub fn Variables::set(self : Variables, name : String, value : Value) -> Unit {
  self.values[name] = value.captured()
}

///|
/// Return a captured value so callers cannot mutate stored collections.
pub fn Variables::get(self : Variables, name : String) -> Value? {
  self.values.get(name).map(fn(value) { value.captured() })
}

///|
/// Binding names in insertion order.
pub fn Variables::names(self : Variables) -> Array[String] {
  self.values.keys().to_array()
}

///|
fn Value::captured(self : Value) -> Value {
  match self {
    Scalar(_) => self
    List(items) => List(items.copy())
    Assoc(entries) => Assoc({ entries: entries.entries.copy(), })
  }
}

///|
pub fn Variables::remove(self : Variables, name : String) -> Unit {
  self.values.remove(name) |> ignore
}

///|
fn problem(
  code : String,
  message : String,
  start : Int32,
  end : Int32,
) -> TemplateError {
  { code, message, span: { start, end, }, }
}

///|
fn chars_text(chars : Array[Char], start : Int32, end : Int32) -> String {
  let out = StringBuilder()
  for i = start; i < end; i = i + 1 {
    out.write_char(chars[i])
  }
  out.to_string()
}

///|
pub extend Span with Debug::{to_repr}

///|
pub extend TemplateError with Debug::{to_repr}

///|
pub extend OrderedEntries with Debug::{to_repr}

///|
pub extend Value with Debug::{to_repr}

///|
pub extend Variables with Debug::{to_repr}

///|
pub extend Template with Debug::{to_repr}