// 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.

///|
pub enum SerializeError {
  InvalidWidth
  OutOfBounds
  OffsetOverflow
  InvalidLink
  NegativeOffset
  VirtualOrder
} derive(Eq, Show)

///|
pub(all) enum SerializeWhence {
  Head
  Tail
  Absolute
} derive(Eq, Show)

///|
pub(all) struct SerializeLink {
  width : Int
  position : Int
  objidx : Int
  is_signed : Bool
  whence : SerializeWhence
  bias : Int
}

///|
pub struct SerializeObject {
  data : Bytes
  real_links : Array[SerializeLink]
  virtual_links : Array[Int]
}

///|
pub fn SerializeObject::new(data : Bytes) -> SerializeObject {
  SerializeObject::{ data, real_links: [], virtual_links: [] }
}

///|
pub fn SerializeLink::new(
  width : Int,
  position : Int,
  objidx : Int,
  is_signed? : Bool = false,
  whence? : SerializeWhence = SerializeWhence::Head,
  bias? : Int = 0,
) -> SerializeLink {
  SerializeLink::{ width, position, objidx, is_signed, whence, bias }
}

fn max_value_for_width(width : Int) -> UInt? {
  if width == 1 {
    Some(0xFFU)
  } else if width == 2 {
    Some(0xFFFFU)
  } else if width == 3 {
    Some(0xFFFFFFU)
  } else if width == 4 {
    Some(0xFFFF_FFFFU)
  } else {
    None
  }
}

fn write_offset(
  data : Array[Byte],
  position : Int,
  width : Int,
  value : UInt,
) -> Result[Unit, SerializeError] {
  let max_value = match max_value_for_width(width) {
    None => return Err(SerializeError::InvalidWidth)
    Some(v) => v
  }
  if value > max_value {
    return Err(SerializeError::OffsetOverflow)
  }
  if position < 0 || position + width > data.length() {
    return Err(SerializeError::OutOfBounds)
  }
  for i in 0..> shift) & 0xFFU).to_byte()
    data[position + i] = byte_value
  }
  Ok(())
}

fn signed_limits(width : Int) -> (Int, Int)? {
  if width <= 0 {
    return None
  }
  let bits = width * 8
  let max = (1 << (bits - 1)) - 1
  let min = -1 * (1 << (bits - 1))
  Some((min, max))
}

///|
/// Serialize objects into a contiguous buffer, patching offsets for real links.
fn serialize_objects_with_starts(
  objects : ArrayView[SerializeObject],
) -> Result[(Bytes, Array[Int]), SerializeError] {
  let count = objects.length()
  let starts : Array[Int] = []
  let mut total = 0
  for obj in objects {
    starts.push(total)
    total = total + obj.data.length()
  }
  let patched : Array[Array[Byte]] = []
  for obj in objects {
    patched.push(obj.data.to_array())
  }
  for i in 0..= count {
        return Err(SerializeError::InvalidLink)
      }
      if target <= i {
        return Err(SerializeError::VirtualOrder)
      }
    }
    for link in obj.real_links {
      if link.width == 0 {
        if link.objidx < 0 || link.objidx >= count {
          return Err(SerializeError::InvalidLink)
        }
        if link.objidx <= i {
          return Err(SerializeError::VirtualOrder)
        }
      }
    }
  }
  for i in 0..= count {
        return Err(SerializeError::InvalidLink)
      }
      let target_start = starts[link.objidx]
      let origin = match link.whence {
        SerializeWhence::Head => base
        SerializeWhence::Tail => base + objects[i].data.length()
        SerializeWhence::Absolute => 0
      }
      let offset = target_start + link.bias - origin
      if !link.is_signed && offset < 0 {
        return Err(SerializeError::NegativeOffset)
      }
      let value = if link.is_signed {
        let limits = match signed_limits(link.width) {
          None => return Err(SerializeError::InvalidWidth)
          Some((min, max)) => (min, max)
        }
        if offset < limits.0 || offset > limits.1 {
          return Err(SerializeError::OffsetOverflow)
        }
        if offset < 0 {
          let mask = 1 << (link.width * 8)
          (offset + mask).reinterpret_as_uint()
        } else {
          offset.reinterpret_as_uint()
        }
      } else {
        offset.reinterpret_as_uint()
      }
      let result = write_offset(
        patched[i],
        link.position,
        link.width,
        value,
      )
      match result {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
  }
  let buffer = @buffer.new(size_hint=total)
  for bytes in patched {
    buffer.write_bytes(Bytes::from_array(bytes))
  }
  Ok((buffer.to_bytes(), starts))
}

///|
/// Serialize objects into a contiguous buffer, patching offsets for real links.
pub fn serialize_objects(
  objects : ArrayView[SerializeObject],
) -> Result[Bytes, SerializeError] {
  match serialize_objects_with_starts(objects) {
    Err(err) => Err(err)
    Ok((bytes, _)) => Ok(bytes)
  }
}

///|
/// Serialize objects and return their start offsets in the original order.
pub fn serialize_objects_with_offsets(
  objects : ArrayView[SerializeObject],
) -> Result[(Bytes, Array[Int]), SerializeError] {
  serialize_objects_with_starts(objects)
}

fn order_objects(objects : ArrayView[SerializeObject]) -> Result[Array[Int], SerializeError] {
  let count = objects.length()
  let edges : Array[Array[Int]] = []
  let indegree : Array[Int] = Array::make(count, 0)
  for _ in 0..= count {
        return Err(SerializeError::InvalidLink)
      }
      edges[i].push(target)
      indegree[target] = indegree[target] + 1
    }
    for link in objects[i].real_links {
      if link.width == 0 {
        if link.objidx < 0 || link.objidx >= count {
          return Err(SerializeError::InvalidLink)
        }
        edges[i].push(link.objidx)
        indegree[link.objidx] = indegree[link.objidx] + 1
      }
    }
  }
  let queue : Array[Int] = []
  for i in 0.. Result[Array[Int], SerializeError] {
  order_objects(objects)
}

fn remap_objects(
  objects : ArrayView[SerializeObject],
  order : ArrayView[Int],
) -> Result[Array[SerializeObject], SerializeError] {
  let count = objects.length()
  let new_index : Array[Int] = Array::make(count, -1)
  for i in 0..= count {
      return Err(SerializeError::InvalidLink)
    }
    new_index[old_idx] = i
  }
  let out : Array[SerializeObject] = []
  for i in 0..= count {
        return Err(SerializeError::InvalidLink)
      }
      let mapped = new_index[link.objidx]
      if mapped < 0 {
        return Err(SerializeError::InvalidLink)
      }
      obj.real_links.push(SerializeLink::{
        width: link.width,
        position: link.position,
        objidx: mapped,
        is_signed: link.is_signed,
        whence: link.whence,
        bias: link.bias,
      })
    }
    for target in old.virtual_links {
      if target < 0 || target >= count {
        return Err(SerializeError::InvalidLink)
      }
      let mapped = new_index[target]
      if mapped < 0 {
        return Err(SerializeError::InvalidLink)
      }
      obj.virtual_links.push(mapped)
    }
    out.push(obj)
  }
  Ok(out)
}

///|
/// Serialize objects in a provided order.
pub fn serialize_objects_in_order(
  objects : ArrayView[SerializeObject],
  order : ArrayView[Int],
) -> Result[Bytes, SerializeError] {
  let remapped = match remap_objects(objects, order) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  serialize_objects(remapped[:])
}

///|
/// Serialize objects after packing them to satisfy virtual link ordering.
pub fn serialize_objects_packed(
  objects : ArrayView[SerializeObject],
) -> Result[Bytes, SerializeError] {
  let order = match order_objects(objects) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let remapped = match remap_objects(objects, order[:]) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  serialize_objects(remapped[:])
}

///|
/// Serialize objects after packing them to satisfy virtual link ordering, returning the order.
pub fn serialize_objects_packed_with_order(
  objects : ArrayView[SerializeObject],
) -> Result[(Bytes, Array[Int]), SerializeError] {
  let order = match order_objects(objects) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let bytes = match serialize_objects_in_order(objects, order[:]) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  Ok((bytes, order))
}