defmodule Livebook do
  @moduledoc """
  This module provides a public Elixir API for integrating with Livebook.

  ## Configuration

  See `config_runtime/0` for bootstrapping the default runtime
  configuration. There are several public configuration entries that
  you can customize.

  ### Custom plugs

  You can list a number of plugs to call directly before the Livebook
  router

      config :livebook, :plugs, [{CustomPlug, []}]

  ### Embedded runtime dependencies

  In case you use the Embedded runtime and support installing
  dependencies with `Mix.install/2`, you can make those discoverable
  in the package search, by configuring a loader function:

      config :livebook, Livebook.Runtime.Embedded,
        load_packages: {Loader, :packages, []}

  The function should return a list of entries like this:

      [
        %{
          dependency: %{dep: {:kino, "~> 0.6.1"}, config: []},
          description: "Interactive widgets for Livebook",
          name: "kino",
          url: "https://hex.pm/packages/kino",
          version: "0.6.1"
        }
      ]

  ### Custom learn notebooks

  **Note that this is compile time configuration.**

  A list of additional notebooks to include in the Learn section.

  Note that the notebooks are loaded and embedded in a compiled module,
  so the paths are accessed at compile time only.

      config :livebook, :learn_notebooks, [
        %{
          # Required notebook path
          path: "/path/to/notebook.livemd",
          # Optional notebook identifier for URLs, as in /learn/notebooks/{slug}
          # By default the slug is inferred from file name, so there is no need to set it
          slug: "my-notebook"
          # Optional list of images
          image_paths: [
            # This image can be sourced as images/myimage.jpg in the notebook
            "/path/to/myimage.jpg"
          ],
          # Optional details for the notebook card. If omitted, the notebook
          # is hidden in the UI, but still accessible under /learn/notebooks/{slug}
          details: %{
            cover_path: "/path/to/logo.png",
            description: "My custom notebook that showcases some amazing stuff."
          }
        },
        %{
          path: "/path/to/other_notebook.livemd"
        }
      ]

  """

  @doc """
  Executes Livebook's `config/runtime.exs`.

  If you use Livebook as a dependency, you can add the following
  to your `config/runtime.exs` to trigger Livebook's `config/runtime.exs`
  configuration:

      Livebook.config_runtime()

  """
  def config_runtime do
    if root = System.get_env("RELEASE_ROOT") do
      for file <- Path.wildcard(Path.join(root, "user/extensions/*.exs")) do
        Code.require_file(file)
      end
    end

    import Config

    config :livebook, :random_boot_id, :crypto.strong_rand_bytes(3)

    config :livebook, LivebookWeb.Endpoint,
      secret_key_base:
        Livebook.Config.secret!("LIVEBOOK_SECRET_KEY_BASE") ||
          Livebook.Utils.random_secret_key_base()

    if level = Livebook.Config.log_level!("LIVEBOOK_LOG_LEVEL") do
      config :logger, level: level
    end

    log_metadata = Livebook.Config.log_metadata!("LIVEBOOK_LOG_METADATA")
    log_format = Livebook.Config.log_format!("LIVEBOOK_LOG_FORMAT") || :text

    config :livebook, :log_format, log_format

    case {log_format, log_metadata} do
      {:json, log_metadata} ->
        config :logger, :default_handler,
          formatter: {LoggerJSON.Formatters.Basic, %{metadata: log_metadata || [:request_id]}}

      {:text, log_metadata} when not is_nil(log_metadata) ->
        config :logger, :default_formatter, metadata: log_metadata

      _ ->
        :ok
    end

    if port = Livebook.Config.port!("LIVEBOOK_PORT") do
      config :livebook, LivebookWeb.Endpoint, http: [port: port]
    end

    if ip = Livebook.Config.ip!("LIVEBOOK_IP") do
      host = Livebook.Utils.ip_to_host(ip)
      config :livebook, LivebookWeb.Endpoint, http: [ip: ip], url: [host: host]
    end

    if base_url_path = Livebook.Config.base_url_path!("LIVEBOOK_BASE_URL_PATH") do
      config :livebook, LivebookWeb.Endpoint, url: [path: base_url_path]
    end

    if public_base_url_path =
         Livebook.Config.base_url_path!("LIVEBOOK_PUBLIC_BASE_URL_PATH") do
      config :livebook, :public_base_url_path, public_base_url_path
    end

    if password = Livebook.Config.password!("LIVEBOOK_PASSWORD") do
      config :livebook, :authentication, {:password, password}
    else
      case Livebook.Config.boolean!("LIVEBOOK_TOKEN_ENABLED", nil) do
        true -> config :livebook, :authentication, :token
        false -> config :livebook, :authentication, :disabled
        # Keep the environment-specific default
        nil -> :ok
      end
    end

    if port = Livebook.Config.port!("LIVEBOOK_IFRAME_PORT") do
      config :livebook, :iframe_port, port
    end

    if url = Livebook.Config.iframe_url!("LIVEBOOK_IFRAME_URL") do
      config :livebook, :iframe_url, url
    end

    if url = Livebook.Config.teams_url!("LIVEBOOK_TEAMS_URL") do
      config :livebook, teams_url: url, warn_on_live_teams_server: false
    end

    if System.get_env("LIVEBOOK_TEAMS_AUTH") do
      config :livebook, :persist_storage, false
    end

    if Livebook.Config.boolean!("LIVEBOOK_SHUTDOWN_ENABLED", false) do
      config :livebook, :shutdown_callback, {System, :stop, []}
    end

    if Livebook.Config.boolean!("LIVEBOOK_WITHIN_IFRAME", false) do
      config :livebook, :within_iframe, true
    end

    if Livebook.Config.boolean!("LIVEBOOK_AWS_CREDENTIALS", false) do
      config :livebook, :aws_credentials, true
    end

    config :livebook,
           :default_runtime,
           Livebook.Config.default_runtime!("LIVEBOOK_DEFAULT_RUNTIME") ||
             Livebook.Runtime.Standalone.new()

    config :livebook, :default_app_runtime, Livebook.Runtime.Standalone.new()

    config :livebook,
           :runtime_modules,
           [
             Livebook.Runtime.Standalone,
             Livebook.Runtime.Attached,
             Livebook.Runtime.Fly,
             Livebook.Runtime.K8s
           ]

    if home = Livebook.Config.writable_dir!("LIVEBOOK_HOME") do
      config :livebook, :home, home
    end

    if data_path = Livebook.Config.writable_dir!("LIVEBOOK_DATA_PATH") do
      config :livebook, :data_path, data_path
    end

    if apps_path = System.get_env("LIVEBOOK_APPS_PATH") do
      config :livebook, :apps_path, apps_path
    end

    if apps_path_password = Livebook.Config.password!("LIVEBOOK_APPS_PATH_PASSWORD") do
      config :livebook, :apps_path_password, apps_path_password
    end

    if apps_path_warmup = Livebook.Config.apps_path_warmup!("LIVEBOOK_APPS_PATH_WARMUP") do
      config :livebook, :apps_path_warmup, apps_path_warmup
    end

    if force_ssl_host = Livebook.Config.force_ssl_host!("LIVEBOOK_FORCE_SSL_HOST") do
      config :livebook, :force_ssl_host, force_ssl_host
    end

    if cacertfile = Livebook.Config.cacertfile!("LIVEBOOK_CACERTFILE") do
      config :livebook, :cacertfile, cacertfile
    end

    config :livebook, :rewrite_on, Livebook.Config.rewrite_on!("LIVEBOOK_PROXY_HEADERS")

    config :livebook,
           :cookie,
           Livebook.Config.cookie!("LIVEBOOK_COOKIE") || Livebook.Utils.random_cookie()

    # TODO: remove in v1.0
    if System.get_env("LIVEBOOK_DISTRIBUTION") == "sname" do
      IO.warn(
        ~s/Ignoring LIVEBOOK_DISTRIBUTION=sname, because short names are no longer supported./,
        []
      )
    end

    if node = Livebook.Config.node!("LIVEBOOK_NODE") do
      config :livebook, :node, node
    end

    if app_service_name = Livebook.Config.app_service_name!("LIVEBOOK_APP_SERVICE_NAME") do
      config :livebook, :app_service_name, app_service_name

      config :livebook,
             :app_service_url,
             Livebook.Config.app_service_url!("LIVEBOOK_APP_SERVICE_URL")
    end

    if update_instructions_url =
         Livebook.Config.update_instructions_url!("LIVEBOOK_UPDATE_INSTRUCTIONS_URL") do
      config :livebook, :update_instructions_url, update_instructions_url
    end

    if allowed_uri_schemes = Livebook.Config.allowed_uri_schemes!("LIVEBOOK_ALLOW_URI_SCHEMES") do
      config :livebook, :allowed_uri_schemes, allowed_uri_schemes
    end

    if identity_provider = Livebook.Config.identity_provider!("LIVEBOOK_IDENTITY_PROVIDER") do
      config :livebook, :identity_provider, identity_provider
    end

    if dns_cluster_query = Livebook.Config.dns_cluster_query!("LIVEBOOK_CLUSTER") do
      config :livebook, :dns_cluster_query, dns_cluster_query
    end

    if agent_name = Livebook.Config.agent_name!("LIVEBOOK_AGENT_NAME") do
      config :livebook, :agent_name, agent_name
    end

    if image_registry_url = Livebook.Config.image_registry_url!("LIVEBOOK_IMAGE_REGISTRY_URL") do
      config :livebook, :image_registry_url, image_registry_url
    end

    if Livebook.Config.boolean!("LIVEBOOK_FIPS", false) do
      if :crypto.enable_fips_mode(true) do
        IO.puts("[Livebook] FIPS mode enabled")
      else
        Livebook.Config.abort!(
          "Requested FIPS mode via LIVEBOOK_FIPS, but this Erlang installation was compiled without FIPS support"
        )
      end
    end
  end

  @doc """
  Parses the given Live Markdown document and converts it to Elixir
  source code.

  ## Limitations

  Note that the resulting script may not compile in some cases, for
  example if you define a macro in one cell and import it in another
  cell, it works fine in Livebook, because each cell is compiled
  separately. However, when running the script it gets compiled as a
  whole and consequently doing so doesn't work.

  Additionally, branching sections are commented out.
  """
  @spec live_markdown_to_elixir(String.t()) :: String.t()
  def live_markdown_to_elixir(markdown) do
    {notebook, _info} = Livebook.LiveMarkdown.notebook_from_livemd(markdown)
    Livebook.Notebook.Export.Elixir.notebook_to_elixir(notebook)
  end
end
