defmodule PhoenixPlayground.Router do
  @moduledoc false

  @behaviour Plug

  @impl true
  def init([]) do
    []
  end

  @impl true
  def call(conn, []) do
    endpoint = conn.private.phoenix_endpoint

    options = endpoint.config(:phoenix_playground)

    cond do
      options[:live] ->
        PhoenixPlayground.Router.LiveRouter.call(conn, [])

      controller = options[:controller] ->
        conn = put_in(conn.private[:phoenix_playground_controller], controller)
        PhoenixPlayground.Router.ControllerRouter.call(conn, [])

      options[:plug] ->
        # always fetch plug from app env to allow code reloading anonymous functions
        plug = Application.fetch_env!(:phoenix_playground, :plug)

        case plug do
          module when is_atom(module) ->
            module.call(conn, module.init([]))

          {module, options} when is_atom(module) ->
            module.call(conn, module.init(options))

          fun when is_function(fun, 1) ->
            fun.(conn)

          fun when is_function(fun, 2) ->
            fun.(conn, [])
        end

      true ->
        raise ArgumentError, "expected :live, :controller, or :plug, got: #{inspect(options)}"
    end
  end

  defmodule ControllerRouter do
    @moduledoc false

    use Phoenix.Router
    import Phoenix.LiveView.Router

    pipeline :browser do
      plug :accepts, ["html", "json"]
      plug :fetch_session
      plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}
      plug :protect_from_forgery
      plug :put_secure_browser_headers
    end

    scope "/" do
      pipe_through :browser

      get "/", PhoenixPlayground.Router.DelegateController, :index
    end
  end

  defmodule DelegateController do
    @moduledoc false

    def init(options) do
      options
    end

    def call(conn, options) do
      controller = conn.private.phoenix_playground_controller
      controller.call(conn, controller.init(options))
    end
  end

  defmodule LiveRouter do
    @moduledoc false

    use Phoenix.Router
    import Phoenix.LiveView.Router

    pipeline :browser do
      plug :accepts, ["html"]
      plug :fetch_session
      plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}
      plug :put_secure_browser_headers
    end

    scope "/" do
      pipe_through :browser

      live "/", PhoenixPlayground.Router.DelegateLive, :index
    end
  end

  defmodule DelegateLive do
    @moduledoc false
    use Phoenix.LiveView

    @impl true
    def mount(params, session, socket) do
      module().mount(params, session, socket)
    end

    @impl true
    def render(assigns) do
      module().render(assigns)
    end

    @impl true
    def handle_params(unsigned_params, uri, socket) do
      if function_exported?(module(), :handle_params, 3) do
        module().handle_params(unsigned_params, uri, socket)
      else
        {:noreply, socket}
      end
    end

    @impl true
    def handle_event(event, params, socket) do
      module().handle_event(event, params, socket)
    end

    @impl true
    def handle_info(message, socket) do
      module().handle_info(message, socket)
    end

    @impl true
    def handle_async(message, result, socket) do
      module().handle_async(message, result, socket)
    end

    def module do
      Application.fetch_env!(:phoenix_playground, :live)
    end
  end
end
