defmodule Raxol.Terminal.ScreenBuffer.ScrollOps do
  @moduledoc """
  Scroll operations for ScreenBuffer: region management and scroll up/down.
  """

  alias Raxol.Terminal.Cell
  alias Raxol.Terminal.ScreenBuffer.Operations

  # ========================================
  # Region management
  # ========================================

  def scroll_to(buffer, top, bottom, line) do
    Operations.scroll_to(buffer, top, bottom, line)
  end

  def reset_scroll_region(buffer), do: clear_scroll_region(buffer)

  def get_scroll_top(buffer) do
    case buffer.scroll_region do
      nil -> 0
      {top, _} -> top
    end
  end

  def get_scroll_bottom(buffer) do
    case buffer.scroll_region do
      nil -> buffer.height - 1
      {_, bottom} -> bottom
    end
  end

  def set_scroll_region(buffer, {top, bottom}) do
    Operations.set_region(buffer, top, bottom)
  end

  def set_scroll_region(buffer, top, bottom)
      when is_integer(top) and is_integer(bottom) do
    Operations.set_region(buffer, top, bottom)
  end

  def clear_scroll_region(buffer), do: %{buffer | scroll_region: nil}

  def get_scroll_region_boundaries(buffer) do
    case buffer.scroll_region do
      nil -> {0, buffer.height - 1}
      {top, bottom} -> {top, bottom}
    end
  end

  def get_scroll_position(buffer), do: buffer.scroll_position || 0

  def get_scroll_region(buffer), do: Operations.get_region(buffer)

  def shift_region_to_line(buffer, region, target_line) do
    Operations.shift_region_to_line(buffer, region, target_line)
  end

  def scroll_down_with_count(buffer, lines, count)
      when is_integer(lines) and is_integer(count) do
    Raxol.Terminal.Commands.Scrolling.scroll_down(
      buffer,
      lines,
      buffer.scroll_region,
      %{}
    )
  end

  def scroll_down_with_count(buffer, _lines, count) when is_integer(count) do
    scroll_down(buffer, count)
  end

  # ========================================
  # Scroll content (formerly in Scrolling)
  # ========================================

  @doc """
  Scrolls the buffer content up by the specified number of lines within the scroll region.
  Returns {buffer, scrolled_lines}.
  """
  def scroll_up(buffer, lines) when lines > 0 do
    {top, bottom} = get_effective_scroll_region(buffer)

    if top < bottom do
      cells = buffer.cells || []
      {before_region, region_and_after} = Enum.split(cells, top)
      {region, after_region} = Enum.split(region_and_after, bottom - top + 1)

      lines_to_scroll = min(lines, length(region))
      scrolled_out = Enum.take(region, lines_to_scroll)
      remaining_region = Enum.drop(region, lines_to_scroll)
      empty_lines = List.duplicate(create_empty_line(buffer.width), lines_to_scroll)
      scrolled_region = remaining_region ++ empty_lines
      new_cells = before_region ++ scrolled_region ++ after_region
      {%{buffer | cells: new_cells}, scrolled_out}
    else
      {buffer, []}
    end
  end

  def scroll_up(buffer, _), do: {buffer, []}

  @doc """
  Scrolls the buffer content down by the specified number of lines within the scroll region.
  """
  def scroll_down(buffer, lines) when lines > 0 do
    {top, bottom} = get_effective_scroll_region(buffer)

    if top < bottom do
      cells = buffer.cells || []
      {before_region, region_and_after} = Enum.split(cells, top)
      {region, after_region} = Enum.split(region_and_after, bottom - top + 1)

      lines_to_scroll = min(lines, length(region))
      empty_lines = List.duplicate(create_empty_line(buffer.width), lines_to_scroll)
      kept_region = Enum.take(region, length(region) - lines_to_scroll)
      scrolled_region = empty_lines ++ kept_region
      new_cells = before_region ++ scrolled_region ++ after_region
      %{buffer | cells: new_cells}
    else
      buffer
    end
  end

  def scroll_down(buffer, _), do: buffer

  @doc """
  Scrolls up within an explicit top/bottom region.
  """
  def scroll_up(buffer, top, bottom, lines) do
    {effective_top, effective_bottom} = normalize_scroll_region(buffer, top, bottom)
    cells = buffer.cells || []

    if effective_top < effective_bottom and lines > 0 do
      {before_region, region_and_after} = Enum.split(cells, effective_top)
      {region, after_region} = Enum.split(region_and_after, effective_bottom - effective_top + 1)

      lines_to_scroll = min(lines, length(region))
      kept_region = Enum.drop(region, lines_to_scroll)
      empty_lines = List.duplicate(create_empty_line(buffer.width), lines_to_scroll)
      scrolled_region = kept_region ++ empty_lines
      new_cells = before_region ++ scrolled_region ++ after_region
      %{buffer | cells: new_cells}
    else
      buffer
    end
  end

  @doc """
  Scrolls down within an explicit top/bottom region.
  """
  def scroll_down(buffer, top, bottom, lines) do
    {effective_top, effective_bottom} = normalize_scroll_region(buffer, top, bottom)
    cells = buffer.cells || []

    if effective_top < effective_bottom and lines > 0 do
      {before_region, region_and_after} = Enum.split(cells, effective_top)
      {region, after_region} = Enum.split(region_and_after, effective_bottom - effective_top + 1)

      lines_to_scroll = min(lines, length(region))
      empty_lines = List.duplicate(create_empty_line(buffer.width), lines_to_scroll)
      kept_region = Enum.take(region, length(region) - lines_to_scroll)
      scrolled_region = empty_lines ++ kept_region
      new_cells = before_region ++ scrolled_region ++ after_region
      %{buffer | cells: new_cells}
    else
      buffer
    end
  end

  # Private helpers

  defp get_effective_scroll_region(buffer) do
    case buffer.scroll_region do
      nil -> {0, buffer.height - 1}
      {top, bottom} -> {top, min(bottom, buffer.height - 1)}
    end
  end

  defp normalize_scroll_region(buffer, top, bottom) do
    {max(0, top), min(buffer.height - 1, bottom)}
  end

  defp create_empty_line(width) when is_integer(width) and width > 0 do
    List.duplicate(Cell.new(), width)
  end

  defp create_empty_line(_width), do: []
end
