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
  """

  require Logger

  @default_width Raxol.Core.Defaults.terminal_width()
  @default_height Raxol.Core.Defaults.terminal_height()
  @default_scrollback Raxol.Core.Defaults.scrollback_limit()

  @behaviour Raxol.Terminal.ScreenBufferBehaviour

  @compile {:no_warn_undefined,
            [
              Raxol.Terminal.ScreenBuffer.WriteOps,
              Raxol.Terminal.ScreenBuffer.ScrollOps,
              Raxol.Terminal.ScreenBuffer.EraseOperations,
              Raxol.Terminal.ScreenBuffer.LineOps
            ]}

  alias Raxol.Core.Utils.Validation
  alias Raxol.Terminal.ANSI.TextFormatting
  alias Raxol.Terminal.Cell

  alias Raxol.Terminal.ScreenBuffer.{
    Attributes,
    BehaviourImpl,
    EraseOperations,
    LineOps,
    Operations,
    RegionOperations,
    ScrollOps,
    Selection,
    WriteOps
  }

  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
  def new(width, height, scrollback_limit \\ @default_scrollback) do
    width = Validation.validate_dimension(width, @default_width)
    height = Validation.validate_dimension(height, @default_height)
    scrollback_limit = Validation.validate_dimension(scrollback_limit, @default_scrollback)

    %__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

  def new do
    new(@default_width, @default_height)
  end

  def new(size) when is_integer(size) and size > 0 do
    new(size, size)
  end

  def resize(buffer, new_width, new_height) do
    validate_positive_dimensions!(new_width, new_height)
    WriteOps.resize(buffer, new_width, new_height)
  end

  def get_lines(%__MODULE__{cells: cells}), do: cells
  def get_lines(_), do: []

  # === Content Operations (WriteOps) ===

  def write_char(buffer, x, y, char),
    do: WriteOps.write_char(buffer, x, y, char)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def write_char(buffer, x, y, char, style),
    do: WriteOps.write_char(buffer, x, y, char, style)

  def write_string(buffer, x, y, string),
    do: WriteOps.write_string(buffer, x, y, string)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def write_string(buffer, x, y, string, style),
    do: WriteOps.write_string(buffer, x, y, string, style)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def get_char(buffer, x, y), do: WriteOps.get_char(buffer, x, y)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def get_cell(buffer, x, y) when x >= 0 and y >= 0,
    do: WriteOps.get_cell(buffer, x, y)

  def get_cell(_, _, _), do: WriteOps.get_cell(nil, -1, -1)

  def get_content(buffer), do: WriteOps.get_content(buffer)

  def put_line(buffer, y, line), do: WriteOps.put_line(buffer, y, line)

  # === Eraser Operations (delegated) ===

  @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 (delegated) ===

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

  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 ===

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def scroll_up(buffer, lines), do: ScrollOps.scroll_up(buffer, lines)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def scroll_down(buffer, lines), do: ScrollOps.scroll_down(buffer, lines)

  def scroll_up(buffer, top, bottom, lines),
    do: ScrollOps.scroll_up(buffer, top, bottom, lines)

  def scroll_down(buffer, top, bottom, lines),
    do: ScrollOps.scroll_down(buffer, top, bottom, lines)

  def scroll_to(buffer, top, bottom, line),
    do: ScrollOps.scroll_to(buffer, top, bottom, line)

  def reset_scroll_region(buffer),
    do: ScrollOps.reset_scroll_region(buffer)

  def get_scroll_top(buffer),
    do: ScrollOps.get_scroll_top(buffer)

  def get_scroll_bottom(buffer),
    do: ScrollOps.get_scroll_bottom(buffer)

  def set_scroll_region(buffer, {top, bottom}),
    do: ScrollOps.set_scroll_region(buffer, {top, bottom})

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def set_scroll_region(buffer, top, bottom)
      when is_integer(top) and is_integer(bottom),
      do: ScrollOps.set_scroll_region(buffer, top, bottom)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def clear_scroll_region(buffer),
    do: ScrollOps.clear_scroll_region(buffer)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def get_scroll_region_boundaries(buffer),
    do: ScrollOps.get_scroll_region_boundaries(buffer)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def get_scroll_position(buffer),
    do: ScrollOps.get_scroll_position(buffer)

  # === Dimension Operations ===

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def get_dimensions(buffer), do: {buffer.width, buffer.height}

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def get_width(buffer), do: buffer.width

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def get_height(buffer), do: buffer.height

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

  # === 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
  defdelegate update_selection(buffer, x, y), to: Selection
  defdelegate get_selection_boundaries(buffer), to: Selection
  defdelegate selection_active?(buffer), to: Selection
  defdelegate get_selection_start(buffer), to: Selection
  defdelegate get_selection_end(buffer), to: Selection

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

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

  # === Erase Operations (EraseOps) ===

  def clear(buffer, style \\ nil), do: EraseOperations.clear(buffer, style)

  def erase_from_cursor_to_end(buffer, x, y, top, bottom),
    do: EraseOperations.erase_from_cursor_to_end(buffer, x, y, top, bottom)

  def erase_from_start_to_cursor(buffer, x, y, top, bottom),
    do: EraseOperations.erase_from_start_to_cursor(buffer, x, y, top, bottom)

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def erase_all(buffer), do: EraseOperations.erase_all(buffer)

  def clear_region(buffer, x, y, width, height),
    do: EraseOperations.clear_region(buffer, x, y, width, height)

  def erase_display(buffer, mode, cursor, min_row, max_row),
    do: EraseOperations.erase_display(buffer, mode, cursor, min_row, max_row)

  def erase_screen(buffer), do: EraseOperations.erase_screen(buffer)

  def erase_line(buffer, mode, cursor, min_col, max_col),
    do: EraseOperations.erase_line(buffer, mode, cursor, min_col, max_col)

  def erase_in_line(buffer, position, type),
    do: EraseOperations.erase_in_line(buffer, position, type)

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

  @impl Raxol.Terminal.ScreenBufferBehaviour
  def erase_from_cursor_to_end(buffer),
    do: EraseOperations.erase_from_cursor_to_end(buffer)

  def delete_chars(buffer, count, cursor, max_col),
    do: EraseOperations.delete_chars(buffer, count, cursor, max_col)

  def insert_chars(buffer, count, cursor, max_col),
    do: EraseOperations.insert_chars(buffer, count, cursor, max_col)

  def delete_characters(buffer, row, col, count, default_style),
    do: EraseOperations.delete_characters(buffer, row, col, count, default_style)

  # === Line Operations (LineOps) ===

  def insert_lines(buffer, y, count, style),
    do: LineOps.insert_lines(buffer, y, count, style)

  def insert_lines(buffer, y, count, style, {top, bottom}),
    do: LineOps.insert_lines(buffer, y, count, style, {top, bottom})

  def insert_lines(buffer, lines, y, top, bottom),
    do: LineOps.insert_lines_in_region(buffer, lines, y, top, bottom)

  def delete_lines(buffer, y, count, style, {top, bottom}),
    do: LineOps.delete_lines(buffer, y, count, style, {top, bottom})

  def delete_lines(buffer, lines, y, top, bottom),
    do: LineOps.delete_lines_in_region(buffer, lines, y, top, bottom)

  def pop_bottom_lines(buffer, count),
    do: LineOps.pop_bottom_lines(buffer, count)

  def get_line(buffer, y), do: LineOps.get_line(buffer, y)

  def get_cell_at(buffer, x, y), do: LineOps.get_cell_at(buffer, x, y)

  # === Query Operations ===

  @impl Raxol.Terminal.ScreenBufferBehaviour
  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

  def cleanup(_buffer), do: :ok

  def mark_damaged(buffer, x, y, width, height, _reason) do
    new_region = {x, y, width, height}
    updated_damage_regions = [new_region | buffer.damage_regions || []]
    %{buffer | damage_regions: updated_damage_regions}
  end

  # === Scrollback Operations ===

  def get_scrollback(buffer), do: buffer.scrollback || []

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

  def get_damaged_regions(buffer), do: buffer.damage_regions || []

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

  def get_scroll_region(buffer),
    do: ScrollOps.get_scroll_region(buffer)

  def shift_region_to_line(buffer, region, target_line),
    do: ScrollOps.shift_region_to_line(buffer, region, target_line)

  def scroll_down(buffer, lines, count)
      when is_integer(lines) and is_integer(count),
      do: ScrollOps.scroll_down_with_count(buffer, lines, count)

  def scroll_down(buffer, lines, count) when is_integer(count),
    do: ScrollOps.scroll_down_with_count(buffer, lines, count)

  # === Behaviour Callback Implementations ===

  @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

  def write(buffer, string, opts) when is_map(buffer) and is_binary(string) do
    write_string(buffer, 0, 0, string, opts[:style] || nil)
  end

  # === Region Operations ===

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

  def update(buffer, changes) when is_map(changes) do
    Map.merge(buffer, changes)
  end

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

  # === Compatibility Functions ===

  def scroll(buffer, lines) when lines > 0, do: scroll_up(buffer, lines)

  def scroll(buffer, lines) when lines < 0,
    do: {scroll_down(buffer, -lines), []}

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

  def write(buffer, x, y, content) when is_binary(content),
    do: write_string(buffer, x, y, content)

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

  # === Private Helpers ===

  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: []

  defp validate_positive_dimensions!(width, height)
       when width <= 0 or height <= 0 do
    raise ArgumentError,
          "ScreenBuffer dimensions must be positive integers, got: #{width}x#{height}"
  end

  defp validate_positive_dimensions!(_width, _height), do: :ok
end
