// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Chunked storage shared by synchronous and asynchronous lexing buffers.
/// Source chunks are retained without rebuilding the whole buffer. New input
/// is merged into the last chunk up to a fixed limit to reduce lookup overhead.
/// Captured ranges are joined once, on demand.
priv struct LexbufStorage {
  mut chunks : Array[String]
  mut chunk_starts : Array[Int]
  mut lookup_chunk : Int
  mut buffer_start : Int
  mut buffer_end : Int
}

///|
const LEXBUF_MERGED_CHUNK_LIMIT : Int = 64

///|
fn LexbufStorage::LexbufStorage() -> LexbufStorage {
  {
    chunks: [],
    chunk_starts: [],
    lookup_chunk: 0,
    buffer_start: 0,
    buffer_end: 0,
  }
}

///|
fn LexbufStorage::from_string(content : String) -> LexbufStorage {
  let storage = LexbufStorage()
  if !content.is_empty() {
    storage.append(content)
  }
  storage
}

///|
fn LexbufStorage::append(self : LexbufStorage, chunk : String) -> Unit {
  guard !chunk.is_empty() else { abort("LexbufStorage: empty chunk") }
  if self.chunks is [.., last] &&
    last.length() + chunk.length() <= LEXBUF_MERGED_CHUNK_LIMIT {
    self.chunks[self.chunks.length() - 1] = last + chunk
    self.buffer_end += chunk.length()
    return
  }
  self.chunk_starts.push(self.buffer_end)
  self.chunks.push(chunk)
  self.buffer_end += chunk.length()
}

///|
fn LexbufStorage::chunk_contains(
  self : LexbufStorage,
  index : Int,
  position : Int,
) -> Bool {
  if index < 0 || index >= self.chunks.length() {
    return false
  }
  let start = self.chunk_starts.unsafe_get(index)
  start <= position && position < start + self.chunks.unsafe_get(index).length()
}

///|
fn LexbufStorage::find_chunk(self : LexbufStorage, position : Int) -> Int {
  let cached = self.lookup_chunk
  if self.chunk_contains(cached, position) {
    return cached
  }
  if self.chunk_contains(cached + 1, position) {
    self.lookup_chunk = cached + 1
    return cached + 1
  }
  if self.chunk_contains(cached - 1, position) {
    self.lookup_chunk = cached - 1
    return cached - 1
  }
  let low = for low = 0, high = self.chunks.length(); low < high; {
    let middle = low + (high - low) / 2
    if self.chunk_starts.unsafe_get(middle) <= position {
      continue middle + 1, high
    } else {
      continue low, middle
    }
  } nobreak {
    low
  }
  let index = low - 1
  guard self.chunk_contains(index, position) else {
    abort("LexbufStorage: position is not buffered")
  }
  self.lookup_chunk = index
  index
}

///|
fn LexbufStorage::unsafe_code_unit_at(
  self : LexbufStorage,
  position : Int,
) -> Int {
  let index = self.find_chunk(position)
  let chunk = self.chunks.unsafe_get(index)
  chunk.unsafe_get(position - self.chunk_starts.unsafe_get(index)).to_int()
}

///|
fn LexbufStorage::get_stringview(
  self : LexbufStorage,
  start : Int,
  end : Int,
) -> StringView {
  if start == end {
    return ""
  }
  let first = self.find_chunk(start)
  let first_chunk = self.chunks.unsafe_get(first)
  let first_start = self.chunk_starts.unsafe_get(first)
  let first_end = first_start + first_chunk.length()
  // Use views rather than `s[a:b]`: lexbuf offsets and chunk boundaries are raw
  // UTF-16 code-unit positions, so they may fall inside a surrogate pair.
  if end <= first_end {
    return first_chunk.view(
      start_offset=start - first_start,
      end_offset=end - first_start,
    )
  }
  let builder = StringBuilder(size_hint=(end - start) * 2)
  builder.write_view(first_chunk.view(start_offset=start - first_start))
  for index in (first + 1).. Unit {
  guard self.buffer_start <= position && position <= self.buffer_end else {
    abort("LexbufStorage: invalid retention state")
  }
  let length = self.chunks.length()
  let first = for index in 0.. position {
      break index
    }
  } nobreak {
    length
  }
  self.buffer_start = position
  if first == length {
    self.chunks.clear()
    self.chunk_starts.clear()
  } else if first > 0 {
    let chunks : Array[String] = []
    let chunk_starts : Array[Int] = []
    for index in first..