lib/nostr/client/workflows/follow.ex

defmodule Nostr.Client.Workflows.Follow do
  @moduledoc """
  A process that's responsible to subscribe and listen to relays so
  it can properly enable a user's to follow a new contact
  """

  use GenServer

  alias Nostr.RelaySocket
  alias Nostr.Event.{Signer, Validator}
  alias Nostr.Event.Types.{ContactsEvent, EndOfStoredEvents}
  alias Nostr.Models.ContactList
  alias Nostr.Keys.PublicKey

  def start_link(relay_pids, follow_pubkey, privkey) do
    GenServer.start(__MODULE__, %{
      relay_pids: relay_pids,
      privkey: privkey,
      follow_pubkey: follow_pubkey
    })
  end

  @impl true
  def init(%{relay_pids: relay_pids, privkey: privkey} = state) do
    case PublicKey.from_private_key(privkey) do
      {:ok, pubkey} ->
        subscriptions = subscribe_contacts(relay_pids, pubkey)

        {
          :ok,
          state
          |> Map.put(:subscriptions, subscriptions)
          |> Map.put(:treated, false)
        }

      {:error, message} ->
        {:stop, message}
    end
  end

  def handle_info(:unsubscribe_contacts, %{subscriptions: subscriptions} = state) do
    unsubscribe_contacts(subscriptions)

    {
      :noreply,
      state
      |> Map.put(:subscriptions, [])
    }
  end

  def handle_info(
        {:follow, contacts},
        %{privkey: privkey, relay_pids: relay_pids, follow_pubkey: follow_pubkey} = state
      ) do
    follow(follow_pubkey, privkey, contacts, relay_pids)

    {:noreply, state}
  end

  @impl true
  def handle_info({_relay, %EndOfStoredEvents{}}, %{privkey: privkey, treated: false} = state) do
    profile_pubkey = Nostr.Keys.PublicKey.from_private_key!(privkey)

    new_contact_list = %Nostr.Models.ContactList{
      pubkey: profile_pubkey,
      created_at: DateTime.utc_now(),
      contacts: []
    }

    send(self(), {:follow, new_contact_list})

    {
      :noreply,
      state
      |> Map.put(:treated, true)
    }
  end

  @impl true
  # when we first get the contacts, time to add a new pubkey on it
  def handle_info({_relay, contacts}, %{treated: false} = state) do
    send(self(), {:follow, contacts})
    send(self(), :unsubscribe_contacts)

    {
      :noreply,
      state
      |> Map.put(:treated, true)
    }
  end

  @impl true
  # when the follow has already been executed
  def handle_info({_relay, _contacts}, %{treated: true} = state) do
    {:noreply, state}
  end

  defp subscribe_contacts(relay_pids, pubkey) do
    relay_pids
    |> Enum.map(fn relay_pid ->
      subscription_id = RelaySocket.subscribe_contacts(relay_pid, pubkey)

      {relay_pid, subscription_id}
    end)
  end

  defp unsubscribe_contacts(subscriptions) do
    for {relaysocket_pid, subscription_id} <- subscriptions do
      RelaySocket.unsubscribe(relaysocket_pid, subscription_id)
    end
  end

  defp follow(follow_pubkey, privkey, contact_list, relay_pids) do
    contact_list = ContactList.add(contact_list, follow_pubkey)

    {:ok, signed_event} =
      contact_list
      |> ContactsEvent.create_event()
      |> Signer.sign_event(privkey)

    :ok = Validator.validate_event(signed_event)

    for relay_pid <- relay_pids do
      RelaySocket.send_event(relay_pid, signed_event)
    end
  end
end