defmodule BDS.Desktop.DeepLink do
  @moduledoc """
  Receives OS URL-scheme events for the `bds2://` scheme and routes them to the
  shell (spec: script.allium `BlogmarkReceived`).

  On macOS the `BDS2.app` bundle registers `bds2://` as a custom URL scheme via
  the `CFBundleURLTypes` entry in its `Info.plist` (built by `BDS.MacBundle` /
  `mix bds.bundle.macos`). When the browser bookmarklet navigates to
  `bds2://new-post?title=&url=`, the OS launches/raises the app and `Desktop.Env`
  delivers an `{:open_url, [url]}` event. This GenServer subscribes to those
  events and forwards recognised `bds2://` links to the live shell over PubSub,
  where `BDS.Blogmark` turns them into draft posts.

  The `bds2://` scheme is distinct from the legacy app's `bds://` so the two
  installs do not contend for the same registration.
  """

  use GenServer

  require Logger

  alias BDS.CliSync.Watcher

  @scheme "bds2://"

  def child_spec(opts) do
    %{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}}
  end

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: Keyword.get(opts, :name, __MODULE__))
  end

  @impl true
  def init(opts) do
    pubsub = Keyword.get(opts, :pubsub, BDS.PubSub)
    topic = Keyword.get(opts, :topic, Watcher.topic())

    subscribe_to_env()

    {:ok, %{pubsub: pubsub, topic: topic}}
  end

  # Desktop.Env delivers OS events as {event_name, args} tuples.
  @impl true
  def handle_info({:open_url, [url | _rest]}, state) when is_binary(url) do
    {:noreply, route(url, state)}
  end

  def handle_info(_message, state), do: {:noreply, state}

  defp route(url, state) do
    if String.starts_with?(url, @scheme) do
      Phoenix.PubSub.broadcast(state.pubsub, state.topic, {:blogmark_deep_link, url})
    else
      Logger.debug("ignoring non-bds2 deep link: #{inspect(url)}")
    end

    state
  end

  # Desktop.Env is only present when the wx desktop adapter is running. Guard the
  # subscribe so the GenServer can still start in headless/test configurations.
  defp subscribe_to_env do
    if Process.whereis(Desktop.Env) do
      try do
        Desktop.Env.subscribe()
      catch
        :exit, reason ->
          Logger.debug("swallowed deep_link Desktop.Env.subscribe exit: #{inspect(reason)}")
          :ok
      end
    end

    :ok
  end
end
