waterui 0.3.0

A modern UI framework for Rust
//
//  Array.swift
//  waterui-swift
//
//  Created by Lexo Liu on 9/30/25.
//

import CWaterUI

// Helper class to store array information without generic parameters
private final class ArrayInfo {
  let baseAddress: UnsafeMutableRawPointer?
  let count: Int
  let elementSize: Int
  private let destroyStorage: (UnsafeMutableRawPointer?, Int) -> Void

  init(
    baseAddress: UnsafeMutableRawPointer?,
    count: Int,
    elementSize: Int,
    destroyStorage: @escaping (UnsafeMutableRawPointer?, Int) -> Void
  ) {
    self.baseAddress = baseAddress
    self.count = count
    self.elementSize = elementSize
    self.destroyStorage = destroyStorage
  }

  func destroy() {
    destroyStorage(baseAddress, count)
  }
}

private let wuiArrayDropFunction: @convention(c) (UnsafeMutableRawPointer?) -> Void = { ptr in
  guard let ptr else { return }
  let box = Unmanaged<AnyObject>.fromOpaque(ptr).takeRetainedValue()
  guard let arrayInfo = box as? ArrayInfo else {
    fatalError("WaterUI FFI array drop received an invalid ArrayInfo object")
  }
  arrayInfo.destroy()
}

private let wuiArraySliceFunction: @convention(c) (UnsafeRawPointer?) -> WuiArraySlice = { ptr in
  guard let ptr else {
    fatalError("WaterUI FFI array slice received a null ArrayInfo pointer")
  }

  let box = Unmanaged<AnyObject>.fromOpaque(ptr).takeUnretainedValue()
  guard let arrayInfo = box as? ArrayInfo else {
    fatalError("WaterUI FFI array slice received an invalid ArrayInfo object")
  }

  return WuiArraySlice(
    head: arrayInfo.baseAddress,
    len: UInt(arrayInfo.count)
  )
}

final class WuiRawArray {
  private var inner: CWaterUI.WuiArray?

  init(_ inner: CWaterUI.WuiArray) {
    self.inner = inner
  }

  func intoInner() -> CWaterUI.WuiArray {
    let v = inner!
    inner = nil
    return v
  }

  init<T>(array: [T]) {
    let baseAddress: UnsafeMutableRawPointer?
    if array.isEmpty {
      baseAddress = nil
    } else {
      let typedPointer = UnsafeMutablePointer<T>.allocate(capacity: array.count)
      typedPointer.initialize(from: array, count: array.count)
      baseAddress = UnsafeMutableRawPointer(typedPointer)
    }

    let vtable = WuiArrayVTable(drop: wuiArrayDropFunction, slice: wuiArraySliceFunction)
    let arrayInfo = ArrayInfo(
      baseAddress: baseAddress,
      count: array.count,
      elementSize: MemoryLayout<T>.size,
      destroyStorage: { rawPointer, count in
        guard let rawPointer else { return }
        let typedPointer = rawPointer.assumingMemoryBound(to: T.self)
        typedPointer.deinitialize(count: count)
        typedPointer.deallocate()
      }
    )
    let ptr = Unmanaged.passRetained(arrayInfo as AnyObject).toOpaque()
    let innerArray = CWaterUI.WuiArray(data: ptr, vtable: vtable)

    self.inner = innerArray
  }

  func toArray<T>() -> [T] {
    let slice = (inner!.vtable.slice)(inner!.data)
    let len = Int(slice.len)
    guard len > 0, let head = slice.head else {
      return []
    }

    let typedHead = head.assumingMemoryBound(to: T.self)
    let buffer = UnsafeBufferPointer<T>(start: typedHead, count: len)
    return Array(buffer)
  }

  func withUnsafeBufferPointer<T, R>(_ body: (UnsafeBufferPointer<T>) -> R) -> R {
    let slice = (inner!.vtable.slice)(inner!.data)
    let len = Int(slice.len)
    guard len > 0, let head = slice.head else {
      return body(UnsafeBufferPointer(start: nil, count: 0))
    }

    let typedHead = head.assumingMemoryBound(to: T.self)
    let buffer = UnsafeBufferPointer<T>(start: typedHead, count: len)
    return body(buffer)
  }

  @MainActor deinit {
    if let inner = inner {
      inner.vtable.drop(inner.data)
    }
  }
}

struct WuiArray<T> {
  var inner: WuiRawArray

  init(raw: WuiRawArray) {
    self.inner = raw
  }

  init(c: CWaterUI.WuiArray) {
    self.inner = .init(c)
  }

  init(array: [T]) {
    self.inner = .init(array: array)
  }

  func intoInner() -> CWaterUI.WuiArray {
    self.inner.intoInner()
  }

  func toArray() -> [T] {
    self.inner.toArray()
  }

  func map<U>(_ transform: (T) -> U) -> [U] {
    self.inner.withUnsafeBufferPointer { buffer in
      buffer.map(transform)
    }
  }

  func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<T>) -> R) -> R {
    self.inner.withUnsafeBufferPointer(body)
  }
}

extension WuiArray<UInt8> {
  init(_ inner: CWaterUI.WuiArray_u8) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray where T == CWaterUI.WuiId {
  init(_ inner: CWaterUI.WuiArray_WuiId) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray where T == CWaterUI.WuiDate {
  init(_ inner: CWaterUI.WuiArray_WuiDate) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }

  func intoWuiDateArray() -> CWaterUI.WuiArray_WuiDate {
    unsafeBitCast(inner.intoInner(), to: CWaterUI.WuiArray_WuiDate.self)
  }
}

extension WuiArray where T == CWaterUI.WuiResolvedGradientStop {
  init(_ inner: CWaterUI.WuiArray_WuiResolvedGradientStop) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray<OpaquePointer> {
  init(_ inner: CWaterUI.WuiArray_____WuiAnyView) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray where T == CWaterUI.WuiHorizontalGuide {
  init(_ inner: CWaterUI.WuiArray_WuiHorizontalGuide) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray where T == CWaterUI.WuiVerticalGuide {
  init(_ inner: CWaterUI.WuiArray_WuiVerticalGuide) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray<CWaterUI.WuiStyledChunk> {
  init(_ inner: CWaterUI.WuiArray_WuiStyledChunk) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray<CWaterUI.WuiStr> {
  func intoWuiStrArray() -> CWaterUI.WuiArray_WuiStr {
    unsafeBitCast(inner.intoInner(), to: CWaterUI.WuiArray_WuiStr.self)
  }
}

extension WuiArray<CWaterUI.WuiVideoAudioTrackInfo> {
  func intoVideoAudioTrackInfoArray() -> CWaterUI.WuiArray_WuiVideoAudioTrackInfo {
    unsafeBitCast(inner.intoInner(), to: CWaterUI.WuiArray_WuiVideoAudioTrackInfo.self)
  }
}

extension WuiArray<CWaterUI.WuiVideoTrackInfo> {
  func intoVideoTrackInfoArray() -> CWaterUI.WuiArray_WuiVideoTrackInfo {
    unsafeBitCast(inner.intoInner(), to: CWaterUI.WuiArray_WuiVideoTrackInfo.self)
  }
}

extension WuiArray<CWaterUI.WuiVideoSubtitleTrackInfo> {
  func intoVideoSubtitleTrackInfoArray() -> CWaterUI.WuiArray_WuiVideoSubtitleTrackInfo {
    unsafeBitCast(inner.intoInner(), to: CWaterUI.WuiArray_WuiVideoSubtitleTrackInfo.self)
  }
}

extension WuiArray<CWaterUI.WuiNavigationView> {
  init(_ inner: CWaterUI.WuiArray_WuiNavigationView) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray<CWaterUI.WuiNavigationToolbarItem> {
  init(_ inner: CWaterUI.WuiArray_WuiNavigationToolbarItem) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

extension WuiArray<CWaterUI.WuiTab> {
  init(_ inner: CWaterUI.WuiArray_WuiTab) {
    let raw = unsafeBitCast(inner, to: CWaterUI.WuiArray.self)
    self.init(c: raw)
  }
}

public struct WuiStr {
  private var inner: WuiArray<UInt8>

  public init(_ inner: CWaterUI.WuiStr) {
    self.inner = WuiArray<UInt8>(inner._0)
  }

  public init(string: String) {
    let bytes = [UInt8](string.utf8)
    self.inner = WuiArray<UInt8>(array: bytes)
  }

  public func toString() -> String {
    inner.withUnsafeBufferPointer { bytes in
      String(decoding: bytes, as: UTF8.self)
    }
  }

  public func intoInner() -> CWaterUI.WuiStr {
    unsafeBitCast(self.inner.intoInner(), to: CWaterUI.WuiStr.self)
  }

  func intoRustOwnedPointer() -> UnsafeMutablePointer<CWaterUI.WuiStr> {
    guard let pointer = waterui_str_box(intoInner()) else {
      fatalError("waterui_str_box returned null")
    }
    return pointer
  }
}