defmodule Raxol.Terminal.Buffer.Formatting do
  @moduledoc """
  Manages text formatting state and operations for the screen buffer.
  This module handles text attributes, colors, and style management.
  """

  alias Raxol.Terminal.ScreenBuffer

  @type t :: %__MODULE__{
          bold: boolean(),
          dim: boolean(),
          italic: boolean(),
          underline: boolean(),
          blink: boolean(),
          reverse: boolean(),
          hidden: boolean(),
          strikethrough: boolean(),
          foreground:
            {non_neg_integer(), non_neg_integer(), non_neg_integer()} | nil,
          background:
            {non_neg_integer(), non_neg_integer(), non_neg_integer()} | nil
        }

  defstruct [
    :bold,
    :dim,
    :italic,
    :underline,
    :blink,
    :reverse,
    :hidden,
    :strikethrough,
    :foreground,
    :background
  ]

  @doc """
  Initializes a new formatting state with default values.
  """
  @spec init() :: t()
  def init do
    %__MODULE__{
      bold: false,
      dim: false,
      italic: false,
      underline: false,
      blink: false,
      reverse: false,
      hidden: false,
      strikethrough: false,
      foreground: nil,
      background: nil
    }
  end

  @doc """
  Gets the current style.

  ## Parameters

  * `buffer` - The screen buffer to query

  ## Returns

  The current style map.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> Formatting.get_style(buffer)
      %{bold: false, dim: false, italic: false, underline: false, blink: false, reverse: false, hidden: false, strikethrough: false, foreground: nil, background: nil}
  """
  @spec get_style(map()) :: map()
  def get_style(buffer) do
    buffer.formatting_state
  end

  @doc """
  Updates the style with new attributes.

  ## Parameters

  * `buffer` - The screen buffer to modify
  * `style` - The new style map

  ## Returns

  The updated screen buffer with new style.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> buffer = Formatting.update_style(buffer, %{bold: true})
      iex> Formatting.get_style(buffer)
      %{bold: true, ...}
  """
  @spec update_style(map(), map()) :: map()
  def update_style(buffer, style) do
    new_formatting_state = Map.merge(buffer.formatting_state, style)
    %{buffer | formatting_state: new_formatting_state}
  end

  @doc """
  Sets a specific attribute.

  ## Parameters

  * `buffer` - The screen buffer to modify
  * `attribute` - The attribute to set (:bold, :dim, :italic, etc.)

  ## Returns

  The updated screen buffer with the attribute set.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> buffer = Formatting.set_attribute(buffer, :bold)
      iex> Formatting.attribute_set?(buffer, :bold)
      true
  """
  @spec set_attribute(map(), atom()) :: map()
  def set_attribute(buffer, attribute) do
    new_formatting_state = %{buffer.formatting_state | attribute => true}
    %{buffer | formatting_state: new_formatting_state}
  end

  @doc """
  Resets a specific attribute.

  ## Parameters

  * `buffer` - The screen buffer to modify
  * `attribute` - The attribute to reset (:bold, :dim, :italic, etc.)

  ## Returns

  The updated screen buffer with the attribute reset.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> buffer = Formatting.reset_attribute(buffer, :bold)
      iex> Formatting.attribute_set?(buffer, :bold)
      false
  """
  @spec reset_attribute(map(), atom()) :: map()
  def reset_attribute(buffer, attribute) do
    new_formatting_state = %{buffer.formatting_state | attribute => false}
    %{buffer | formatting_state: new_formatting_state}
  end

  @doc """
  Sets the foreground color.

  ## Parameters

  * `buffer` - The screen buffer to modify
  * `color` - The RGB color tuple {r, g, b}

  ## Returns

  The updated screen buffer with new foreground color.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> buffer = Formatting.set_foreground(buffer, {255, 0, 0})
      iex> Formatting.get_foreground(buffer)
      {255, 0, 0}
  """
  @spec set_foreground(
          map(),
          {non_neg_integer(), non_neg_integer(), non_neg_integer()}
        ) :: map()
  def set_foreground(buffer, color) do
    new_formatting_state = %{buffer.formatting_state | foreground: color}
    %{buffer | formatting_state: new_formatting_state}
  end

  @doc """
  Sets the background color.

  ## Parameters

  * `buffer` - The screen buffer to modify
  * `color` - The RGB color tuple {r, g, b}

  ## Returns

  The updated screen buffer with new background color.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> buffer = Formatting.set_background(buffer, {0, 0, 255})
      iex> Formatting.get_background(buffer)
      {0, 0, 255}
  """
  @spec set_background(
          map(),
          {non_neg_integer(), non_neg_integer(), non_neg_integer()}
        ) :: map()
  def set_background(buffer, color) do
    new_formatting_state = %{buffer.formatting_state | background: color}
    %{buffer | formatting_state: new_formatting_state}
  end

  @doc """
  Resets all attributes to their default values.

  ## Parameters

  * `buffer` - The screen buffer to modify

  ## Returns

  The updated screen buffer with all attributes reset.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> buffer = Formatting.reset_all(buffer)
      iex> Formatting.get_style(buffer)
      %{bold: false, dim: false, italic: false, underline: false, blink: false, reverse: false, hidden: false, strikethrough: false, foreground: nil, background: nil}
  """
  @spec reset_all(map()) :: map()
  def reset_all(buffer) do
    %{buffer | formatting_state: init()}
  end

  @doc """
  Gets the current foreground color.

  ## Parameters

  * `buffer` - The screen buffer to query

  ## Returns

  The current foreground color or nil if not set.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> Formatting.get_foreground(buffer)
      nil
  """
  @spec get_foreground(ScreenBuffer.t()) ::
          {non_neg_integer(), non_neg_integer(), non_neg_integer()} | nil
  def get_foreground(buffer) do
    buffer.formatting_state.foreground
  end

  @doc """
  Gets the current background color.

  ## Parameters

  * `buffer` - The screen buffer to query

  ## Returns

  The current background color or nil if not set.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> Formatting.get_background(buffer)
      nil
  """
  @spec get_background(ScreenBuffer.t()) ::
          {non_neg_integer(), non_neg_integer(), non_neg_integer()} | nil
  def get_background(buffer) do
    buffer.formatting_state.background
  end

  @doc """
  Checks if a specific attribute is set.

  ## Parameters

  * `buffer` - The screen buffer to query
  * `attribute` - The attribute to check

  ## Returns

  A boolean indicating if the attribute is set.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> Formatting.attribute_set?(buffer, :bold)
      false
  """
  @spec attribute_set?(ScreenBuffer.t(), atom()) :: boolean()
  def attribute_set?(buffer, attribute) do
    Map.get(buffer.formatting_state, attribute, false)
  end

  @doc """
  Gets all currently set attributes.

  ## Parameters

  * `buffer` - The screen buffer to query

  ## Returns

  A list of set attributes.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> buffer = Formatting.set_attribute(buffer, :bold)
      iex> Formatting.get_set_attributes(buffer)
      [:bold]
  """
  @spec get_set_attributes(ScreenBuffer.t()) :: list(atom())
  def get_set_attributes(buffer) do
    buffer.formatting_state
    |> Map.to_list()
    |> Enum.filter(fn {_key, value} -> value == true end)
    |> Enum.map(fn {key, _value} -> key end)
  end
end
