defmodule Raxol.Terminal.Commands.WindowHandler do
  @moduledoc """
  Handles window-related commands and operations for the terminal.
  """

  alias Raxol.Terminal.ScreenBuffer

  def default_char_width_px, do: 8

  def default_char_height_px, do: 16

  def calculate_width_chars(pixel_width) do
    div(pixel_width, default_char_width_px())
  end

  def calculate_height_chars(pixel_height) do
    div(pixel_height, default_char_height_px())
  end

  def handle_t(emulator, params) do
    op = Enum.at(params, 0, 0)
    handler = get_handler(op)
    call_handler(handler, emulator, params)
  end

  defp call_handler(handler, emulator, params) do
    case :erlang.fun_info(handler, :arity) do
      {:arity, 1} -> handler.(emulator)
      {:arity, 2} -> handler.(emulator, params)
      _ -> {:ok, emulator}
    end
  end

  defp get_handler(op) do
    Map.get(window_handlers(), op, &default_handler/2)
  end

  defp default_handler(emulator, _params), do: {:ok, emulator}

  defp window_handlers do
    %{
      0 => &handle_window_title/2,
      1 => &handle_deiconify/1,
      2 => &handle_iconify/1,
      3 => &handle_move/2,
      4 => &handle_resize/2,
      5 => &handle_raise/1,
      6 => &handle_lower/1,
      7 => &handle_refresh/1,
      8 => &handle_icon_name/2,
      9 => &handle_maximize/1,
      10 => &handle_restore/1,
      11 => &handle_report_state/1,
      13 => &handle_report_size_pixels/1,
      14 => &handle_report_position/1,
      18 => &handle_report_text_area_size/1,
      19 => &handle_report_desktop_size/1
    }
  end

  def handle_window_title(emulator, params) do
    # Get title from params or emulator.window_title or use empty string
    title = Enum.at(params, 1, emulator.window_title || "")
    output = "\x1b]0;#{title}\x07"
    updated_emulator = %{emulator | output_buffer: output}
    {:ok, updated_emulator}
  end

  def handle_icon_name(emulator, params) do
    # ESC]1;iconBEL
    icon = Enum.at(params, 1, "")
    output = "\x1b]1;#{icon}\x07"
    updated_emulator = %{emulator | output_buffer: output}
    {:ok, updated_emulator}
  end

  def handle_icon_title(emulator, params) do
    # ESC]2;titleBEL
    title = Enum.at(params, 1, "")
    output = "\x1b]2;#{title}\x07"
    updated_emulator = %{emulator | output_buffer: output}
    {:ok, updated_emulator}
  end

  def handle_deiconify(emulator) do
    updated_window_state = %{emulator.window_state | iconified: false}
    updated_emulator = %{emulator | window_state: updated_window_state}
    {:ok, updated_emulator}
  end

  def handle_iconify(emulator) do
    updated_window_state = %{emulator.window_state | iconified: true}
    updated_emulator = %{emulator | window_state: updated_window_state}
    {:ok, updated_emulator}
  end

  def handle_move(emulator, params) do
    y = Enum.at(params, 1, 0)
    x = Enum.at(params, 2, 0)

    # Ensure non-negative values, default to 0 for invalid input
    safe_x =
      case is_integer(x) and x >= 0 do
        true -> x
        false -> 0
      end

    safe_y =
      case is_integer(y) and y >= 0 do
        true -> y
        false -> 0
      end

    updated_window_state = %{emulator.window_state | position: {safe_x, safe_y}}
    updated_emulator = %{emulator | window_state: updated_window_state}
    {:ok, updated_emulator}
  end

  @doc """
  Resizes the window with the given parameters.
  Parameters can be a list of integers representing width and/or height.
  """
  def resize(emulator, params) do
    case handle_resize(emulator, params) do
      {:ok, updated_emulator} -> updated_emulator
    end
  end

  def handle_resize(emulator, params) do
    {width_px, height_px} = parse_resize_params(emulator, params)

    {safe_width_px, safe_height_px} =
      validate_dimensions(emulator, width_px, height_px)

    {char_width, char_height} =
      calculate_char_dimensions(safe_width_px, safe_height_px)

    updated_emulator =
      update_emulator_size(
        emulator,
        char_width,
        char_height,
        safe_width_px,
        safe_height_px
      )

    {:ok, updated_emulator}
  end

  defp validate_dimensions(emulator, width_px, height_px) do
    safe_width =
      validate_dimension(width_px, elem(emulator.window_state.size_pixels, 0))

    safe_height =
      validate_dimension(height_px, elem(emulator.window_state.size_pixels, 1))

    {safe_width, safe_height}
  end

  defp calculate_char_dimensions(width_px, height_px) do
    {calculate_width_chars(width_px), calculate_height_chars(height_px)}
  end

  defp update_emulator_size(
         emulator,
         char_width,
         char_height,
         width_px,
         height_px
       ) do
    size = {char_width, char_height}
    size_pixels = {width_px, height_px}

    updated_window_state = %{
      emulator.window_state
      | size: size,
        size_pixels: size_pixels
    }

    updated_main_buffer =
      resize_screen_buffer(emulator.main_screen_buffer, char_width, char_height)

    %{
      emulator
      | window_state: updated_window_state,
        main_screen_buffer: updated_main_buffer,
        width: char_width,
        height: char_height
    }
  end

  defp parse_resize_params(emulator, params) do
    case params do
      [op, h, w] when is_integer(op) and op == 4 ->
        {w, h}

      [op, h] when is_integer(op) and op == 4 ->
        {default_width_px(), h}

      [op] when is_integer(op) and op == 4 ->
        {default_width_px(), default_height_px()}

      [w] when is_integer(w) ->
        {w, get_current_height(emulator)}

      [w, h] when is_integer(w) and is_integer(h) ->
        {w, h}

      _ ->
        emulator.window_state.size_pixels
    end
  end

  defp default_width_px, do: 80 * default_char_width_px()
  defp default_height_px, do: 24 * default_char_height_px()

  defp get_current_height(emulator),
    do: elem(emulator.window_state.size_pixels, 1)

  defp validate_dimension(value, fallback) do
    case is_integer(value) and value > 0 do
      true -> value
      false -> fallback
    end
  end

  defp resize_screen_buffer(buffer, width, height) do
    case {buffer, width, height} do
      {nil, _, _} ->
        ScreenBuffer.new(width, height)

      {buf, w, h} when w > 0 and h > 0 ->
        case Raxol.Core.ErrorHandling.safe_call(fn ->
               ScreenBuffer.resize(buf, w, h)
             end) do
          {:ok, resized_buffer} -> resized_buffer
          {:error, _reason} -> ScreenBuffer.new(w, h)
        end

      _ ->
        buffer
    end
  end

  def handle_raise(emulator) do
    updated_window_state = %{emulator.window_state | stacking_order: :above}
    updated_emulator = %{emulator | window_state: updated_window_state}
    {:ok, updated_emulator}
  end

  def handle_lower(emulator) do
    updated_window_state = %{emulator.window_state | stacking_order: :below}
    updated_emulator = %{emulator | window_state: updated_window_state}
    {:ok, updated_emulator}
  end

  def handle_refresh(emulator) do
    # Refresh is a no-op
    {:ok, emulator}
  end

  def handle_maximize(emulator) do
    # Store current size before maximizing
    current_size = emulator.window_state.size
    # Default maximized size
    maximized_size = {160, 60}

    updated_window_state = %{
      emulator.window_state
      | maximized: true,
        previous_size: current_size,
        size: maximized_size,
        size_pixels:
          {elem(maximized_size, 0) * default_char_width_px(),
           elem(maximized_size, 1) * default_char_height_px()}
    }

    # Update screen buffer dimensions
    updated_main_buffer =
      ScreenBuffer.resize(emulator.main_screen_buffer, 160, 60)

    updated_emulator = %{
      emulator
      | window_state: updated_window_state,
        main_screen_buffer: updated_main_buffer,
        width: 160,
        height: 60
    }

    {:ok, updated_emulator}
  end

  def handle_restore(emulator) do
    # Restore to previous size, fallback to {80, 24} if missing/invalid
    previous_size =
      case emulator.window_state.previous_size do
        {w, h} when is_integer(w) and is_integer(h) and w > 0 and h > 0 ->
          {w, h}

        _ ->
          {80, 24}
      end

    char_width = elem(previous_size, 0)
    char_height = elem(previous_size, 1)

    updated_window_state = %{
      emulator.window_state
      | maximized: false,
        size: previous_size,
        size_pixels:
          {char_width * default_char_width_px(), char_height * default_char_height_px()}
    }

    # Update screen buffer dimensions, create new if nil
    updated_main_buffer =
      case emulator.main_screen_buffer do
        nil -> ScreenBuffer.new(char_width, char_height)
        buffer -> ScreenBuffer.resize(buffer, char_width, char_height)
      end

    updated_emulator = %{
      emulator
      | window_state: updated_window_state,
        main_screen_buffer: updated_main_buffer,
        width: char_width,
        height: char_height
    }

    {:ok, updated_emulator}
  end

  def handle_report_state(emulator) do
    # Report window state via output buffer
    state_code =
      case emulator.window_state.iconified do
        true -> "2"
        false -> "1"
      end

    output = "\x1b[#{state_code}t"
    updated_emulator = %{emulator | output_buffer: output}
    {:ok, updated_emulator}
  end

  def handle_report_size_pixels(emulator) do
    # Report window size in pixels - use actual pixel size
    {width_px, height_px} = emulator.window_state.size_pixels
    output = "\x1b[4;#{height_px};#{width_px}t"
    updated_emulator = %{emulator | output_buffer: output}
    {:ok, updated_emulator}
  end

  def handle_report_position(emulator) do
    # Report window position
    {x, y} = emulator.window_state.position
    output = "\x1b[3;#{y};#{x}t"
    updated_emulator = %{emulator | output_buffer: output}
    {:ok, updated_emulator}
  end

  def handle_report_text_area_size(emulator) do
    # Report text area size in characters
    {width, height} = emulator.window_state.size
    output = "\x1b[8;#{height};#{width}t"
    updated_emulator = %{emulator | output_buffer: output}
    {:ok, updated_emulator}
  end

  def handle_report_desktop_size(emulator) do
    # Report desktop size in characters (hardcoded defaults)
    output = "\x1b[9;60;160t"
    updated_emulator = %{emulator | output_buffer: output}
    {:ok, updated_emulator}
  end

  # Caching support functions
  # Originally from WindowHandler module

  # FontMetricsCache lives in main raxol; guarded at runtime
  @compile {:no_warn_undefined, Raxol.Performance.Caches.FontMetricsCache}

  alias Raxol.Terminal.Font.Manager, as: FontManager

  @default_font_size 14
  @default_line_height 1.143

  @doc """
  Gets cached font dimensions for performance.
  """
  def get_cached_char_dimensions do
    font_manager = get_default_font_manager()

    if Code.ensure_loaded?(Raxol.Performance.Caches.FontMetricsCache) do
      Raxol.Performance.Caches.FontMetricsCache.get_font_dimensions(font_manager)
    else
      {default_char_width_px(), default_char_height_px()}
    end
  end

  defp get_default_font_manager do
    %FontManager{
      family: "monospace",
      size: @default_font_size,
      weight: :normal,
      style: :normal,
      line_height: @default_line_height,
      letter_spacing: 0,
      fallback_fonts: [],
      custom_fonts: %{}
    }
  end

  @doc """
  Cached version of char width calculation.
  """
  def cached_char_width_px do
    {char_width, _} = get_cached_char_dimensions()
    char_width
  end

  @doc """
  Cached version of char height calculation.
  """
  def cached_char_height_px do
    {_, char_height} = get_cached_char_dimensions()
    char_height
  end
end
