///|
/// Render a number with two decimals, rounded to nearest hundredth,
/// e.g. `46.3` -> `"46.30"`.
fn fixed_two(x : Float) -> String {
  let hundredths = (x * 100.0).round().to_int()
  let whole = hundredths / 100
  let frac = hundredths % 100
  let frac_text = if frac < 10 { "0\{frac}" } else { "\{frac}" }
  "\{whole}.\{frac_text}"
}

///|
/// A device's fake memory footprint: `used` and `total`, both in GiB.
/// Only these two numbers are picked by the model; the display text
/// (two-decimal GiB values and usage percentage) is derived from them.
pub struct Memory {
  used : Float
  total : Float
}

///|
/// Format as fastfetch does, e.g. `46.30 GiB / 128.00 GiB (36%)`.
pub fn Memory::render(self : Memory) -> String {
  let percent = (self.used / self.total * 100.0).round().to_int()
  "\{fixed_two(self.used)} GiB / \{fixed_two(self.total)} GiB (\{percent}%)"
}

///|
/// A display resolution: `width` and `height` in pixels.
pub struct Resolution {
  width : Int
  height : Int
}

///|
/// Format as fastfetch does, e.g. `3456x2234`.
pub fn Resolution::render(self : Resolution) -> String {
  "\{self.width}x\{self.height}"
}

///|
/// System uptime in whole `hours` and `mins`.
pub struct Uptime {
  hours : Int
  mins : Int
}

///|
/// Format as fastfetch does, e.g. `3 hours, 12 mins`.
pub fn Uptime::render(self : Uptime) -> String {
  "\{self.hours} hours, \{self.mins} mins"
}

///|
/// A CPU: vendor/model `name`, `cores`, and base `clock` in GHz.
pub struct Cpu {
  name : String
  cores : Int
  clock : Float
}

///|
/// Format as fastfetch does, e.g. `Apple M3 Max (16) @ 4.05 GHz`.
pub fn Cpu::render(self : Cpu) -> String {
  "\{self.name} (\{self.cores}) @ \{fixed_two(self.clock)} GHz"
}

///|
/// A GPU: vendor/model `name` and discrete VRAM in GB.
/// `vram` is 0 when the GPU has no discrete memory (integrated/Apple silicon).
pub struct Gpu {
  name : String
  vram : Int
}

///|
/// Format as fastfetch does, e.g. `NVIDIA GeForce RTX 4090 (24GB)`; a GPU
/// without discrete VRAM renders as its bare name.
pub fn Gpu::render(self : Gpu) -> String {
  if self.vram > 0 {
    "\{self.name} (\{self.vram}GB)"
  } else {
    self.name
  }
}

///|
/// A coherent set of fake hardware/software values for one real device model.
/// Every field is a plausible, internally-consistent value for that product.
pub struct DeviceModel {
  name : String
  about : String
  host : String
  os : String
  device : String
  kernel : String
  uptime : Uptime
  shell : String
  resolution : Resolution
  de : String
  wm : String
  wm_theme : String
  terminal : String
  term_font : String
  cpu : Cpu
  gpu : Gpu
  memory : Memory
  logo : String
}

///|
/// The name of the model used when `--model` is not given.
pub fn default_model_name() -> String {
  "m3-max"
}

///|
/// The device profile used when `--model` is not given.
pub fn default_model() -> DeviceModel {
  m3_max_model()
}

///|
fn mac_model(
  name~ : String,
  about~ : String,
  host~ : String,
  os~ : String,
  device~ : String,
  kernel~ : String,
  resolution~ : Resolution,
  cpu~ : Cpu,
  gpu~ : Gpu,
  memory~ : Memory,
) -> DeviceModel {
  {
    name,
    about,
    host,
    os,
    device,
    kernel,
    uptime: { hours: 3, mins: 12 },
    shell: "zsh 5.9",
    resolution,
    de: "Quartz Compositor",
    wm: "Quartz Compositor",
    wm_theme: "Aqua",
    terminal: "Apple_Terminal",
    term_font: "SF Mono 12",
    cpu,
    gpu,
    memory,
    logo: "macos",
  }
}

///|
fn linux_model(
  name~ : String,
  about~ : String,
  host~ : String,
  os~ : String,
  device~ : String,
  kernel~ : String,
  resolution~ : Resolution,
  de~ : String,
  wm~ : String,
  wm_theme~ : String,
  terminal~ : String,
  term_font~ : String,
  cpu~ : Cpu,
  gpu~ : Gpu,
  memory~ : Memory,
  logo~ : String,
) -> DeviceModel {
  {
    name,
    about,
    host,
    os,
    device,
    kernel,
    uptime: { hours: 5, mins: 47 },
    shell: "zsh 5.9",
    resolution,
    de,
    wm,
    wm_theme,
    terminal,
    term_font,
    cpu,
    gpu,
    memory,
    logo,
  }
}

///|
fn windows_model(
  name~ : String,
  about~ : String,
  host~ : String,
  os~ : String,
  device~ : String,
  kernel~ : String,
  resolution~ : Resolution,
  de~ : String,
  wm~ : String,
  wm_theme~ : String,
  terminal~ : String,
  term_font~ : String,
  cpu~ : Cpu,
  gpu~ : Gpu,
  memory~ : Memory,
) -> DeviceModel {
  {
    name,
    about,
    host,
    os,
    device,
    kernel,
    uptime: { hours: 6, mins: 41 },
    shell: "powershell 5.1",
    resolution,
    de,
    wm,
    wm_theme,
    terminal,
    term_font,
    cpu,
    gpu,
    memory,
    logo: "windows",
  }
}

///|
/// MacBook Pro 16" (2023), Apple M3 Max, 128 GB unified memory, 2 TB SSD.
fn m3_max_model() -> DeviceModel {
  mac_model(
    name="m3-max",
    about="macOS MacBook Pro 16\" M3 Max 128 GB / 2 TB",
    host="mbp16",
    os="macOS 15.5 (Sequoia) 24F74 arm64",
    device="MacBook Pro (Mac15,6)",
    kernel="Darwin 24.5.0",
    resolution={ width: 3456, height: 2234 },
    cpu={ name: "Apple M3 Max", cores: 16, clock: 4.05 },
    gpu=gpu("Apple M3 Max"),
    memory={ used: 46.30, total: 128.00 },
  )
}

///|
/// Mac Studio (2025), Apple M3 Ultra, 512 GB unified memory, 4 TB SSD.
fn m3_ultra_model() -> DeviceModel {
  mac_model(
    name="m3-ultra",
    about="macOS Mac Studio M3 Ultra 512 GB / 4 TB",
    host="studio",
    os="macOS 15.5 (Sequoia) 24F74 arm64",
    device="Mac Studio (Mac16,9)",
    kernel="Darwin 24.5.0",
    resolution={ width: 5120, height: 2880 },
    cpu={ name: "Apple M3 Ultra", cores: 32, clock: 4.05 },
    gpu=gpu("Apple M3 Ultra"),
    memory={ used: 245.76, total: 512.00 },
  )
}

///|
/// MacBook Pro 14" (2023), Apple M2 Pro, 32 GB unified memory, 1 TB SSD.
fn m2_pro_model() -> DeviceModel {
  mac_model(
    name="m2-pro",
    about="macOS MacBook Pro 14\" M2 Pro 32 GB / 1 TB",
    host="mbp14",
    os="macOS 14.6 (Sonoma) 23G80 arm64",
    device="MacBook Pro (Mac14,9)",
    kernel="Darwin 23.6.0",
    resolution={ width: 3024, height: 1964 },
    cpu={ name: "Apple M2 Pro", cores: 12, clock: 3.50 },
    gpu=gpu("Apple M2 Pro"),
    memory={ used: 20.10, total: 32.00 },
  )
}

///|
/// MacBook Air (M1, 2020), Apple M1, 8 GB unified memory, 512 GB SSD.
fn m1_air_model() -> DeviceModel {
  mac_model(
    name="m1-air",
    about="macOS MacBook Air (M1) 8 GB / 512 GB",
    host="mba",
    os="macOS 13.6 (Ventura) 22G120 arm64",
    device="MacBook Air (MacBookAir9,1)",
    kernel="Darwin 22.6.0",
    resolution={ width: 2560, height: 1600 },
    cpu={ name: "Apple M1", cores: 8, clock: 3.20 },
    gpu=gpu("Apple M1"),
    memory={ used: 5.40, total: 8.00 },
  )
}

///|
/// ThinkPad X1 Carbon Gen 11, Intel Core i7-1365U, 32 GB RAM, Arch Linux.
fn thinkpad_x1_model() -> DeviceModel {
  linux_model(
    name="thinkpad-x1",
    about="Linux ThinkPad X1 Carbon Gen 11 (Arch)",
    host="x1",
    os="Arch Linux x86_64",
    device="ThinkPad X1 Carbon Gen 11",
    kernel="Linux 6.9.3-arch1-1",
    resolution={ width: 2880, height: 1800 },
    de="GNOME (Wayland)",
    wm="Mutter",
    wm_theme="Adwaita-dark",
    terminal="GNOME Terminal",
    term_font="Monospace 11",
    cpu={ name: "Intel Core i7-1365U", cores: 10, clock: 5.20 },
    gpu=gpu("Intel Iris Xe Graphics"),
    memory={ used: 12.30, total: 32.00 },
    logo="arch",
  )
}

///|
/// Custom desktop: AMD Ryzen 9 7950X, NVIDIA RTX 4090, 64 GB RAM, NixOS.
fn ryzen_desktop_model() -> DeviceModel {
  linux_model(
    name="ryzen-desktop",
    about="Linux desktop Ryzen 9 7950X / RTX 4090 64 GB (NixOS)",
    host="helios",
    os="NixOS 24.11 (Vicuna) x86_64",
    device="MSI MAG B650 TOMAHAWK WIFI",
    kernel="Linux 6.9.9-nixos",
    resolution={ width: 2560, height: 1440 },
    de="KDE Plasma",
    wm="KWin",
    wm_theme="Breeze Dark",
    terminal="kitty",
    term_font="Hack Nerd Font 11",
    cpu={ name: "AMD Ryzen 9 7950X", cores: 32, clock: 5.70 },
    gpu=gpu("NVIDIA GeForce RTX 4090"),
    memory={ used: 28.80, total: 64.00 },
    logo="nixos",
  )
}

///|
/// ASUS ROG Air 16 (2025), Intel Core Ultra 9 285H, RTX 5070, Windows 11.
fn rog_model() -> DeviceModel {
  windows_model(
    name="rog-air-16",
    about="Windows ASUS ROG Air 16 (2025) 32 GB / 1 TB",
    host="rog16",
    os="Windows 11 Pro 23H2 (22631.3880)",
    device="ASUS ROG Air 16 (GA612)",
    kernel="Win32 10.0.22631 Build 22631",
    resolution={ width: 2560, height: 1600 },
    de="Windows Desktop Manager",
    wm="DWM",
    wm_theme="Dark",
    terminal="Windows Terminal",
    term_font="Cascadia Mono 12",
    cpu={ name: "Intel Core Ultra 9 285H", cores: 16, clock: 5.40 },
    gpu=gpu("NVIDIA GeForce RTX 5070 Laptop GPU"),
    memory={ used: 12.60, total: 32.00 },
  )
}

///|
/// All available device profiles, in the order they are listed in help.
pub fn all_models() -> Array[DeviceModel] {
  [
    m3_max_model(),
    m3_ultra_model(),
    m2_pro_model(),
    m1_air_model(),
    thinkpad_x1_model(),
    ryzen_desktop_model(),
    rog_model(),
  ]
}

///|
/// Look up a device profile by its `--model` name.
pub fn model_by_name(name : String) -> DeviceModel? {
  all_models().iter().find_first(m => m.name == name)
}

///|
/// The built-in GPU catalog. Discrete GPUs carry a VRAM size in GB; integrated
/// and Apple-silicon GPUs use 0 and render as their bare name.
pub fn all_gpus() -> Array[Gpu] {
  [
    { name: "Apple M1", vram: 0 },
    { name: "Apple M2 Pro", vram: 0 },
    { name: "Apple M3 Max", vram: 0 },
    { name: "Apple M3 Ultra", vram: 0 },
    { name: "Intel Iris Xe Graphics", vram: 0 },
    { name: "NVIDIA GeForce RTX 5070 Laptop GPU", vram: 8 },
    { name: "NVIDIA GeForce RTX 4090", vram: 24 },
  ]
}

///|
/// A GPU by catalog name, defaulting to a bare 0-VRAM entry for unknown names.
fn gpu(name : String) -> Gpu {
  all_gpus().iter().find_first(g => g.name == name).unwrap_or({ name, vram: 0 })
}

///|
fn model_about() -> String {
  let names = all_models().map(m => m.name).join(", ")
  "device model: \{names} (default: \{default_model_name()})"
}