defmodule Raxol.Terminal.Input.InputBuffer do
  @moduledoc """
  A simple data structure for managing input buffer state.

  This module provides a stateless API for managing input buffer data,
  separate from the GenServer-based Buffer module that handles process-based buffering.
  """

  defstruct contents: "",
            max_size: 1024,
            overflow_mode: :truncate

  @type overflow_mode :: :truncate | :wrap | :error
  @type t :: %__MODULE__{
          contents: binary(),
          max_size: non_neg_integer(),
          overflow_mode: overflow_mode()
        }

  @doc """
  Creates a new input buffer with default values.
  """
  @spec new() :: t()
  def new do
    %__MODULE__{}
  end

  @doc """
  Creates a new input buffer with custom max_size and overflow_mode.
  """
  @spec new(non_neg_integer(), overflow_mode()) :: t()
  def new(max_size, overflow_mode) do
    %__MODULE__{
      max_size: max_size,
      overflow_mode: overflow_mode
    }
  end

  @doc """
  Gets the current contents of the buffer.
  """
  @spec get_contents(t()) :: binary()
  def get_contents(%__MODULE__{contents: contents}), do: contents

  @doc """
  Gets the maximum size of the buffer.
  """
  @spec max_size(t()) :: non_neg_integer()
  def max_size(%__MODULE__{max_size: max_size}), do: max_size

  @doc """
  Gets the overflow mode of the buffer.
  """
  @spec overflow_mode(t()) :: overflow_mode()
  def overflow_mode(%__MODULE__{overflow_mode: overflow_mode}),
    do: overflow_mode

  @doc """
  Sets the contents of the buffer, handling overflow according to the buffer's mode.
  """
  @spec set_contents(t(), binary()) :: t()
  def set_contents(%__MODULE__{} = buffer, new_contents) do
    processed_contents = handle_overflow(buffer, new_contents)
    %{buffer | contents: processed_contents}
  end

  @doc """
  Prepends data to the buffer.
  """
  @spec prepend(t(), binary()) :: t()
  def prepend(
        %__MODULE__{
          contents: current_contents,
          max_size: max_size,
          overflow_mode: overflow_mode
        } = buffer,
        new_data
      ) do
    combined_contents = new_data <> current_contents

    processed_contents =
      case overflow_mode do
        :truncate when byte_size(combined_contents) > max_size ->
          # For prepend + truncate, keep the rightmost content
          start_pos = byte_size(combined_contents) - max_size
          binary_part(combined_contents, start_pos, max_size)

        :wrap when byte_size(combined_contents) > max_size ->
          # For prepend + wrap, keep the leftmost content
          binary_part(combined_contents, 0, max_size)

        _ ->
          handle_overflow(buffer, combined_contents)
      end

    %{buffer | contents: processed_contents}
  end

  @doc """
  Appends data to the buffer.
  """
  @spec append(t(), binary()) :: t()
  def append(%__MODULE__{contents: current_contents} = buffer, new_data) do
    combined_contents = current_contents <> new_data
    processed_contents = handle_overflow(buffer, combined_contents)
    %{buffer | contents: processed_contents}
  end

  @doc """
  Clears the buffer contents.
  """
  @spec clear(t()) :: t()
  def clear(%__MODULE__{} = buffer) do
    %{buffer | contents: ""}
  end

  @doc """
  Gets the current size (byte count) of the buffer contents.
  """
  @spec size(t()) :: non_neg_integer()
  def size(%__MODULE__{contents: contents}), do: byte_size(contents)

  @doc """
  Checks if the buffer is empty.
  """
  @spec empty?(t()) :: boolean()
  def empty?(%__MODULE__{contents: ""}), do: true
  def empty?(%__MODULE__{}), do: false

  @doc """
  Sets the maximum size of the buffer.
  """
  @spec set_max_size(t(), non_neg_integer()) :: t()
  def set_max_size(%__MODULE__{} = buffer, new_max_size) do
    updated_buffer = %{buffer | max_size: new_max_size}
    processed_contents = handle_overflow(updated_buffer, buffer.contents)
    %{updated_buffer | contents: processed_contents}
  end

  @doc """
  Sets the overflow mode of the buffer.
  """
  @spec set_overflow_mode(t(), overflow_mode()) :: t()
  def set_overflow_mode(%__MODULE__{} = buffer, new_overflow_mode) do
    %{buffer | overflow_mode: new_overflow_mode}
  end

  @doc """
  Removes the last character from the buffer (backspace).
  """
  @spec backspace(t()) :: t()
  def backspace(%__MODULE__{contents: ""} = buffer), do: buffer

  def backspace(%__MODULE__{contents: contents} = buffer) do
    graphemes = String.graphemes(contents)
    new_contents = graphemes |> Enum.drop(-1) |> Enum.join()
    %{buffer | contents: new_contents}
  end

  @doc """
  Removes the first character from the buffer.
  """
  @spec delete_first(t()) :: t()
  def delete_first(%__MODULE__{contents: ""} = buffer), do: buffer

  def delete_first(%__MODULE__{contents: contents} = buffer) do
    graphemes = String.graphemes(contents)
    new_contents = graphemes |> Enum.drop(1) |> Enum.join()
    %{buffer | contents: new_contents}
  end

  @doc """
  Inserts a character at the specified position.
  """
  @spec insert_at(t(), non_neg_integer(), binary()) :: t()
  def insert_at(%__MODULE__{contents: contents} = buffer, position, char) do
    graphemes = String.graphemes(contents)
    length = length(graphemes)

    if position < 0 or position > length do
      raise ArgumentError, "Position out of bounds"
    end

    {before, after_part} = Enum.split(graphemes, position)
    new_contents = (before ++ [char] ++ after_part) |> Enum.join()
    processed_contents = handle_overflow(buffer, new_contents)
    %{buffer | contents: processed_contents}
  end

  @doc """
  Replaces a character at the specified position.
  """
  @spec replace_at(t(), non_neg_integer(), binary()) :: t()
  def replace_at(%__MODULE__{contents: contents} = buffer, position, char) do
    graphemes = String.graphemes(contents)
    length = length(graphemes)

    if position < 0 or position >= length do
      raise ArgumentError, "Position out of bounds"
    end

    new_graphemes = List.replace_at(graphemes, position, char)
    new_contents = Enum.join(new_graphemes)
    processed_contents = handle_overflow(buffer, new_contents)
    %{buffer | contents: processed_contents}
  end

  # Private helper functions

  defp handle_overflow(
         %__MODULE__{max_size: max_size, overflow_mode: mode},
         contents
       ) do
    content_length = byte_size(contents)

    if content_length <= max_size do
      contents
    else
      apply_overflow_mode(mode, contents, max_size)
    end
  end

  defp apply_overflow_mode(:truncate, contents, max_size) do
    binary_part(contents, 0, max_size)
  end

  defp apply_overflow_mode(:wrap, contents, max_size) do
    content_length = byte_size(contents)

    if content_length <= max_size do
      contents
    else
      # Take the last max_size bytes
      start_pos = content_length - max_size
      binary_part(contents, start_pos, max_size)
    end
  end

  defp apply_overflow_mode(:error, contents, max_size) do
    if byte_size(contents) > max_size do
      raise RuntimeError, "Buffer overflow"
    else
      contents
    end
  end
end
