///|
/// 文档分块器:按段落切分,块之间保留重叠内容。
pub struct Chunker {
  max_chars : Int
  overlap : Int
} derive(ToJson, FromJson)

///|
pub fn Chunker::new(max_chars? : Int = 800, overlap? : Int = 80) -> Chunker {
  let m = if max_chars < 1 { 1 } else { max_chars }
  let o = if overlap < 0 { 0 } else if overlap >= m { m / 2 } else { overlap }
  { max_chars: m, overlap: o, }
}

///|
/// 将文档切成带编号的分块。
pub fn Chunker::chunk(
  self : Chunker,
  doc_id : String,
  text : String,
) -> Array[Chunk] {
  let paragraphs : Array[String] = []
  for line in text.split("\n") {
    let t = line.trim().to_owned()
    if !t.is_empty() {
      paragraphs.push(t)
    }
  }
  let chunks : Array[Chunk] = []
  let current : Array[String] = []
  let mut current_len = 0
  let mut index = 0
  for para in paragraphs {
    if para.length() >= self.max_chars {
      if !current.is_empty() {
        index = flush_chunk(chunks, doc_id, current, index)
        current.clear()
        current_len = 0
      }
      for piece in split_long(para, self.max_chars) {
        chunks.push({ doc_id, index, text: piece, })
        index = index + 1
      }
      continue
    }
    if current_len + para.length() + 1 > self.max_chars && !current.is_empty() {
      let last_text = current.join("\n")
      index = flush_chunk(chunks, doc_id, current, index)
      current.clear()
      if self.overlap > 0 && last_text.length() > self.overlap {
        current.push(
          last_text
          .exact_view(
            start=last_text.length() - self.overlap,
            end=last_text.length(),
          )
          .to_owned(),
        )
        current_len = self.overlap
      } else {
        current_len = 0
      }
    }
    current.push(para)
    current_len = current_len + para.length() + 1
  }
  if !current.is_empty() {
    ignore(flush_chunk(chunks, doc_id, current, index))
  }
  chunks
}

///|
fn flush_chunk(
  chunks : Array[Chunk],
  doc_id : String,
  lines : Array[String],
  index : Int,
) -> Int {
  chunks.push({ doc_id, index, text: lines.join("\n"), })
  index + 1
}

///|
fn split_long(text : String, max : Int) -> Array[String] {
  let out : Array[String] = []
  let chars = text.to_array()
  let mut i = 0
  while i < chars.length() {
    let end = if i + max < chars.length() { i + max } else { chars.length() }
    out.push(
      StringView::from_iter(chars.exact_view(start=i, end~).iter()).to_owned(),
    )
    i = end
  }
  out
}