Architecture
============

This page defines the repository structure and public API organization rules
for FRIDOM.

These rules are strict for new code. Existing code may contain legacy patterns
that should only be normalized when there is a clear reason to do so.

Package structure
-----------------

The main package code lives in ``src/fridom/``.

The repository is structured around a core framework and model-specific
packages:

- ``src/fridom/framework/`` contains the reusable core framework
- ``src/fridom/shallowwater/`` contains the shallow water model
- ``src/fridom/nonhydro/`` contains the non-hydrostatic model
- ``src/fridom/hydrostatic/`` contains the hydrostatic model

When adding new code, place it in the most specific existing subpackage that
matches its responsibility.

Public API organization
-----------------------

FRIDOM uses ``lazypimp`` to define package-level lazy exports.

In lazily-loaded packages, public modules, classes, and functions are declared
twice in ``__init__.py``:

- once in the ``TYPE_CHECKING`` block for static analysis and editor support
- once in ``all_modules_by_origin`` and/or ``all_imports_by_origin`` for lazy
  runtime exports

These declarations must remain synchronized.

If a symbol should be importable from the package namespace, it must be added to
the appropriate lazy export table.

If a symbol is internal-only, do not add it to the package-level lazy export
tables.

File naming
-----------

For new code, prefer one public class per file.

The filename should be the snake_case form of the class name.

Examples:

- ``ScalarField`` -> ``scalar_field.py``
- ``ModelSettings`` -> ``model_settings.py``
- ``PressureGradientTendency`` -> ``pressure_gradient_tendency.py``

Existing multi-class files may remain as legacy exceptions.

New exceptions should be made only when the public classes are tightly coupled
or splitting them would reduce clarity.

Template for ``__init__.py``
----------------------------

In lazily-loaded packages, use the following structure:

.. code-block:: python

   from typing import TYPE_CHECKING

   from lazypimp import setup

   # ================================================================
   #  Disable lazy loading for type checking
   # ================================================================
   if TYPE_CHECKING:  # pragma: no cover
       from . import subpackage
       from .my_class import MyClass
       from .my_function import my_function

   # ================================================================
   #  Setup lazy loading
   # ================================================================
   base = "fridom.example"

   all_modules_by_origin = {
       base: ["subpackage"],
   }

   all_imports_by_origin = {
       f"{base}.my_class": ["MyClass"],
       f"{base}.my_function": ["my_function"],
   }

   setup(__name__, all_modules_by_origin, all_imports_by_origin)

Use ``all_modules_by_origin`` for subpackages or submodules that should be
available from the package namespace.

Use ``all_imports_by_origin`` for classes, functions, and other symbols that
should be available from the package namespace.

Every public symbol imported in the ``TYPE_CHECKING`` block should have a
matching lazy export entry.

Do not
------

- do not add a public symbol to a lazily-loaded package without updating both
  the ``TYPE_CHECKING`` imports and the lazy export tables
- do not let the ``TYPE_CHECKING`` block and lazy export tables drift apart
- do not place unrelated public classes in the same file without a clear reason
- do not add public symbols to broad package roots when a more specific
  subpackage already exists
