Configuration
=============

ArrayKit configuration objects provide dot-notation access to nested settings.

Classes:

- ``Infocyph\ArrayKit\Config\Config``
- ``Infocyph\ArrayKit\Config\LazyFileConfig``

``Config`` supports optional hooks via explicit ``getWithHooks()``,
``setWithHooks()``, and ``fillWithHooks()`` methods.
``LazyFileConfig`` loads namespace files only on first keyed access.

Loading Configuration
---------------------

You can load config from an array or a PHP file that returns an array.

.. code-block:: php

    <?php
    use Infocyph\ArrayKit\Config\Config;

    $config = new Config();

    $ok = $config->loadArray([
        'app' => ['name' => 'ArrayKit', 'env' => 'local'],
        'db' => ['host' => 'localhost', 'port' => 3306],
    ]);

    // Or from file:
    // $ok = $config->loadFile(__DIR__.'/config.php');

Important behavior:

- ``loadArray()`` and ``loadFile()`` only load when config is currently empty.
- If already loaded, they return ``false`` and do not overwrite existing items.
- ``replace()`` always replaces in-memory config items.
- ``reload()`` replaces from array or readable file path.
- Facade-based config creation is documented in :doc:`facade`.

Reading Values
--------------

.. code-block:: php

    <?php
    use Infocyph\ArrayKit\Config\Config;

    $config = new Config();
    $config->loadArray([
        'app' => ['name' => 'ArrayKit', 'env' => 'local'],
        'queue' => ['driver' => 'sync'],
    ]);

    $all = $config->all();
    $name = $config->get('app.name');                         // ArrayKit
    $fallback = $config->get('app.debug', false);             // false
    $many = $config->get(['app.name', 'queue.driver']);

    $has = $config->has('app.name');                          // true
    $hasAny = $config->hasAny(['missing.path', 'queue.driver']); // true
    $required = $config->getOrFail('app.name');               // throws if missing

Typed Getters
-------------

Use nullable/default-friendly typed getters when you want soft type access:

.. code-block:: php

    <?php
    $name = $config->getString('app.name');
    $port = $config->getInt('db.port', 3306);
    $ratio = $config->getFloat('metrics.sample_ratio', 0.5);
    $debug = $config->getBool('app.debug', false);
    $hosts = $config->getList('cluster.hosts', []);
    $cache = $config->getArray('cache', []);
    $mode = $config->getEnum('app.mode', AppMode::class, AppMode::Prod);

Writing Values
--------------

Single key:

.. code-block:: php

    <?php
    $config->set('cache.driver', 'file');
    $config->set('db.port', 5432);

Bulk set:

.. code-block:: php

    <?php
    $config->set([
        'app.env' => 'production',
        'cache.prefix' => 'arraykit_',
    ]);

Overwrite control:

.. code-block:: php

    <?php
    // Do not overwrite existing value
    $config->set('app.env', 'local', overwrite: false);

Fill Missing Values
-------------------

``fill()`` writes only if target key does not already exist.

.. code-block:: php

    <?php
    $config->fill('mail.driver', 'smtp');
    $config->fill([
        'mail.host' => 'localhost',
        'mail.port' => 1025,
    ]);

    // Existing keys are preserved
    $config->fill('app.env', 'staging');

Removing Values
---------------

.. code-block:: php

    <?php
    $config->forget('cache.prefix');
    $config->forget(['mail.host', 'mail.port']);

Merging, Snapshots, and Read-Only Mode
--------------------------------------

.. code-block:: php

    <?php
    $config->snapshot('before-runtime');

    $config->merge(['app' => ['env' => 'production']]);   // deep merge
    $config->overlay(['features' => ['beta' => true]]);   // top-level overlay

    $changed = $config->changed('before-runtime');         // true/false
    $config->restore('before-runtime');                    // rollback

    $config->readonly();                                   // lock writes
    $locked = $config->isReadonly();                       // true
    $config->readonly(false);                              // unlock

Array-Value Helpers
-------------------

``prepend()`` and ``append()`` are useful for list-type config nodes.

.. code-block:: php

    <?php
    $config->set('middleware', ['auth']);
    $config->append('middleware', 'throttle');
    $config->prepend('middleware', 'cors');

    // ['cors', 'auth', 'throttle']
    $middleware = $config->get('middleware');

Config Hooks (Explicit)
-----------------------

``Config`` allows per-key transformation on read/write, while keeping
``get()/set()/fill()`` hook-free for maximum base-path performance.

.. code-block:: php

    <?php
    use Infocyph\ArrayKit\Config\Config;

    $config = new Config();

    $config->onSet('user.name', fn ($v) => strtoupper((string) $v));
    $config->onGet('user.name', fn ($v) => strtolower((string) $v));

    $config->setWithHooks('user.name', 'Alice');
    echo $config->getWithHooks('user.name'); // alice

Bulk operations with hooks:

.. code-block:: php

    <?php
    $config->onSet('user.email', fn ($v) => trim((string) $v));

    $config->setWithHooks([
        'user.name' => 'JOHN',
        'user.email' => ' john@example.com ',
    ]);

    $vals = $config->getWithHooks(['user.name', 'user.email']);

Practical Pattern
-----------------

Use config as a mutable runtime container for app setup:

.. code-block:: php

    <?php
    $config = new Config();
    $config->loadFile(__DIR__.'/config.php');

    // Normalize selected runtime values
    $config->onSet('app.timezone', fn ($v) => trim((string) $v));
    $config->onGet('app.timezone', fn ($v) => strtoupper((string) $v));

    $config->setWithHooks('app.timezone', ' utc ');
    $tz = $config->getWithHooks('app.timezone'); // UTC

LazyFileConfig
--------------

Use ``LazyFileConfig`` when configuration is split into top-level namespace files
like ``db.php``, ``cache.php``, ``queue.php``.

Rules:

- Key format is ``namespace.path.to.key``.
- On first access, only ``{directory}/{namespace}.php`` is loaded.
- Remaining key segments are resolved using dot notation.

.. code-block:: php

    <?php
    use Infocyph\ArrayKit\Config\LazyFileConfig;

    $config = new LazyFileConfig(__DIR__.'/config');

    // Loads only config/db.php:
    $host = $config->get('db.host', '127.0.0.1');

    // Optional warm-up:
    $config->preload(['db', 'cache']);

    $loaded = $config->loadedNamespaces(); // ['db', 'cache']
    $isLoaded = $config->loaded('db');     // alias of isLoaded()

Important behavior:

- ``get()`` requires at least one key.
- ``all()`` is intentionally disabled and throws.
- Namespace file must return an array.
- Missing namespace file returns the provided default.
- ``replace()`` and ``reload()`` reset resolved-namespace tracking.
- read-only mode applies to ``set/fill/forget/replace/reload``-style mutators.

Method Summary
--------------

Config methods:

- ``loadFile()``, ``loadArray()``, ``all()``
- ``get()``, ``has()``, ``hasAny()``
- ``getOrFail()``
- ``set()``, ``fill()``, ``forget()``
- ``prepend()``, ``append()``
- ``replace()``, ``reload()``
- ``getString()/getInt()/getFloat()/getBool()/getArray()/getList()/getEnum()``
- ``merge()``, ``overlay()``
- ``snapshot()``, ``restore()``, ``changed()``
- ``readonly()``, ``isReadonly()``

LazyFileConfig methods:

- ``get()`` (requires key)
- ``has()``, ``hasAny()``
- ``set()``, ``fill()``, ``forget()``
- ``preload()``, ``isLoaded()``, ``loaded()``, ``loadedNamespaces()``
- ``replace()``, ``reload()``
- ``all()`` (throws by design)

Hook-aware methods (Config and LazyFileConfig):

- ``getWithHooks()``
- ``setWithHooks()``
- ``fillWithHooks()``
- ``onGet()``, ``onSet()``
