defmodule Spawn.Cluster.Provisioner.Scheduler do
  @moduledoc """
  The `Spawn.Cluster.Provisioner.Scheduler` module is responsible for scheduling tasks and invoking functions in a distributed actor system.
  It handles creating worker pools and executing functions with the given task configuration.

  This module also contains an implementation of the `Executor` protocol for the `SpawnTask` struct,
  defining the execution behavior for tasks in the context of provisioning actors in the cluster.
  """

  alias Spawn.Cluster.Provisioner.SpawnTask
  alias Spawn.Cluster.ProvisionerPoolSupervisor
  import Spawn.Utils.Common, only: [build_worker_pool_name: 2]

  defimpl Spawn.Cluster.Provisioner.Executor, for: Spawn.Cluster.Provisioner.SpawnTask do
    @doc """
    Defines the `Executor` protocol for the `SpawnTask` struct.

    This implementation handles the execution of a given function (`func`) in the context of a task,
    using the specified actor name, invocation details, options (`opts`), and state.

    The task is executed through a worker pool, created using the `build_worker_pool_name/2` function,
    and the function is invoked with the `{invocation, opts}` tuple and the current state.

    ## Parameters

    - `%SpawnTask{}`: The task struct containing details about the actor provisioning process.
    - `func`: The function to be invoked, which takes the task's `invocation`, `opts`, and `state`.

    ## Returns

    The result of executing the provided function within the context of the actor provisioning system.
    """
    def execute(
          %SpawnTask{
            actor_name: actor_name,
            invocation: invocation,
            opts: opts,
            state: state,
            async: false
          },
          func
        )
        when is_function(func) do
      opts = Keyword.merge(opts, link: false)

      build_worker_pool_name(ProvisionerPoolSupervisor, actor_name)
      |> FLAME.call(fn -> func.({invocation, opts}, state) end, opts)
    end

    def execute(
          %SpawnTask{
            actor_name: actor_name,
            invocation: invocation,
            opts: opts,
            state: state,
            async: true
          },
          func
        )
        when is_function(func) do
      opts = Keyword.merge(opts, link: false)

      build_worker_pool_name(ProvisionerPoolSupervisor, actor_name)
      |> FLAME.cast(fn -> func.({invocation, opts}, state) end, opts)
    end
  end

  @doc """
  Schedules and invokes a task for actor provisioning in another k8s POD.

  This function wraps the scheduling logic by leveraging the `Executor` protocol to execute the provided
  function (`func`). The function is called with the `invocation`, `opts`, and `state` details encapsulated in a `SpawnTask` struct.

  ## Parameters

    - `actor_name`: The actor name reference used to create the worker pool for the task execution.
    - `invocation`: The details of the invocation, typically containing metadata about the actor's execution.
    - `opts`: Options passed along with the task, which may modify how the invocation is performed.
    - `state`: The current state of the process, to be passed to the function being invoked.
    - `func`: A function that will be called with the `{invocation, opts}` tuple and the current `state`.

  ## Example

  ```elixir
  task = %SpawnTask{
    actor: actor,
    invocation: invocation,
    opts: opts,
    state: state
  }

  Spawn.Cluster.Provisioner.Scheduler.schedule_and_invoke(task, &some_function/2)
  """
  def schedule_and_invoke(task, func) when is_function(func) do
    Spawn.Cluster.Provisioner.Executor.execute(task, func)
  end
end
