defmodule Raxol.Terminal.Scroll.ScrollServer do
  @moduledoc """
  Unified scroll management system for the terminal.

  This module consolidates all scroll-related functionality including:
  - Scroll buffer management
  - Scroll operations (up/down)
  - Scroll region handling
  - Memory management
  - Performance optimization
  """

  alias Raxol.Terminal.Cell

  @type t :: %__MODULE__{
          buffer: list(list(Cell.t())),
          position: non_neg_integer(),
          height: non_neg_integer(),
          max_height: non_neg_integer(),
          scroll_region: {non_neg_integer(), non_neg_integer()} | nil,
          compression_ratio: float(),
          memory_limit: non_neg_integer(),
          memory_usage: non_neg_integer(),
          cache: map()
        }

  defstruct [
    :buffer,
    :position,
    :height,
    :max_height,
    :scroll_region,
    :compression_ratio,
    :memory_limit,
    :memory_usage,
    :cache
  ]

  @doc """
  Creates a new scroll buffer with the given dimensions and configuration.
  """
  def new(max_height, memory_limit \\ 5_000_000) do
    %__MODULE__{
      buffer: [],
      position: 0,
      height: 0,
      max_height: max_height,
      scroll_region: nil,
      compression_ratio: 1.0,
      memory_limit: memory_limit,
      memory_usage: 0,
      cache: %{}
    }
  end

  @doc """
  Adds a line to the scroll buffer.
  """
  def add_line(%__MODULE__{} = scroll, line) do
    new_buffer = [line | scroll.buffer]

    new_buffer =
      case length(new_buffer) > scroll.max_height do
        true -> Enum.take(new_buffer, scroll.max_height)
        false -> new_buffer
      end

    new_usage = calculate_memory_usage(new_buffer)

    {new_buffer, new_ratio} =
      case new_usage > scroll.memory_limit do
        true -> compress_buffer(new_buffer)
        false -> {new_buffer, scroll.compression_ratio}
      end

    %{
      scroll
      | buffer: new_buffer,
        height: length(new_buffer),
        compression_ratio: new_ratio,
        memory_usage: new_usage,
        cache: %{}
    }
  end

  @doc """
  Gets a view of the scroll buffer at the current position.
  """
  def get_view(%__MODULE__{} = scroll, view_height) do
    cache_key = {:view, scroll.position, view_height}

    case Map.get(scroll.cache, cache_key) do
      nil ->
        view = Enum.slice(scroll.buffer, scroll.position, view_height)
        new_cache = Map.put(scroll.cache, cache_key, view)
        {view, %{scroll | cache: new_cache}}

      cached_view ->
        {cached_view, scroll}
    end
  end

  @doc """
  Scrolls the buffer by the given amount.
  """
  def scroll(%__MODULE__{} = scroll, amount) do
    new_position =
      :erlang.max(0, :erlang.min(scroll.position + amount, scroll.height))

    %{scroll | position: new_position, cache: %{}}
  end

  def scroll(%__MODULE__{} = scroll, direction, amount) do
    case direction do
      :up -> scroll(scroll, -amount)
      :down -> scroll(scroll, amount)
      _ -> scroll
    end
  end

  @doc """
  Sets the scroll region.
  """
  def set_scroll_region(%__MODULE__{} = scroll, top, bottom)
      when is_integer(top) and is_integer(bottom) and top < bottom do
    %{scroll | scroll_region: {top, bottom}}
  end

  def set_scroll_region(%__MODULE__{} = scroll, _top, _bottom) do
    scroll
  end

  @doc """
  Clears the scroll region.
  """
  def clear_scroll_region(%__MODULE__{} = scroll) do
    %{scroll | scroll_region: nil}
  end

  @doc """
  Gets the current scroll position.
  """
  def get_position(%__MODULE__{} = scroll) do
    scroll.position
  end

  @doc """
  Gets the total height of the scroll buffer.
  """
  def get_height(%__MODULE__{} = scroll) do
    scroll.height
  end

  @doc """
  Gets the visible region of the scroll buffer.
  """
  def get_visible_region(%__MODULE__{} = scroll) do
    case scroll.scroll_region do
      nil -> {0, scroll.height - 1}
      {top, bottom} -> {top, bottom}
    end
  end

  @doc """
  Clears the scroll buffer.
  """
  def clear(%__MODULE__{} = scroll) do
    %{scroll | buffer: [], position: 0, height: 0, memory_usage: 0, cache: %{}}
  end

  @doc """
  Updates the maximum height of the scroll buffer.
  """
  def set_max_height(%__MODULE__{} = scroll, new_max_height)
      when is_integer(new_max_height) and new_max_height >= 0 do
    new_buffer =
      case length(scroll.buffer) > new_max_height do
        true -> Enum.take(scroll.buffer, new_max_height)
        false -> scroll.buffer
      end

    new_memory_usage = calculate_memory_usage(new_buffer)

    %__MODULE__{
      scroll
      | buffer: new_buffer,
        max_height: new_max_height,
        height: length(new_buffer),
        position: min(scroll.position, length(new_buffer) - 1) |> max(0),
        memory_usage: new_memory_usage,
        cache: %{}
    }
  end

  @doc """
  Resizes the scroll buffer to the new height.
  """
  def resize(%__MODULE__{} = scroll, new_height) do
    %{scroll | height: new_height, cache: %{}}
  end

  @doc """
  Updates the scroll buffer with new commands.
  """
  def update(scroll_buffer, _commands) do
    scroll_buffer
  end

  @doc """
  Cleans up the scroll buffer.
  """
  def cleanup(_scroll_buffer) do
    :ok
  end

  defp calculate_memory_usage(buffer) do
    Enum.reduce(buffer, 0, fn line, acc ->
      line_size =
        Enum.reduce(line, 0, fn cell, cell_acc ->
          cell_acc + byte_size(cell.char)
        end)

      acc + line_size
    end)
  end

  defp compress_buffer(buffer) do
    compressed =
      Enum.map(buffer, fn line ->
        Enum.chunk_by(line, &(&1.char == ""))
        |> Enum.map(&compress_chunk/1)
      end)

    {compressed, 0.5}
  end

  defp compress_chunk([]), do: Cell.new(" ")
  defp compress_chunk([cell | _]), do: cell
end
