defmodule Raxol.Terminal.Buffer.Scroller do
  @moduledoc """
  Handles scrolling operations for the terminal buffer.
  """

  require Raxol.Core.Runtime.Log

  alias Raxol.Terminal.ScreenBuffer

  @doc """
  Scrolls the buffer up by the specified number of lines.
  """
  def scroll_up(buffer, count) do
    do_scroll_up(buffer, count)
  end

  @doc """
  Scrolls the buffer down by the specified number of lines.
  """
  def scroll_down(buffer, count) do
    do_scroll_down(buffer, count)
  end

  @doc """
  Gets the scroll top position.
  """
  def get_scroll_top(_buffer, scroll_margins) do
    scroll_margins.top
  end

  @doc """
  Gets the scroll bottom position.
  """
  def get_scroll_bottom(_buffer, scroll_margins) do
    scroll_margins.bottom
  end

  @doc """
  Scrolls the entire buffer up by the specified number of lines.

  ## Parameters

  * `buffer` - The screen buffer to scroll
  * `count` - The number of lines to scroll up

  ## Returns

  A tuple containing :ok and the updated buffer.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> {:ok, new_buffer} = Scroller.scroll_entire_buffer_up(buffer, 1)
      iex> length(new_buffer.content)
      24
  """
  @spec scroll_entire_buffer_up(ScreenBuffer.t(), non_neg_integer()) ::
          {:ok, ScreenBuffer.t()}
  def scroll_entire_buffer_up(buffer, count) do
    {new_buffer, _removed_lines} = ScreenBuffer.pop_bottom_lines(buffer, count)
    empty_lines = List.duplicate(List.duplicate(%{}, buffer.width), count)
    new_cells = empty_lines ++ new_buffer.cells
    {:ok, %{new_buffer | cells: new_cells}}
  end

  @doc """
  Scrolls the entire buffer down by the specified number of lines.

  ## Parameters

  * `buffer` - The screen buffer to scroll
  * `count` - The number of lines to scroll down

  ## Returns

  A tuple containing :ok and the updated buffer.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> {:ok, new_buffer} = Scroller.scroll_entire_buffer_down(buffer, 1)
      iex> length(new_buffer.content)
      24
  """
  @spec scroll_entire_buffer_down(ScreenBuffer.t(), non_neg_integer()) ::
          {:ok, ScreenBuffer.t()}
  def scroll_entire_buffer_down(buffer, count) do
    {new_buffer, _removed_lines} = ScreenBuffer.pop_bottom_lines(buffer, count)
    empty_lines = List.duplicate(List.duplicate(%{}, buffer.width), count)
    new_cells = new_buffer.cells ++ empty_lines
    {:ok, %{new_buffer | cells: new_cells}}
  end

  @doc """
  Scrolls a specific region of the buffer up by the specified number of lines.

  ## Parameters

  * `buffer` - The screen buffer to scroll
  * `count` - The number of lines to scroll up
  * `top` - The top boundary of the scroll region
  * `bottom` - The bottom boundary of the scroll region

  ## Returns

  A tuple containing :ok and the updated buffer.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> {:ok, new_buffer} = Scroller.scroll_region_up(buffer, 1, 5, 15)
      iex> length(new_buffer.content)
      24
  """
  @spec scroll_region_up(
          ScreenBuffer.t(),
          non_neg_integer(),
          non_neg_integer(),
          non_neg_integer()
        ) :: {:ok, ScreenBuffer.t()}
  def scroll_region_up(buffer, count, top, bottom) do
    region_lines = Enum.slice(buffer.cells, top..bottom)
    {_to_scroll, remaining} = Enum.split(region_lines, count)
    empty_lines = List.duplicate(List.duplicate(%{}, buffer.width), count)
    new_region = remaining ++ empty_lines
    new_cells = List.replace_at(buffer.cells, top, new_region)
    {:ok, %{buffer | cells: new_cells}}
  end

  @doc """
  Scrolls a specific region of the buffer down by the specified number of lines.

  ## Parameters

  * `buffer` - The screen buffer to scroll
  * `count` - The number of lines to scroll down
  * `top` - The top boundary of the scroll region
  * `bottom` - The bottom boundary of the scroll region

  ## Returns

  A tuple containing :ok and the updated buffer.

  ## Examples

      iex> buffer = ScreenBuffer.new(80, 24)
      iex> {:ok, new_buffer} = Scroller.scroll_region_down(buffer, 1, 5, 15)
      iex> length(new_buffer.content)
      24
  """
  @spec scroll_region_down(
          ScreenBuffer.t(),
          non_neg_integer(),
          non_neg_integer(),
          non_neg_integer()
        ) :: {:ok, ScreenBuffer.t()}
  def scroll_region_down(buffer, count, top, bottom) do
    region_lines = Enum.slice(buffer.cells, top..bottom)
    {remaining, _to_scroll} = Enum.split(region_lines, -count)
    empty_lines = List.duplicate(List.duplicate(%{}, buffer.width), count)
    new_region = empty_lines ++ remaining
    new_cells = List.replace_at(buffer.cells, top, new_region)
    {:ok, %{buffer | cells: new_cells}}
  end

  # Private helper functions

  defp do_scroll_up(buffer, count) do
    case buffer.scroll_region do
      nil ->
        {:ok, new_buffer} = scroll_entire_buffer_up(buffer, count)
        new_buffer

      region ->
        {:ok, new_buffer} =
          scroll_region_up(buffer, count, region.top, region.bottom)

        new_buffer
    end
  end

  defp do_scroll_down(buffer, count) do
    case buffer.scroll_region do
      nil ->
        {:ok, new_buffer} = scroll_entire_buffer_down(buffer, count)
        new_buffer

      region ->
        {:ok, new_buffer} =
          scroll_region_down(buffer, count, region.top, region.bottom)

        new_buffer
    end
  end
end
