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

///|
struct CpuSnapshot {
  total : UInt64
  idle : UInt64
} derive(Show, Eq)

///|
pub(all) struct System {
  mut processes_data : Map[Pid, Process]
  mut cpus_data : Array[Cpu]
  mut total_memory_data : UInt64
  mut free_memory_data : UInt64
  mut available_memory_data : UInt64
  mut used_memory_data : UInt64
  mut total_swap_data : UInt64
  mut free_swap_data : UInt64
  mut used_swap_data : UInt64
  mut cgroup_limits_data : CGroupLimits?
  mut last_cpu_refresh_ms : UInt64
  mut prev_cpu_snapshots : Array[CpuSnapshot]
  mut prev_global_cpu_snapshot : CpuSnapshot?
  mut prev_proc_cpu_total : Map[Pid, UInt64]
  mut prev_proc_system_total : UInt64
}

///|
pub fn set_open_files_limit(new_limit : Int) -> Bool {
  if get_os_tag() == "linux" {
    set_open_files_limit_ffi(new_limit) == 1
  } else {
    false
  }
}

///|
pub fn System::new() -> System {
  System::new_with_specifics(RefreshKind::nothing())
}

///|
pub fn System::new_all() -> System {
  System::new_with_specifics(RefreshKind::everything())
}

///|
pub fn System::new_with_specifics(refreshes : RefreshKind) -> System {
  let s : System = {
    processes_data: {},
    cpus_data: [],
    total_memory_data: 0UL,
    free_memory_data: 0UL,
    available_memory_data: 0UL,
    used_memory_data: 0UL,
    total_swap_data: 0UL,
    free_swap_data: 0UL,
    used_swap_data: 0UL,
    cgroup_limits_data: None,
    last_cpu_refresh_ms: 0UL,
    prev_cpu_snapshots: [],
    prev_global_cpu_snapshot: None,
    prev_proc_cpu_total: {},
    prev_proc_system_total: 0UL,
  }
  s.refresh_specifics(refreshes)
  s
}

///|
pub fn System::refresh_specifics(
  self : System,
  refreshes : RefreshKind,
) -> Unit {
  match refreshes.memory() {
    Some(kind) => self.refresh_memory_specifics(kind)
    None => ()
  }
  match refreshes.cpu() {
    Some(kind) => self.refresh_cpu_specifics(kind)
    None => ()
  }
  match refreshes.processes() {
    Some(kind) =>
      ignore(
        self.refresh_processes_specifics(ProcessesToUpdate::All, true, kind),
      )
    None => ()
  }
}

///|
pub fn System::refresh_all(self : System) -> Unit {
  self.refresh_memory()
  self.refresh_cpu_all()
  ignore(self.refresh_processes(ProcessesToUpdate::All, true))
}

///|
pub fn System::refresh_memory(self : System) -> Unit {
  self.refresh_memory_specifics(MemoryRefreshKind::everything())
}

///|
pub fn System::refresh_memory_specifics(
  self : System,
  refresh_kind : MemoryRefreshKind,
) -> Unit {
  if !refresh_kind.ram() && !refresh_kind.swap() {
    return
  }
  if get_os_tag() == "linux" {
    let mem = parse_linux_meminfo()
    if refresh_kind.ram() {
      let total = mem.get_or_default("MemTotal", 0UL) * 1024UL
      let free = mem.get_or_default("MemFree", 0UL) * 1024UL
      let available = mem.get_or_default("MemAvailable", free / 1024UL) * 1024UL
      self.total_memory_data = total
      self.free_memory_data = free
      self.available_memory_data = available
      self.used_memory_data = if total > available {
        total - available
      } else {
        0UL
      }
    }
    if refresh_kind.swap() {
      let total_swap = mem.get_or_default("SwapTotal", 0UL) * 1024UL
      let free_swap = mem.get_or_default("SwapFree", 0UL) * 1024UL
      self.total_swap_data = total_swap
      self.free_swap_data = free_swap
      self.used_swap_data = if total_swap > free_swap {
        total_swap - free_swap
      } else {
        0UL
      }
    }
    self.cgroup_limits_data = read_cgroup_limits_linux(self.total_memory_data)
  } else if get_os_tag() == "macos" {
    let stats = split_whitespace(decode_bytes(macos_memory_stats_ffi()))
    let total = if stats.length() >= 1 {
      parse_uint64_or(stats[0], 0UL)
    } else {
      0UL
    }
    let free = if stats.length() >= 2 {
      parse_uint64_or(stats[1], 0UL)
    } else {
      0UL
    }
    let available = if stats.length() >= 3 {
      parse_uint64_or(stats[2], 0UL)
    } else {
      free
    }
    let total_swap = if stats.length() >= 4 {
      parse_uint64_or(stats[3], 0UL)
    } else {
      0UL
    }
    let used_swap = if stats.length() >= 5 {
      parse_uint64_or(stats[4], 0UL)
    } else {
      0UL
    }
    if refresh_kind.ram() {
      self.total_memory_data = total
      self.free_memory_data = free
      self.available_memory_data = available
      self.used_memory_data = if total > available {
        total - available
      } else {
        0UL
      }
    }
    if refresh_kind.swap() {
      let free_swap = if total_swap > used_swap {
        total_swap - used_swap
      } else {
        0UL
      }
      self.total_swap_data = total_swap
      self.free_swap_data = free_swap
      self.used_swap_data = used_swap
    }
    self.cgroup_limits_data = None
  }
}

///|
pub fn System::refresh_cpu_usage(self : System) -> Unit {
  self.refresh_cpu_specifics(CpuRefreshKind::nothing().with_cpu_usage())
}

///|
pub fn System::refresh_cpu_frequency(self : System) -> Unit {
  self.refresh_cpu_specifics(CpuRefreshKind::nothing().with_frequency())
}

///|
pub fn System::refresh_cpu_list(
  self : System,
  refresh_kind : CpuRefreshKind,
) -> Unit {
  self.refresh_cpu_specifics(refresh_kind)
}

///|
pub fn System::refresh_cpu_all(self : System) -> Unit {
  self.refresh_cpu_specifics(CpuRefreshKind::everything())
}

///|
pub fn System::refresh_cpu_specifics(
  self : System,
  refresh_kind : CpuRefreshKind,
) -> Unit {
  if !refresh_kind.cpu_usage() && !refresh_kind.frequency() {
    return
  }
  if get_os_tag() == "linux" {
    refresh_cpu_linux(self, refresh_kind)
  } else if get_os_tag() == "macos" {
    refresh_cpu_macos(self, refresh_kind)
  }
}

///|
pub fn System::refresh_processes(
  self : System,
  processes_to_update : ProcessesToUpdate,
  remove_dead_processes : Bool,
) -> Int {
  self.refresh_processes_specifics(
    processes_to_update,
    remove_dead_processes,
    ProcessRefreshKind::nothing()
    .with_cpu()
    .with_memory()
    .with_disk_usage()
    .with_user(UpdateKind::OnlyIfNotSet),
  )
}

///|
fn merge_update_array_field(
  kind : UpdateKind,
  old_value : Array[String],
  new_value : Array[String],
) -> Array[String] {
  match kind {
    UpdateKind::Always => new_value
    UpdateKind::OnlyIfNotSet =>
      if old_value.is_empty() {
        new_value
      } else {
        old_value
      }
    UpdateKind::Never => old_value
  }
}

///|
fn[T] merge_update_option_field(
  kind : UpdateKind,
  old_value : T?,
  new_value : T?,
) -> T? {
  match kind {
    UpdateKind::Always => new_value
    UpdateKind::OnlyIfNotSet =>
      match old_value {
        Some(_) => old_value
        None => new_value
      }
    UpdateKind::Never => old_value
  }
}

///|
fn merge_process_with_refresh_kind(
  previous : Process?,
  current : Process,
  refresh_kind : ProcessRefreshKind,
) -> Process {
  match previous {
    None => current
    Some(old) => {
      let cmd = merge_update_array_field(
        refresh_kind.cmd(),
        old.cmd(),
        current.cmd(),
      )
      let environ = merge_update_array_field(
        refresh_kind.environ(),
        old.environ(),
        current.environ(),
      )
      let exe = merge_update_option_field(
        refresh_kind.exe(),
        old.exe(),
        current.exe(),
      )
      let cwd = merge_update_option_field(
        refresh_kind.cwd(),
        old.cwd(),
        current.cwd(),
      )
      let root = merge_update_option_field(
        refresh_kind.root(),
        old.root(),
        current.root(),
      )
      let user_id = merge_update_option_field(
        refresh_kind.user(),
        old.user_id(),
        current.user_id(),
      )
      let effective_user_id = merge_update_option_field(
        refresh_kind.user(),
        old.effective_user_id(),
        current.effective_user_id(),
      )
      let group_id = merge_update_option_field(
        refresh_kind.user(),
        old.group_id(),
        current.group_id(),
      )
      let effective_group_id = merge_update_option_field(
        refresh_kind.user(),
        old.effective_group_id(),
        current.effective_group_id(),
      )
      {
        pid: current.pid(),
        parent: current.parent(),
        name: current.name(),
        cmd,
        environ,
        exe,
        cwd,
        root,
        memory: if refresh_kind.memory() {
          current.memory()
        } else {
          old.memory()
        },
        virtual_memory: if refresh_kind.memory() {
          current.virtual_memory()
        } else {
          old.virtual_memory()
        },
        status: current.status(),
        start_time: current.start_time(),
        run_time: current.run_time(),
        cpu_usage: if refresh_kind.cpu() {
          current.cpu_usage()
        } else {
          old.cpu_usage()
        },
        accumulated_cpu_time: if refresh_kind.cpu() {
          current.accumulated_cpu_time()
        } else {
          old.accumulated_cpu_time()
        },
        disk_usage: if refresh_kind.disk_usage() {
          current.disk_usage()
        } else {
          old.disk_usage()
        },
        user_id,
        effective_user_id,
        group_id,
        effective_group_id,
        session_id: current.session_id(),
        tasks: if refresh_kind.tasks() {
          current.tasks()
        } else {
          old.tasks()
        },
        thread_kind: current.thread_kind(),
        open_files: current.open_files(),
        open_files_limit: current.open_files_limit(),
      }
    }
  }
}

///|
pub fn System::refresh_processes_specifics(
  self : System,
  processes_to_update : ProcessesToUpdate,
  remove_dead_processes : Bool,
  refresh_kind : ProcessRefreshKind,
) -> Int {
  let selected_pids = match processes_to_update {
    ProcessesToUpdate::All => None
    ProcessesToUpdate::Selected(pids) => Some(pids)
  }
  let all_processes = if get_os_tag() == "linux" {
    collect_processes_linux(self, refresh_kind, selected_pids)
  } else if get_os_tag() == "macos" {
    collect_processes_macos(self, refresh_kind, selected_pids)
  } else {
    {}
  }

  let mut updated = 0
  match processes_to_update {
    ProcessesToUpdate::All => {
      updated = all_processes.length()
      if remove_dead_processes {
        self.processes_data = all_processes
      } else {
        for pid, process in all_processes {
          self.processes_data[pid] = merge_process_with_refresh_kind(
            self.processes_data.get(pid),
            process,
            refresh_kind,
          )
        }
      }
    }
    ProcessesToUpdate::Selected(pids) =>
      for pid in pids {
        match all_processes.get(pid) {
          Some(process) => {
            self.processes_data[pid] = merge_process_with_refresh_kind(
              self.processes_data.get(pid),
              process,
              refresh_kind,
            )
            updated = updated + 1
          }
          None => if remove_dead_processes { self.processes_data.remove(pid) }
        }
      }
  }

  let next_cpu_totals : Map[Pid, UInt64] = {}
  for pid, process in self.processes_data {
    next_cpu_totals[pid] = process.accumulated_cpu_time()
  }
  self.prev_proc_cpu_total = next_cpu_totals

  updated
}

///|
pub fn System::processes(self : System) -> Map[Pid, Process] {
  self.processes_data
}

///|
pub fn System::process(self : System, pid : Pid) -> Process? {
  self.processes_data.get(pid)
}

///|
pub fn System::processes_by_name(
  self : System,
  name : String,
) -> Array[(Pid, Process)] {
  let out : Array[(Pid, Process)] = []
  for pid, process in self.processes_data {
    if process.name().to_lower().contains(name.to_lower()) {
      out.push((pid, process))
    }
  }
  out
}

///|
pub fn System::processes_by_exact_name(
  self : System,
  name : String,
) -> Array[(Pid, Process)] {
  let out : Array[(Pid, Process)] = []
  for pid, process in self.processes_data {
    if process.name() == name {
      out.push((pid, process))
    }
  }
  out
}

///|
pub fn System::global_cpu_usage(self : System) -> Double {
  if self.cpus_data.is_empty() {
    0.0
  } else {
    let mut sum = 0.0
    for cpu in self.cpus_data {
      sum += cpu.cpu_usage()
    }
    sum / self.cpus_data.length().to_double()
  }
}

///|
pub fn System::cpus(self : System) -> Array[Cpu] {
  self.cpus_data
}

///|
pub fn System::total_memory(self : System) -> UInt64 {
  self.total_memory_data
}

///|
pub fn System::free_memory(self : System) -> UInt64 {
  self.free_memory_data
}

///|
pub fn System::available_memory(self : System) -> UInt64 {
  self.available_memory_data
}

///|
pub fn System::used_memory(self : System) -> UInt64 {
  self.used_memory_data
}

///|
pub fn System::total_swap(self : System) -> UInt64 {
  self.total_swap_data
}

///|
pub fn System::free_swap(self : System) -> UInt64 {
  self.free_swap_data
}

///|
pub fn System::used_swap(self : System) -> UInt64 {
  self.used_swap_data
}

///|
pub fn System::cgroup_limits(self : System) -> CGroupLimits? {
  self.cgroup_limits_data
}

///|
pub fn System::uptime() -> UInt64 {
  if get_os_tag() == "unknown" {
    0UL
  } else {
    uptime_seconds_ffi()
  }
}

///|
pub fn System::boot_time() -> UInt64 {
  let boot = boot_time_seconds_ffi()
  if boot <= 0L {
    0UL
  } else {
    boot.to_int().to_uint64()
  }
}

///|
pub fn System::load_average() -> LoadAvg {
  let fields = split_whitespace(decode_bytes(load_average_ffi()))
  if fields.length() >= 3 {
    {
      one: parse_double_or(fields[0], 0.0),
      five: parse_double_or(fields[1], 0.0),
      fifteen: parse_double_or(fields[2], 0.0),
    }
  } else {
    LoadAvg::zero()
  }
}

///|
pub fn System::name() -> String? {
  let os = get_os_tag()
  if os == "linux" {
    Some("Linux")
  } else if os == "macos" {
    Some("macOS")
  } else {
    None
  }
}

///|
pub fn System::kernel_version() -> String? {
  let value = decode_bytes_trimmed(kernel_version_ffi())
  if value == "" {
    None
  } else {
    Some(value)
  }
}

///|
pub fn System::os_version() -> String? {
  if get_os_tag() == "linux" {
    let content = read_text_file_or_empty("/etc/os-release")
    for line in content.split("\n") {
      if line.has_prefix("VERSION_ID=") {
        return Some(line[11:].trim(chars="\"").to_string())
      }
    }
    None
  } else if get_os_tag() == "macos" {
    let v = decode_bytes_trimmed(macos_os_version_ffi())
    if v == "" {
      None
    } else {
      Some(v)
    }
  } else {
    None
  }
}

///|
pub fn System::long_os_version() -> String? {
  if get_os_tag() == "linux" {
    let content = read_text_file_or_empty("/etc/os-release")
    for line in content.split("\n") {
      if line.has_prefix("PRETTY_NAME=") {
        return Some(line[12:].trim(chars="\"").to_string())
      }
    }
    None
  } else if get_os_tag() == "macos" {
    let version = decode_bytes_trimmed(macos_os_version_ffi())
    if version == "" {
      None
    } else {
      Some("macOS \{version}")
    }
  } else {
    None
  }
}

///|
pub fn System::distribution_id() -> String {
  if get_os_tag() == "linux" {
    let content = read_text_file_or_empty("/etc/os-release")
    for line in content.split("\n") {
      if line.has_prefix("ID=") {
        return line[3:].trim(chars="\"").to_string()
      }
    }
    "linux"
  } else if get_os_tag() == "macos" {
    "macos"
  } else {
    "unknown"
  }
}

///|
pub fn System::distribution_id_like() -> Array[String] {
  if get_os_tag() == "linux" {
    let content = read_text_file_or_empty("/etc/os-release")
    for line in content.split("\n") {
      if line.has_prefix("ID_LIKE=") {
        return split_whitespace(
          line[8:].trim(chars="\"").replace(old=" ", new=" ").to_string(),
        )
      }
    }
    []
  } else if get_os_tag() == "macos" {
    ["darwin"]
  } else {
    []
  }
}

///|
pub fn System::kernel_long_version() -> String {
  decode_bytes_trimmed(kernel_long_version_ffi())
}

///|
pub fn System::host_name() -> String? {
  let h = decode_bytes_trimmed(host_name_ffi())
  if h == "" {
    None
  } else {
    Some(h)
  }
}

///|
pub fn System::cpu_arch() -> String {
  let arch = decode_bytes_trimmed(cpu_arch_ffi())
  if arch == "" {
    "unknown"
  } else {
    arch
  }
}

///|
pub fn System::physical_core_count() -> Int? {
  if get_os_tag() == "unknown" {
    None
  } else {
    let count = physical_cpu_count_ffi()
    if count > 0 {
      Some(count)
    } else {
      None
    }
  }
}

///|
pub fn System::open_files_limit() -> Int? {
  if get_os_tag() == "unknown" {
    return None
  }
  let value = get_open_files_limit_ffi()
  if value > 0 {
    Some(value)
  } else {
    None
  }
}

///|
fn parse_linux_meminfo() -> Map[String, UInt64] {
  let out : Map[String, UInt64] = {}
  match read_text_file("/proc/meminfo") {
    Some(content) =>
      for line in content.split("\n") {
        let trimmed = line.trim().to_string()
        if trimmed == "" {
          continue
        }
        let parts = split_whitespace(trimmed)
        if parts.length() >= 2 {
          let key = parts[0].trim(chars=":").to_string()
          let value = parse_uint64_or(parts[1], 0UL)
          out[key] = value
        }
      }
    None => ()
  }
  out
}

///|
fn read_cgroup_value(path : String) -> UInt64? {
  match read_text_file(path) {
    Some(content) => {
      let v = content.trim().to_string()
      if v == "" || v == "max" {
        None
      } else {
        Some(parse_uint64_or(v, 0UL))
      }
    }
    None => None
  }
}

///|
fn read_cgroup_limits_linux(total_memory : UInt64) -> CGroupLimits? {
  let mem_max = read_cgroup_value("/sys/fs/cgroup/memory.max")
  let mem_current = read_cgroup_value("/sys/fs/cgroup/memory.current")
  match (mem_max, mem_current) {
    (Some(max_v), Some(cur_v)) => {
      let swap_max = read_cgroup_value("/sys/fs/cgroup/memory.swap.max")
      let swap_current = read_cgroup_value("/sys/fs/cgroup/memory.swap.current")
      let free_mem = if max_v > cur_v { max_v - cur_v } else { 0UL }
      let free_swap = match (swap_max, swap_current) {
        (Some(sm), Some(sc)) => if sm > sc { sm - sc } else { 0UL }
        _ => 0UL
      }
      Some({
        total_memory: if max_v == 0UL {
          total_memory
        } else {
          max_v
        },
        free_memory: free_mem,
        free_swap,
      })
    }
    _ => None
  }
}

///|
fn parse_cpu_line(line : String) -> (String, CpuSnapshot)? {
  let fields = split_whitespace(line)
  if fields.length() < 5 || !fields[0].has_prefix("cpu") {
    return None
  }
  let mut total = 0UL
  for i in 1..= 6 {
    idle += parse_uint64_or(fields[5], 0UL)
  }
  Some((fields[0], { total, idle }))
}

///|
fn split_tab_fields(line : String) -> Array[String] {
  line.split("\t").to_array().map(part => part.to_string())
}

///|
fn refresh_cpu_linux(system : System, refresh_kind : CpuRefreshKind) -> Unit {
  match read_text_file("/proc/stat") {
    Some(content) => {
      let mut global : CpuSnapshot? = None
      let per_cpu : Array[(String, CpuSnapshot)] = []
      for line in content.split("\n") {
        let trimmed = line.trim().to_string()
        if !trimmed.has_prefix("cpu") {
          continue
        }
        match parse_cpu_line(trimmed) {
          Some((name, snapshot)) =>
            if name == "cpu" {
              global = Some(snapshot)
            } else {
              per_cpu.push((name, snapshot))
            }
          None => ()
        }
      }

      if system.cpus_data.length() != per_cpu.length() {
        system.cpus_data = []
        system.prev_cpu_snapshots = []
        let vendor = parse_linux_cpuinfo_value("vendor_id")
        let brand = parse_linux_cpuinfo_value("model name")
        let mut frequency = 0UL
        if refresh_kind.frequency() {
          let mhz = parse_double_or(parse_linux_cpuinfo_value("cpu MHz"), 0.0)
          if mhz > 0.0 {
            frequency = mhz.to_int().to_uint64()
          }
        }
        for pair in per_cpu {
          let (cpu_name, _) = pair
          system.cpus_data.push({
            name: cpu_name,
            vendor_id: vendor,
            brand,
            frequency,
            cpu_usage: 0.0,
          })
          system.prev_cpu_snapshots.push({ total: 0UL, idle: 0UL })
        }
      }

      if refresh_kind.frequency() {
        let mhz = parse_double_or(parse_linux_cpuinfo_value("cpu MHz"), 0.0)
        let freq = if mhz > 0.0 { mhz.to_int().to_uint64() } else { 0UL }
        for cpu in system.cpus_data {
          cpu.frequency = freq
        }
      }

      if refresh_kind.cpu_usage() {
        for i in 0.. ()
  }
}

///|
fn refresh_cpu_macos(system : System, refresh_kind : CpuRefreshKind) -> Unit {
  let cpu_info = split_tab_fields(decode_bytes(macos_cpu_info_ffi()))
  let logical_count = if cpu_info.length() >= 1 {
    Int::max(parse_int_or(cpu_info[0], 1), 1)
  } else {
    Int::max(logical_cpu_count_ffi(), 1)
  }
  let vendor = if cpu_info.length() >= 2 && cpu_info[1] != "" {
    cpu_info[1]
  } else {
    "Apple"
  }
  let brand = if cpu_info.length() >= 3 && cpu_info[2] != "" {
    cpu_info[2]
  } else {
    "Apple Silicon"
  }
  let frequency_mhz = if cpu_info.length() >= 4 {
    parse_uint64_or(cpu_info[3], 0UL) / 1000000UL
  } else {
    0UL
  }

  if system.cpus_data.length() != logical_count {
    system.cpus_data = []
    system.prev_cpu_snapshots = []
    for i in 0.. Array[CpuSnapshot] {
  let out : Array[CpuSnapshot] = []
  for line in raw.split("\n") {
    let fields = split_whitespace(line.trim().to_string())
    if fields.length() < 2 {
      continue
    }
    let total = parse_uint64_or(fields[0], 0UL)
    let idle = parse_uint64_or(fields[1], 0UL)
    out.push({ total, idle })
  }
  out
}

///|
fn compute_usage(previous : CpuSnapshot, current : CpuSnapshot) -> Double {
  if previous.total == 0UL || current.total <= previous.total {
    0.0
  } else {
    let diff_total = current.total - previous.total
    let diff_idle = if current.idle > previous.idle {
      current.idle - previous.idle
    } else {
      0UL
    }
    if diff_total == 0UL {
      0.0
    } else {
      let active = if diff_idle >= diff_total {
        0UL
      } else {
        diff_total - diff_idle
      }
      active.to_double() * 100.0 / diff_total.to_double()
    }
  }
}

///|
fn parse_linux_cpuinfo_value(key : String) -> String {
  match read_text_file("/proc/cpuinfo") {
    Some(content) => {
      for line in content.split("\n") {
        if line.has_prefix("\{key}") {
          let parts = line.split(":").to_array()
          if parts.length() >= 2 {
            return parts[1].trim().to_string()
          }
        }
      }
      ""
    }
    None => ""
  }
}

///|
fn read_system_total_jiffies_linux() -> UInt64 {
  match read_text_file("/proc/stat") {
    Some(content) => {
      for line in content.split("\n") {
        let t = line.trim().to_string()
        if t.has_prefix("cpu ") {
          let fields = split_whitespace(t)
          let mut total = 0UL
          for i in 1.. 0UL
  }
}

///|
fn parse_linux_proc_key_values(path : String) -> Map[String, String] {
  let out : Map[String, String] = {}
  match read_text_file(path) {
    Some(content) =>
      for line in content.split("\n") {
        match line.find(":") {
          Some(idx) => {
            let key = line[:idx].trim().to_string()
            let value = line[idx + 1:].trim().to_string()
            out[key] = value
          }
          None => ()
        }
      }
    None => ()
  }
  out
}

///|
fn parse_proc_kb_value(value : String) -> UInt64 {
  let fields = split_whitespace(value)
  if fields.is_empty() {
    0UL
  } else {
    parse_uint64_or(fields[0], 0UL)
  }
}

///|
fn parse_null_separated_file(path : String) -> Array[String] {
  let bytes = @fs.read_file_to_bytes(path) catch { _ => b"" }
  if bytes.length() == 0 {
    return []
  }
  let out : Array[String] = []
  let mut start = 0
  for i in 0.. start {
        out.push(@utf8.decode_lossy(bytes[start:i]))
      }
      start = i + 1
    }
  }
  if start < bytes.length() {
    out.push(@utf8.decode_lossy(bytes[start:]))
  }
  out
}

///|
fn parse_uid_pair(value : String) -> (Uid?, Uid?) {
  let fields = split_whitespace(value)
  if fields.length() >= 2 {
    let uid0 = parse_int_or(fields[0], -1)
    let uid1 = parse_int_or(fields[1], -1)
    (
      if uid0 >= 0 {
        Some(Uid::from(uid0))
      } else {
        None
      },
      if uid1 >= 0 {
        Some(Uid::from(uid1))
      } else {
        None
      },
    )
  } else {
    (None, None)
  }
}

///|
fn parse_gid_pair(value : String) -> (Gid?, Gid?) {
  let fields = split_whitespace(value)
  if fields.length() >= 2 {
    let gid0 = parse_int_or(fields[0], -1)
    let gid1 = parse_int_or(fields[1], -1)
    (
      if gid0 >= 0 {
        Some(Gid::from(gid0))
      } else {
        None
      },
      if gid1 >= 0 {
        Some(Gid::from(gid1))
      } else {
        None
      },
    )
  } else {
    (None, None)
  }
}

///|
fn parse_proc_disk_usage(path : String, previous : DiskUsage) -> DiskUsage {
  let kv = parse_linux_proc_key_values(path)
  let total_read = parse_uint64_or(kv.get_or_default("read_bytes", "0"), 0UL)
  let total_written = parse_uint64_or(
    kv.get_or_default("write_bytes", "0"),
    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 parse_proc_stat_linux(
  stat_text : String,
) -> (String, ProcessStatus, Pid?, Pid?, UInt64, UInt64)? {
  let left = stat_text.find("(")
  let right = stat_text.rev_find(")")
  match (left, right) {
    (Some(l), Some(r)) => {
      if r <= l || r + 2 > stat_text.length() {
        return None
      }
      let name = stat_text[l + 1:r].to_string()
      let fields = split_whitespace(stat_text[r + 2:].to_string())
      if fields.length() < 20 {
        return None
      }
      let status = map_status_char(fields[0][:1].to_string())
      let ppid_i = parse_int_or(fields[1], -1)
      let parent = if ppid_i > 0 { Some(Pid::from(ppid_i)) } else { None }
      let sid_i = parse_int_or(fields[3], -1)
      let session = if sid_i > 0 { Some(Pid::from(sid_i)) } else { None }
      let utime = parse_uint64_or(fields[11], 0UL)
      let stime = parse_uint64_or(fields[12], 0UL)
      let start_ticks = parse_uint64_or(fields[19], 0UL)
      Some((name, status, parent, session, utime + stime, start_ticks))
    }
    _ => None
  }
}

///|
fn map_status_char(ch : String) -> ProcessStatus {
  if ch == "R" {
    ProcessStatus::Run
  } else if ch == "S" {
    ProcessStatus::Sleep
  } else if ch == "D" {
    ProcessStatus::UninterruptibleDiskSleep
  } else if ch == "K" {
    ProcessStatus::Wakekill
  } else if ch == "W" {
    ProcessStatus::Waking
  } else if ch == "P" {
    ProcessStatus::Parked
  } else if ch == "Z" {
    ProcessStatus::Zombie
  } else if ch == "T" {
    ProcessStatus::Stop
  } else if ch == "t" {
    ProcessStatus::Tracing
  } else if ch == "X" || ch == "x" {
    ProcessStatus::Dead
  } else if ch == "L" {
    ProcessStatus::LockBlocked
  } else {
    ProcessStatus::Unknown(0)
  }
}

///|
fn collect_processes_linux(
  system : System,
  refresh_kind : ProcessRefreshKind,
  selected_pids : Array[Pid]?,
) -> Map[Pid, Process] {
  let out : Map[Pid, Process] = {}
  let entries = match selected_pids {
    Some(pids) => pids.map(pid => pid.to_int().to_string())
    None => list_dir("/proc")
  }
  let now_s = @env.now() / 1000UL
  let boot_time = System::boot_time()
  let ticks_per_second = Int::max(clock_ticks_per_second_ffi(), 100)
  let system_total_jiffies = read_system_total_jiffies_linux()
  let prev_system_total = system.prev_proc_system_total
  let cpu_count = Int::max(system.cpus_data.length(), 1).to_double()

  for entry in entries {
    let pid_i = parse_int_or(entry, -1)
    if pid_i <= 0 {
      continue
    }
    let pid = Pid::from(pid_i)
    let proc_dir = "/proc/\{pid_i}"
    let stat_text = read_text_file_or_empty("\{proc_dir}/stat")
    if stat_text == "" {
      continue
    }
    match parse_proc_stat_linux(stat_text) {
      Some((name, status, parent, session_id, proc_total, start_ticks)) => {
        let status_map = parse_linux_proc_key_values("\{proc_dir}/status")
        let cmd = if refresh_kind.cmd() == UpdateKind::Never {
          []
        } else {
          let parsed = parse_null_separated_file("\{proc_dir}/cmdline")
          if parsed.is_empty() {
            [name]
          } else {
            parsed
          }
        }
        let environ = if refresh_kind.environ() == UpdateKind::Never {
          []
        } else {
          parse_null_separated_file("\{proc_dir}/environ")
        }
        let exe = if refresh_kind.exe() == UpdateKind::Never {
          None
        } else {
          readlink_path("\{proc_dir}/exe")
        }
        let cwd = if refresh_kind.cwd() == UpdateKind::Never {
          None
        } else {
          readlink_path("\{proc_dir}/cwd")
        }
        let root = if refresh_kind.root() == UpdateKind::Never {
          None
        } else {
          readlink_path("\{proc_dir}/root")
        }

        let (memory, virtual_memory) = if refresh_kind.memory() {
          (
            parse_proc_kb_value(status_map.get_or_default("VmRSS", "")) * 1024UL,
            parse_proc_kb_value(status_map.get_or_default("VmSize", "")) *
            1024UL,
          )
        } else {
          (0UL, 0UL)
        }

        let (user_id, effective_user_id) = if refresh_kind.user() ==
          UpdateKind::Never {
          (None, None)
        } else {
          parse_uid_pair(status_map.get_or_default("Uid", ""))
        }
        let (group_id, effective_group_id) = if refresh_kind.user() ==
          UpdateKind::Never {
          (None, None)
        } else {
          parse_gid_pair(status_map.get_or_default("Gid", ""))
        }

        let open_files = if file_exists("\{proc_dir}/fd") {
          Some(list_dir("\{proc_dir}/fd").length())
        } else {
          None
        }

        let tasks = if refresh_kind.tasks() {
          let tids : Array[Pid] = []
          for tid_entry in list_dir("\{proc_dir}/task") {
            let tid = parse_int_or(tid_entry, -1)
            if tid > 0 {
              tids.push(Pid::from(tid))
            }
          }
          Some(tids)
        } else {
          None
        }

        let previous_total = system.prev_proc_cpu_total.get_or_default(
          pid, proc_total,
        )
        let cpu_usage = if refresh_kind.cpu() {
          let diff_proc = if proc_total > previous_total {
            proc_total - previous_total
          } else {
            0UL
          }
          let diff_sys = if system_total_jiffies > prev_system_total {
            system_total_jiffies - prev_system_total
          } else {
            0UL
          }
          if diff_sys == 0UL {
            0.0
          } else {
            diff_proc.to_double() * 100.0 * cpu_count / diff_sys.to_double()
          }
        } else {
          0.0
        }
        system.prev_proc_cpu_total[pid] = proc_total

        let previous_disk_usage = match system.processes_data.get(pid) {
          Some(old_proc) => old_proc.disk_usage()
          None => DiskUsage::zero()
        }
        let disk_usage = if refresh_kind.disk_usage() {
          parse_proc_disk_usage("\{proc_dir}/io", previous_disk_usage)
        } else {
          DiskUsage::zero()
        }

        let start_time = if ticks_per_second > 0 {
          boot_time + start_ticks / ticks_per_second.to_uint64()
        } else {
          0UL
        }
        let run_time = if now_s > start_time { now_s - start_time } else { 0UL }

        out[pid] = {
          pid,
          parent,
          name,
          cmd,
          environ,
          exe,
          cwd,
          root,
          memory,
          virtual_memory,
          status,
          start_time,
          run_time,
          cpu_usage,
          accumulated_cpu_time: proc_total,
          disk_usage,
          user_id,
          effective_user_id,
          group_id,
          effective_group_id,
          session_id,
          tasks,
          thread_kind: None,
          open_files,
          open_files_limit: System::open_files_limit(),
        }

        match tasks {
          Some(tids) =>
            for tid in tids {
              if tid == pid {
                continue
              }
              let task_dir = "\{proc_dir}/task/\{tid.to_int()}"
              let task_stat_text = read_text_file_or_empty("\{task_dir}/stat")
              if task_stat_text == "" {
                continue
              }
              match parse_proc_stat_linux(task_stat_text) {
                Some(
                  (
                    task_name,
                    task_status,
                    _task_parent,
                    task_session_id,
                    task_total,
                    task_start_ticks,
                  )
                ) => {
                  let previous_task_total = system.prev_proc_cpu_total.get_or_default(
                    tid, task_total,
                  )
                  let task_cpu_usage = if refresh_kind.cpu() {
                    let diff_proc = if task_total > previous_task_total {
                      task_total - previous_task_total
                    } else {
                      0UL
                    }
                    let diff_sys = if system_total_jiffies > prev_system_total {
                      system_total_jiffies - prev_system_total
                    } else {
                      0UL
                    }
                    if diff_sys == 0UL {
                      0.0
                    } else {
                      diff_proc.to_double() *
                      100.0 *
                      cpu_count /
                      diff_sys.to_double()
                    }
                  } else {
                    0.0
                  }
                  system.prev_proc_cpu_total[tid] = task_total

                  let task_start_time = if ticks_per_second > 0 {
                    boot_time + task_start_ticks / ticks_per_second.to_uint64()
                  } else {
                    0UL
                  }
                  let task_run_time = if now_s > task_start_time {
                    now_s - task_start_time
                  } else {
                    0UL
                  }

                  out[tid] = {
                    pid: tid,
                    parent: Some(pid),
                    name: task_name,
                    cmd: [],
                    environ: [],
                    exe: None,
                    cwd: None,
                    root: None,
                    memory: 0UL,
                    virtual_memory: 0UL,
                    status: task_status,
                    start_time: task_start_time,
                    run_time: task_run_time,
                    cpu_usage: task_cpu_usage,
                    accumulated_cpu_time: task_total,
                    disk_usage: DiskUsage::zero(),
                    user_id,
                    effective_user_id,
                    group_id,
                    effective_group_id,
                    session_id: task_session_id,
                    tasks: None,
                    thread_kind: Some(ThreadKind::Userland),
                    open_files: None,
                    open_files_limit: None,
                  }
                }
                None => ()
              }
            }
          None => ()
        }
      }
      None => ()
    }
  }
  system.prev_proc_system_total = system_total_jiffies
  out
}

///|
fn collect_processes_macos(
  system : System,
  refresh_kind : ProcessRefreshKind,
  selected_pids : Array[Pid]?,
) -> Map[Pid, Process] {
  collect_processes_from_ps_macos(system, refresh_kind, selected_pids)
}

///|
fn collect_processes_from_ps_macos(
  system : System,
  refresh_kind : ProcessRefreshKind,
  selected_pids : Array[Pid]?,
) -> Map[Pid, Process] {
  let out : Map[Pid, Process] = {}
  let now_s = @env.now() / 1000UL
  let selected : Map[Int, Bool] = {}
  let has_selected = selected_pids is Some(_)
  let selected_empty = match selected_pids {
    Some(pids) => pids.is_empty()
    None => false
  }
  match selected_pids {
    Some(pids) =>
      for pid in pids {
        selected[pid.to_int()] = true
      }
    None => ()
  }
  if selected_empty {
    return out
  }
  let ticks_per_second = Int::max(clock_ticks_per_second_ffi(), 100)
  let lines = split_non_empty_lines(decode_bytes(macos_process_table_ffi()))
  for line in lines {
    let fields = split_tab_fields(line)
    if fields.length() < 13 {
      continue
    }
    let pid_i = parse_int_or(fields[0], -1)
    if has_selected && !selected.contains(pid_i) {
      continue
    }
    let pid = Pid::from(pid_i)
    if pid.to_int() <= 0 {
      continue
    }
    let ppid_val = parse_int_or(fields[1], -1)
    let ppid = if ppid_val > 0 { Some(Pid::from(ppid_val)) } else { None }
    let effective_uid_i = parse_int_or(fields[2], -1)
    let real_uid_i = parse_int_or(fields[3], -1)
    let effective_gid_i = parse_int_or(fields[4], -1)
    let real_gid_i = parse_int_or(fields[5], -1)
    let cpu = parse_double_or(fields[6], 0.0)
    let rss_kb = parse_uint64_or(fields[7], 0UL)
    let vsz_kb = parse_uint64_or(fields[8], 0UL)
    let status_char = fields[9]
    let status = if status_char == "" {
      ProcessStatus::Unknown(0)
    } else {
      map_status_char(status_char[:1].to_string())
    }
    let run_time = parse_uint64_or(fields[10], 0UL)
    let session_i = parse_int_or(fields[11], -1)
    let session_id = if session_i >= 0 {
      Some(Pid::from(session_i))
    } else {
      None
    }
    let name = fields[12]
    if name == "" || status == ProcessStatus::Zombie {
      continue
    }

    let cmd = if refresh_kind.cmd() == UpdateKind::Never {
      []
    } else {
      let parsed = read_macos_process_args(pid)
      if parsed.is_empty() {
        [name]
      } else {
        parsed
      }
    }

    let environ = if refresh_kind.environ() == UpdateKind::Never {
      []
    } else {
      read_macos_process_environ(pid)
    }

    let (user_id, effective_user_id, group_id, effective_group_id) = if refresh_kind.user() ==
      UpdateKind::Never {
      (None, None, None, None)
    } else {
      (
        if real_uid_i >= 0 {
          Some(Uid::from(real_uid_i))
        } else {
          None
        },
        if effective_uid_i >= 0 {
          Some(Uid::from(effective_uid_i))
        } else {
          None
        },
        if real_gid_i >= 0 {
          Some(Gid::from(real_gid_i))
        } else {
          None
        },
        if effective_gid_i >= 0 {
          Some(Gid::from(effective_gid_i))
        } else {
          None
        },
      )
    }

    let open_files = read_macos_process_open_files(pid)

    let prev_cpu_total = system.prev_proc_cpu_total.get_or_default(pid, 0UL)
    let cpu_total = if refresh_kind.cpu() && cpu > 0.0 {
      (cpu * run_time.to_double() * ticks_per_second.to_double() / 100.0)
      .to_int()
      .to_uint64()
    } else {
      prev_cpu_total
    }
    system.prev_proc_cpu_total[pid] = cpu_total

    let previous_disk_usage = match system.processes_data.get(pid) {
      Some(old_proc) => old_proc.disk_usage()
      None => DiskUsage::zero()
    }
    let disk_usage = if refresh_kind.disk_usage() {
      read_macos_process_disk_usage(pid, previous_disk_usage)
    } else {
      DiskUsage::zero()
    }

    out[pid] = {
      pid,
      parent: ppid,
      name,
      cmd,
      environ,
      exe: if refresh_kind.exe() == UpdateKind::Never {
        None
      } else {
        read_macos_process_path("exe", pid)
      },
      cwd: if refresh_kind.cwd() == UpdateKind::Never {
        None
      } else {
        read_macos_process_path("cwd", pid)
      },
      root: if refresh_kind.root() == UpdateKind::Never {
        None
      } else {
        read_macos_process_path("root", pid)
      },
      memory: if refresh_kind.memory() {
        rss_kb * 1024UL
      } else {
        0UL
      },
      virtual_memory: if refresh_kind.memory() {
        vsz_kb * 1024UL
      } else {
        0UL
      },
      status,
      start_time: if now_s > run_time {
        now_s - run_time
      } else {
        0UL
      },
      run_time,
      cpu_usage: if refresh_kind.cpu() {
        cpu
      } else {
        0.0
      },
      accumulated_cpu_time: cpu_total,
      disk_usage,
      user_id,
      effective_user_id,
      group_id,
      effective_group_id,
      session_id,
      tasks: if refresh_kind.tasks() {
        None
      } else {
        None
      },
      thread_kind: None,
      open_files,
      open_files_limit: System::open_files_limit(),
    }
  }
  out
}

///|
pub(all) struct Disks {
  mut disks_data : Array[Disk]
} derive(Show)

///|
pub fn Disks::new() -> Disks {
  { disks_data: [] }
}

///|
pub fn Disks::new_with_refreshed_list() -> Disks {
  Disks::new_with_refreshed_list_specifics(DiskRefreshKind::everything())
}

///|
pub fn Disks::new_with_refreshed_list_specifics(
  refreshes : DiskRefreshKind,
) -> Disks {
  let disks = Disks::new()
  disks.refresh_specifics(false, refreshes)
  disks
}

///|
pub fn Disks::list(self : Disks) -> Array[Disk] {
  self.disks_data
}

///|
pub fn Disks::list_mut(self : Disks) -> Array[Disk] {
  self.disks_data
}

///|
pub fn Disks::refresh(self : Disks, remove_not_listed_disks : Bool) -> Unit {
  self.refresh_specifics(remove_not_listed_disks, DiskRefreshKind::everything())
}

///|
pub fn Disks::refresh_specifics(
  self : Disks,
  remove_not_listed_disks : Bool,
  refreshes : DiskRefreshKind,
) -> Unit {
  let refreshed = collect_disks(self.disks_data, refreshes)
  if remove_not_listed_disks {
    self.disks_data = refreshed
  } else {
    self.disks_data = refreshed
  }
}

///|
pub fn Disk::refresh(self : Disk) -> Bool {
  self.refresh_specifics(DiskRefreshKind::everything())
}

///|
pub fn Disk::refresh_specifics(
  self : Disk,
  refreshes : DiskRefreshKind,
) -> Bool {
  let disks = collect_disks([self], refreshes)
  for disk in disks {
    if disk.mount_point() == self.mount_point() {
      self.kind = disk.kind
      self.total_space = disk.total_space
      self.available_space = disk.available_space
      self.usage = disk.usage
      return true
    }
  }
  false
}

///|
struct DiskIoSnapshot {
  total_read_bytes : UInt64
  total_written_bytes : UInt64
} derive(Show, Eq)

///|
fn DiskIoSnapshot::zero() -> DiskIoSnapshot {
  { total_read_bytes: 0UL, total_written_bytes: 0UL }
}

///|
struct MacosDiskInfo {
  io : DiskIoSnapshot
  kind : DiskKind
} derive(Show, Eq)

///|
struct MountEntry {
  file_system : String
  mount_point : String
  file_system_type : String
  total_space : UInt64
  available_space : UInt64
  is_read_only : Bool
  is_removable : Bool
} derive(Show, Eq)

///|
fn collect_disks(
  previous : Array[Disk],
  refreshes : DiskRefreshKind,
) -> Array[Disk] {
  if get_os_tag() == "linux" {
    collect_disks_linux(previous, refreshes)
  } else if get_os_tag() == "macos" {
    collect_disks_macos(previous, refreshes)
  } else {
    []
  }
}

///|
fn collect_disks_linux(
  previous : Array[Disk],
  refreshes : DiskRefreshKind,
) -> Array[Disk] {
  let out : Array[Disk] = []
  let stats = if refreshes.io_usage() { collect_linux_diskstats() } else { {} }
  let mounts = parse_mount_table_entries()
  for entry in mounts {
    let previous_usage = find_previous_disk_usage(previous, entry.mount_point)
    let io = if refreshes.io_usage() {
      find_linux_disk_io(stats, entry.file_system)
    } else {
      DiskIoSnapshot::zero()
    }
    out.push(
      build_disk_entry(
        entry.file_system,
        entry.mount_point,
        entry.total_space,
        entry.available_space,
        refreshes,
        previous_usage,
        io,
        infer_disk_kind(entry.file_system),
        entry.file_system_type,
        entry.is_read_only,
        entry.is_removable,
      ),
    )
  }
  out
}

///|
fn collect_disks_macos(
  previous : Array[Disk],
  refreshes : DiskRefreshKind,
) -> Array[Disk] {
  let out : Array[Disk] = []
  let global_io_fallback = if refreshes.io_usage() {
    read_macos_global_disk_io_snapshot()
  } else {
    DiskIoSnapshot::zero()
  }
  let disk_info_table = if refreshes.kind() || refreshes.io_usage() {
    parse_macos_disk_table_entries()
  } else {
    {}
  }
  let mounts = parse_mount_table_entries()
  for entry in mounts {
    let previous_usage = find_previous_disk_usage(previous, entry.mount_point)
    let disk_key = normalize_macos_disk_device(entry.file_system)
    let disk_info = disk_info_table.get_or_default(disk_key, {
      io: DiskIoSnapshot::zero(),
      kind: infer_disk_kind(entry.file_system),
    })
    let io = if refreshes.io_usage() {
      if disk_info.io.total_read_bytes > 0UL ||
        disk_info.io.total_written_bytes > 0UL {
        disk_info.io
      } else {
        global_io_fallback
      }
    } else {
      DiskIoSnapshot::zero()
    }
    out.push(
      build_disk_entry(
        entry.file_system,
        entry.mount_point,
        entry.total_space,
        entry.available_space,
        refreshes,
        previous_usage,
        io,
        disk_info.kind,
        entry.file_system_type,
        entry.is_read_only,
        entry.is_removable,
      ),
    )
  }
  out
}

///|
fn read_macos_global_disk_io_snapshot() -> DiskIoSnapshot {
  let fields = split_whitespace(decode_bytes(macos_vm_io_counters_ffi()))
  if fields.length() < 2 {
    return DiskIoSnapshot::zero()
  }
  {
    total_read_bytes: parse_uint64_or(fields[0], 0UL),
    total_written_bytes: parse_uint64_or(fields[1], 0UL),
  }
}

///|
fn normalize_macos_disk_device(device : String) -> String {
  let base_view = if device.has_prefix("/dev/") { device[5:] } else { device }
  let base = base_view.to_string()
  if !base.has_prefix("disk") {
    return base
  }
  let mut end = 4
  while end < base.length() {
    let ch = base[end]
    if ch >= '0' && ch <= '9' {
      end = end + 1
    } else {
      break
    }
  }
  if end > 4 {
    base[:end].to_string()
  } else {
    base
  }
}

///|
fn parse_macos_disk_table_entries() -> Map[String, MacosDiskInfo] {
  let out : Map[String, MacosDiskInfo] = {}
  let lines = split_non_empty_lines(decode_bytes(macos_disk_table_ffi()))
  for line in lines {
    let fields = split_tab_fields(line)
    if fields.length() < 4 {
      continue
    }
    let key = normalize_macos_disk_device(fields[0])
    if key == "" {
      continue
    }
    let read_total = parse_uint64_or(fields[1], 0UL)
    let write_total = parse_uint64_or(fields[2], 0UL)
    let is_ssd = parse_int_or(fields[3], 0) != 0
    let current : MacosDiskInfo = {
      io: { total_read_bytes: read_total, total_written_bytes: write_total },
      kind: if is_ssd {
        DiskKind::SSD
      } else {
        DiskKind::HDD
      },
    }
    match out.get(key) {
      Some(old) =>
        out[key] = {
          io: {
            total_read_bytes: if old.io.total_read_bytes >=
              current.io.total_read_bytes {
              old.io.total_read_bytes
            } else {
              current.io.total_read_bytes
            },
            total_written_bytes: if old.io.total_written_bytes >=
              current.io.total_written_bytes {
              old.io.total_written_bytes
            } else {
              current.io.total_written_bytes
            },
          },
          kind: match (old.kind, current.kind) {
            (DiskKind::SSD, _) => DiskKind::SSD
            (_, DiskKind::SSD) => DiskKind::SSD
            _ => old.kind
          },
        }
      None => out[key] = current
    }
  }
  out
}

///|
fn parse_mount_table_entries() -> Array[MountEntry] {
  let out : Array[MountEntry] = []
  let lines = split_non_empty_lines(decode_bytes(mount_table_ffi()))
  for line in lines {
    let fields = split_tab_fields(line)
    if fields.length() < 7 {
      continue
    }
    out.push({
      file_system: fields[0],
      mount_point: fields[1],
      file_system_type: fields[2],
      total_space: parse_uint64_or(fields[3], 0UL),
      available_space: parse_uint64_or(fields[4], 0UL),
      is_read_only: parse_int_or(fields[5], 0) != 0,
      is_removable: parse_int_or(fields[6], 0) != 0,
    })
  }
  out
}

///|
fn build_disk_entry(
  fs : String,
  mount : String,
  total : UInt64,
  avail : UInt64,
  refreshes : DiskRefreshKind,
  previous_usage : DiskUsage,
  io : DiskIoSnapshot,
  inferred_kind : DiskKind,
  file_system_type : String,
  is_read_only : Bool,
  is_removable : Bool,
) -> Disk {
  {
    kind: if refreshes.kind() {
      inferred_kind
    } else {
      DiskKind::Unknown("")
    },
    name: fs,
    file_system: file_system_type,
    mount_point: mount,
    total_space: if refreshes.storage() {
      total
    } else {
      0UL
    },
    available_space: if refreshes.storage() {
      avail
    } else {
      0UL
    },
    is_removable,
    is_read_only,
    usage: if refreshes.io_usage() {
      {
        total_read_bytes: io.total_read_bytes,
        read_bytes: if io.total_read_bytes >= previous_usage.total_read_bytes {
          io.total_read_bytes - previous_usage.total_read_bytes
        } else {
          0UL
        },
        total_written_bytes: io.total_written_bytes,
        written_bytes: if io.total_written_bytes >=
          previous_usage.total_written_bytes {
          io.total_written_bytes - previous_usage.total_written_bytes
        } else {
          0UL
        },
      }
    } else {
      DiskUsage::zero()
    },
  }
}

///|
fn find_previous_disk_usage(
  previous : Array[Disk],
  mount : String,
) -> DiskUsage {
  for disk in previous {
    if disk.mount_point() == mount {
      return disk.usage()
    }
  }
  DiskUsage::zero()
}

///|
fn collect_linux_diskstats() -> Map[String, DiskIoSnapshot] {
  let out : Map[String, DiskIoSnapshot] = {}
  match read_text_file("/proc/diskstats") {
    Some(content) =>
      for line in content.split("\n") {
        let fields = split_whitespace(line.trim().to_string())
        if fields.length() < 10 {
          continue
        }
        let name = fields[2]
        let read_sectors = parse_uint64_or(fields[5], 0UL)
        let written_sectors = parse_uint64_or(fields[9], 0UL)
        out[name] = {
          total_read_bytes: read_sectors * 512UL,
          total_written_bytes: written_sectors * 512UL,
        }
      }
    None => ()
  }
  out
}

///|
fn find_linux_disk_io(
  stats : Map[String, DiskIoSnapshot],
  file_system : String,
) -> DiskIoSnapshot {
  let base = if file_system.has_prefix("/dev/") {
    file_system[5:].to_string()
  } else {
    file_system
  }
  match stats.get(base) {
    Some(io) => io
    None => {
      let parent = parent_linux_block_device(base)
      stats.get_or_default(parent, DiskIoSnapshot::zero())
    }
  }
}

///|
fn parent_linux_block_device(device : String) -> String {
  let mut end = device.length()
  while end > 0 {
    let ch = device[end - 1]
    if ch >= '0' && ch <= '9' {
      end = end - 1
    } else {
      break
    }
  }
  let trimmed = device[:end].to_string()
  if trimmed.has_suffix("p") {
    trimmed[:trimmed.length() - 1].to_string()
  } else {
    trimmed
  }
}

///|
fn infer_disk_kind(name : String) -> DiskKind {
  let lower = name.to_lower()
  if lower.contains("nvme") || lower.contains("ssd") {
    DiskKind::SSD
  } else if lower.has_prefix("/dev/sd") || lower.contains("disk") {
    DiskKind::HDD
  } else {
    DiskKind::Unknown(name)
  }
}

///|
pub(all) struct Networks {
  mut networks_data : Map[String, NetworkData]
} derive(Show)

///|
pub fn Networks::new() -> Networks {
  { networks_data: {} }
}

///|
pub fn Networks::new_with_refreshed_list() -> Networks {
  let networks = Networks::new()
  networks.refresh(false)
  networks
}

///|
pub fn Networks::list(self : Networks) -> Map[String, NetworkData] {
  self.networks_data
}

///|
pub fn Networks::refresh(
  self : Networks,
  remove_not_listed_interfaces : Bool,
) -> Unit {
  let now_data = collect_networks(self.networks_data)
  if remove_not_listed_interfaces {
    self.networks_data = now_data
  } else {
    for name, data in now_data {
      self.networks_data[name] = data
    }
  }
}

///|
fn collect_networks(
  previous : Map[String, NetworkData],
) -> Map[String, NetworkData] {
  if get_os_tag() == "unknown" {
    {}
  } else {
    collect_networks_from_native_table(previous)
  }
}

///|
fn network_delta(current : UInt64, previous : UInt64) -> UInt64 {
  if current >= previous {
    current - previous
  } else {
    0UL
  }
}

///|
fn parse_ip_networks_blob(blob : String) -> Array[IpNetwork] {
  let out : Array[IpNetwork] = []
  for token in blob.split(",") {
    let value = token.trim().to_string()
    if value != "" {
      out.push({ raw: value })
    }
  }
  out
}

///|
fn collect_networks_from_native_table(
  previous : Map[String, NetworkData],
) -> Map[String, NetworkData] {
  let out : Map[String, NetworkData] = {}
  let lines = split_non_empty_lines(decode_bytes(network_table_ffi()))
  for line in lines {
    let fields = split_tab_fields(line)
    if fields.length() < 9 {
      continue
    }
    let name = fields[0]
    let total_rx = parse_uint64_or(fields[1], 0UL)
    let total_tx = parse_uint64_or(fields[2], 0UL)
    let total_rx_packets = parse_uint64_or(fields[3], 0UL)
    let total_tx_packets = parse_uint64_or(fields[4], 0UL)
    let total_rx_errors = parse_uint64_or(fields[5], 0UL)
    let total_tx_errors = parse_uint64_or(fields[6], 0UL)
    let mtu = parse_uint64_or(fields[7], 0UL)
    let mac : MacAddr = if fields[8].contains(":") {
      { raw: fields[8].to_lower() }
    } else {
      { raw: "00:00:00:00:00:00" }
    }
    let ip_networks = if fields.length() >= 10 {
      parse_ip_networks_blob(fields[9])
    } else {
      []
    }
    let old = previous.get(name)
    let prev_rx = old.map(d => d.total_received()).unwrap_or(0UL)
    let prev_tx = old.map(d => d.total_transmitted()).unwrap_or(0UL)
    let prev_rxp = old.map(d => d.total_packets_received()).unwrap_or(0UL)
    let prev_txp = old.map(d => d.total_packets_transmitted()).unwrap_or(0UL)
    let prev_rxe = old.map(d => d.total_errors_on_received()).unwrap_or(0UL)
    let prev_txe = old.map(d => d.total_errors_on_transmitted()).unwrap_or(0UL)
    out[name] = {
      received: network_delta(total_rx, prev_rx),
      total_received: total_rx,
      transmitted: network_delta(total_tx, prev_tx),
      total_transmitted: total_tx,
      packets_received: network_delta(total_rx_packets, prev_rxp),
      total_packets_received: total_rx_packets,
      packets_transmitted: network_delta(total_tx_packets, prev_txp),
      total_packets_transmitted: total_tx_packets,
      errors_on_received: network_delta(total_rx_errors, prev_rxe),
      total_errors_on_received: total_rx_errors,
      errors_on_transmitted: network_delta(total_tx_errors, prev_txe),
      total_errors_on_transmitted: total_tx_errors,
      mac_address: mac,
      ip_networks,
      mtu,
    }
  }
  out
}

///|
pub(all) struct Components {
  mut components_data : Array[Component]
} derive(Show)

///|
pub fn Components::new() -> Components {
  { components_data: [] }
}

///|
pub fn Components::new_with_refreshed_list() -> Components {
  let components = Components::new()
  components.refresh(false)
  components
}

///|
pub fn Components::list(self : Components) -> Array[Component] {
  self.components_data
}

///|
pub fn Components::list_mut(self : Components) -> Array[Component] {
  self.components_data
}

///|
pub fn Components::refresh(
  self : Components,
  remove_not_listed_components : Bool,
) -> Unit {
  let now = collect_components()
  if remove_not_listed_components {
    self.components_data = now
  } else {
    self.components_data = now
  }
}

///|
pub fn Component::refresh(self : Component) -> Unit {
  let now = collect_components()
  for c in now {
    if c.label() == self.label() {
      self.temperature = c.temperature
      self.max = c.max
      self.critical = c.critical
      self.id = c.id
      return
    }
  }
}

///|
fn collect_components() -> Array[Component] {
  if get_os_tag() == "linux" {
    collect_components_linux()
  } else if get_os_tag() == "macos" {
    collect_components_macos()
  } else {
    []
  }
}

///|
fn collect_components_linux() -> Array[Component] {
  let out : Array[Component] = []
  let base = "/sys/class/hwmon"
  if !file_exists(base) {
    return out
  }
  let hwmons = list_dir(base)
  for hw in hwmons {
    let hw_path = "\{base}/\{hw}"
    let entries = list_dir(hw_path)
    for entry in entries {
      if entry.has_prefix("temp") && entry.has_suffix("_input") {
        let temp_path = "\{hw_path}/\{entry}"
        let raw = read_text_file_or_empty(temp_path).trim().to_string()
        if raw == "" {
          continue
        }
        let milli = parse_double_or(raw, 0.0)
        let temp = if milli > 0.0 { Some(milli / 1000.0) } else { None }
        let base_name = entry.replace(old="_input", new="")
        let label_path = "\{hw_path}/\{base_name}_label"
        let max_path = "\{hw_path}/\{base_name}_max"
        let crit_path = "\{hw_path}/\{base_name}_crit"
        let label = if file_exists(label_path) {
          read_text_file_or_empty(label_path).trim().to_string()
        } else {
          "\{hw}:\{base_name}"
        }
        let max_raw = read_text_file_or_empty(max_path).trim().to_string()
        let crit_raw = read_text_file_or_empty(crit_path).trim().to_string()
        let max = if max_raw == "" {
          None
        } else {
          Some(parse_double_or(max_raw, 0.0) / 1000.0)
        }
        let critical = if crit_raw == "" {
          None
        } else {
          Some(parse_double_or(crit_raw, 0.0) / 1000.0)
        }
        out.push({
          temperature: temp,
          max,
          critical,
          label,
          id: Some("\{hw}/\{base_name}"),
        })
      }
    }
  }
  out
}

///|
fn collect_components_macos() -> Array[Component] {
  let out : Array[Component] = []
  let raw_temp = macos_battery_temperature_ffi()
  if raw_temp > 0 {
    out.push({
      temperature: Some(raw_temp.to_double() / 10.0 - 273.15),
      max: None,
      critical: None,
      label: "Battery",
      id: Some("AppleSmartBattery"),
    })
  }
  out
}

///|
pub(all) struct Users {
  mut users_data : Array[User]
} derive(Show)

///|
pub fn Users::new() -> Users {
  { users_data: [] }
}

///|
pub fn Users::new_with_refreshed_list() -> Users {
  let users = Users::new()
  users.refresh()
  users
}

///|
pub fn Users::list(self : Users) -> Array[User] {
  self.users_data
}

///|
pub fn Users::list_mut(self : Users) -> Array[User] {
  self.users_data
}

///|
pub fn Users::refresh(self : Users) -> Unit {
  self.users_data = collect_users()
}

///|
pub fn Users::get_user_by_id(self : Users, user_id : Uid) -> User? {
  for user in self.users_data {
    if user.id() == user_id {
      return Some(user)
    }
  }
  None
}

///|
pub(all) struct Groups {
  mut groups_data : Array[Group]
} derive(Show)

///|
pub fn Groups::new() -> Groups {
  { groups_data: [] }
}

///|
pub fn Groups::new_with_refreshed_list() -> Groups {
  let groups = Groups::new()
  groups.refresh()
  groups
}

///|
pub fn Groups::list(self : Groups) -> Array[Group] {
  self.groups_data
}

///|
pub fn Groups::list_mut(self : Groups) -> Array[Group] {
  self.groups_data
}

///|
pub fn Groups::refresh(self : Groups) -> Unit {
  self.groups_data = collect_group_table().values().to_array()
}

///|
fn collect_group_table() -> Map[Gid, Group] {
  let out : Map[Gid, Group] = {}
  let lines = split_non_empty_lines(decode_bytes(group_table_ffi()))
  for line in lines {
    let fields = split_tab_fields(line)
    if fields.length() < 2 {
      continue
    }
    let gid_i = parse_int_or(fields[0], -1)
    if gid_i < 0 {
      continue
    }
    let gid = Gid::from(gid_i)
    out[gid] = { id: gid, name: fields[1] }
  }
  out
}

///|
fn collect_group_membership_table() -> Map[String, Array[Group]] {
  let groups = collect_group_table()
  let out : Map[String, Array[Group]] = {}
  let lines = split_non_empty_lines(decode_bytes(group_table_ffi()))
  for line in lines {
    let fields = split_tab_fields(line)
    if fields.length() < 3 {
      continue
    }
    let gid_i = parse_int_or(fields[0], -1)
    if gid_i < 0 {
      continue
    }
    let gid = Gid::from(gid_i)
    match groups.get(gid) {
      Some(group) => {
        let members = fields[2].split(",").to_array()
        for member_name in members {
          let name = member_name.trim().to_string()
          if name == "" {
            continue
          }
          let list = out.get_or_default(name, [])
          list.push(group)
          out[name] = list
        }
      }
      None => ()
    }
  }
  out
}

///|
fn collect_users() -> Array[User] {
  let groups = collect_group_table()
  let membership = collect_group_membership_table()
  let out : Array[User] = []
  let lines = split_non_empty_lines(decode_bytes(user_table_ffi()))
  for line in lines {
    let fields = split_tab_fields(line)
    if fields.length() < 3 {
      continue
    }
    let uid_i = parse_int_or(fields[0], -1)
    let gid_i = parse_int_or(fields[1], -1)
    if uid_i < 0 || gid_i < 0 {
      continue
    }
    let name = fields[2]
    let gid = Gid::from(gid_i)
    let user_groups : Array[Group] = []
    let seen : Map[Gid, Bool] = {}
    let primary_group = groups.get_or_default(gid, { id: gid, name: "" })
    user_groups.push(primary_group)
    seen[primary_group.id()] = true
    match membership.get(name) {
      Some(extra_groups) =>
        for group in extra_groups {
          if !seen.contains(group.id()) {
            seen[group.id()] = true
            user_groups.push(group)
          }
        }
      None => ()
    }
    out.push({ id: Uid::from(uid_i), group_id: gid, name, groups: user_groups })
  }
  out
}