defmodule Raxol.Terminal.ScreenBuffer.Operations.Erasing do
  @moduledoc """
  Erasing operations for the screen buffer.

  This module handles various erasing operations including
  erase in display and erase in line with different modes.
  """

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

  @doc """
  Erases in display based on mode.

  Mode values:
  - 0: From cursor to end of display
  - 1: From start to cursor
  - 2: Entire display
  - 3: Entire display including scrollback
  """
  @spec erase_in_display(ScreenBuffer.t(), non_neg_integer(), map()) ::
          ScreenBuffer.t()
  def erase_in_display(buffer, mode, cursor_pos \\ %{x: 0, y: 0})

  def erase_in_display(buffer, 0, cursor_pos) do
    # Erase from cursor to end of display
    erase_from_cursor_to_end(buffer, cursor_pos)
  end

  def erase_in_display(buffer, 1, cursor_pos) do
    # Erase from start to cursor
    erase_from_start_to_cursor(buffer, cursor_pos)
  end

  def erase_in_display(buffer, 2, _cursor_pos) do
    # Erase entire display
    clear_display(buffer)
  end

  def erase_in_display(buffer, 3, _cursor_pos) do
    # Erase entire display including scrollback
    clear_display_with_scrollback(buffer)
  end

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

  @doc """
  Erases in line based on mode.

  Mode values:
  - 0: From cursor to end of line
  - 1: From start of line to cursor
  - 2: Entire line
  """
  @spec erase_in_line(ScreenBuffer.t(), non_neg_integer(), map()) ::
          ScreenBuffer.t()
  def erase_in_line(buffer, mode, cursor_pos \\ %{x: 0, y: 0})

  def erase_in_line(buffer, 0, cursor_pos) do
    # Erase from cursor to end of line
    erase_line_from_cursor_to_end(buffer, cursor_pos)
  end

  def erase_in_line(buffer, 1, cursor_pos) do
    # Erase from start of line to cursor
    erase_line_from_start_to_cursor(buffer, cursor_pos)
  end

  def erase_in_line(buffer, 2, cursor_pos) do
    # Erase entire line
    clear_line(buffer, cursor_pos.y)
  end

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

  # Private helper functions

  defp erase_from_cursor_to_end(buffer, %{x: x, y: y}) do
    cells = buffer.cells || []

    new_cells =
      cells
      |> Enum.with_index()
      |> Enum.map(fn {row, row_idx} ->
        cond do
          row_idx < y -> row
          row_idx == y -> erase_row_from(row, x)
          true -> create_empty_row(buffer.width)
        end
      end)

    %{buffer | cells: new_cells}
  end

  defp erase_from_start_to_cursor(buffer, %{x: x, y: y}) do
    cells = buffer.cells || []

    new_cells =
      cells
      |> Enum.with_index()
      |> Enum.map(fn {row, row_idx} ->
        cond do
          row_idx < y -> create_empty_row(buffer.width)
          row_idx == y -> erase_row_to(row, x)
          true -> row
        end
      end)

    %{buffer | cells: new_cells}
  end

  defp erase_line_from_cursor_to_end(buffer, %{x: x, y: y}) do
    cells = buffer.cells || []

    new_cells =
      cells
      |> Enum.with_index()
      |> Enum.map(fn {row, row_idx} ->
        if row_idx == y do
          erase_row_from(row, x)
        else
          row
        end
      end)

    %{buffer | cells: new_cells}
  end

  defp erase_line_from_start_to_cursor(buffer, %{x: x, y: y}) do
    cells = buffer.cells || []

    new_cells =
      cells
      |> Enum.with_index()
      |> Enum.map(fn {row, row_idx} ->
        if row_idx == y do
          erase_row_to(row, x)
        else
          row
        end
      end)

    %{buffer | cells: new_cells}
  end

  defp clear_display(buffer) do
    empty_cells =
      for _ <- 0..(buffer.height - 1) do
        create_empty_row(buffer.width)
      end

    %{buffer | cells: empty_cells}
  end

  defp clear_display_with_scrollback(buffer) do
    buffer
    |> clear_display()
    |> Map.put(:scrollback, [])
  end

  defp clear_line(buffer, y) when y >= 0 and y < buffer.height do
    cells = buffer.cells || []

    new_cells =
      cells
      |> Enum.with_index()
      |> Enum.map(fn {row, row_idx} ->
        if row_idx == y do
          create_empty_row(buffer.width)
        else
          row
        end
      end)

    %{buffer | cells: new_cells}
  end

  defp clear_line(buffer, _), do: buffer

  defp create_empty_row(width) do
    List.duplicate(Cell.new(), width)
  end

  defp erase_row_from(row, x) do
    row
    |> Enum.with_index()
    |> Enum.map(fn {cell, idx} ->
      if idx >= x, do: Cell.new(), else: cell
    end)
  end

  defp erase_row_to(row, x) do
    row
    |> Enum.with_index()
    |> Enum.map(fn {cell, idx} ->
      if idx <= x, do: Cell.new(), else: cell
    end)
  end
end
