if Phoenix.Sync.sandbox_enabled?() do
  defmodule Phoenix.Sync.Sandbox.Inspector do
    @moduledoc false

    use GenServer

    @behaviour Electric.Postgres.Inspector

    @impl Electric.Postgres.Inspector

    def load_relation_oid(relation, stack_id) do
      with {:ok, pid} <- validate_stack_alive(stack_id) do
        GenServer.call(pid, {:load_relation_oid, relation})
      end
    end

    @impl Electric.Postgres.Inspector
    def load_relation_info(relation, stack_id) do
      with {:ok, pid} <- validate_stack_alive(stack_id) do
        GenServer.call(pid, {:load_relation_info, relation})
      end
    end

    @impl Electric.Postgres.Inspector
    def load_column_info(relation, stack_id) do
      with {:ok, pid} <- validate_stack_alive(stack_id) do
        GenServer.call(pid, {:load_column_info, relation})
      end
    end

    @impl Electric.Postgres.Inspector
    def clean(_, _), do: true

    @impl Electric.Postgres.Inspector
    def list_relations_with_stale_cache(_), do: {:ok, []}

    def start_link(args) do
      GenServer.start_link(__MODULE__, args, name: name(args[:stack_id]))
    end

    def name(stack_id) do
      Phoenix.Sync.Sandbox.name({__MODULE__, stack_id})
    end

    defp validate_stack_alive(stack_id) do
      case GenServer.whereis(name(stack_id)) do
        nil ->
          {:error, "stack #{inspect(stack_id)} is not available"}

        pid ->
          {:ok, pid}
      end
    end

    @impl GenServer
    def init(args) do
      {:ok, stack_id} = Keyword.fetch(args, :stack_id)
      {:ok, repo} = Keyword.fetch(args, :repo)

      {:ok, %{repo: repo, stack_id: stack_id, relations: %{}, columns: %{}, oids: %{}}}
    end

    @impl GenServer
    def handle_call({:load_relation_oid, relation}, _from, state) do
      {result, state} =
        fetch_lazy(state, :oids, relation, fn ->
          Electric.Postgres.Inspector.DirectInspector.load_relation_oid(relation, pool(state))
        end)

      {:reply, result, state}
    end

    def handle_call({:load_relation_info, relation}, _from, state) do
      {result, state} =
        fetch_lazy(state, :relations, relation, fn ->
          Electric.Postgres.Inspector.DirectInspector.load_relation_info(relation, pool(state))
        end)

      {:reply, result, state}
    end

    def handle_call({:load_column_info, relation}, _from, state) do
      {result, state} =
        fetch_lazy(state, :columns, relation, fn ->
          Electric.Postgres.Inspector.DirectInspector.load_column_info(relation, pool(state))
        end)

      {:reply, result, state}
    end

    defp pool(state) do
      %{pid: pool} = Ecto.Adapter.lookup_meta(state.repo.get_dynamic_repo())
      pool
    end

    defp fetch_lazy(state, cache, key, fun) do
      case Map.fetch(Map.fetch!(state, cache), key) do
        {:ok, value} ->
          {{:ok, value}, state}

        :error ->
          case handling_exits(fun, state) do
            {:ok, value} ->
              {{:ok, value}, Map.update!(state, cache, &Map.put(&1, key, value))}

            {:error, _reason} = error ->
              {error, state}
          end
      end
    end

    defp handling_exits(fun, %{stack_id: stack_id}) do
      try do
        fun.()
      catch
        :exit, _reason -> {:error, "Stack #{inspect(stack_id)} down"}
      end
    end
  end
end
