defmodule Dagger.Codegen.ElixirGenerator.ObjectRendererTest do
  use Dagger.Codegen.RendererCase, async: true

  alias Dagger.Codegen.ElixirGenerator.ObjectRenderer

  test "return object node" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.Client do
        @moduledoc \"""
        The root of the DAG.
        \"""

        use Dagger.Core.Base, kind: :object, name: "Query"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        Create a new TypeDef.
        \"""
        @spec type_def(t()) :: Dagger.TypeDef.t()
        def type_def(%__MODULE__{} = client) do
          query_builder =
            client.query_builder |> QB.select("typeDef")

          %Dagger.TypeDef{
            query_builder: query_builder,
            client: client.client
          }
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/chain-selection.json")
    )
  end

  test "return a list of leaf node" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.Container do
        @moduledoc \"""
        The root of the DAG.
        \"""

        use Dagger.Core.Base, kind: :object, name: "Container"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        Retrieves the list of environment variables passed to commands.
        \"""
        @spec env_variables(t()) :: {:ok, [Dagger.EnvVariable.t()]} | {:error, term()}
        def env_variables(%__MODULE__{} = container) do
          query_builder =
            container.query_builder |> QB.select("envVariables") |> QB.select("id")

          with {:ok, items} <- Client.execute(container.client, query_builder) do
            {:ok,
             for %{"id" => id} <- items do
               %Dagger.EnvVariable{
                 query_builder:
                   QB.query()
                   |> QB.select("loadEnvVariableFromID")
                   |> QB.put_arg("id", id),
                 client: container.client
               }
             end}
          end
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/list-leaf-nodes.json")
    )
  end

  test "execute leaf node" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.EnvVariable do
        @moduledoc \"""
        An environment variable name and value.
        \"""

        use Dagger.Core.Base, kind: :object, name: "EnvVariable"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        The environment variable name.
        \"""
        @spec name(t()) :: {:ok, String.t()} | {:error, term()}
        def name(%__MODULE__{} = env_variable) do
          query_builder =
            env_variable.query_builder |> QB.select("name")

          Client.execute(env_variable.client, query_builder)
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/execute-leaf-node.json")
    )
  end

  test "accept struct type of id argument" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.Client do
        @moduledoc \"""
        The root of the DAG.
        \"""

        use Dagger.Core.Base, kind: :object, name: "Query"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        Load a Container from its ID.
        \"""
        @spec load_container_from_id(t(), Dagger.ContainerID.t()) :: Dagger.Container.t()
        def load_container_from_id(%__MODULE__{} = client, id) do
          query_builder =
            client.query_builder |> QB.select("loadContainerFromID") |> QB.put_arg("id", id)

          %Dagger.Container{
            query_builder: query_builder,
            client: client.client
          }
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/id-arg.json")
    )
  end

  test "iss-7788 sync function return object instead of id type" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.Container do
        @moduledoc \"""
        The root of the DAG.
        \"""

        use Dagger.Core.Base, kind: :object, name: "Container"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        @derive Dagger.Sync
        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        Forces evaluation of the pipeline in the engine.

        It doesn't run the default command if no exec has been set.
        \"""
        @spec sync(t()) :: {:ok, Dagger.Container.t()} | {:error, term()}
        def sync(%__MODULE__{} = container) do
          query_builder =
            container.query_builder |> QB.select("sync")

          with {:ok, id} <- Client.execute(container.client, query_builder) do
            {:ok,
             %Dagger.Container{
               query_builder:
                 QB.query()
                 |> QB.select("loadContainerFromID")
                 |> QB.put_arg("id", id),
               client: container.client
             }}
          end
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/iss-7788.json")
    )
  end

  test "return void" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.FunctionCall do
        @moduledoc \"""
        An active function call.
        \"""

        use Dagger.Core.Base, kind: :object, name: "FunctionCall"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        Set the return value of the function call to the provided value.
        \"""
        @spec return_value(t(), Dagger.JSON.t()) :: :ok | {:error, term()}
        def return_value(%__MODULE__{} = function_call, value) do
          query_builder =
            function_call.query_builder |> QB.select("returnValue") |> QB.put_arg("value", value)

          case Client.execute(function_call.client, query_builder) do
            {:ok, _} -> :ok
            error -> error
          end
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/return-void.json")
    )
  end

  test "return scalar" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.TypeDef do
        @moduledoc \"""
        A definition of a parameter or return type in a Module.
        \"""

        use Dagger.Core.Base, kind: :object, name: "TypeDef"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        The kind of type this is (e.g. primitive, list, object).
        \"""
        @spec kind(t()) :: {:ok, Dagger.TypeDefKind.t()} | {:error, term()}
        def kind(%__MODULE__{} = type_def) do
          query_builder =
            type_def.query_builder |> QB.select("kind")

          case Client.execute(type_def.client, query_builder) do
            {:ok, enum} -> {:ok, Dagger.TypeDefKind.from_string(enum)}
            error -> error
          end
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/return-scalar.json")
    )
  end

  test "return list of enums" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.GhaSettings do
        @moduledoc \"""
        Dagger.GhaSettings
        \"""

        use Dagger.Core.Base, kind: :object, name: "GhaSettings"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @spec permissions(t()) :: {:ok, [Dagger.GhaPermission.t()]} | {:error, term()}
        def permissions(%__MODULE__{} = gha_settings) do
          query_builder =
            gha_settings.query_builder |> QB.select("permissions")

          case Client.execute(gha_settings.client, query_builder) do
            {:ok, enums} -> {:ok, Enum.map(enums, &Dagger.GhaPermission.from_string/1)}
            error -> error
          end
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/return-list-of-enums.json")
    )
  end

  test "iss 8610" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.Directory do
        @moduledoc \"""
        The root of the DAG.
        \"""

        use Dagger.Core.Base, kind: :object, name: "Directory"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        Retrieves this directory plus a directory written at the given path.
        \"""
        @spec with_directory(t(), String.t(), Dagger.Directory.t(), [
                {:exclude, [String.t()]},
                {:include, [String.t()]}
              ]) :: Dagger.Directory.t()
        def with_directory(%__MODULE__{} = directory_, path, directory, optional_args \\\\ []) do
          query_builder =
            directory_.query_builder
            |> QB.select("withDirectory")
            |> QB.put_arg("path", path)
            |> QB.put_arg("directory", Dagger.ID.id!(directory))
            |> QB.maybe_put_arg("exclude", optional_args[:exclude])
            |> QB.maybe_put_arg("include", optional_args[:include])

          %Dagger.Directory{
            query_builder: query_builder,
            client: directory_.client
          }
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/iss-8610.json")
    )
  end

  test "generate protocol" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.Module do
        @moduledoc \"""
        A Dagger module.
        \"""

        use Dagger.Core.Base, kind: :object, name: "Module"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        @derive Dagger.ID
        @derive Dagger.Sync
        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        The dependencies of the module.
        \"""
        @spec dependencies(t()) :: {:ok, [Dagger.Module.t()]} | {:error, term()}
        def dependencies(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("dependencies") |> QB.select("id")

          with {:ok, items} <- Client.execute(module.client, query_builder) do
            {:ok,
             for %{"id" => id} <- items do
               %Dagger.Module{
                 query_builder:
                   QB.query()
                   |> QB.select("loadModuleFromID")
                   |> QB.put_arg("id", id),
                 client: module.client
               }
             end}
          end
        end

        @doc \"""
        The doc string of the module, if any
        \"""
        @spec description(t()) :: {:ok, String.t()} | {:error, term()}
        def description(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("description")

          Client.execute(module.client, query_builder)
        end

        @doc \"""
        Enumerations served by this module.
        \"""
        @spec enums(t()) :: {:ok, [Dagger.TypeDef.t()]} | {:error, term()}
        def enums(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("enums") |> QB.select("id")

          with {:ok, items} <- Client.execute(module.client, query_builder) do
            {:ok,
             for %{"id" => id} <- items do
               %Dagger.TypeDef{
                 query_builder:
                   QB.query()
                   |> QB.select("loadTypeDefFromID")
                   |> QB.put_arg("id", id),
                 client: module.client
               }
             end}
          end
        end

        @doc \"""
        The generated files and directories made on top of the module source's context directory.
        \"""
        @spec generated_context_directory(t()) :: Dagger.Directory.t()
        def generated_context_directory(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("generatedContextDirectory")

          %Dagger.Directory{
            query_builder: query_builder,
            client: module.client
          }
        end

        @doc \"""
        A unique identifier for this Module.
        \"""
        @spec id(t()) :: {:ok, Dagger.ModuleID.t()} | {:error, term()}
        def id(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("id")

          Client.execute(module.client, query_builder)
        end

        @doc \"""
        Interfaces served by this module.
        \"""
        @spec interfaces(t()) :: {:ok, [Dagger.TypeDef.t()]} | {:error, term()}
        def interfaces(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("interfaces") |> QB.select("id")

          with {:ok, items} <- Client.execute(module.client, query_builder) do
            {:ok,
             for %{"id" => id} <- items do
               %Dagger.TypeDef{
                 query_builder:
                   QB.query()
                   |> QB.select("loadTypeDefFromID")
                   |> QB.put_arg("id", id),
                 client: module.client
               }
             end}
          end
        end

        @doc \"""
        The name of the module
        \"""
        @spec name(t()) :: {:ok, String.t()} | {:error, term()}
        def name(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("name")

          Client.execute(module.client, query_builder)
        end

        @doc \"""
        Objects served by this module.
        \"""
        @spec objects(t()) :: {:ok, [Dagger.TypeDef.t()]} | {:error, term()}
        def objects(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("objects") |> QB.select("id")

          with {:ok, items} <- Client.execute(module.client, query_builder) do
            {:ok,
             for %{"id" => id} <- items do
               %Dagger.TypeDef{
                 query_builder:
                   QB.query()
                   |> QB.select("loadTypeDefFromID")
                   |> QB.put_arg("id", id),
                 client: module.client
               }
             end}
          end
        end

        @doc \"""
        The container that runs the module's entrypoint. It will fail to execute if the module doesn't compile.
        \"""
        @spec runtime(t()) :: Dagger.Container.t()
        def runtime(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("runtime")

          %Dagger.Container{
            query_builder: query_builder,
            client: module.client
          }
        end

        @doc \"""
        The SDK config used by this module.
        \"""
        @spec sdk(t()) :: Dagger.SDKConfig.t() | nil
        def sdk(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("sdk")

          %Dagger.SDKConfig{
            query_builder: query_builder,
            client: module.client
          }
        end

        @doc \"""
        Serve a module's API in the current session.

        Note: this can only be called once per session. In the future, it could return a stream or service to remove the side effect.
        \"""
        @spec serve(t()) :: :ok | {:error, term()}
        def serve(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("serve")

          case Client.execute(module.client, query_builder) do
            {:ok, _} -> :ok
            error -> error
          end
        end

        @doc \"""
        The source for the module.
        \"""
        @spec source(t()) :: Dagger.ModuleSource.t()
        def source(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("source")

          %Dagger.ModuleSource{
            query_builder: query_builder,
            client: module.client
          }
        end

        @doc \"""
        Forces evaluation of the module, including any loading into the engine and associated validation.
        \"""
        @spec sync(t()) :: {:ok, Dagger.Module.t()} | {:error, term()}
        def sync(%__MODULE__{} = module) do
          query_builder =
            module.query_builder |> QB.select("sync")

          with {:ok, id} <- Client.execute(module.client, query_builder) do
            {:ok,
             %Dagger.Module{
               query_builder:
                 QB.query()
                 |> QB.select("loadModuleFromID")
                 |> QB.put_arg("id", id),
               client: module.client
             }}
          end
        end

        @doc \"""
        Retrieves the module with the given description
        \"""
        @spec with_description(t(), String.t()) :: Dagger.Module.t()
        def with_description(%__MODULE__{} = module, description) do
          query_builder =
            module.query_builder
            |> QB.select("withDescription")
            |> QB.put_arg("description", description)

          %Dagger.Module{
            query_builder: query_builder,
            client: module.client
          }
        end

        @doc \"""
        This module plus the given Enum type and associated values
        \"""
        @spec with_enum(t(), Dagger.TypeDef.t()) :: Dagger.Module.t()
        def with_enum(%__MODULE__{} = module, enum) do
          query_builder =
            module.query_builder |> QB.select("withEnum") |> QB.put_arg("enum", Dagger.ID.id!(enum))

          %Dagger.Module{
            query_builder: query_builder,
            client: module.client
          }
        end

        @doc \"""
        This module plus the given Interface type and associated functions
        \"""
        @spec with_interface(t(), Dagger.TypeDef.t()) :: Dagger.Module.t()
        def with_interface(%__MODULE__{} = module, iface) do
          query_builder =
            module.query_builder
            |> QB.select("withInterface")
            |> QB.put_arg("iface", Dagger.ID.id!(iface))

          %Dagger.Module{
            query_builder: query_builder,
            client: module.client
          }
        end

        @doc \"""
        This module plus the given Object type and associated functions.
        \"""
        @spec with_object(t(), Dagger.TypeDef.t()) :: Dagger.Module.t()
        def with_object(%__MODULE__{} = module, object) do
          query_builder =
            module.query_builder
            |> QB.select("withObject")
            |> QB.put_arg("object", Dagger.ID.id!(object))

          %Dagger.Module{
            query_builder: query_builder,
            client: module.client
          }
        end
      end

      defimpl Jason.Encoder, for: Dagger.Module do
        def encode(module, opts) do
          {:ok, id} = Dagger.Module.id(module)
          Jason.Encode.string(id, opts)
        end
      end

      defimpl Nestru.Decoder, for: Dagger.Module do
        def decode_fields_hint(_struct, _context, id) do
          {:ok, Dagger.Client.load_module_from_id(Dagger.Global.dag(), id)}
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/gen-protocol.json")
    )
  end

  test "escape docs" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.Doc do
        @moduledoc \"""
        Doc contains " and \\\\
        \"""

        use Dagger.Core.Base, kind: :object, name: "Doc"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        Function doc contains " and \\\\
        \"""
        @spec doc_single_line(t()) :: {:ok, Dagger.TypeDefKind.t()} | {:error, term()}
        def doc_single_line(%__MODULE__{} = doc) do
          query_builder =
            doc.query_builder |> QB.select("doc_single_line")

          case Client.execute(doc.client, query_builder) do
            {:ok, enum} -> {:ok, Dagger.TypeDefKind.from_string(enum)}
            error -> error
          end
        end

        @doc \"""
        Function doc multiple line 
        contains " and \\\\
        \"""
        @spec doc_multi_line(t()) :: {:ok, Dagger.TypeDefKind.t()} | {:error, term()}
        def doc_multi_line(%__MODULE__{} = doc) do
          query_builder =
            doc.query_builder |> QB.select("doc_multi_line")

          case Client.execute(doc.client, query_builder) do
            {:ok, enum} -> {:ok, Dagger.TypeDefKind.from_string(enum)}
            error -> error
          end
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/escape-docs.json")
    )
  end

  test "experimental doc" do
    auto_assert(
      """
      # This file generated by `dagger_codegen`. Please DO NOT EDIT.
      defmodule Dagger.Client do
        @moduledoc \"""
        The root of the DAG.
        \"""

        use Dagger.Core.Base, kind: :object, name: "Query"

        alias Dagger.Core.Client
        alias Dagger.Core.QueryBuilder, as: QB

        defstruct [:query_builder, :client]

        @type t() :: %__MODULE__{}

        @doc \"""
        Initialize a new environment

        > #### Experimental {: .warning}
        >
        > "Environments are not yet stabilized"
        \"""
        @spec env(t(), [{:privileged, boolean() | nil}, {:writable, boolean() | nil}]) :: Dagger.Env.t()
        def env(%__MODULE__{} = client, optional_args \\\\ []) do
          query_builder =
            client.query_builder
            |> QB.select("env")
            |> QB.maybe_put_arg("privileged", optional_args[:privileged])
            |> QB.maybe_put_arg("writable", optional_args[:writable])

          %Dagger.Env{
            query_builder: query_builder,
            client: client.client
          }
        end
      end\
      """ <- render_type(ObjectRenderer, "test/fixtures/objects/experimental.json")
    )
  end
end
