defmodule Microwaveprop.Format do
  @moduledoc """
  Shared human-facing formatters. Miles are the primary distance unit
  across the site; km appears parenthetically for readers working in
  metric.
  """

  @doc """
  Format a distance in km as "X mi (Y km)". Under 10 miles the output
  carries one decimal so near-LOS paths don't round to zero; longer
  distances round to whole numbers.
  """
  @spec distance_km(number() | Decimal.t() | nil) :: String.t()
  def distance_km(nil), do: "—"

  def distance_km(%Decimal{} = d), do: d |> Decimal.to_float() |> distance_km()

  def distance_km(km) when is_number(km) do
    mi = km / 1.609344

    {mi_str, km_str} =
      if mi < 10.0,
        do: {one_decimal(mi), one_decimal(km)},
        else: {Integer.to_string(round(mi)), Integer.to_string(round(km))}

    "#{mi_str} mi (#{km_str} km)"
  end

  defp one_decimal(n), do: :erlang.float_to_binary(n * 1.0, decimals: 1)

  @doc """
  Thousand-grouped integer for UI counters (row counts, job totals, etc).
  Floats round to nearest integer first; non-numerics are passed through
  to `to_string/1` so a `nil` or `:unknown` renders without crashing.
  """
  @spec number(integer() | float() | any()) :: String.t()
  def number(n) when is_integer(n) do
    n
    |> Integer.to_string()
    |> String.graphemes()
    |> Enum.reverse()
    |> Enum.chunk_every(3)
    |> Enum.map_join(",", &Enum.join/1)
    |> String.reverse()
    |> String.replace(~r/^,/, "")
  end

  def number(n) when is_float(n), do: number(round(n))
  def number(n), do: to_string(n)

  @doc """
  Binary-prefix byte count (1024-based) with one decimal. Intended for
  admin / status surfaces where "1.4 MB" is more useful than "1468006".
  """
  @spec bytes(non_neg_integer()) :: String.t()
  def bytes(b) when b >= 1_073_741_824, do: "#{Float.round(b / 1_073_741_824, 1)} GB"
  def bytes(b) when b >= 1_048_576, do: "#{Float.round(b / 1_048_576, 1)} MB"
  def bytes(b) when b >= 1024, do: "#{Float.round(b / 1024, 1)} KB"
  def bytes(b), do: "#{b} B"
end
