defmodule Raxol.Terminal.Scroll.Manager do @moduledoc """ Manages terminal scrolling operations with advanced features. Features: - Predictive scrolling for smooth performance - Scroll caching for efficient memory usage - Scroll optimization for better performance - Scroll synchronization across splits """ alias Raxol.Terminal.Scroll.Optimizer alias Raxol.Terminal.Scroll.Predictor alias Raxol.Terminal.Scroll.Sync @type t :: %__MODULE__{ predictor: Predictor.t(), optimizer: Optimizer.t(), sync: Sync.t(), metrics: %{ scrolls: non_neg_integer(), predictions: non_neg_integer(), cache_hits: non_neg_integer(), cache_misses: non_neg_integer(), optimizations: non_neg_integer() } } defstruct [ :predictor, :optimizer, :sync, :metrics ] @doc """ Creates a new scroll manager. ## Options * `:prediction_enabled` - Whether to enable predictive scrolling (default: true) * `:optimization_enabled` - Whether to enable scroll optimization (default: true) * `:sync_enabled` - Whether to enable scroll synchronization (default: true) """ @spec new(keyword()) :: t() def new(opts \\ []) do prediction_enabled = Keyword.get(opts, :prediction_enabled, true) optimization_enabled = Keyword.get(opts, :optimization_enabled, true) sync_enabled = Keyword.get(opts, :sync_enabled, true) %__MODULE__{ predictor: create_predictor(prediction_enabled), optimizer: create_optimizer(optimization_enabled), sync: create_sync(sync_enabled), metrics: %{ scrolls: 0, predictions: 0, cache_hits: 0, cache_misses: 0, optimizations: 0 } } end @doc """ Scrolls the terminal content. ## Parameters * `manager` - The scroll manager * `direction` - Scroll direction (:up or :down) * `lines` - Number of lines to scroll * `opts` - Scroll options * `:predict` - Whether to use prediction (default: true) * `:optimize` - Whether to optimize the scroll (default: true) * `:sync` - Whether to sync across splits (default: true) """ @spec scroll(t(), :up | :down, non_neg_integer(), keyword()) :: {:ok, t()} | {:error, term()} def scroll(manager, direction, amount, opts \\ []) do case get_cached_scroll(manager, direction, amount) do {:hit, cached_result} -> _manager = update_metrics(manager, :cache_hit) cached_result {:miss, _} -> _manager = update_metrics(manager, :cache_miss) perform_scroll(manager, direction, amount, opts) end end @doc """ Gets the scroll history. ## Parameters * `manager` - The scroll manager * `opts` - History options * `:limit` - Maximum number of entries to return (default: all) * `:direction` - Filter by direction (:up or :down) """ @spec get_history(t(), keyword()) :: {:ok, [map()], t()} def get_history(manager, opts \\ []) do limit = Keyword.get(opts, :limit) direction = Keyword.get(opts, :direction) case Raxol.Terminal.Cache.System.get(:history, namespace: :scroll) do {:ok, history} -> result = history |> filter_by_direction(direction) |> limit_results(limit) {:ok, result, manager} {:error, _} -> {:ok, [], manager} end end @doc """ Gets the current scroll metrics. """ @spec get_metrics(t()) :: map() def get_metrics(manager) do manager.metrics end @doc """ Optimizes the scroll manager based on current metrics. """ @spec optimize(t()) :: t() def optimize(manager) do case Raxol.Terminal.Cache.System.stats(namespace: :scroll) do {:ok, stats} -> handle_cache_stats(stats) _ -> :ok end manager end defp get_cached_scroll(_manager, direction, amount) do cache_key = {direction, amount} case Raxol.Terminal.Cache.System.get(cache_key, namespace: :scroll) do {:ok, result} -> {:hit, result} {:error, _} -> {:miss, nil} end end defp perform_scroll(manager, direction, amount, opts) do predict = Keyword.get(opts, :predict, true) optimize = Keyword.get(opts, :optimize, true) sync = Keyword.get(opts, :sync, true) manager = update_metrics(manager, :scroll) manager = apply_prediction(manager, predict, direction, amount) manager = apply_optimization(manager, optimize, direction, amount) manager = apply_sync(manager, sync, direction, amount) scroll_entry = %{ direction: direction, amount: amount, timestamp: System.monotonic_time(), options: opts } case Raxol.Terminal.Cache.System.get(:history, namespace: :scroll) do {:ok, history} -> updated_history = [scroll_entry | history] Raxol.Terminal.Cache.System.put(:history, updated_history, namespace: :scroll) {:error, _} -> Raxol.Terminal.Cache.System.put(:history, [scroll_entry], namespace: :scroll) end {:ok, manager} end defp update_metrics(manager, :cache_hit) do %{ manager | metrics: %{manager.metrics | cache_hits: manager.metrics.cache_hits + 1} } end defp update_metrics(manager, :cache_miss) do %{ manager | metrics: %{ manager.metrics | cache_misses: manager.metrics.cache_misses + 1 } } end defp update_metrics(manager, :scroll) do %{ manager | metrics: %{manager.metrics | scrolls: manager.metrics.scrolls + 1} } end defp update_metrics(manager, :prediction) do %{ manager | metrics: %{ manager.metrics | predictions: manager.metrics.predictions + 1 } } end defp update_metrics(manager, :optimization) do %{ manager | metrics: %{ manager.metrics | optimizations: manager.metrics.optimizations + 1 } } end defp filter_by_direction(history, nil), do: history defp filter_by_direction(history, direction) do Enum.filter(history, fn entry -> entry.direction == direction end) end defp limit_results(history, nil), do: history defp limit_results(history, limit), do: Enum.take(history, limit) @doc """ Clears the scroll history. """ @spec clear_history(t()) :: {:ok, t()} def clear_history(manager) do case Raxol.Terminal.Cache.System.clear(namespace: :scroll) do :ok -> {:ok, manager} {:error, _} -> {:ok, manager} end end defp create_predictor(true), do: Predictor.new() defp create_predictor(false), do: nil defp create_optimizer(true), do: Optimizer.new() defp create_optimizer(false), do: nil defp create_sync(true), do: Sync.new() defp create_sync(false), do: nil defp apply_prediction(manager, predict, direction, amount) do case {predict, manager.predictor} do {true, predictor} when not is_nil(predictor) -> %{ manager | predictor: Predictor.predict(predictor, direction, amount) } |> update_metrics(:prediction) _ -> manager end end defp apply_optimization(manager, optimize, direction, amount) do case {optimize, manager.optimizer} do {true, optimizer} when not is_nil(optimizer) -> %{ manager | optimizer: Optimizer.optimize(optimizer, direction, amount) } |> update_metrics(:optimization) _ -> manager end end defp apply_sync(manager, sync, direction, amount) do case {sync, manager.sync} do {true, sync_module} when not is_nil(sync_module) -> %{manager | sync: Sync.sync(sync_module, direction, amount)} _ -> manager end end defp handle_cache_stats(stats) do case stats.size > stats.max_size * 0.8 do true -> Raxol.Terminal.Cache.System.clear(namespace: :scroll) false -> :ok end end end