defmodule Raxol.Terminal.ScreenBuffer do
  @moduledoc """
  Manages the terminal's screen buffer state (grid, scrollback, selection).
  This module serves as the main interface for terminal buffer operations,
  delegating specific operations to specialized modules in Raxol.Terminal.Buffer.*.

  ## Structure

  The buffer consists of:
  * A main grid of cells (the visible screen)
  * A scrollback buffer for history
  * Selection state
  * Scroll region settings
  * Dimensions (width and height)

  ## Operations

  The module delegates operations to specialized modules:
  * `Content` - Writing and content management
  * `ScrollRegion` - Scroll region and scrolling operations
  * `LineOperations` - Line manipulation
  * `CharEditor` - Character editing
  * `LineEditor` - Line editing
  * `Eraser` - Clearing operations
  * `Selection` - Text selection
  * `Scrollback` - History management
  * `Queries` - State querying
  * `Initializer` - Buffer creation and validation
  * `Cursor` - Cursor state management
  * `Charset` - Character set management
  * `Formatting` - Text formatting and styling
  """

  @behaviour Raxol.Terminal.ScreenBufferBehaviour

  alias Raxol.Terminal.Cell
  alias Raxol.Terminal.ANSI.TextFormatting

  alias Raxol.Terminal.Buffer.{
    CharEditor,
    Writer
  }

  alias Raxol.Core.Utils.Validation

  alias Raxol.Terminal.ScreenBuffer.{
    EraseOperations,
    MemoryUtils,
    RegionOperations,
    BehaviourImpl,
    Operations,
    Attributes,
    Selection
  }

  defstruct [
    :cells,
    :scrollback,
    :scrollback_limit,
    :selection,
    :scroll_region,
    :scroll_position,
    :width,
    :height,
    :damage_regions,
    :default_style,
    cursor_position: {0, 0},
    cursor_style: :block,
    cursor_visible: true,
    cursor_blink: true,
    alternate_screen: false
  ]

  @type t :: %__MODULE__{
          cells: list(list(Cell.t())),
          scrollback: list(list(Cell.t())),
          scrollback_limit: non_neg_integer(),
          selection: {integer(), integer(), integer(), integer()} | nil,
          scroll_region: {integer(), integer()} | nil,
          scroll_position: non_neg_integer(),
          width: non_neg_integer(),
          height: non_neg_integer(),
          cursor_position: {non_neg_integer(), non_neg_integer()},
          cursor_style: atom(),
          cursor_visible: boolean(),
          cursor_blink: boolean(),
          damage_regions: [
            {non_neg_integer(), non_neg_integer(), non_neg_integer(),
             non_neg_integer()}
          ],
          default_style: TextFormatting.text_style(),
          alternate_screen: boolean()
        }

  # === Core Operations ===

  @doc """
  Creates a new screen buffer with the specified dimensions.
  Validates and normalizes the input dimensions to ensure they are valid.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec new(integer(), integer()) :: t()
  def new(width, height, scrollback_limit \\ 1000) do
    width = validate_dimension(width, 80)
    height = validate_dimension(height, 24)
    scrollback_limit = validate_dimension(scrollback_limit, 1000)

    %__MODULE__{
      cells: create_empty_grid(width, height),
      scrollback: [],
      scrollback_limit: scrollback_limit,
      selection: nil,
      scroll_region: nil,
      scroll_position: 0,
      width: width,
      height: height,
      cursor_position: {0, 0},
      cursor_style: :block,
      cursor_visible: true,
      cursor_blink: true,
      damage_regions: [],
      default_style: TextFormatting.new()
    }
  end

  @spec validate_dimension(integer(), non_neg_integer()) :: non_neg_integer()
  defp validate_dimension(dimension, default) do
    Validation.validate_dimension(dimension, default)
  end

  @spec create_empty_grid(non_neg_integer(), non_neg_integer()) ::
          list(list(Cell.t()))
  defp create_empty_grid(width, height) when width > 0 and height > 0 do
    for _y <- 0..(height - 1) do
      for _x <- 0..(width - 1) do
        Cell.new()
      end
    end
  end

  defp create_empty_grid(_width, _height) do
    []
  end

  def resize(buffer, new_width, new_height) do
    # Validate input dimensions
    validate_dimensions(
      new_width <= 0 or new_height <= 0,
      new_width,
      new_height
    )

    # Create a new ScreenBuffer with the new dimensions
    default_cell = %Raxol.Terminal.Cell{
      char: " ",
      style: nil,
      dirty: nil,
      wide_placeholder: false
    }

    # Create new cells array with the new dimensions
    new_cells =
      List.duplicate(List.duplicate(default_cell, new_width), new_height)

    # Copy existing content, truncating or padding as needed
    # Handle case where buffer.cells might be nil or corrupted
    cells = buffer.cells || []

    new_cells =
      Enum.reduce(0..min(buffer.height - 1, new_height - 1), new_cells, fn row,
                                                                           acc ->
        row_data = Enum.at(cells, row, [])

        Enum.reduce(0..min(buffer.width - 1, new_width - 1), acc, fn col,
                                                                     row_acc ->
          existing_cell =
            Enum.at(row_data, col) || default_cell

          current_row =
            Enum.at(row_acc, row, List.duplicate(default_cell, new_width))

          updated_row = List.replace_at(current_row, col, existing_cell)

          List.replace_at(row_acc, row, updated_row)
        end)
      end)

    # Clear selection and scroll region after resize
    %{
      buffer
      | width: new_width,
        height: new_height,
        cells: new_cells,
        selection: nil,
        scroll_region: nil
    }
  end

  @doc """
  Gets all lines from the buffer as a list of lines.
  """
  @spec get_lines(t()) :: list(list(Cell.t()))
  def get_lines(%__MODULE__{cells: cells}), do: cells
  def get_lines(_), do: []

  # === Content Operations ===

  @spec write_char(t(), non_neg_integer(), non_neg_integer(), String.t()) :: t()
  def write_char(buffer, x, y, char) do
    write_char(buffer, x, y, char, buffer.default_style)
  end

  @doc """
  Writes a character at the specified position with optional styling.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec write_char(
          t(),
          non_neg_integer(),
          non_neg_integer(),
          String.t(),
          TextFormatting.text_style() | nil
        ) :: t()
  def write_char(buffer, x, y, char, style) when x >= 0 and y >= 0 do
    case {x < buffer.width, y < buffer.height} do
      {true, true} ->
        # Use Writer module to handle wide characters properly
        Writer.write_char(buffer, x, y, char, style)

      _ ->
        buffer
    end
  end

  @spec write_string(t(), non_neg_integer(), non_neg_integer(), String.t()) ::
          t()
  def write_string(buffer, x, y, string),
    do: write_string(buffer, x, y, string, nil)

  @doc """
  Writes a string starting at the specified position.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec write_string(
          t(),
          non_neg_integer(),
          non_neg_integer(),
          String.t(),
          TextFormatting.text_style() | nil
        ) :: t()
  def write_string(buffer, x, y, string, style) when x >= 0 and y >= 0 do
    case {x < buffer.width, y < buffer.height} do
      {true, true} ->
        # Use the Writer module which properly handles wide characters
        Writer.write_string(buffer, x, y, string, style)

      _ ->
        buffer
    end
  end

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def get_char(buffer, x, y) do
    case {x >= 0 and x < buffer.width, y >= 0 and y < buffer.height} do
      {true, true} ->
        row = Enum.at(buffer.cells, y, [])
        cell = Enum.at(row, x)

        case cell do
          %{char: char} -> char
          _ -> " "
        end

      _ ->
        " "
    end
  end

  @doc """
  Gets a specific cell from the buffer.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec get_cell(t(), non_neg_integer(), non_neg_integer()) :: map()
  def get_cell(buffer, x, y) when x >= 0 and y >= 0 do
    cell =
      if x < buffer.width and y < buffer.height do
        case buffer.cells do
          nil ->
            Cell.new()

          cells ->
            cells
            |> Enum.at(y, [])
            |> Enum.at(x, Cell.new())
        end
      else
        Cell.new()
      end

    # Convert Cell struct to map for behaviour compliance
    Map.from_struct(cell)
  end

  def get_cell(_, _, _), do: Map.from_struct(Cell.new())

  @doc """
  Gets the content of the buffer as a string representation.
  """
  @spec get_content(t()) :: String.t()
  def get_content(buffer) do
    # Convert cells to string representation for compatibility with tests
    case buffer.cells do
      nil ->
        ""

      cells when is_list(cells) ->
        cells
        |> Enum.map(fn line when is_list(line) ->
          line
          |> Enum.map(fn
            %Cell{char: char} ->
              char

            cell ->
              case Map.get(cell, :char) do
                nil -> " "
                char -> char
              end
          end)
          |> Enum.join("")
          |> String.trim_trailing()
        end)
        |> Enum.reverse()
        |> Enum.drop_while(&(&1 == ""))
        |> Enum.reverse()
        |> case do
          [] -> ""
          lines -> Enum.join(lines, "\n")
        end

      _ ->
        ""
    end
  end

  @doc """
  Puts a line of cells at the specified y position.
  """
  @spec put_line(t(), non_neg_integer(), list(Cell.t())) :: t()
  def put_line(buffer, y, line) when y >= 0 and y < buffer.height do
    new_cells = List.replace_at(buffer.cells || [], y, line)
    %{buffer | cells: new_cells}
  end

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

  # === Eraser Operations ===

  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate clear_line(buffer, line), to: Operations
  defdelegate clear_line(buffer, line, style), to: Operations
  defdelegate erase_chars(buffer, count), to: Operations
  defdelegate erase_chars(buffer, x, y, count), to: Operations
  defdelegate erase_display(buffer, mode), to: Operations
  defdelegate erase_line(buffer, mode), to: Operations
  defdelegate erase_line(buffer, line, mode), to: Operations

  # === Line Operations ===

  defdelegate insert_lines(buffer, count), to: Operations
  defdelegate delete_lines(buffer, count), to: Operations

  # This delegation doesn't exist in Operations yet, keeping original
  defdelegate delete_lines_in_region(buffer, lines, y, top, bottom),
    to: Raxol.Terminal.Buffer.LineOperations

  defdelegate insert_chars(buffer, count), to: Operations
  defdelegate delete_chars(buffer, count), to: Operations
  defdelegate prepend_lines(buffer, lines), to: Operations

  # === Scroll Operations ===

  @doc """
  Scrolls the buffer content up by the specified number of lines.
  Returns {buffer, scrolled_lines} where scrolled_lines are the lines that were scrolled out.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec scroll_up(t(), non_neg_integer()) :: {t(), list(list(Cell.t()))}
  def scroll_up(buffer, lines) when lines > 0 do
    {top, bottom} = get_effective_scroll_region(buffer)

    if lines > 0 and top < bottom do
      # Move lines up within the scroll region
      cells = buffer.cells || []

      # Extract the region
      {before_region, region_and_after} = Enum.split(cells, top)
      {region, after_region} = Enum.split(region_and_after, bottom - top + 1)

      # Get the lines that will be scrolled out
      lines_to_scroll = min(lines, length(region))
      scrolled_out = Enum.take(region, lines_to_scroll)

      # Scroll the region
      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

      # Reconstruct the buffer
      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.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec scroll_down(t(), non_neg_integer()) :: t()
  def scroll_down(buffer, lines) when lines > 0 do
    {top, bottom} = get_effective_scroll_region(buffer)

    if lines > 0 and top < bottom do
      # Move lines down within the scroll region
      cells = buffer.cells || []

      # Extract the region
      {before_region, region_and_after} = Enum.split(cells, top)
      {region, after_region} = Enum.split(region_and_after, bottom - top + 1)

      # Scroll the region
      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

      # Reconstruct the buffer
      new_cells = before_region ++ scrolled_region ++ after_region
      %{buffer | cells: new_cells}
    else
      buffer
    end
  end

  def scroll_down(buffer, _), do: buffer

  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
    # Return empty list for invalid width
    []
  end

  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

  # Additional scroll functions for ScrollOperations
  def scroll_up(buffer, top, bottom, lines) do
    do_scroll_up(buffer, lines, top, bottom)
  end

  def scroll_down(buffer, top, bottom, lines) do
    do_scroll_down(buffer, lines, top, bottom)
  end

  # Internal scroll implementations
  defp do_scroll_up(buffer, lines, top, bottom) do
    {effective_top, effective_bottom} =
      normalize_scroll_region(buffer, top, bottom)

    cells = buffer.cells || []

    if effective_top < effective_bottom and lines > 0 do
      # Extract the region
      {before_region, region_and_after} = Enum.split(cells, effective_top)

      {region, after_region} =
        Enum.split(region_and_after, effective_bottom - effective_top + 1)

      # Scroll the region
      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

      # Reconstruct the buffer
      new_cells = before_region ++ scrolled_region ++ after_region
      %{buffer | cells: new_cells}
    else
      buffer
    end
  end

  defp do_scroll_down(buffer, lines, top, bottom) do
    {effective_top, effective_bottom} =
      normalize_scroll_region(buffer, top, bottom)

    cells = buffer.cells || []

    if effective_top < effective_bottom and lines > 0 do
      # Extract the region
      {before_region, region_and_after} = Enum.split(cells, effective_top)

      {region, after_region} =
        Enum.split(region_and_after, effective_bottom - effective_top + 1)

      # Scroll the region
      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

      # Reconstruct the buffer
      new_cells = before_region ++ scrolled_region ++ after_region
      %{buffer | cells: new_cells}
    else
      buffer
    end
  end

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

  def scroll_to(buffer, top, bottom, line) do
    # This needs to be implemented or delegated elsewhere
    Operations.scroll_to(buffer, top, bottom, line)
  end

  def reset_scroll_region(buffer) do
    clear_scroll_region(buffer)
  end

  @doc """
  Gets the top boundary of the scroll region.
  """
  @spec get_scroll_top(t()) :: non_neg_integer()
  def get_scroll_top(buffer) do
    case buffer.scroll_region do
      nil -> 0
      {top, _} -> top
    end
  end

  @doc """
  Gets the bottom boundary of the scroll region.
  """
  @spec get_scroll_bottom(t()) :: non_neg_integer()
  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

  @doc """
  Sets the scroll region with individual top and bottom parameters.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  def set_scroll_region(buffer, top, bottom)
      when is_integer(top) and is_integer(bottom) do
    Operations.set_region(buffer, top, bottom)
  end

  # === Dimension Operations ===

  @doc """
  Gets the dimensions of the buffer.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec get_dimensions(t()) :: {non_neg_integer(), non_neg_integer()}
  def get_dimensions(buffer) do
    {buffer.width, buffer.height}
  end

  @doc """
  Gets the width of the buffer.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec get_width(t()) :: non_neg_integer()
  def get_width(buffer) do
    buffer.width
  end

  @doc """
  Gets the height of the buffer.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec get_height(t()) :: non_neg_integer()
  def get_height(buffer) do
    buffer.height
  end

  # === Cursor Operations ===

  defdelegate set_cursor_position(buffer, x, y), to: Attributes
  defdelegate get_cursor_position(buffer), to: Attributes

  defdelegate set_cursor_visibility(buffer, visible),
    to: Attributes

  defdelegate cursor_visible?(buffer), to: Attributes
  defdelegate set_cursor_style(buffer, style), to: Attributes
  defdelegate get_cursor_style(buffer), to: Attributes
  defdelegate set_cursor_blink(buffer, blink), to: Attributes
  defdelegate cursor_blinking?(buffer), to: Attributes

  # === Charset Operations ===

  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate designate_charset(buffer, slot, charset),
    to: Attributes

  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_designated_charset(buffer, slot),
    to: Attributes

  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate invoke_g_set(buffer, slot), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_current_g_set(buffer), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate apply_single_shift(buffer, slot), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_single_shift(buffer), to: Attributes
  defdelegate reset_charset_state(buffer), to: Attributes

  # === Formatting Operations ===

  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_style(buffer), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate update_style(buffer, style), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate set_attribute(buffer, attribute), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate reset_attribute(buffer, attribute), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate set_foreground(buffer, color), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate set_background(buffer, color), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate reset_all_attributes(buffer), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_foreground(buffer), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_background(buffer), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate attribute_set?(buffer, attribute), to: Attributes
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_set_attributes(buffer), to: Attributes

  # === Selection Operations ===

  defdelegate start_selection(buffer, x, y), to: Selection
  defdelegate clear_selection(buffer), to: Selection
  defdelegate get_selected_text(buffer), to: Selection

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

  def get_selection(buffer), do: buffer.selection

  def in_selection?(buffer, x, y), do: Selection.selected?(buffer, x, y)

  def get_selection_boundaries(buffer), do: buffer.selection

  defdelegate get_text_in_region(buffer, start_x, start_y, end_x, end_y),
    to: Attributes

  def selection_active?(buffer), do: buffer.selection != nil

  def get_selection_start(buffer) do
    case buffer.selection do
      {sx, sy, _, _} -> {sx, sy}
      nil -> nil
    end
  end

  def get_selection_end(buffer) do
    case buffer.selection do
      {sx, sy, sx, sy} -> nil
      {_, _, ex, ey} -> {ex, ey}
      nil -> nil
    end
  end

  # === Scroll Region Operations ===

  @doc """
  Clears the scroll region, resetting to full screen.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec clear_scroll_region(t()) :: t()
  def clear_scroll_region(buffer) do
    %{buffer | scroll_region: nil}
  end

  @doc """
  Gets the current scroll region boundaries.
  Returns {0, height-1} if no region is set.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec get_scroll_region_boundaries(t()) ::
          {non_neg_integer(), non_neg_integer()}
  def get_scroll_region_boundaries(buffer) do
    case buffer.scroll_region do
      nil -> {0, buffer.height - 1}
      {top, bottom} -> {top, bottom}
    end
  end

  # === Query Operations ===

  @doc """
  Gets a specific line from the buffer.
  """
  @spec get_line(t(), non_neg_integer()) :: list(Cell.t())
  def get_line(buffer, y) when y >= 0 and y < buffer.height do
    case buffer.cells do
      nil -> []
      cells -> Enum.at(cells, y, [])
    end
  end

  def get_line(_, _), do: []

  @doc """
  Gets the cell at the specified position in the buffer.
  """
  @spec get_cell_at(t(), non_neg_integer(), non_neg_integer()) :: map()
  def get_cell_at(buffer, x, y) do
    get_cell(buffer, x, y)
  end

  @doc """
  Checks if the buffer is empty.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec empty?(t()) :: boolean()
  def empty?(buffer) do
    case buffer.cells do
      nil ->
        true

      cells ->
        Enum.all?(cells, fn line ->
          Enum.all?(line, &Cell.empty?/1)
        end)
    end
  end

  # === Scrollback Operations ===

  @doc """
  Gets the current scroll position within the scrollback.
  """
  @impl Raxol.Terminal.ScreenBufferBehaviour
  @spec get_scroll_position(t()) :: non_neg_integer()
  def get_scroll_position(buffer) do
    buffer.scroll_position || 0
  end

  # === Cleanup ===

  def cleanup(_buffer), do: :ok

  # Higher-arity insert_lines for command handlers
  @doc """
  Inserts blank lines at a specific position with style.
  """
  def insert_lines(buffer, y, count, style) do
    Operations.insert_lines(buffer, y, count, style)
  end

  @doc """
  Inserts lines at a specific position within a scroll region.

  ## Parameters
  - y: position to insert at
  - count: number of lines to insert
  - style: style for the blank lines
  - {top, bottom}: scroll region boundaries
  """
  def insert_lines(buffer, y, count, style, {top, bottom}) do
    Operations.insert_lines(buffer, y, count, style, {top, bottom})
  end

  # Higher-arity insert_lines for region (legacy interface)
  def insert_lines(buffer, lines, y, top, bottom) do
    Operations.insert_lines(buffer, lines, y, top, bottom)
  end

  # Higher-arity delete_lines for command handlers
  @doc """
  Deletes lines at a specific position.

  ## Parameters
  - For command handlers: y, count, style, and region boundaries
  - For region operations: lines, y, top, and bottom positions
  """
  def delete_lines(buffer, y, count, style, {top, bottom}) do
    Operations.delete_lines(
      buffer,
      y,
      count,
      style,
      {top, bottom}
    )
  end

  # Higher-arity delete_lines for region
  def delete_lines(buffer, lines, y, top, bottom) do
    Operations.delete_lines(buffer, lines, y, top, bottom)
  end

  # === Screen Operations ===

  def clear(buffer, _style \\ nil) do
    # Clear the entire buffer by setting all cells to empty
    # Use Cell.new() to match the default cell structure
    new_cells = create_empty_grid(buffer.width, buffer.height)
    %{buffer | cells: new_cells}
  end

  def erase_from_cursor_to_end(buffer, x, y, top, bottom) do
    try do
      EraseOperations.erase_from_cursor_to_end(buffer, x, y, top, bottom)
    rescue
      _ -> buffer
    end
  end

  def erase_from_start_to_cursor(buffer, x, y, top, bottom) do
    try do
      EraseOperations.erase_from_start_to_cursor(buffer, x, y, top, bottom)
    rescue
      _ -> buffer
    end
  end

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def erase_all(buffer) do
    try do
      EraseOperations.erase_all(buffer)
    rescue
      _ -> buffer
    end
  end

  def clear_region(buffer, x, y, width, height) do
    try do
      EraseOperations.clear_region(buffer, x, y, width, height)
    rescue
      _ -> buffer
    end
  end

  def mark_damaged(buffer, x, y, width, height, _reason) do
    # Add the new damage region to the existing list
    new_region = {x, y, width, height}
    updated_damage_regions = [new_region | buffer.damage_regions || []]
    %{buffer | damage_regions: updated_damage_regions}
  end

  def pop_bottom_lines(buffer, count) do
    # Remove count lines from bottom and return {buffer, removed_lines}
    cells = buffer.cells || []
    cells_count = length(cells)
    lines_to_remove = min(count, cells_count)

    {removed_lines, remaining_cells} = Enum.split(cells, -lines_to_remove)
    new_buffer = %{buffer | cells: remaining_cells}
    {new_buffer, removed_lines}
  end

  def erase_display(buffer, mode, _cursor, _min_row, _max_row) do
    # Simple implementation for erase display
    case mode do
      0 -> erase_from_cursor_to_end(buffer)
      1 -> erase_from_start_to_cursor(buffer)
      2 -> clear(buffer)
      _ -> buffer
    end
  end

  @doc """
  Erases the entire screen.
  """
  @spec erase_screen(t()) :: t()
  def erase_screen(buffer) do
    case EraseOperations.erase_all(buffer) do
      %__MODULE__{} = updated -> updated
      _ -> buffer
    end
  end

  def erase_line(buffer, mode, cursor, _min_col, _max_col) do
    # Use EraseOperations.erase_in_line which actually exists
    {cursor_x, cursor_y} = {elem(cursor, 0), elem(cursor, 1)}

    case mode do
      0 ->
        EraseOperations.erase_in_line(buffer, {cursor_x, cursor_y}, :to_end)

      1 ->
        EraseOperations.erase_in_line(
          buffer,
          {cursor_x, cursor_y},
          :to_beginning
        )

      2 ->
        EraseOperations.erase_in_line(buffer, {cursor_x, cursor_y}, :all)

      _ ->
        buffer
    end
  end

  def delete_chars(buffer, count, cursor, _max_col) do
    # Simple implementation for delete chars
    {cursor_x, cursor_y} = cursor

    CharEditor.delete_characters(
      buffer,
      cursor_y,
      cursor_x,
      count,
      buffer.default_style
    )
  end

  def insert_chars(buffer, _count, _cursor, _max_col) do
    # Simple implementation for insert chars - just return buffer for now
    buffer
  end

  def set_dimensions(buffer, width, height) do
    resize(buffer, width, height)
  end

  def get_scrollback(buffer) do
    buffer.scrollback || []
  end

  def set_scrollback(buffer, scrollback) do
    %{buffer | scrollback: scrollback}
  end

  def get_damaged_regions(buffer) do
    buffer.damage_regions || []
  end

  def clear_damaged_regions(buffer) do
    %{buffer | damage_regions: []}
  end

  def get_scroll_region(buffer) do
    Operations.get_region(buffer)
  end

  def shift_region_to_line(buffer, region, target_line) do
    try do
      Operations.shift_region_to_line(buffer, region, target_line)
    rescue
      _ -> buffer
    end
  end

  @doc """
  Gets the estimated memory usage of the screen buffer.
  """
  @spec get_memory_usage(t()) :: non_neg_integer()
  defdelegate get_memory_usage(buffer), to: MemoryUtils

  def erase_in_line(buffer, position, type) do
    try do
      EraseOperations.erase_in_line(buffer, position, type)
    rescue
      _ -> buffer
    end
  end

  def erase_in_display(buffer, position, type) do
    EraseOperations.erase_in_display(buffer, position, type)
  end

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def erase_from_cursor_to_end(buffer) do
    try do
      EraseOperations.erase_from_cursor_to_end(buffer)
    rescue
      _ -> buffer
    end
  end

  # Higher-arity delete_characters for command handlers
  @doc """
  Deletes a specified number of characters starting from the given position in the buffer.
  Delegates to CharEditor.delete_characters/5.
  """
  def delete_characters(buffer, row, col, count, default_style) do
    CharEditor.delete_characters(buffer, row, col, count, default_style)
  end

  @doc """
  Scrolls the buffer down by the specified number of lines with additional parameters.
  """
  def scroll_down(buffer, lines, count)
      when is_integer(lines) and is_integer(count) do
    try do
      case Raxol.Terminal.Commands.Scrolling.scroll_down(
             buffer,
             lines,
             buffer.scroll_region,
             %{}
           ) do
        {updated_buffer, _} -> updated_buffer
        updated_buffer -> updated_buffer
      end
    rescue
      _ -> buffer
    end
  end

  # Handle case where lines parameter is a list (from tests)
  def scroll_down(buffer, _lines, count) when is_integer(count) do
    _ =
      Raxol.Terminal.Commands.Scrolling.scroll_down(
        buffer,
        count,
        buffer.scroll_region,
        %{}
      )

    buffer
  end

  # === Behaviour Callback Implementations ===

  # All behaviour implementations delegated to BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate cleanup_file_watching(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate clear_output_buffer(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate clear_saved_states(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate clear_screen(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate collect_metrics(buffer, type), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate create_chart(buffer, type, options), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate current_theme(), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate enqueue_control_sequence(buffer, sequence), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate erase_all_with_scrollback(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate erase_from_cursor_to_end_of_line(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate erase_from_start_of_line_to_cursor(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate erase_from_start_to_cursor(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate erase_line(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate flush_output(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_config(), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_current_state(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_metric(buffer, type, name), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_metric_value(buffer, name), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_metrics_by_type(buffer, type), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_output_buffer(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_preferences(), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_saved_states_count(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_size(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_state_stack(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate get_update_settings(), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate handle_csi_sequence(buffer, command, params), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate handle_debounced_events(buffer, events, delay), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate handle_file_event(buffer, event), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate handle_mode(buffer, mode, value), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate has_saved_states?(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate light_theme(), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate mark_damaged(buffer, x, y, width, height), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate record_metric(buffer, type, name, value), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate record_operation(buffer, operation, duration), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate record_performance(buffer, metric, value), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate record_resource(buffer, type, value), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate reset_state(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate restore_state(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate save_state(buffer), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate set_config(config), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate set_preferences(preferences), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate update_current_state(buffer, updates), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate update_state_stack(buffer, stack), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate verify_metrics(buffer, type), to: BehaviourImpl
  @impl Raxol.Terminal.ScreenBufferBehaviour
  defdelegate write(buffer, data), to: BehaviourImpl

  # Overloaded write for compatibility with legacy code
  def write(buffer, string, opts) when is_map(buffer) and is_binary(string) do
    # Merge opts into the buffer or use separately
    write_string(buffer, 0, 0, string, opts[:style] || nil)
  end

  # === Scroll Operations ===

  defdelegate fill_region(buffer, x, y, width, height, cell),
    to: RegionOperations

  def update(buffer, changes) when is_map(changes) do
    Enum.reduce(changes, buffer, fn {key, value}, acc ->
      Map.put(acc, key, value)
    end)
  end

  defdelegate handle_single_line_replacement(
                lines_list,
                row,
                start_col,
                end_col,
                replacement
              ),
              to: RegionOperations

  defp validate_dimensions(true, new_width, new_height) do
    raise ArgumentError,
          "ScreenBuffer dimensions must be positive integers, got: #{new_width}x#{new_height}"
  end

  defp validate_dimensions(false, _new_width, _new_height), do: :ok

  # Functions for compatibility
  @doc """
  Creates a new buffer with default dimensions.
  """
  def new do
    new(80, 24)
  end

  @doc """
  Creates a new buffer with a single dimension parameter.
  Creates a square buffer.
  """
  def new(size) when is_integer(size) and size > 0 do
    new(size, size)
  end

  # Additional compatibility functions for tests and legacy code
  @doc """
  Scrolls the buffer content.
  Returns {buffer, scrolled_lines}.
  """
  def scroll(buffer, lines) when lines > 0 do
    scroll_up(buffer, lines)
  end

  def scroll(buffer, lines) when lines < 0 do
    # scroll_down doesn't return scrolled_lines, so we return empty list
    {scroll_down(buffer, -lines), []}
  end

  def scroll(buffer, 0), do: {buffer, []}

  @doc """
  Writes content to the buffer at the specified position.
  This is a convenience function for write_string.
  """
  def write(buffer, x, y, content) when is_binary(content) do
    write_string(buffer, x, y, content)
  end

  def write(buffer, x, y, content) do
    write_char(buffer, x, y, to_string(content))
  end
end
