defmodule Raxol.Terminal.ScreenBuffer.Attributes do
  @moduledoc """
  Manages buffer attributes including formatting, charset, and cursor state.
  Consolidates: Formatting, TextFormatting, Charset, Cursor functionality.
  """

  alias Raxol.Terminal.ANSI.TextFormatting
  alias Raxol.Terminal.ScreenBuffer.SharedOperations

  # Cursor operations

  @doc """
  Sets the cursor position.
  """
  def set_cursor_position(buffer, x, y) do
    x = max(0, min(x, buffer.width - 1))
    y = max(0, min(y, buffer.height - 1))
    %{buffer | cursor_position: {x, y}}
  end

  @doc """
  Gets the cursor position.
  """
  def get_cursor_position(buffer) do
    buffer.cursor_position
  end

  @doc """
  Moves the cursor relative to its current position.
  """
  def move_cursor(buffer, dx, dy) do
    {x, y} = buffer.cursor_position
    set_cursor_position(buffer, x + dx, y + dy)
  end

  @doc """
  Sets cursor visibility.
  """
  def set_cursor_visible(buffer, visible) do
    %{buffer | cursor_visible: visible}
  end

  @doc """
  Sets cursor style.
  """
  def set_cursor_style(buffer, style)
      when style in [:block, :underline, :bar] do
    %{buffer | cursor_style: style}
  end

  def set_cursor_style(buffer, _), do: buffer

  @doc """
  Sets cursor blink state.
  """
  def set_cursor_blink(buffer, blink) do
    %{buffer | cursor_blink: blink}
  end

  @doc """
  Saves the current cursor position.
  """
  def save_cursor(buffer) do
    Map.put(buffer, :saved_cursor_position, buffer.cursor_position)
  end

  @doc """
  Restores the saved cursor position.
  """
  def restore_cursor(buffer) do
    case Map.get(buffer, :saved_cursor_position) do
      {x, y} -> set_cursor_position(buffer, x, y)
      nil -> buffer
    end
  end

  # Formatting operations

  @doc """
  Sets the default text style for the buffer.
  """
  def set_default_style(buffer, style) do
    %{buffer | default_style: style}
  end

  @doc """
  Gets the default text style.
  """
  def get_default_style(buffer) do
    buffer.default_style || TextFormatting.new()
  end

  @doc """
  Creates a text style from SGR parameters.
  """
  def create_style(params) do
    # Create a default style and apply SGR parameters
    initial_style = TextFormatting.new()

    Enum.reduce(params, initial_style, fn param, style ->
      TextFormatting.parse_sgr_param(param, style)
    end)
  end

  @doc """
  Merges two styles, with the second taking precedence.
  """
  def merge_styles(base, override) do
    Map.merge(base, override)
  end

  # Charset operations

  @doc """
  Sets the active charset (G0, G1, G2, G3).
  """
  def set_charset(buffer, slot, charset) when slot in [:g0, :g1, :g2, :g3] do
    charsets =
      Map.get(buffer, :charsets, %{
        g0: :ascii,
        g1: :ascii,
        g2: :ascii,
        g3: :ascii
      })

    new_charsets = Map.put(charsets, slot, charset)
    Map.put(buffer, :charsets, new_charsets)
  end

  def set_charset(buffer, _, _), do: buffer

  @doc """
  Gets the active charset for a slot.
  """
  def get_charset(buffer, slot) when slot in [:g0, :g1, :g2, :g3] do
    charsets =
      Map.get(buffer, :charsets, %{
        g0: :ascii,
        g1: :ascii,
        g2: :ascii,
        g3: :ascii
      })

    Map.get(charsets, slot, :ascii)
  end

  def get_charset(_buffer, _), do: :ascii

  @doc """
  Selects which charset slot is active.
  """
  def select_charset(buffer, slot) when slot in [:g0, :g1, :g2, :g3] do
    Map.put(buffer, :active_charset, slot)
  end

  def select_charset(buffer, _), do: buffer

  @doc """
  Gets the currently active charset.
  """
  def get_active_charset(buffer) do
    slot = Map.get(buffer, :active_charset, :g0)
    get_charset(buffer, slot)
  end

  @doc """
  Translates a character according to the active charset.
  """
  def translate_char(buffer, char) do
    charset = get_active_charset(buffer)
    translate_with_charset(char, charset)
  end

  # Screen mode operations

  @doc """
  Switches between main and alternate screen buffers.
  """
  def set_alternate_screen(buffer, use_alternate) do
    %{buffer | alternate_screen: use_alternate}
  end

  @doc """
  Checks if using alternate screen.
  """
  def using_alternate_screen?(buffer) do
    buffer.alternate_screen
  end

  # Tab stop operations

  @doc """
  Sets a tab stop at the current cursor position.
  """
  def set_tab_stop(buffer) do
    {x, _y} = buffer.cursor_position
    tab_stops = Map.get(buffer, :tab_stops, default_tab_stops(buffer.width))
    new_tab_stops = MapSet.put(tab_stops, x)
    Map.put(buffer, :tab_stops, new_tab_stops)
  end

  @doc """
  Clears a tab stop at the current cursor position.
  """
  def clear_tab_stop(buffer) do
    {x, _y} = buffer.cursor_position
    tab_stops = Map.get(buffer, :tab_stops, default_tab_stops(buffer.width))
    new_tab_stops = MapSet.delete(tab_stops, x)
    Map.put(buffer, :tab_stops, new_tab_stops)
  end

  @doc """
  Clears all tab stops.
  """
  def clear_all_tab_stops(buffer) do
    Map.put(buffer, :tab_stops, MapSet.new())
  end

  @doc """
  Resets tab stops to default (every 8 columns).
  """
  def reset_tab_stops(buffer) do
    Map.put(buffer, :tab_stops, default_tab_stops(buffer.width))
  end

  @doc """
  Finds the next tab stop position from the current cursor.
  """
  def next_tab_stop(buffer) do
    {x, _y} = buffer.cursor_position
    tab_stops = Map.get(buffer, :tab_stops, default_tab_stops(buffer.width))

    # Find next tab stop after current position
    tab_stops
    |> Enum.filter(fn stop -> stop > x end)
    |> Enum.min(fn -> buffer.width - 1 end)
  end

  # Private helper functions

  defp translate_with_charset(char, :ascii), do: char

  defp translate_with_charset(char, :dec_special) do
    # DEC Special Graphics character set mapping
    case char do
      "`" -> "◆"
      "a" -> "▒"
      "b" -> "␉"
      "c" -> "␌"
      "d" -> "␍"
      "e" -> "␊"
      "f" -> "°"
      "g" -> "±"
      "h" -> "␤"
      "i" -> "␋"
      "j" -> "┘"
      "k" -> "┐"
      "l" -> "┌"
      "m" -> "└"
      "n" -> "┼"
      "o" -> "⎺"
      "p" -> "⎻"
      "q" -> "─"
      "r" -> "⎼"
      "s" -> "⎽"
      "t" -> "├"
      "u" -> "┤"
      "v" -> "┴"
      "w" -> "┬"
      "x" -> "│"
      "y" -> "≤"
      "z" -> "≥"
      "{" -> "π"
      "|" -> "≠"
      "}" -> "£"
      "~" -> "·"
      _ -> char
    end
  end

  defp translate_with_charset(char, _), do: char

  defp default_tab_stops(width) do
    # Default tab stops every 8 columns
    0..(width - 1)
    |> Enum.filter(fn x -> rem(x, 8) == 0 end)
    |> MapSet.new()
  end

  # === Stub Implementations for Test Compatibility ===
  # These functions are referenced by delegations but not critical for core functionality

  @doc """
  Checks if cursor is visible (stub).
  """
  def cursor_visible?(buffer), do: buffer.cursor_visible

  @doc """
  Checks if cursor is blinking (stub).
  """
  def cursor_blinking?(buffer), do: Map.get(buffer, :cursor_blink, true)

  @doc """
  Gets cursor style (stub).
  """
  def get_cursor_style(buffer), do: buffer.cursor_style

  @doc """
  Gets text style (stub).
  """
  def get_style(buffer), do: buffer.default_style

  @doc """
  Updates text style (stub).
  """
  def update_style(buffer, style), do: %{buffer | default_style: style}

  @doc """
  Gets foreground color (stub).
  """
  def get_foreground(buffer), do: buffer.default_style.foreground

  @doc """
  Gets background color (stub).
  """
  def get_background(buffer), do: buffer.default_style.background

  @doc """
  Starts selection (stub).
  """
  def start_selection(buffer, x, y), do: %{buffer | selection: {x, y, nil, nil}}

  @doc """
  Updates selection (stub).
  """
  def update_selection(buffer, x, y) do
    case buffer.selection do
      {sx, sy, _, _} -> %{buffer | selection: {sx, sy, x, y}}
      nil -> buffer
    end
  end

  @doc """
  Clears selection (stub).
  """
  def clear_selection(buffer), do: %{buffer | selection: nil}

  @doc """
  Gets selection (stub).
  """
  def get_selection(buffer), do: buffer.selection

  @doc """
  Gets selection start (stub).
  """
  def get_selection_start(buffer) do
    case buffer.selection do
      {sx, sy, _, _} -> {sx, sy}
      nil -> nil
    end
  end

  @doc """
  Gets selection end (stub).
  """
  def get_selection_end(buffer) do
    case buffer.selection do
      {_, _, nil, nil} -> nil
      {_, _, ex, ey} -> {ex, ey}
      nil -> nil
    end
  end

  @doc """
  Gets selection boundaries (stub).
  """
  def get_selection_boundaries(buffer), do: buffer.selection

  @doc """
  Checks if position is in selection (stub).
  """
  def in_selection?(buffer, x, y) do
    case buffer.selection do
      {sx, sy, ex, ey} when ex != nil and ey != nil ->
        {start_x, start_y, end_x, end_y} =
          SharedOperations.normalize_selection(sx, sy, ex, ey)

        SharedOperations.position_in_selection?(
          x,
          y,
          start_x,
          start_y,
          end_x,
          end_y
        )

      _ ->
        false
    end
  end

  @doc """
  Gets text in region.
  """
  def get_text_in_region(buffer, x1, y1, x2, y2) do
    case buffer.cells do
      cells when is_list(cells) ->
        start_y = max(0, min(y1, y2))
        end_y = min(length(cells) - 1, max(y1, y2))
        col_range = {max(0, min(x1, x2)), max(x1, x2)}

        if start_y > end_y do
          ""
        else
          cells
          |> Enum.slice(start_y..end_y)
          |> Enum.map(&extract_line_text(&1, col_range))
          |> Enum.reject(&(&1 == ""))
          |> Enum.join("\n")
        end

      _ ->
        ""
    end
  end

  defp extract_line_text(line, {start_x, max_x}) when is_list(line) do
    end_x = min(length(line) - 1, max_x)

    if start_x > end_x do
      ""
    else
      line
      |> Enum.slice(start_x..end_x)
      |> Enum.map_join("", fn
        %{char: char} -> char
        _ -> " "
      end)
      |> String.trim_trailing()
    end
  end

  defp extract_line_text(_line, _col_range), do: ""

  @doc """
  Checks if attribute is set (stub).
  """
  def attribute_set?(_buffer, _attr), do: false

  @doc """
  Gets set attributes (stub).
  """
  def get_set_attributes(_buffer), do: []

  @doc """
  Applies single shift (stub).
  """
  def apply_single_shift(buffer, _set), do: buffer

  @doc """
  Gets single shift state (stub).
  """
  def get_single_shift(_buffer), do: nil

  @doc """
  Gets current G set (stub).
  """
  def get_current_g_set(_buffer), do: 0

  @doc """
  Designates charset (stub).
  """
  def designate_charset(buffer, _set, _charset), do: buffer

  @doc """
  Gets designated charset (stub).
  """
  def get_designated_charset(_buffer, _set), do: :us

  @doc """
  Invokes G set (stub).
  """
  def invoke_g_set(buffer, _set), do: buffer

  @doc """
  Resets all attributes to defaults (stub).
  """
  def reset_all_attributes(buffer) do
    %{buffer | default_style: TextFormatting.default_style()}
  end

  @doc """
  Resets specific attribute (stub).
  """
  def reset_attribute(buffer, _attr), do: buffer

  @doc """
  Resets charset state (stub).
  """
  def reset_charset_state(buffer), do: buffer

  @doc """
  Checks if selection is active (stub).
  """
  def selection_active?(buffer), do: buffer.selection != nil

  @doc """
  Sets specific attribute (stub).
  """
  def set_attribute(buffer, _attr), do: buffer

  @doc """
  Sets background color (stub).
  """
  def set_background(buffer, color) do
    style = Map.put(buffer.default_style, :background, color)
    %{buffer | default_style: style}
  end

  @doc """
  Sets cursor visibility (stub).
  """
  def set_cursor_visibility(buffer, visible) do
    %{buffer | cursor_visible: visible}
  end

  @doc """
  Sets foreground color (stub).
  """
  def set_foreground(buffer, color) do
    style = Map.put(buffer.default_style, :foreground, color)
    %{buffer | default_style: style}
  end
end
