defmodule Raxol.Terminal.ScreenBuffer.SharedOperations do
  @moduledoc """
  Shared operations for screen buffer modules to eliminate code duplication.
  This module contains common functionality used across different screen buffer implementations.
  """

  @default_width Raxol.Core.Defaults.terminal_width()

  alias Raxol.Terminal.Cell

  @doc """
  Erases a specified number of characters at a given position.
  Replaces characters with empty cells using the buffer's default style.

  ## Parameters
    - buffer: The buffer to modify
    - x: Starting column position
    - y: Row position
    - count: Number of characters to erase

  ## Returns
    Updated buffer with erased characters
  """
  @spec erase_chars_at_position(
          map(),
          non_neg_integer(),
          non_neg_integer(),
          non_neg_integer()
        ) :: map()
  def erase_chars_at_position(buffer, x, y, count)
      when x >= 0 and y >= 0 and count > 0 do
    cells = buffer.cells || []
    row = Enum.at(cells, y, [])

    new_row =
      row
      |> Enum.with_index()
      |> Enum.map(fn {cell, idx} ->
        if idx >= x and idx < x + count do
          %Cell{char: " ", style: buffer.default_style}
        else
          cell
        end
      end)

    new_cells = List.replace_at(cells, y, new_row)
    %{buffer | cells: new_cells}
  end

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

  @doc """
  Inserts a character at the specified position, shifting content right.
  Core logic for character insertion without damage tracking.

  ## Parameters
    - buffer: The buffer to modify
    - x: Column position
    - y: Row position
    - char: Character to insert
    - style: Style to apply (uses buffer default if nil)

  ## Returns
    Buffer with updated cells (damage tracking handled by caller)
  """
  @spec insert_char_core_logic(
          map(),
          non_neg_integer(),
          non_neg_integer(),
          String.t(),
          map() | nil
        ) :: map()
  def insert_char_core_logic(buffer, x, y, char, style) do
    if within_bounds?(buffer, x, y) do
      style = style || buffer.default_style
      new_cell = %Cell{char: char, style: style}

      new_cells =
        List.update_at(buffer.cells, y, fn row ->
          {before, after_} = Enum.split(row, x)
          before ++ [new_cell] ++ Enum.take(after_, buffer.width - x - 1)
        end)

      %{buffer | cells: new_cells}
    else
      buffer
    end
  end

  @doc """
  Core logic for deleting lines from a buffer.
  Removes specified lines and adds empty lines at the bottom.

  ## Parameters
    - buffer: The buffer to modify
    - y: Starting line position
    - count: Number of lines to delete

  ## Returns
    Updated buffer with deleted lines
  """
  @spec delete_lines_core_logic(map(), non_neg_integer(), non_neg_integer()) ::
          map()
  def delete_lines_core_logic(buffer, y, count) when count > 0 and y >= 0 do
    cells = buffer.cells || []

    {before, at_and_after} = Enum.split(cells, y)
    remaining = Enum.drop(at_and_after, count)
    empty_lines = List.duplicate(create_empty_line(buffer), count)

    new_cells =
      (before ++ remaining ++ empty_lines)
      |> Enum.take(buffer.height)

    %{buffer | cells: new_cells}
  end

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

  # Helper function to create an empty line
  defp create_empty_line(buffer) do
    width = Map.get(buffer, :width, @default_width)

    Enum.map(0..(width - 1), fn _ ->
      %Cell{char: " ", style: buffer.default_style}
    end)
  end

  # Helper function to check if coordinates are within buffer bounds
  defp within_bounds?(buffer, x, y) do
    x >= 0 and y >= 0 and x < buffer.width and y < buffer.height
  end

  @doc """
  Normalizes selection coordinates so that start is always before end.
  Returns {start_x, start_y, end_x, end_y} in proper order.

  ## Parameters
    - x1, y1: First selection point
    - x2, y2: Second selection point

  ## Returns
    Tuple with normalized coordinates {start_x, start_y, end_x, end_y}
  """
  @spec normalize_selection(integer(), integer(), integer(), integer()) ::
          {integer(), integer(), integer(), integer()}
  def normalize_selection(x1, y1, x2, y2) do
    if y1 < y2 or (y1 == y2 and x1 <= x2) do
      {x1, y1, x2, y2}
    else
      {x2, y2, x1, y1}
    end
  end

  @doc """
  Checks if a position (x, y) is within the selection boundaries.

  ## Parameters
    - x, y: Position to check
    - start_x, start_y: Selection start coordinates
    - end_x, end_y: Selection end coordinates

  ## Returns
    Boolean indicating if position is within selection
  """
  @spec position_in_selection?(
          integer(),
          integer(),
          integer(),
          integer(),
          integer(),
          integer()
        ) :: boolean()
  def position_in_selection?(x, y, start_x, start_y, end_x, end_y) do
    cond do
      y < start_y or y > end_y -> false
      y > start_y and y < end_y -> true
      y == start_y and y == end_y -> x >= start_x and x <= end_x
      y == start_y -> x >= start_x
      y == end_y -> x <= end_x
      true -> false
    end
  end
end
