defmodule Raxol.Terminal.Cursor.Manager do
  @moduledoc """
  Manages cursor state and operations in the terminal.
  Handles cursor position, visibility, style, and blinking state.
  """

  use Raxol.Core.Behaviours.BaseManager

  require Raxol.Core.Runtime.Log

  alias Raxol.Core.Runtime.Log
  alias Raxol.Terminal.Cursor.{Callbacks, Movement}
  alias Raxol.Terminal.Cursor.CursorState, as: State
  alias Raxol.Terminal.Emulator

  defstruct row: 0,
            col: 0,
            visible: true,
            blinking: true,
            style: :block,
            color: nil,
            saved_row: nil,
            saved_col: nil,
            saved_style: nil,
            saved_visible: nil,
            saved_blinking: nil,
            saved_color: nil,
            top_margin: 0,
            bottom_margin: 23,
            blink_timer: nil,
            state: :visible,
            # {row, col} format (row first, then column)
            position: {0, 0},
            blink: true,
            custom_shape: nil,
            custom_dimensions: nil,
            blink_rate: 530,
            saved_position: nil,
            history: [],
            history_index: 0,
            history_limit: 100,
            shape: {1, 1}

  @type cursor_style :: :block | :underline | :bar | :custom
  @type color :: {non_neg_integer(), non_neg_integer(), non_neg_integer()} | nil

  @type t :: %__MODULE__{
          row: non_neg_integer(),
          col: non_neg_integer(),
          visible: boolean(),
          blinking: boolean(),
          style: cursor_style(),
          color: color(),
          saved_row: non_neg_integer() | nil,
          saved_col: non_neg_integer() | nil,
          saved_style: cursor_style() | nil,
          saved_visible: boolean() | nil,
          saved_blinking: boolean() | nil,
          saved_color: color() | nil,
          top_margin: non_neg_integer(),
          bottom_margin: non_neg_integer(),
          blink_timer: non_neg_integer() | nil,
          state: atom(),
          position: {non_neg_integer(), non_neg_integer()},
          blink: boolean(),
          custom_shape: atom() | String.t() | nil,
          custom_dimensions: {non_neg_integer(), non_neg_integer()} | nil,
          blink_rate: non_neg_integer(),
          saved_position: {non_neg_integer(), non_neg_integer()} | nil,
          history: list(),
          history_index: non_neg_integer(),
          history_limit: non_neg_integer(),
          shape: {non_neg_integer(), non_neg_integer()}
        }

  # Client API

  @doc """
  Creates a new cursor manager instance.
  """
  def new do
    %__MODULE__{}
  end

  @doc """
  Creates a new cursor manager.
  """
  def new(opts) when is_map(opts) do
    struct!(__MODULE__, opts)
  end

  def new(opts) when is_list(opts) do
    struct!(__MODULE__, Map.new(opts))
  end

  def new(row, col) when is_integer(row) and is_integer(col) do
    %__MODULE__{
      row: row,
      col: col,
      position: {row, col}
    }
  end

  @doc """
  Gets the current cursor position.
  """
  def get_position(pid \\ __MODULE__)

  def get_position(pid) when is_pid(pid) do
    Raxol.Core.Runtime.Log.debug("get_position called with pid: #{inspect(pid)}")

    result = GenServer.call(pid, :get_position)

    Raxol.Core.Runtime.Log.debug("get_position(pid) returned: #{inspect(result)}")

    result
  end

  def get_position(%__MODULE__{} = cursor) do
    cursor.position
  end

  def get_position(%{position: position}) when is_tuple(position) do
    position
  end

  def get_position(_), do: {0, 0}

  @doc """
  Sets the cursor position.
  """
  def set_position(pid, {row, col}) when is_pid(pid) do
    GenServer.call(pid, {:set_position, row, col})
  end

  def set_position(%__MODULE__{} = cursor, {row, col}) do
    %{cursor | row: row, col: col, position: {row, col}}
  end

  def set_position(other, _pos), do: other

  @doc """
  Moves the cursor relative to its current position.
  """
  def move_cursor(pid \\ __MODULE__, direction, count \\ 1) do
    GenServer.call(pid, {:move_cursor, direction, count})
  end

  @doc """
  Gets the cursor visibility state.
  """
  def get_visibility(pid \\ __MODULE__)

  def get_visibility(pid) when is_pid(pid) do
    GenServer.call(pid, :get_visibility)
  end

  def get_visibility(%__MODULE__{} = cursor) do
    cursor.visible
  end

  def get_visibility(_), do: true

  @doc """
  Sets the cursor visibility state.
  """
  def set_visibility(pid \\ __MODULE__, visible)

  def set_visibility(pid, visible) when is_pid(pid) do
    GenServer.call(pid, {:set_visibility, visible})
  end

  def set_visibility(%__MODULE__{} = cursor, visible) do
    %{cursor | visible: visible}
  end

  def set_visibility(other, _visible), do: other

  @doc """
  Moves the cursor to a specific position.
  """
  def move_to(nil, row, col) do
    %{row: row, col: col, position: {row, col}}
  end

  def move_to(%__MODULE__{} = cursor, row, col) do
    %{cursor | row: row, col: col, position: {row, col}}
  end

  # Handle plain maps (for backward compatibility)
  def move_to(%{} = cursor, row, col) do
    %{cursor | row: row, col: col, position: {row, col}}
  end

  def move_to(pid, row, col) when is_pid(pid) do
    GenServer.call(pid, {:move_to, row, col})
    pid
  end

  @doc """
  Moves the cursor to a specific position with bounds clamping.
  """
  def move_to(%__MODULE__{} = cursor, row, col, width, height) do
    clamped_row = max(0, min(row, height - 1))
    clamped_col = max(0, min(col, width - 1))

    %{
      cursor
      | row: clamped_row,
        col: clamped_col,
        position: {clamped_row, clamped_col}
    }
  end

  def move_to(pid, row, col, width, height) when is_pid(pid) do
    GenServer.call(pid, {:move_to_bounded, row, col, width, height})
    pid
  end

  # Movement operations - delegated to Movement module
  defdelegate move_up(cursor, lines, width, height), to: Movement
  defdelegate move_down(cursor, lines, width, height), to: Movement
  defdelegate move_left(cursor, cols, width, height), to: Movement
  defdelegate move_right(cursor, cols, width, height), to: Movement
  defdelegate move_to_line_start(cursor), to: Movement
  defdelegate move_to_line_end(cursor, line_width), to: Movement
  defdelegate move_to_column(cursor, column), to: Movement
  defdelegate move_to_column(cursor, column, width, height), to: Movement
  defdelegate constrain_position(cursor, width, height), to: Movement
  defdelegate move_to_line(cursor, line), to: Movement
  defdelegate move_home(cursor, width, height), to: Movement
  defdelegate move_to_next_tab(cursor, tab_size, width, height), to: Movement
  defdelegate move_to_prev_tab(cursor, tab_size, width, height), to: Movement

  # State management operations - delegated to State module
  defdelegate set_margins(cursor, top, bottom), to: State
  defdelegate get_margins(cursor), to: State
  defdelegate save_state(state), to: State
  defdelegate restore_state(state), to: State
  defdelegate reset(state), to: State
  # set_state is handled specifically below for PID and struct cases
  defdelegate set_custom_shape(state, shape, params), to: State
  defdelegate update_position_from_text(cursor, text), to: State
  defdelegate update_blink(state), to: State

  # GenServer-based state operations
  def get_blink(pid \\ __MODULE__)

  def get_blink(pid) when is_pid(pid) do
    GenServer.call(pid, :get_blink)
  end

  def get_blink(%__MODULE__{} = cursor) do
    cursor.blinking
  end

  def get_blink(_), do: true

  def set_blink(pid \\ __MODULE__, blink)

  def set_blink(pid, blink) when is_pid(pid) do
    GenServer.call(pid, {:set_blink, blink})
  end

  def set_blink(%__MODULE__{} = cursor, blink) do
    %{cursor | blinking: blink}
  end

  def set_blink(other, _blink), do: other

  def get_style(pid \\ __MODULE__)

  def get_style(pid) when is_pid(pid) do
    GenServer.call(pid, :get_style)
  end

  def get_style(%__MODULE__{} = cursor) do
    cursor.style
  end

  def get_style(_), do: :block

  def set_style(%__MODULE__{} = state, style), do: %{state | style: style}

  def set_style(pid, style) when is_pid(pid) do
    GenServer.call(pid, {:set_style, style})
    pid
  end

  def set_style(style) when is_atom(style),
    do: GenServer.call(__MODULE__, {:set_style, style})

  def get_color(%__MODULE__{} = state) do
    state.color
  end

  def set_color(%__MODULE__{} = state, color) do
    %{state | color: color}
  end

  def reset_color(%__MODULE__{} = state) do
    %{state | color: nil}
  end

  def set_custom_shape(shape, params) when is_atom(shape),
    do: GenServer.call(__MODULE__, {:set_custom_shape, shape, params})

  def update_position(pid \\ __MODULE__, position)

  def update_position(pid, {row, col})
      when is_integer(row) and is_integer(col) do
    GenServer.call(pid, {:update_position, row, col})
  end

  def update_position(pid, text) when is_binary(text) do
    GenServer.call(pid, {:update_position_from_text, text})
  end

  def reset_position(pid \\ __MODULE__) do
    GenServer.call(pid, :reset_position)
  end

  def update_blink, do: GenServer.call(__MODULE__, :update_blink)

  @doc """
  Updates the cursor position after a resize operation.
  Returns the updated emulator.
  """
  @spec update_cursor_position(
          Emulator.t(),
          non_neg_integer(),
          non_neg_integer()
        ) :: Emulator.t()
  def update_cursor_position(emulator, new_width, new_height) do
    cursor = emulator.cursor
    col = min(cursor.col, new_width - 1)
    row = min(cursor.row, new_height - 1)
    %{emulator | cursor: %{cursor | col: col, row: row}}
  end

  @doc """
  Updates the scroll region after a resize operation.
  Returns the updated emulator.
  """
  @spec update_scroll_region_for_resize(map(), non_neg_integer()) :: map()
  def update_scroll_region_for_resize(emulator, new_height) do
    scroll_region = emulator.scroll_region
    top = min(scroll_region.top, new_height - 1)
    bottom = min(scroll_region.bottom, new_height - 1)
    %{emulator | scroll_region: %{scroll_region | top: top, bottom: bottom}}
  end

  @doc """
  Moves the cursor up by the specified number of lines.
  Returns the updated emulator.
  """
  @spec move_up(Emulator.t(), non_neg_integer()) :: Emulator.t()
  def move_up(emulator, count \\ 1) do
    cursor = emulator.cursor
    row = max(0, cursor.row - count)
    %{emulator | cursor: %{cursor | row: row}}
  end

  @doc """
  Moves the cursor down by the specified number of lines.
  Returns the updated emulator.
  """
  @spec move_down(Emulator.t(), non_neg_integer()) :: Emulator.t()
  def move_down(emulator, count \\ 1) do
    cursor = emulator.cursor
    row = min(emulator.height - 1, cursor.row + count)
    %{emulator | cursor: %{cursor | row: row}}
  end

  @doc """
  Moves the cursor left by the specified number of columns.
  Returns the updated emulator.
  """
  @spec move_left(Emulator.t(), non_neg_integer()) :: Emulator.t()
  def move_left(emulator, count \\ 1) do
    cursor = emulator.cursor
    col = max(0, cursor.col - count)
    %{emulator | cursor: %{cursor | col: col}}
  end

  @doc """
  Moves the cursor right by the specified number of columns.
  Returns the updated emulator.
  """
  @spec move_right(Emulator.t(), non_neg_integer()) :: Emulator.t()
  def move_right(emulator, count \\ 1) do
    cursor = emulator.cursor
    col = min(emulator.width - 1, cursor.col + count)
    %{emulator | cursor: %{cursor | col: col}}
  end

  @spec get_emulator_position(Emulator.t()) :: {integer(), integer()}
  def get_emulator_position(emulator) do
    emulator.cursor.position
  end

  @spec set_emulator_position(
          Emulator.t(),
          non_neg_integer(),
          non_neg_integer()
        ) :: Emulator.t()
  def set_emulator_position(emulator, x, y) do
    x = max(0, min(x, emulator.width - 1))
    y = max(0, min(y, emulator.height - 1))
    %{emulator | cursor: %{emulator.cursor | position: {x, y}}}
  end

  @spec get_emulator_style(Emulator.t()) :: atom()
  def get_emulator_style(emulator) do
    emulator.cursor.style
  end

  @spec set_emulator_style(Emulator.t(), atom()) :: Emulator.t()
  def set_emulator_style(emulator, style) do
    %{emulator | cursor: %{emulator.cursor | style: style}}
  end

  @spec emulator_visible?(Emulator.t()) :: boolean()
  def emulator_visible?(emulator) do
    emulator.cursor.visible
  end

  @spec set_emulator_visibility(Emulator.t(), boolean()) :: Emulator.t()
  def set_emulator_visibility(emulator, visible) do
    %{emulator | cursor: %{emulator.cursor | visible: visible}}
  end

  @spec emulator_blinking?(Emulator.t()) :: boolean()
  def emulator_blinking?(emulator) do
    emulator.cursor.blink_state
  end

  @spec set_emulator_blink(Emulator.t(), boolean()) :: Emulator.t()
  def set_emulator_blink(emulator, blinking) do
    %{emulator | cursor: %{emulator.cursor | blink_state: blinking}}
  end

  # Additional state management operations - delegated to State module
  defdelegate save_position(state), to: State
  defdelegate restore_position(state), to: State
  defdelegate add_to_history(state), to: State
  defdelegate restore_from_history(state), to: State

  # PID-specific functions must come before delegation
  def get_state(pid) when is_pid(pid), do: GenServer.call(pid, :get_state_atom)
  def get_state(%__MODULE__{} = state), do: State.get_state(state)

  def set_state(pid, state_atom) when is_pid(pid) do
    # DEBUG output removed
    # DEBUG output removed
    GenServer.call(pid, {:set_state_atom, state_atom})
  end

  def set_state(%__MODULE__{} = state, state_atom) do
    # DEBUG output removed
    # DEBUG output removed
    State.set_state(state, state_atom)
  end

  # BaseManager Implementation

  @impl true
  def init_manager(_opts) do
    {:ok, new()}
  end

  # Manager Callbacks - delegated to Callbacks module
  @impl true
  def handle_manager_call(:get_position, _from, state) do
    {result, new_state} = Callbacks.handle_get_position(state)
    {:reply, result, new_state}
  end

  def handle_manager_call({:set_position, row, col}, _from, state) do
    {result, new_state} = Callbacks.handle_set_position(state, row, col)
    {:reply, result, new_state}
  end

  def handle_manager_call({:move_cursor, direction, count}, _from, state) do
    {result, new_state} = Callbacks.handle_move_cursor(state, direction, count)
    {:reply, result, new_state}
  end

  def handle_manager_call(:get_visibility, _from, state) do
    {result, new_state} = Callbacks.handle_get_visibility(state)
    {:reply, result, new_state}
  end

  def handle_manager_call({:set_visibility, visible}, _from, state) do
    {result, new_state} = Callbacks.handle_set_visibility(state, visible)
    {:reply, result, new_state}
  end

  def handle_manager_call(:get_style, _from, state) do
    {result, new_state} = Callbacks.handle_get_style(state)
    {:reply, result, new_state}
  end

  def handle_manager_call({:set_style, style}, _from, state) do
    {result, new_state} = Callbacks.handle_set_style(state, style)
    {:reply, result, new_state}
  end

  def handle_manager_call(:get_blink, _from, state) do
    {result, new_state} = Callbacks.handle_get_blink(state)
    {:reply, result, new_state}
  end

  def handle_manager_call({:set_blink, blink}, _from, state) do
    {result, new_state} = Callbacks.handle_set_blink(state, blink)
    {:reply, result, new_state}
  end

  def handle_manager_call({:set_custom_shape, shape, params}, _from, state) do
    {result, new_state} =
      Callbacks.handle_set_custom_shape(state, shape, params)

    {:reply, result, new_state}
  end

  def handle_manager_call({:update_position, row, col}, _from, state) do
    {result, new_state} = Callbacks.handle_update_position(state, row, col)
    {:reply, result, new_state}
  end

  def handle_manager_call(:reset_position, _from, state) do
    {result, new_state} = Callbacks.handle_reset_position(state)
    {:reply, result, new_state}
  end

  def handle_manager_call({:update_position_from_text, text}, _from, state) do
    {result, new_state} =
      Callbacks.handle_update_position_from_text(state, text)

    {:reply, result, new_state}
  end

  def handle_manager_call(:update_blink, _from, state) do
    {result, new_state} = Callbacks.handle_update_blink(state)
    {:reply, result, new_state}
  end

  def handle_manager_call(:get_state, _from, state) do
    {result, new_state} = Callbacks.handle_get_state(state)
    {:reply, result, new_state}
  end

  def handle_manager_call(:get_state_atom, _from, state) do
    {result, new_state} = Callbacks.handle_get_state_atom(state)
    {:reply, result, new_state}
  end

  def handle_manager_call({:move_down, count, width, height}, _from, state) do
    {result, new_state} =
      Callbacks.handle_move_down(state, count, width, height)

    {:reply, result, new_state}
  end

  def handle_manager_call({:move_up, lines, width, height}, _from, state) do
    {result, new_state} = Callbacks.handle_move_up(state, lines, width, height)
    {:reply, result, new_state}
  end

  def handle_manager_call({:move_right, cols, width, height}, _from, state) do
    {result, new_state} =
      Callbacks.handle_move_right(state, cols, width, height)

    {:reply, result, new_state}
  end

  def handle_manager_call({:move_left, cols, width, height}, _from, state) do
    {result, new_state} = Callbacks.handle_move_left(state, cols, width, height)
    {:reply, result, new_state}
  end

  def handle_manager_call({:move_to, row, col}, _from, state) do
    {result, new_state} = Callbacks.handle_move_to(state, row, col)
    {:reply, result, new_state}
  end

  def handle_manager_call({:move_to_column, column}, _from, state) do
    {result, new_state} = Callbacks.handle_move_to_column(state, column)
    {:reply, result, new_state}
  end

  def handle_manager_call(
        {:move_to_column, column, width, height},
        _from,
        state
      ) do
    {result, new_state} =
      Callbacks.handle_move_to_column_bounded(state, column, width, height)

    {:reply, result, new_state}
  end

  def handle_manager_call({:move_to, row, col, width, height}, _from, state) do
    {result, new_state} =
      Callbacks.handle_move_to_bounded(state, row, col, width, height)

    {:reply, result, new_state}
  end

  def handle_manager_call(
        {:move_to_bounded, row, col, width, height},
        _from,
        state
      ) do
    {result, new_state} =
      Callbacks.handle_move_to_bounded_position(state, row, col, width, height)

    {:reply, result, new_state}
  end

  def handle_manager_call({:set_state_atom, state_atom}, _from, state) do
    {result, new_state} = Callbacks.handle_set_state_atom(state, state_atom)
    {:reply, result, new_state}
  end

  def handle_manager_call(request, _from, state) do
    {result, new_state} = Callbacks.handle_unknown_request(request, state)
    {:reply, result, new_state}
  end

  @impl true
  def handle_manager_cast({:set_visibility, visible}, state) do
    {new_state} = Callbacks.handle_set_visibility_cast(state, visible)
    {:noreply, new_state}
  end

  @impl true
  def handle_manager_info({:blink, timer_id}, state) do
    {new_state} = Callbacks.handle_blink_info(state, timer_id)
    {:noreply, new_state}
  end

  @doc """
  Gets the cursor position as a tuple {row, col}.
  """
  def get_position_tuple(cursor) do
    {cursor.row, cursor.col}
  end
end
