lib/foundry/application.ex

defmodule Foundry.Application do
  @moduledoc false

  use Application

  require Logger

  @impl true
  def start(_type, _args) do
    case Foundry.Studio.parse_studio_argv(System.argv()) do
      {:ok, launch_opts} ->
        start_studio_mode(launch_opts)

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

      :no_command ->
        start_default_mode()
    end
  end

  defp start_default_mode do
    # Initialize Mnesia schema and tables before starting the supervisor
    init_mnesia()

    children = [
      {DNSCluster, query: Application.get_env(:foundry, :dns_cluster_query) || :ignore},
      {Phoenix.PubSub, name: Foundry.PubSub},
      Foundry.Context.ScenarioCache,
      {Foundry.PreviewServer, []},
      {Foundry.ProjectManager, []}
    ]

    Supervisor.start_link(children, strategy: :one_for_one, name: Foundry.Supervisor)
  end

  defp start_studio_mode(launch_opts) do
    init_mnesia()

    # In standalone mode, the port was already selected by runtime.exs before
    # the endpoint started. Read it from the endpoint config instead of
    # re-selecting via prepare_launch, which would diverge from the running endpoint.
    port = get_in(
      Application.get_env(:foundry_web, FoundryWeb.Endpoint, []),
      [:http, :port]
    ) || 4000

    project_root = Keyword.get(launch_opts, :project_root, File.cwd!()) |> Path.expand()
    open_browser? = Keyword.get(launch_opts, :open_browser?, true)
    url = Foundry.Studio.url_for_port(port)

    :ok = Foundry.Studio.configure_runtime(project_root)

    launch = %{
      open_browser?: open_browser?,
      port: port,
      project_root: project_root,
      reused?: false,
      url: url
    }

    children = [
      {DNSCluster, query: Application.get_env(:foundry, :dns_cluster_query) || :ignore},
      {Phoenix.PubSub, name: Foundry.PubSub},
      Foundry.Context.ScenarioCache,
      {Foundry.PreviewServer, []},
      {Foundry.ProjectManager, []},
      {Task, fn -> finalize_studio_launch(launch) end}
    ]

    Supervisor.start_link(children, strategy: :one_for_one, name: Foundry.Supervisor)
  end

  defp finalize_studio_launch(launch) do
    case Foundry.Studio.finalize_launch(launch) do
      :ok ->
        :ok

      {:error, {:health_timeout, port}} ->
        Logger.error("Foundry Studio did not become healthy on port #{port}.")
        exit(:shutdown)

      {:error, reason} ->
        Logger.error("Failed to finalize Foundry Studio launch: #{inspect(reason)}")
        exit(:shutdown)
    end
  end

  defp init_mnesia do
    :mnesia.create_schema([node()])
    :mnesia.start()

    table_config =
      if node() == :nonode@nohost do
        [ram_copies: [node()]]
      else
        [disc_copies: [node()]]
      end

    case :mnesia.create_table(
           :foundry_chat_sessions,
           Keyword.merge(
             [attributes: [:_pkey, :val]],
             table_config
           )
         ) do
      {:aborted, {:already_exists, _}} ->
        :ok

      {:atomic, :ok} ->
        :ok

      error ->
        Logger.warning("Failed to create Mnesia table: #{inspect(error)}")
    end
  end
end