defmodule Raxol.Terminal.AdvancedFeatures do
  @moduledoc """
  Implements advanced terminal features for modern terminal emulators.

  This module provides support for:
  - OSC 8 Hyperlinks - Clickable links in terminal output
  - Synchronized Output (DEC 2026) - Flicker-free rendering
  - Focus Events - Terminal focus/blur detection
  - Enhanced Bracketed Paste - Improved paste handling
  - Window Manipulation - Advanced terminal control

  These features enable rich, interactive terminal applications with modern UX patterns.
  """

  require Logger

  @type hyperlink_id :: String.t()
  @type url :: String.t()
  @type hyperlink_params :: %{
          optional(:id) => hyperlink_id(),
          optional(:tooltip) => String.t(),
          optional(:params) => map()
        }

  # OSC 8 Hyperlink Implementation

  @doc """
  Creates a clickable hyperlink using OSC 8 escape sequences.

  ## Parameters

  - `text` - The text to display as clickable
  - `url` - The URL to open when clicked
  - `options` - Additional hyperlink options

  ## Examples

      iex> AdvancedFeatures.create_hyperlink("Visit GitHub", "https://github.com")
      "\\e]8;;https://github.com\\e\\\\Visit GitHub\\e]8;;\\e\\\\"

      iex> AdvancedFeatures.create_hyperlink("Click me", "https://example.com", %{
      ...>   id: "link1",
      ...>   tooltip: "Opens example.com"
      ...> })
  """
  @spec create_hyperlink(String.t(), url(), hyperlink_params()) :: String.t()
  def create_hyperlink(text, url, options \\ %{}) do
    # Build OSC 8 parameters
    params = build_osc8_params(options)

    param_string =
      case params do
        "" -> ""
        _ -> "#{params}:"
      end

    # OSC 8 format: \e]8;params;url\e\text\e]8;;\e\
    "\e]8;#{param_string}#{url}\e\\#{text}\e]8;;\e\\"
  end

  @doc """
  Creates multiple hyperlinks with shared parameters.

  ## Examples

      links = [
        {"GitHub", "https://github.com"},
        {"GitLab", "https://gitlab.com"}
      ]

      AdvancedFeatures.create_hyperlinks(links, %{tooltip: "Git repository"})
  """
  @spec create_hyperlinks([{String.t(), url()}], hyperlink_params()) :: [
          String.t()
        ]
  def create_hyperlinks(text_url_pairs, shared_options \\ %{}) do
    Enum.map(text_url_pairs, fn {text, url} ->
      create_hyperlink(text, url, shared_options)
    end)
  end

  @doc """
  Detects if the current terminal supports OSC 8 hyperlinks.
  """
  @spec supports_hyperlinks?() :: boolean()
  def supports_hyperlinks? do
    # Check environment variables and terminal type
    terminal_type = System.get_env("TERM_PROGRAM") || ""
    term = System.get_env("TERM") || ""

    case {terminal_type, term} do
      {"iTerm.app", _} ->
        true

      {"WezTerm", _} ->
        true

      {"kitty", _} ->
        true

      {"Alacritty", _} ->
        true

      {_, term} when term in ["xterm-kitty", "screen-256color"] ->
        true

      _ ->
        # Try to detect by querying terminal capabilities
        query_hyperlink_support()
    end
  end

  # Synchronized Output (DEC 2026) Implementation

  @doc """
  Enables synchronized output mode for flicker-free rendering.

  When enabled, terminal output is buffered until explicitly flushed,
  preventing screen flickering during complex updates.

  ## Examples

      AdvancedFeatures.begin_synchronized_output()
      # ... perform multiple terminal updates ...
      AdvancedFeatures.end_synchronized_output()
  """
  @spec begin_synchronized_output() :: :ok
  def begin_synchronized_output do
    # DEC 2026: Begin Synchronized Output
    IO.write("\e[?2026h")
    :ok
  end

  @doc """
  Disables synchronized output mode and flushes buffered content.
  """
  @spec end_synchronized_output() :: :ok
  def end_synchronized_output do
    # DEC 2026: End Synchronized Output
    IO.write("\e[?2026l")
    :ok
  end

  @doc """
  Executes a function with synchronized output enabled.

  ## Examples

      AdvancedFeatures.with_synchronized_output(fn ->
        Log.info("Line 1")
        Log.info("Line 2")
        # These will appear atomically
      end)
  """
  @spec with_synchronized_output((-> any())) :: any()
  def with_synchronized_output(fun) when is_function(fun, 0) do
    begin_synchronized_output()

    try do
      fun.()
    after
      end_synchronized_output()
    end
  end

  @doc """
  Detects if the terminal supports synchronized output.
  """
  @spec supports_synchronized_output?() :: boolean()
  def supports_synchronized_output? do
    terminal_type = System.get_env("TERM_PROGRAM") || ""

    case terminal_type do
      "kitty" -> true
      "WezTerm" -> true
      "iTerm.app" -> check_iterm_version_for_sync()
      _ -> query_synchronized_output_support()
    end
  end

  # Focus Events Implementation

  @doc """
  Enables terminal focus events.

  When enabled, the terminal will send escape sequences when it gains
  or loses focus, allowing applications to respond to focus changes.
  """
  @spec enable_focus_events() :: :ok
  def enable_focus_events do
    # Enable focus reporting: CSI ? 1004 h
    IO.write("\e[?1004h")
    :ok
  end

  @doc """
  Disables terminal focus events.
  """
  @spec disable_focus_events() :: :ok
  def disable_focus_events do
    # Disable focus reporting: CSI ? 1004 l
    IO.write("\e[?1004l")
    :ok
  end

  @doc """
  Parses focus event sequences.

  Returns:
  - `{:focus_in}` - Terminal gained focus
  - `{:focus_out}` - Terminal lost focus
  - `{:unknown, data}` - Unrecognized sequence
  """
  @spec parse_focus_event(binary()) ::
          {:focus_in} | {:focus_out} | {:unknown, binary()}
  def parse_focus_event("\e[I"), do: {:focus_in}
  def parse_focus_event("\e[O"), do: {:focus_out}
  def parse_focus_event(data), do: {:unknown, data}

  # Enhanced Bracketed Paste Implementation

  @doc """
  Enables enhanced bracketed paste mode.

  This prevents pasted content from being interpreted as terminal commands
  and provides better handling of multiline pastes.
  """
  @spec enable_bracketed_paste() :: :ok
  def enable_bracketed_paste do
    # Enable bracketed paste: CSI ? 2004 h
    IO.write("\e[?2004h")
    :ok
  end

  @doc """
  Disables bracketed paste mode.
  """
  @spec disable_bracketed_paste() :: :ok
  def disable_bracketed_paste do
    # Disable bracketed paste: CSI ? 2004 l
    IO.write("\e[?2004l")
    :ok
  end

  @doc """
  Parses bracketed paste sequences.

  Returns:
  - `{:paste_start}` - Beginning of pasted content
  - `{:paste_end}` - End of pasted content
  - `{:paste_content, data}` - Pasted content
  """
  @spec parse_paste_event(binary()) ::
          {:paste_start}
          | {:paste_end}
          | {:paste_content, binary()}
  def parse_paste_event("\e[200~"), do: {:paste_start}
  def parse_paste_event("\e[201~"), do: {:paste_end}
  def parse_paste_event(data), do: {:paste_content, data}

  # Window Manipulation Implementation

  @doc """
  Gets the current terminal window size.

  Returns `{:ok, {width, height}}` or `{:error, reason}`.
  """
  @spec get_window_size() ::
          {:ok, {non_neg_integer(), non_neg_integer()}} | {:error, term()}
  def get_window_size do
    # Query terminal size: CSI 18 t
    IO.write("\e[18t")

    # In a real implementation, this would wait for the response
    # For now, fall back to environment variables
    case {System.get_env("COLUMNS"), System.get_env("LINES")} do
      {width_str, height_str}
      when is_binary(width_str) and is_binary(height_str) ->
        with {width, ""} <- Integer.parse(width_str),
             {height, ""} <- Integer.parse(height_str) do
          {:ok, {width, height}}
        else
          _ -> {:error, :invalid_size_format}
        end

      _ ->
        {:error, :size_not_available}
    end
  end

  @doc """
  Sets the terminal window title.
  """
  @spec set_window_title(String.t()) :: :ok
  def set_window_title(title) do
    # OSC 0 or OSC 2: Set window title
    IO.write("\e]0;#{title}\e\\")
    :ok
  end

  @doc """
  Gets current terminal capabilities and features.
  """
  @spec get_terminal_capabilities() :: %{
          hyperlinks: boolean(),
          synchronized_output: boolean(),
          focus_events: boolean(),
          bracketed_paste: boolean(),
          window_manipulation: boolean(),
          terminal_type: String.t(),
          term_variable: String.t()
        }
  def get_terminal_capabilities do
    %{
      hyperlinks: supports_hyperlinks?(),
      synchronized_output: supports_synchronized_output?(),
      focus_events: supports_focus_events?(),
      bracketed_paste: supports_bracketed_paste?(),
      window_manipulation: supports_window_manipulation?(),
      terminal_type: System.get_env("TERM_PROGRAM") || "unknown",
      term_variable: System.get_env("TERM") || "unknown"
    }
  end

  # Private Helper Functions

  defp build_osc8_params(options) do
    params = []

    params =
      case Map.get(options, :id) do
        nil -> params
        id -> ["id=#{id}" | params]
      end

    params =
      case Map.get(options, :tooltip) do
        nil -> params
        tooltip -> ["tooltip=#{URI.encode(tooltip)}" | params]
      end

    # Add custom parameters
    params =
      case Map.get(options, :params) do
        nil ->
          params

        custom_params when is_map(custom_params) ->
          Enum.reduce(custom_params, params, fn {key, value}, acc ->
            ["#{key}=#{URI.encode(to_string(value))}" | acc]
          end)

        _ ->
          params
      end

    Enum.join(params, ":")
  end

  defp query_hyperlink_support do
    # In a real implementation, this would query terminal capabilities
    # For now, return false as the safe default
    false
  end

  defp query_synchronized_output_support do
    # Query for DEC 2026 support
    # This would involve sending a query and waiting for response
    false
  end

  defp check_iterm_version_for_sync do
    # Check if iTerm2 version supports synchronized output
    # Requires iTerm2 3.4+
    case System.get_env("TERM_PROGRAM_VERSION") do
      nil ->
        false

      version_str ->
        case parse_version(version_str) do
          {:ok, {major, minor, _patch}}
          when major > 3 or (major == 3 and minor >= 4) ->
            true

          _ ->
            false
        end
    end
  end

  defp parse_version(version_str) do
    case String.split(version_str, ".") do
      [major, minor, patch] ->
        with {maj, ""} <- Integer.parse(major),
             {min, ""} <- Integer.parse(minor),
             {pat, ""} <- Integer.parse(patch) do
          {:ok, {maj, min, pat}}
        else
          _ -> {:error, :invalid_version}
        end

      _ ->
        {:error, :invalid_version}
    end
  end

  defp supports_focus_events? do
    # Most modern terminals support focus events
    terminal_type = System.get_env("TERM_PROGRAM") || ""
    terminal_type != ""
  end

  defp supports_bracketed_paste? do
    # Almost all modern terminals support bracketed paste
    term = System.get_env("TERM") || ""
    not String.starts_with?(term, "dumb")
  end

  defp supports_window_manipulation? do
    # Check for window manipulation support
    terminal_type = System.get_env("TERM_PROGRAM") || ""
    terminal_type in ["iTerm.app", "kitty", "WezTerm", "Alacritty"]
  end
end
