///|
/// A named place in the generated code. A section can carry data for the
/// interceptors that change its text.
pub(all) struct Section {
  name : String
  data : Map[String, TemplateValue]
}

///|
pub fn Section::new(
  name : String,
  data~ : Map[String, TemplateValue] = Map([]),
) -> Section {
  { name, data, }
}

///|
/// A function that changes the text of the sections with one name. The
/// function gets a child writer, the section and the previous text. The text
/// of the child writer becomes the new text of the section.
pub struct Interceptor {
  priv section_name : String
  priv run : (CodeWriter, Section, String) -> Unit raise CodegenError
}

///|
pub fn Interceptor::new(
  section_name : String,
  run : (CodeWriter, Section, String) -> Unit raise CodegenError,
) -> Interceptor {
  { section_name, run, }
}

///|
/// An interceptor that keeps the previous text and writes after it.
pub fn Interceptor::appender(
  section_name : String,
  run : (CodeWriter, Section) -> Unit raise CodegenError,
) -> Interceptor {
  Interceptor::new(section_name, (child, section, previous) => {
    child.write_raw(previous)
    run(child, section)
  })
}

///|
/// An interceptor that keeps the previous text and writes before it.
pub fn Interceptor::prepender(
  section_name : String,
  run : (CodeWriter, Section) -> Unit raise CodegenError,
) -> Interceptor {
  Interceptor::new(section_name, (child, section, previous) => {
    run(child, section)
    child.write_raw(previous)
  })
}

///|
/// The name of the sections that the interceptor changes.
pub fn Interceptor::section_name(self : Interceptor) -> String {
  self.section_name
}

///|
/// A section that is open: its data and the number of states when it started.
priv struct OpenSection {
  section : Section
  depth : Int32
}

///|
/// Write the text as it is and end the line. It is not a template. Nothing is
/// written when the text is empty.
fn CodeWriter::write_raw(self : CodeWriter, text : String) -> Unit {
  if !text.is_empty() {
    self.emit(text, true)
  }
}

///|
/// Add an interceptor. It applies to the sections that this writer and its
/// child writers end. Interceptors run in the order that they were added.
pub fn CodeWriter::on_section(
  self : CodeWriter,
  interceptor : Interceptor,
) -> Unit {
  self.interceptors.push(interceptor)
}

///|
/// Start a section. It saves the state, as `push_state` does, with no
/// indentation and no newline prefix. Until `pop_section`, the writer captures
/// the text that it writes.
pub fn CodeWriter::push_section(self : CodeWriter, section : Section) -> Unit {
  self.push_state()
  let state = self.state()
  state.indent_level = 0
  state.newline_prefix = ""
  self.open_sections.push({ section, depth: self.states.length(), })
  self.buffers.push(OutputBuffer::new())
}

///|
/// End the section and restore the state. The interceptors of the section
/// change its text in the order that they were added. Then I write the text
/// with the indentation of the enclosing writer. I write nothing when the
/// text is empty. A non-empty text ends the outer line, also when the section
/// opened in the middle of a line. It raises `StateError` when no section is
/// open, or when a `push_state` inside the section is still open. When an
/// interceptor raises an error, the error passes through and the section is
/// already ended: its text and its state are gone, and the writer does not
/// write the section.
pub fn CodeWriter::pop_section(self : CodeWriter) -> Unit raise CodegenError {
  guard self.open_sections.last() is Some(open) else {
    raise StateError("I found pop_section with no open section.")
  }
  if open.depth != self.states.length() {
    raise StateError(
      "I could not end section `\{open.section.name}`: a push_state inside it is still open.",
    )
  }
  let _ = self.open_sections.pop()
  let captured = match self.buffers.pop() {
    Some(buffer) => buffer.out.to_string()
    None => ""
  }
  let _ = self.states.pop()
  let mut text = normalize_output(captured).trim_end(chars="\n").to_owned()
  // An interceptor can add interceptors, so I run a copy of the array.
  for interceptor in self.interceptors.copy() {
    if interceptor.section_name == open.section.name {
      let child = self.child()
      (interceptor.run)(child, open.section, text)
      text = child.to_string().trim_end(chars="\n").to_owned()
    }
  }
  self.write_raw(text)
}

///|
/// Start a section and end it at once, so the interceptors can add text.
pub fn CodeWriter::inject_section(
  self : CodeWriter,
  section : Section,
) -> Unit raise CodegenError {
  self.push_section(section)
  self.pop_section()
}