///|
/// Window state mapped from native window manager.
pub(all) enum WindowState {
  Unknown = 0
  Created = 1
  Running = 2
  Hidden = 3
  Closing = 4
  Closed = 5
} derive(Eq)

///|
pub fn WindowState::int(self : WindowState) -> Int = "%identity"

///|
test {
  inspect(WindowState::Created.int(), content="1")
}

///|
/// Process type returned by native window manager.
pub(all) enum ProcessType {
  Main = 0
  Child = 1
  Unknown = 2
} derive(Eq)

///|
/// IPC message kind.
pub(all) enum IpcMessageType {
  Data = 0
  Command = 1
  Event = 2
  Request = 3
  Response = 4
} derive(Eq)

///|
pub fn IpcMessageType::int(self : IpcMessageType) -> Int = "%identity"

///|
pub(all) enum TitleBarStyle {
  /// Title bar style: default native title bar.
  Default = 0
  /// Title bar style: hidden title bar (macOS keeps traffic lights).
  Hidden = 1
} derive(Debug, Eq)

///|
/// Thin high-level wrapper over native window manager (stub.c wm_* APIs).
pub struct WindowManager {
  process_type : ProcessType
  mut window_ids : Array[Int]
}

///|
/// Snapshot of one IPC message copied out of the native queue.
pub struct IpcMessage {
  source_window_id : Int
  target_window_id : Int
  message_type : IpcMessageType
  message_id : Int
  subtype : String
  data : String
}

///|
fn i32_le_at(wire : Bytes, offset : Int) -> Int? {
  guard offset >= 0 && offset + 3 < wire.length() else { None }
  let b0 = wire[offset].to_int()
  let b1 = wire[offset + 1].to_int()
  let b2 = wire[offset + 2].to_int()
  let b3 = wire[offset + 3].to_int()
  Some(b0 + (b1 << 8) + (b2 << 16) + (b3 << 24))
}

///|
fn ipc_message_type_from_int(value : Int) -> IpcMessageType {
  match value {
    0 => IpcMessageType::Data
    1 => Command
    2 => Event
    3 => Request
    4 => Response
    _ => Data
  }
}

///|
fn decode_ipc_message_wire(wire : Bytes) -> IpcMessage? {
  guard wire.length() >= 24 else { None }
  let source_window_id = i32_le_at(wire, 0).unwrap()
  let target_window_id = i32_le_at(wire, 4).unwrap()
  let message_type = ipc_message_type_from_int(i32_le_at(wire, 8).unwrap())
  let message_id = i32_le_at(wire, 12).unwrap()
  let subtype_len = i32_le_at(wire, 16).unwrap()
  let data_len = i32_le_at(wire, 20).unwrap()
  guard subtype_len >= 0 && data_len >= 0 else { None }
  let subtype_start = 24
  let data_start = subtype_start + subtype_len
  guard data_start + data_len <= wire.length() else { None }
  let subtype = @encoding/utf8.decode(
    wire.exact_view(start=subtype_start, end=data_start),
  ) catch {
    _ => ""
  }
  let data = @encoding/utf8.decode(
    wire.exact_view(start=data_start, end=data_start + data_len),
  ) catch {
    _ => ""
  }
  Some({
    source_window_id,
    target_window_id,
    message_type,
    message_id,
    subtype,
    data,
  })
}

///|
fn i32_le_bytes(value : Int) -> Bytes {
  Bytes::makei(4, i => {
    match i {
      0 => (value & 0xFF).to_byte()
      1 => ((value >> 8) & 0xFF).to_byte()
      2 => ((value >> 16) & 0xFF).to_byte()
      _ => ((value >> 24) & 0xFF).to_byte()
    }
  })
}

///|
test "decode_ipc_message_wire" {
  let subtype = @encoding/utf8.encode("ready")
  let data = @encoding/utf8.encode("{\"ok\":true}")
  let wire = i32_le_bytes(7) +
    i32_le_bytes(9) +
    i32_le_bytes(IpcMessageType::Request.int()) +
    i32_le_bytes(42) +
    i32_le_bytes(subtype.length()) +
    i32_le_bytes(data.length()) +
    subtype +
    data
  let message = decode_ipc_message_wire(wire).unwrap()
  inspect(message.source_window_id, content="7")
  inspect(message.target_window_id, content="9")
  inspect(message.message_type == Request, content="true")
  inspect(message.message_id, content="42")
  inspect(message.subtype, content="ready")
  inspect(message.data, content="{\"ok\":true}")
  inspect(decode_ipc_message_wire(b"") is None, content="true")
  inspect(decode_ipc_message_wire(b"\x01\x02") is None, content="true")
}

///|
/// Initialize WM runtime for current process.
///
/// - main process: pass `true`
/// - child process: pass `false`
pub fn WindowManager::init(is_main? : Bool = true) -> WindowManager {
  let ok = wm_init(if is_main { 1 } else { 0 })
  guard ok == 0 else { abort("WindowManager::init failed") }
  let process_type = match wm_get_process_type() {
    0 => ProcessType::Main
    1 => Child
    _ => Unknown
  }
  { process_type, window_ids: [], }
}

///|
/// Cleanup native WM runtime.
pub fn WindowManager::destroy(self : WindowManager) -> Unit {
  wm_cleanup()
  self.window_ids = []
}

///|
/// Create a native window in current process.
pub fn WindowManager::create_window(
  self : WindowManager,
  title : String,
  url : String,
  width? : Int = 800,
  height? : Int = 600,
) -> Int {
  let id = wm_create_window(
    @encoding/utf8.encode(title),
    @encoding/utf8.encode(url),
    width,
    height,
    -1,
    -1,
    0,
    0,
    -1,
  )
  guard id > 0 else { abort("WindowManager::create_window failed") }
  self.window_ids.push(id)
  id
}

///|
/// Spawn a child process window. Returns child process pid.
pub fn WindowManager::create_child_window(
  _self : WindowManager,
  title : String,
  url : String,
  width? : Int = 800,
  height? : Int = 600,
  parent_id? : Int = -1,
) -> Int {
  wm_create_child_window(
    @encoding/utf8.encode(title),
    @encoding/utf8.encode(url),
    width,
    height,
    parent_id,
  )
}

///|
/// Fork the current process. The parent receives the child pid, the child receives `0`.
pub fn WindowManager::fork_process(_self : WindowManager) -> Int {
  wm_fork_process()
}

///|
/// Spawn a fresh child process by exec-ing the given program path with one extra argument.
pub fn WindowManager::spawn_process(
  _self : WindowManager,
  program : String,
  arg1 : String,
) -> Int {
  wm_spawn_process(@encoding/utf8.encode(program), @encoding/utf8.encode(arg1))
}

///|
/// Connect current process to the main-process IPC server and switch into child mode.
pub fn WindowManager::connect_child_process() -> Int {
  wm_connect_child_process()
}

///|
/// Return true when running in main process.
pub fn WindowManager::is_main_process(self : WindowManager) -> Bool {
  self.process_type == Main
}

///|
/// Return true when running in child process.
pub fn WindowManager::is_child_process(self : WindowManager) -> Bool {
  self.process_type == Child
}

///|
/// Run a window event loop.
pub fn WindowManager::run_window(_self : WindowManager, window_id : Int) -> Int {
  wm_run_window(window_id)
}

///|
/// Destroy a window.
pub fn WindowManager::destroy_window(
  _self : WindowManager,
  window_id : Int,
) -> Int {
  wm_destroy_window(window_id)
}

///|
/// Apply native custom-window style flags.
pub fn WindowManager::set_window_customization(
  _self : WindowManager,
  window_id : Int,
  frameless : Bool,
  resizable : Bool,
  closeable : Bool,
  always_on_top : Bool,
  transparent : Bool,
  title_bar_style : TitleBarStyle,
  title_bar_overlay : Bool,
) -> Int {
  wm_set_window_customization(
    window_id,
    if frameless {
      1
    } else {
      0
    },
    if resizable {
      1
    } else {
      0
    },
    if closeable {
      1
    } else {
      0
    },
    if always_on_top {
      1
    } else {
      0
    },
    if transparent {
      1
    } else {
      0
    },
    title_bar_style,
    if title_bar_overlay {
      1
    } else {
      0
    },
  )
}

///|
/// Set macOS traffic-light buttons position.
pub fn WindowManager::set_traffic_light_position(
  _self : WindowManager,
  window_id : Int,
  x : Int,
  y : Int,
) -> Int {
  wm_set_traffic_light_position(window_id, x, y)
}

///|
/// Minimize native window.
pub fn WindowManager::minimize_window(
  _self : WindowManager,
  window_id : Int,
) -> Int {
  wm_minimize_window(window_id)
}

///|
/// Maximize native window.
pub fn WindowManager::maximize_window(
  _self : WindowManager,
  window_id : Int,
) -> Int {
  wm_maximize_window(window_id)
}

///|
/// Restore native window from maximized state.
pub fn WindowManager::unmaximize_window(
  _self : WindowManager,
  window_id : Int,
) -> Int {
  wm_unmaximize_window(window_id)
}

///|
/// Toggle native maximize state.
pub fn WindowManager::toggle_maximize_window(
  _self : WindowManager,
  window_id : Int,
) -> Int {
  wm_toggle_maximize_window(window_id)
}

///|
/// Set native fullscreen state.
pub fn WindowManager::set_fullscreen_window(
  _self : WindowManager,
  window_id : Int,
  fullscreen : Bool,
) -> Int {
  wm_set_fullscreen_window(window_id, if fullscreen { 1 } else { 0 })
}

///|
/// Toggle native fullscreen state.
pub fn WindowManager::toggle_fullscreen_window(
  _self : WindowManager,
  window_id : Int,
) -> Int {
  wm_toggle_fullscreen_window(window_id)
}

///|
/// Start native window drag/move gesture.
pub fn WindowManager::start_drag_window(
  _self : WindowManager,
  window_id : Int,
) -> Int {
  wm_start_drag_window(window_id)
}

///|
/// Close native window.
pub fn WindowManager::close_window(
  _self : WindowManager,
  window_id : Int,
) -> Int {
  wm_close_window(window_id)
}

///|
/// Enable or disable developer tools integration.
pub fn WindowManager::set_devtools(
  _self : WindowManager,
  window_id : Int,
  enabled : Bool,
) -> Int {
  wm_set_devtools(window_id, if enabled { 1 } else { 0 })
}

///|
/// Send an IPC message.
pub fn WindowManager::send_message(
  _self : WindowManager,
  source_window_id : Int,
  target_window_id : Int,
  message_type : IpcMessageType,
  subtype : String,
  data : String,
) -> Int {
  wm_ipc_send(
    source_window_id,
    target_window_id,
    message_type.int(),
    @encoding/utf8.encode(subtype),
    @encoding/utf8.encode(data),
  )
}

///|
/// Broadcast an event message.
pub fn WindowManager::broadcast(
  self : WindowManager,
  source_window_id : Int,
  subtype : String,
  data : String,
) -> Int {
  self.send_message(source_window_id, 0, Event, subtype, data)
}

///|
/// Send an IPC request and wait for the response body.
pub fn WindowManager::request(
  _self : WindowManager,
  source_window_id : Int,
  target_window_id : Int,
  subtype : String,
  data : String,
  timeout_ms? : Int = 0,
) -> String {
  @encoding/utf8.decode(
    wm_ipc_request_bytes(
      source_window_id,
      target_window_id,
      @encoding/utf8.encode(subtype),
      @encoding/utf8.encode(data),
      timeout_ms,
    ),
  ) catch {
    _ => ""
  }
}

///|
/// Send an IPC response for a previously received request.
pub fn WindowManager::respond(
  _self : WindowManager,
  source_window_id : Int,
  target_window_id : Int,
  request_id : Int,
  data : String,
) -> Int {
  wm_ipc_respond(
    source_window_id,
    target_window_id,
    request_id,
    @encoding/utf8.encode(data),
  )
}

///|
/// Try to pop one queued IPC message for the given queue id.
pub fn WindowManager::try_pop_message(
  _self : WindowManager,
  queue_id : Int,
) -> IpcMessage? {
  decode_ipc_message_wire(wm_ipc_pop_message_wire(queue_id))
}

///|
/// Non-blocking child process status check.
pub fn WindowManager::wait_child_noblock(
  _self : WindowManager,
  pid : Int,
) -> Int {
  wm_wait_child_noblock(pid)
}