///|
struct ListObject {
  values : Array[TclValue]
  encoded : Array[String]
  mut units : Int
  mut representation : String?
} derive(Debug)

///|
fn ListObject::from_value(value : TclValue) -> ListObject raise TclError {
  let object = ListObject::{
    values: [],
    encoded: [],
    units: 0,
    representation: Some(value.text),
  }
  object.append(value.as_list())
  object.representation = Some(value.text)
  object
}

///|
fn ListObject::append(
  self : ListObject,
  items : Array[TclValue],
) -> Unit raise TclError {
  if items.is_empty() {
    return
  }
  if self.values.length() + items.length() > 100000 {
    raise Invalid("list element limit")
  }
  let encoded = []
  let mut units = self.units
  for i in 0.. 1000000 {
      raise Invalid("list size limit")
    }
    encoded.push(text)
  }
  for item in items {
    self.values.push(item)
  }
  for text in encoded {
    self.encoded.push(text)
  }
  self.units = units
  self.representation = None
}

///|
fn ListObject::text(self : ListObject) -> String {
  match self.representation {
    Some(text) => text
    None => {
      let text = self.encoded.join(" ")
      self.representation = Some(text)
      text
    }
  }
}

///|
struct DictObject {
  keys : Array[TclValue]
  values : Array[TclValue]
  index : Map[String, Int]
  encoded : Array[String]
  mut units : Int
  mut representation : String?
} derive(Debug)

///|
fn DictObject::from_value(value : TclValue) -> DictObject raise TclError {
  let object = DictObject::{
    keys: [],
    values: [],
    index: Map([]),
    encoded: [],
    units: 0,
    representation: Some(value.text),
  }
  for (key, value) in dictionary_values(value) {
    object.put(key, value)
  }
  object.representation = Some(value.text)
  object
}

///|
fn DictObject::get(self : DictObject, key : String) -> TclValue? {
  self.index.get(key).map(i => self.values[i])
}

///|
fn DictObject::put(
  self : DictObject,
  key : TclValue,
  value : TclValue,
) -> Unit raise TclError {
  let index = self.index.get(key.text)
  let encoded = quote_element(value.text, false)
  let (units, encoded_key) = match index {
    Some(i) =>
      (self.units - self.encoded[i * 2 + 1].length() + encoded.length(), "")
    None => {
      if self.keys.length() >= 50000 {
        raise Invalid("dictionary element limit")
      }
      let k = quote_element(key.text, self.keys.is_empty())
      (
        self.units +
        k.length() +
        encoded.length() +
        (if self.keys.is_empty() { 1 } else { 2 }),
        k,
      )
    }
  }
  if units > 1000000 {
    raise Invalid("dictionary size limit")
  }
  match index {
    Some(i) => {
      self.values[i] = value
      self.encoded[i * 2 + 1] = encoded
    }
    None => {
      self.index[key.text] = self.keys.length()
      self.keys.push(key)
      self.values.push(value)
      self.encoded.push(encoded_key)
      self.encoded.push(encoded)
    }
  }
  self.units = units
  self.representation = None
}

///|
fn DictObject::text(self : DictObject) -> String {
  match self.representation {
    Some(text) => text
    None => {
      let text = self.encoded.join(" ")
      self.representation = Some(text)
      text
    }
  }
}

///|
fn Interpreter::append_list(
  self : Interpreter,
  name : String,
  items : Array[TclValue],
  discard_result : Bool,
) -> TclValue raise TclError {
  let binding = self.binding(name, true).unwrap()
  if binding.index is Some(_) {
    let values = binding.read_value().unwrap_or(text_value("")).as_list() +
      items
    let result = list_value(values)
    self.set_value(name, result)
    return if discard_result { text_value("") } else { result }
  }
  let object = match binding.cell.value {
    Some(Sequence(object)) => object
    Some(Elements(_)) => raise Invalid("variable is array")
    _ => ListObject::from_value(binding.read_value().unwrap_or(text_value("")))
  }
  object.append(items)
  binding.cell.value = Some(Sequence(object))
  if discard_result {
    text_value("")
  } else {
    binding.read_value().unwrap()
  }
}

///|
fn Interpreter::update_dictionary(
  self : Interpreter,
  input : Array[TclValue],
  depth : Int,
  discard_result : Bool,
) -> TclValue raise TclError {
  let args = input.map(v => v.text)
  let name = args[2]
  let key = args[3]
  let binding = self.binding(name, true).unwrap()
  if binding.index is Some(_) {
    return self.dict_command(input, depth, object_path=false)
  }
  let object = match binding.cell.value {
    Some(Mapping(object)) => object
    Some(Elements(_)) => raise Invalid("variable is array")
    _ => DictObject::from_value(binding.read_value().unwrap_or(text_value("")))
  }
  let previous = object.get(key)
  let value = match args[1] {
    "set" => input[4]
    "incr" =>
      text_value(
        integer_add(
          previous.map(v => v.text).unwrap_or("0"),
          if args.length() == 5 {
            args[4]
          } else {
            "1"
          },
        ),
      )
    "append" =>
      if args[4:].iter().all(v => v.is_empty()) {
        previous.unwrap_or(text_value(""))
      } else {
        text_value(
          previous.map(v => v.text).unwrap_or("") + args[4:].to_owned().join(""),
        )
      }
    "lappend" =>
      list_value(
        previous.unwrap_or(text_value("")).as_list() + input[4:].to_owned(),
      )
    _ => raise Invalid("dictionary update operation")
  }
  object.put(input[3], value)
  binding.cell.value = Some(Mapping(object))
  if discard_result {
    text_value("")
  } else {
    binding.read_value().unwrap()
  }
}

///|
fn ListObject::snapshot(self : ListObject) -> TclValue {
  { text: self.text(), payload: Items(self.values.copy()), }
}

///|
fn DictObject::snapshot(self : DictObject) -> TclValue {
  let pairs = []
  for i in 0..