///|
/// Atlas cache serialization — save/load GlyphAtlas to/from flat Int arrays.
///
/// Format: [magic, width, height, page_id, entry_count, next_x_bits_hi, next_x_bits_lo,
///          next_y_bits_hi, next_y_bits_lo, row_height_bits_hi, row_height_bits_lo,
///          entries..., pixels...]

///|
let cache_magic : Int = 0x47_4C_43_41 // "GLCA"

///|
/// Serialize a GlyphAtlas to a flat Int array for persistent caching.
pub fn GlyphAtlas::serialize(self : GlyphAtlas) -> Array[Int] {
  let cache = self.cache
  let entry_count = cache.entry_count()
  let result : Array[Int] = []
  // Header
  result.push(cache_magic)
  result.push(self.width)
  result.push(self.height)
  result.push(cache.atlas_page_id)
  result.push(entry_count)
  // next_x, next_y, row_height as f64 → two i32s each
  push_f64(result, cache.next_x)
  push_f64(result, cache.next_y)
  push_f64(result, cache.row_height)
  // Entries: [key, ax_hi, ax_lo, ay_hi, ay_lo, aw_hi, aw_lo, ah_hi, ah_lo] × N
  cache.entries.each(fn(key, entry) {
    result.push(key)
    push_f64(result, entry.atlas_x)
    push_f64(result, entry.atlas_y)
    push_f64(result, entry.atlas_w)
    push_f64(result, entry.atlas_h)
  })
  // Pixels (RGBA8)
  for px in self.pixels {
    result.push(px)
  }
  result
}

///|
/// Deserialize a GlyphAtlas from a flat Int array.
pub fn GlyphAtlas::deserialize(data : Array[Int]) -> GlyphAtlas? {
  if data.length() < 11 {
    return None
  }
  if data[0] != cache_magic {
    return None
  }
  let width = data[1]
  let height = data[2]
  let page_id = data[3]
  let entry_count = data[4]
  let mut pos = 5
  let next_x = read_f64(data, pos)
  pos += 2
  let next_y = read_f64(data, pos)
  pos += 2
  let row_height = read_f64(data, pos)
  pos += 2
  // Read entries
  let cache = GlyphCache::new(width.to_double(), height.to_double(), page_id)
  cache.next_x = next_x
  cache.next_y = next_y
  cache.row_height = row_height
  for _i = 0; _i < entry_count; _i = _i + 1 {
    if pos + 9 > data.length() {
      return None
    }
    let key = data[pos]
    pos += 1
    let ax = read_f64(data, pos)
    pos += 2
    let ay = read_f64(data, pos)
    pos += 2
    let aw = read_f64(data, pos)
    pos += 2
    let ah = read_f64(data, pos)
    pos += 2
    let entry : GlyphCacheEntry = {
      glyph_id: key,
      atlas_x: ax,
      atlas_y: ay,
      atlas_w: aw,
      atlas_h: ah,
      atlas_page_id: page_id,
    }
    cache.entries.set(key, entry)
  }
  // Read pixels
  let pixel_count = width * height * 4
  let pixels : Array[Int] = Array::make(pixel_count, 0)
  for i = 0; i < pixel_count; i = i + 1 {
    if pos + i < data.length() {
      pixels[i] = data[pos + i]
    }
  }
  Some({
    cache,
    width,
    height,
    pixels,
    dirty: true, // Mark dirty so GPU uploads on first use
    last_used_generation: 0,
  })
}

///|
/// Serialize all pages of a TextRenderer.
pub fn TextRenderer::serialize_cache(self : TextRenderer) -> Array[Array[Int]] {
  let result : Array[Array[Int]] = []
  for page in self.pages {
    if page.cache.entry_count() > 0 {
      result.push(page.serialize())
    }
  }
  result
}

///|
/// Restore atlas pages from serialized data.
/// Returns number of pages loaded.
pub fn TextRenderer::load_cache(
  self : TextRenderer,
  pages_data : Array[Array[Int]],
) -> Int {
  let mut loaded = 0
  for data in pages_data {
    match GlyphAtlas::deserialize(data) {
      Some(atlas) => {
        if loaded < self.pages.length() {
          self.pages[loaded] = atlas
        } else if self.pages.length() < self.max_pages {
          self.pages.push(atlas)
        }
        loaded += 1
      }
      None => ()
    }
  }
  loaded
}

///|
/// Encode a Double as two Ints: integer part and fractional part (×1000000).
fn push_f64(arr : Array[Int], value : Double) -> Unit {
  let int_part = value.to_int()
  let frac_part = ((value - int_part.to_double()) * 1000000.0).to_int()
  arr.push(int_part)
  arr.push(frac_part)
}

///|
/// Decode a Double from two Ints (integer + fractional×1000000).
fn read_f64(data : Array[Int], pos : Int) -> Double {
  data[pos].to_double() + data[pos + 1].to_double() / 1000000.0
}