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

///|
extern "c" fn get_pid_ffi() -> Int = "sysinfo_get_pid"

///|
extern "c" fn sleep_millis_ffi(millis : Int) = "sysinfo_sleep_millis"

///|
extern "c" fn is_supported_system_ffi() -> Int = "sysinfo_is_supported_system"

///|
extern "c" fn os_tag_ffi() -> Bytes = "sysinfo_get_os_tag"

///|
extern "c" fn host_name_ffi() -> Bytes = "sysinfo_host_name"

///|
extern "c" fn kernel_version_ffi() -> Bytes = "sysinfo_kernel_version"

///|
extern "c" fn cpu_arch_ffi() -> Bytes = "sysinfo_cpu_arch"

///|
extern "c" fn kernel_long_version_ffi() -> Bytes = "sysinfo_kernel_long_version"

///|
extern "c" fn clock_ticks_per_second_ffi() -> Int = "sysinfo_clock_ticks_per_second"

///|
extern "c" fn logical_cpu_count_ffi() -> Int = "sysinfo_logical_cpu_count"

///|
extern "c" fn physical_cpu_count_ffi() -> Int = "sysinfo_physical_cpu_count"

///|
extern "c" fn process_exists_ffi(pid : Int) -> Int = "sysinfo_process_exists"

///|
#borrow(path)
extern "c" fn readlink_path_ffi(path : Bytes) -> Bytes = "sysinfo_readlink_path"

///|
extern "c" fn boot_time_seconds_ffi() -> Int64 = "sysinfo_boot_time_seconds"

///|
extern "c" fn uptime_seconds_ffi() -> UInt64 = "sysinfo_uptime_seconds"

///|
extern "c" fn load_average_ffi() -> Bytes = "sysinfo_load_average"

///|
extern "c" fn macos_os_version_ffi() -> Bytes = "sysinfo_macos_os_version"

///|
extern "c" fn macos_battery_temperature_ffi() -> Int = "sysinfo_macos_battery_temperature"

///|
extern "c" fn macos_memory_stats_ffi() -> Bytes = "sysinfo_macos_memory_stats"

///|
extern "c" fn macos_cpu_info_ffi() -> Bytes = "sysinfo_macos_cpu_info"

///|
extern "c" fn macos_cpu_snapshots_ffi() -> Bytes = "sysinfo_macos_cpu_snapshots"

///|
extern "c" fn macos_vm_io_counters_ffi() -> Bytes = "sysinfo_macos_vm_io_counters"

///|
extern "c" fn macos_process_table_ffi() -> Bytes = "sysinfo_macos_process_table"

///|
extern "c" fn mount_table_ffi() -> Bytes = "sysinfo_mount_table"

///|
extern "c" fn network_table_ffi() -> Bytes = "sysinfo_network_table"

///|
extern "c" fn user_table_ffi() -> Bytes = "sysinfo_user_table"

///|
extern "c" fn group_table_ffi() -> Bytes = "sysinfo_group_table"

///|
extern "c" fn macos_disk_table_ffi() -> Bytes = "sysinfo_macos_disk_table"

///|
extern "c" fn kill_pid_ffi(pid : Int, signal : Int) -> Int = "sysinfo_kill"

///|
#borrow(status)
extern "c" fn wait_pid_ffi(
  pid : Int,
  options : Int,
  status : @ref.Ref[Int],
) -> Int = "sysinfo_wait_pid"

///|
extern "c" fn get_open_files_limit_ffi() -> Int = "sysinfo_get_open_files_soft_limit"

///|
extern "c" fn set_open_files_limit_ffi(limit : Int) -> Int = "sysinfo_set_open_files_soft_limit"

///|
extern "c" fn proc_args_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_args_macos"

///|
extern "c" fn proc_environ_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_environ_macos"

///|
extern "c" fn proc_exe_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_exe_macos"

///|
extern "c" fn proc_cwd_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_cwd_macos"

///|
extern "c" fn proc_root_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_root_macos"

///|
extern "c" fn proc_disk_usage_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_disk_usage_macos"

///|
extern "c" fn proc_open_files_macos_ffi(pid : Int) -> Int = "sysinfo_proc_open_files_macos"

///|
fn sleep_millis_native(millis : Int) -> Unit {
  if millis > 0 {
    sleep_millis_ffi(millis)
  }
}

///|
fn decode_bytes(bytes : Bytes) -> String {
  @utf8.decode_lossy(bytes)
}

///|
fn decode_bytes_trimmed(bytes : Bytes) -> String {
  decode_bytes(bytes).trim().to_string()
}

///|
fn readlink_path(path : String) -> String? {
  let raw = decode_bytes_trimmed(readlink_path_ffi(@utf8.encode(path)))
  if raw == "" {
    None
  } else {
    Some(raw)
  }
}

///|
fn read_text_file(path : String) -> String? {
  Some(@fs.read_file_to_string(path)) catch {
    _ => None
  }
}

///|
fn read_text_file_or_empty(path : String) -> String {
  match read_text_file(path) {
    Some(s) => s
    None => ""
  }
}

///|
fn list_dir(path : String) -> Array[String] {
  @fs.read_dir(path) catch {
    _ => []
  }
}

///|
fn file_exists(path : String) -> Bool {
  @fs.path_exists(path)
}

///|
fn split_non_empty_lines(s : String) -> Array[String] {
  let out : Array[String] = []
  for line in s.split("\n") {
    let trimmed = line.trim().to_string()
    if trimmed != "" {
      out.push(trimmed)
    }
  }
  out
}

///|
fn split_whitespace(s : String) -> Array[String] {
  let out : Array[String] = []
  let mut cur = StringBuilder::new()
  for ch in s {
    if ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' {
      if !cur.is_empty() {
        out.push(cur.to_string())
        cur = StringBuilder::new()
      }
    } else {
      cur.write_char(ch)
    }
  }
  if !cur.is_empty() {
    out.push(cur.to_string())
  }
  out
}

///|
fn parse_int_or(value : String, fallback : Int) -> Int {
  @string.parse_int(value) catch {
    _ => fallback
  }
}

///|
fn parse_uint64_or(value : String, fallback : UInt64) -> UInt64 {
  @string.parse_uint64(value) catch {
    _ => fallback
  }
}

///|
fn parse_double_or(value : String, fallback : Double) -> Double {
  @string.parse_double(value) catch {
    _ => fallback
  }
}

///|
fn map_signal_to_native(signal : Signal) -> Int {
  match signal {
    Signal::Abort => 6
    Signal::Alarm => 14
    Signal::Bus => 10
    Signal::Child => 17
    Signal::Continue => 18
    Signal::FloatingPointException => 8
    Signal::Hangup => 1
    Signal::IO => 29
    Signal::IOT => 6
    Signal::Illegal => 4
    Signal::Interrupt => 2
    Signal::Kill => 9
    Signal::Pipe => 13
    Signal::Poll => 29
    Signal::Power => 30
    Signal::Profiling => 27
    Signal::Quit => 3
    Signal::Segv => 11
    Signal::Stop => 19
    Signal::Sys => 31
    Signal::TSTP => 20
    Signal::TTIN => 21
    Signal::TTOU => 22
    Signal::Term => 15
    Signal::Trap => 5
    Signal::Urgent => 23
    Signal::User1 => 10
    Signal::User2 => 12
    Signal::VirtualAlarm => 26
    Signal::Winch => 28
    Signal::XCPU => 24
    Signal::XFSZ => 25
  }
}

///|
fn get_os_tag() -> String {
  decode_bytes(os_tag_ffi()).trim().to_string()
}

///|
fn process_exists(pid : Pid) -> Bool {
  process_exists_ffi(pid.to_int()) == 1
}

///|
pub fn get_current_pid() -> Result[Pid, String] {
  let pid = get_pid_ffi()
  if pid > 0 {
    Ok(Pid::from(pid))
  } else {
    Err("failed to get current pid")
  }
}

///|
fn read_macos_process_args(pid : Pid) -> Array[String] {
  split_non_empty_lines(decode_bytes(proc_args_macos_ffi(pid.to_int())))
}

///|
fn read_macos_process_environ(pid : Pid) -> Array[String] {
  split_non_empty_lines(decode_bytes(proc_environ_macos_ffi(pid.to_int())))
}

///|
fn read_macos_process_path(path_kind : String, pid : Pid) -> String? {
  let from_proc = if path_kind == "exe" {
    decode_bytes(proc_exe_macos_ffi(pid.to_int()))
  } else if path_kind == "cwd" {
    decode_bytes(proc_cwd_macos_ffi(pid.to_int()))
  } else {
    decode_bytes(proc_root_macos_ffi(pid.to_int()))
  }
  let trimmed = from_proc.trim().to_string()
  if trimmed == "" {
    None
  } else {
    Some(trimmed)
  }
}

///|
fn read_macos_process_disk_usage(pid : Pid, previous : DiskUsage) -> DiskUsage {
  let fields = split_whitespace(
    decode_bytes(proc_disk_usage_macos_ffi(pid.to_int())),
  )
  if fields.length() < 2 {
    return DiskUsage::zero()
  }
  let total_read = parse_uint64_or(fields[0], 0UL)
  let total_written = parse_uint64_or(fields[1], 0UL)
  {
    total_read_bytes: total_read,
    read_bytes: if total_read >= previous.total_read_bytes {
      total_read - previous.total_read_bytes
    } else {
      0UL
    },
    total_written_bytes: total_written,
    written_bytes: if total_written >= previous.total_written_bytes {
      total_written - previous.total_written_bytes
    } else {
      0UL
    },
  }
}

///|
fn read_macos_process_open_files(pid : Pid) -> Int? {
  let value = proc_open_files_macos_ffi(pid.to_int())
  if value >= 0 {
    Some(value)
  } else {
    None
  }
}