Skip to main content

lib/alaja/components/bar.ex

defmodule Alaja.Components.Bar do
  @moduledoc """
  Static progress bar component for terminal output.

  Renders a horizontal bar representing a value as a proportion of a maximum.

  ## Usage

      iex> Alaja.Components.Bar.print(75, 100)
      # [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░] 75%

      iex> Alaja.Components.Bar.print(0.6, 1.0, label: "CPU", width: 40)
      # CPU [▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░] 60%
  """

  @filled_char "▓"
  @empty_char "░"
  @default_filled_color {0, 180, 216}
  @default_empty_color {50, 50, 50}

  @doc """
  Prints a progress bar directly to stdout.

  ## Parameters

  - `value` - Current value (numeric)
  - `max` - Maximum value (numeric, default: 100)

  ## Options

  - `:label` - Optional label prefix
  - `:show_percent` - Show percentage at end (default: true)
  - `:width` - Bar width in chars (default: 40)
  - `:filled_char` - Character for filled portion (default: `"▓"`)
  - `:empty_char` - Character for empty portion (default: `"░"`)
  - `:filled_color` - RGB tuple for filled section
  - `:empty_color` - RGB tuple for empty section
  """
  @spec print(number(), number(), keyword()) :: :ok
  def print(value, max \\ 100, opts \\ []) do
    value |> render(max, opts) |> IO.write()
    IO.puts("")
  end

  @doc """
  Renders a progress bar to iodata without printing.
  """
  @spec render(number(), number(), keyword()) :: iodata()
  def render(value, max \\ 100, opts \\ []) do
    label = Keyword.get(opts, :label)
    show_percent = Keyword.get(opts, :show_percent, true)
    width = Keyword.get(opts, :width, 40)
    filled_char = Keyword.get(opts, :filled_char, @filled_char)
    empty_char = Keyword.get(opts, :empty_char, @empty_char)
    {fr, fg, fb} = Keyword.get(opts, :filled_color, @default_filled_color)
    {er, eg, eb} = Keyword.get(opts, :empty_color, @default_empty_color)

    ratio = if max > 0, do: min(max(value / max, 0.0), 1.0), else: 0.0
    filled = round(ratio * width)
    empty = width - filled

    percent_str = if show_percent, do: " #{round(ratio * 100)}%", else: ""
    label_str = if label, do: "#{label} ", else: ""

    [
      label_str,
      "[",
      Pote.Orchestrator.to_ansi({fr, fg, fb}),
      String.duplicate(filled_char, filled),
      Pote.Orchestrator.to_ansi({er, eg, eb}),
      String.duplicate(empty_char, empty),
      Alaja.ANSI.reset_attributes(),
      "]",
      percent_str
    ]
  end
end