defmodule Pyex.Stdlib do
  @moduledoc """
  Registry of Python standard library modules available to the
  interpreter via `import`.

  Each module is represented as a map of attribute names to values
  (typically `{:builtin, fun}` tuples for callable functions, or
  plain maps for sub-modules).
  """

  @modules %{
    "requests" => Pyex.Stdlib.Requests,
    "json" => Pyex.Stdlib.JSON,
    "math" => Pyex.Stdlib.Math,
    "fastapi" => Pyex.Stdlib.FastAPI,
    "random" => Pyex.Stdlib.Random,
    "re" => Pyex.Stdlib.Re,
    "time" => Pyex.Stdlib.Time,
    "datetime" => Pyex.Stdlib.Datetime,
    "collections" => Pyex.Stdlib.Collections,
    "csv" => Pyex.Stdlib.CSV,
    "html" => Pyex.Stdlib.HTML,
    "itertools" => Pyex.Stdlib.Itertools,
    "jinja2" => Pyex.Stdlib.Jinja2,
    "markdown" => Pyex.Stdlib.Markdown,
    "unittest" => Pyex.Stdlib.Unittest,
    "uuid" => Pyex.Stdlib.UUID,
    "sql" => Pyex.Stdlib.SQL,
    "pydantic" => Pyex.Stdlib.Pydantic,
    "boto3" => Pyex.Stdlib.Boto3,
    "hashlib" => Pyex.Stdlib.Hashlib,
    "hmac" => Pyex.Stdlib.Hmac,
    "base64" => Pyex.Stdlib.Base64,
    "secrets" => Pyex.Stdlib.Secrets,
    "pandas" => Pyex.Stdlib.Pandas,
    "yaml" => Pyex.Stdlib.YAML,
    "decimal" => Pyex.Stdlib.DecimalModule,
    "fractions" => Pyex.Stdlib.Fractions,
    "importlib" => Pyex.Stdlib.Importlib,
    "logging" => Pyex.Stdlib.Logging,
    "traceback" => Pyex.Stdlib.Traceback,
    "ast" => Pyex.Stdlib.AST,
    "inspect" => Pyex.Stdlib.Inspect,
    "glob" => Pyex.Stdlib.Glob,
    "pathlib" => Pyex.Stdlib.Pathlib,
    "fnmatch" => Pyex.Stdlib.Fnmatch,
    "urllib" => Pyex.Stdlib.Urllib,
    "shutil" => Pyex.Stdlib.Shutil,
    "dataclasses" => Pyex.Stdlib.Dataclasses,
    "heapq" => Pyex.Stdlib.Heapq,
    "functools" => Pyex.Stdlib.Functools,
    "contextlib" => Pyex.Stdlib.Contextlib,
    "copy" => Pyex.Stdlib.Copy,
    "io" => Pyex.Stdlib.Io,
    "abc" => Pyex.Stdlib.Abc,
    "asyncio" => Pyex.Stdlib.Asyncio,
    "enum" => Pyex.Stdlib.EnumModule,
    "string" => Pyex.Stdlib.String,
    "typing" => Pyex.Stdlib.Typing,
    "textwrap" => Pyex.Stdlib.Textwrap,
    "sys" => Pyex.Stdlib.Sys,
    "crypto" => Pyex.Stdlib.Crypto,
    "zoneinfo" => Pyex.Stdlib.Zoneinfo,
    "operator" => Pyex.Stdlib.Operator,
    "bisect" => Pyex.Stdlib.Bisect,
    "store" => Pyex.Stdlib.Store,
    "pygments" => Pyex.Stdlib.Pygments,
    "zipfile" => Pyex.Stdlib.Zipfile,
    "statistics" => Pyex.Stdlib.Statistics,
    "opentelemetry" => Pyex.Stdlib.Opentelemetry
  }

  @doc """
  Returns the module value for the given Python module name,
  or `:unknown_module`.
  """
  @spec fetch(String.t()) :: {:ok, Pyex.Stdlib.Module.module_value()} | :unknown_module
  def fetch(name) do
    case Map.fetch(@modules, name) do
      # Some stdlib modules are compiled only when their optional backend dep is
      # present (`sql` needs :postgrex, `pandas` needs :explorer). When the dep
      # isn't installed the module isn't defined, so `import` it like any other
      # absent module — a clean ImportError, not a host crash.
      {:ok, mod} ->
        if Code.ensure_loaded?(mod) and function_exported?(mod, :module_value, 0) do
          {:ok, mod.module_value()}
        else
          :unknown_module
        end

      :error ->
        :unknown_module
    end
  end

  @doc """
  Returns a sorted list of all available stdlib module names.
  """
  @spec module_names() :: [String.t()]
  def module_names do
    @modules
    |> Enum.filter(fn {_name, mod} ->
      Code.ensure_loaded?(mod) and function_exported?(mod, :module_value, 0)
    end)
    |> Enum.map(&elem(&1, 0))
    |> Enum.sort()
  end
end
