# Introduction and modes

MaxoAdapt turns callbacks inside a `behaviour` block into documented, typed facade functions:

```elixir
defmodule Storage do
  use MaxoAdapt

  behaviour do
    @callback get(binary()) :: {:ok, term()} | {:error, atom()}
    @callback put(binary(), term()) :: :ok | {:error, atom()}
  end
end
```

The module remains a normal Elixir behaviour, so implementations use `@behaviour` and `@impl` normally. Required callback name/arity pairs are validated when a mutable mode is configured. Declared `@optional_callbacks` remain optional behaviour declarations and are not generated as mandatory facade functions.

## Generated API

Every adapter facade exposes:

- one function for each required callback;
- `configure/1` (raising in immutable `:get_compiled` mode); and
- `__maxo_adapt__/0`, which returns the effective implementation or `nil`.

Callback specs and consecutive `@doc` attributes are copied to generated facade functions.

## `:compile` mode

This is the default. MaxoAdapt creates a private redirect module under the `MaxoAdapt.*` namespace and the facade delegates to it. `configure/1` validates the new implementation and regenerates direct delegates. Calls remain comparable to ordinary `defdelegate` dispatch, while occasional runtime switching remains possible.

Configuration is process-global because the generated module is global. Do not concurrently reconfigure the same facade from unrelated owners.

## `:get_compiled` mode

The implementation is read through `Application.compile_env/3` while the facade compiles. Generated functions delegate directly to that implementation. Runtime `configure/1` raises because changing application environment cannot change already-compiled delegates.

Use this mode when the implementation is fixed per build/release.

## `:get_env` mode

Every call resolves the implementation with `Application.get_env/3`. Runtime configuration uses `Application.put_env/4`. Switching is inexpensive, but every dispatched call pays for an application-environment lookup and configuration is global to the BEAM instance.

## `:get_dict` mode

Every process can configure its own implementation. On a local miss, MaxoAdapt searches live ancestor process dictionaries and caches the first result locally. This works naturally with `Task` descendants and allows async tests to select different adapters.

The cache is a snapshot: changing a parent after a child has inherited an adapter does not invalidate the child's cached value. Missing or dead ancestors are ignored safely.
