defmodule PhoenixLiveState.LiveView.Integration do
  @moduledoc """
  Integration for clients.

  Clients are usually LiveView processes that want to share state with other
  LiveView processes.
  """
  @moduledoc since: "0.1.0"

  alias PhoenixLiveState.StateServer
  alias PhoenixLiveState.StateSpec

  @doc """
  Connects the current LiveView process to the given state server, identified by the
  key.

  If the state server is not yet alive, it will be spawned.
  """
  @doc since: "0.1.0"
  @spec live_state(Phoenix.LiveView.Socket.t(), term()) :: Phoenix.LiveView.Socket.t()
  def live_state(socket, key, _opts \\ []) do
    live_state_config = socket.private[:live_state]

    if live_state_config do
      state_spec = live_state_config[:state_spec]

      updated_config = Keyword.put(live_state_config, :key, key)

      result =
        DynamicSupervisor.start_child(
          PhoenixLiveState.Supervisor,
          {StateServer, key: key, state_spec: state_spec}
        )

      dbg(result)

      if Phoenix.LiveView.connected?(socket) do
        {:ok, current_state} = StateServer.attach(key)

        socket
        |> Phoenix.LiveView.put_private(:live_state, updated_config)
        |> Phoenix.Component.assign(:live_assigns, current_state)
      else
        {:ok, %StateSpec{initial_state: initial_value}} = StateSpec.mount(state_spec, key)
        Phoenix.Component.assign(socket, :live_assigns, initial_value)
      end
    else
      raise "You tried to call live_state/2 but the spec is not configured"
    end
  end

  @doc """
  Adds or updates a live assign to the state server.

  Once updated, the changes are broadcast to all clients and the `live_assigns`
  is automatically updated.
  """
  @doc since: "0.1.0"
  @spec live_assign(Phoenix.LiveView.Socket.t(), atom(), term()) :: Phoenix.LiveView.Socket.t()
  def live_assign(socket, property_key, value) do
    live_state_settings = socket.private[:live_state]
    server_key = Keyword.fetch!(live_state_settings, :key)
    StateServer.put(server_key, property_key, value)
    socket
  end

  @doc """
  Same as `live_assign/3`, but in bulk.
  """
  @doc since: "0.1.0"
  @spec live_assign(Phoenix.LiveView.Socket.t(), Access.t()) :: Phoenix.LiveView.Socket.t()
  def live_assign(socket, properties) do
    live_state_settings = socket.private[:live_state]
    server_key = Keyword.fetch!(live_state_settings, :key)

    Enum.each(properties, fn {k, v} ->
      StateServer.put(server_key, k, v)
    end)

    socket
  end
end
