"""QVF (Quantum Visualization Format) writer for vibe-qc.

Produces ``{stem}.qvf`` -- a zip archive with a JSON manifest
(``manifest.json``) and typed binary or text payloads, one per output
section. Each section is keyed by a ``kind`` string from the QVF
registry. Consumers support whichever kinds they recognise and
silently skip the rest.

v1 scope (implemented)
----------------------
* ``structure`` -- atom positions, Z, labels, optional lattice
* ``volume.density`` -- total electron density as raw float32 .dat
* ``volume.orbital`` -- per-MO wavefunction on a grid
* ``atom_properties`` -- Mulliken / Löwdin charges, spin populations
* ``trajectory`` -- geometry-optimisation / IRC frames
* ``vibrations`` -- normal-mode frequencies + displacements
* ``spectra.ir`` -- IR spectrum from Hessian
* ``bands`` -- band structure (eigenvalues + k-path)
* ``provenance`` -- method, functional, basis, energy, convergence
* ``citations`` -- BibTeX references

Producer rules
--------------
* Float32 default for volumetric arrays; float64 opt-in via
  ``volume_dtype="float64"``.
* Deflate default compression; zstd if ``zipfile-zstd`` is importable.
* ``manifest.json`` always stored uncompressed.
* Every binary member carries a sha256 hex digest in the manifest.

Public API
----------

``write_qvf(stem, plan, **context) -> Path``
    Write ``{stem}.qvf`` from an :class:`OutputPlan` and result data.
    Returns the written path.

``validate_qvf(path) -> dict``
    Open a ``.qvf`` file and validate its manifest + binary payloads.
    Returns a validation report dict.
"""

from __future__ import annotations

import copy
import datetime as _dt
import hashlib
import io
import json
import math
import os
import struct
import tempfile
import zipfile
from pathlib import Path
from typing import Any, Optional, Sequence

import numpy as np

from .._text_safety import safe_json_bytes
from ..plan import OutputPlan

__all__ = [
    "write_qvf",
    "qvf_bytes",
    "validate_qvf",
    "write_reaction_path_qvf",
    "write_scan_surface_qvf",
    "QVF_FORMAT_VERSION",
    "qvf_density_data",
    "qvf_mo_data",
    "qvf_wf_data",
    "qvf_bloch_wf_data",
    "scf_history_from_result",
    "qvf_ao_data",
    "QVF_BLOCH_WF_KIND",
]

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

QVF_FORMAT_VERSION = 1
QVF_FORMAT_VERSION_V2 = 2
_SCHEMA_URI = "https://vibe-qc.org/spec/qvf/1/manifest.schema.json"
_SCHEMA_URI_V2 = "https://vibe-qc.org/spec/qvf/2/manifest.schema.json"
_PRODUCER_NAME = "vibe-qc"
QVF_BLOCH_WF_KIND = "x_vibeqc.bloch_wavefunction"

# Bohr -> Ångström (CODATA 2018).  Must match vibeqc.output.formats.xyz.
_BOHR_TO_ANGSTROM = 0.529177210903
# Hartree -> eV (CODATA 2018).
_HARTREE_TO_EV = 27.211386245988

# Single source of truth for archive size caps. The writer's voxel
# guard and the validator's per-zip-member zip-bomb guard must agree:
# a payload write_qvf will produce must not later be rejected by
# validate_qvf as "too large". 1 Gvoxel covers ~8 GiB float64, which
# is also the per-member uncompressed cap for binary blobs (large MO
# coefficient matrices and trajectory coord arrays count against the
# same limit, not just voxel grids). Compressed-on-disk bytes are
# typically much smaller; the bomb guard runs against the
# uncompressed `file_size` reported by zipfile.
_MAX_VOXELS = 1024**3
_MAX_MEMBER_UNCOMPRESSED_BYTES = _MAX_VOXELS * 8  # float64 worst case

_REAL_ARTIFACT_IMAG_ABS_TOL = 1.0e-10
_REAL_ARTIFACT_IMAG_REL_TOL = 1.0e-7


def _real_array_for_artifact(
    data: Any,
    *,
    dtype: np.dtype | type | str,
    label: str,
    abs_tol: float = _REAL_ARTIFACT_IMAG_ABS_TOL,
    rel_tol: float = _REAL_ARTIFACT_IMAG_REL_TOL,
) -> np.ndarray:
    """Return a real ndarray for real-only QVF members.

    NumPy's ``np.asarray(complex, dtype=float)`` emits ``ComplexWarning``
    while discarding the imaginary component implicitly. QVF v1 scalar
    volumes and Γ-point wavefunction payloads are real-valued by schema, so
    do the projection here intentionally: tiny imaginary residuals from
    Bloch/time-reversal folds are accepted; genuinely complex payloads raise
    and let the optional writer be skipped instead of writing misleading data.
    """
    arr = np.asarray(data)
    if np.iscomplexobj(arr):
        if arr.size:
            max_imag = float(np.max(np.abs(arr.imag)))
            max_real = float(np.max(np.abs(arr.real)))
        else:
            max_imag = 0.0
            max_real = 0.0
        limit = max(float(abs_tol), float(rel_tol) * max(max_real, 1.0))
        if max_imag > limit:
            raise ValueError(
                f"{label} is real-only but carries a non-negligible "
                f"imaginary component (max|Im|={max_imag:.2e}, "
                f"max|Re|={max_real:.2e}, tolerance={limit:.2e})"
            )
        arr = arr.real
    return np.asarray(arr, dtype=dtype)


def _mo_coefficients_for_artifact(
    data: Any,
    *,
    label: str,
    abs_tol: float = _REAL_ARTIFACT_IMAG_ABS_TOL,
    rel_tol: float = _REAL_ARTIFACT_IMAG_REL_TOL,
) -> tuple[np.ndarray, str]:
    """Return a float64 MO-coefficient payload plus its QVF encoding.

    Real coefficients are stored as the historical 2-D float64 matrix.
    Genuine complex Bloch coefficients are stored losslessly as a float64 array
    with a trailing component axis ``[..., 0] = Re`` and ``[..., 1] = Im``.
    Tiny imaginary residuals keep the old real encoding.
    """
    arr = np.asarray(data)
    if np.iscomplexobj(arr):
        if arr.size:
            max_imag = float(np.max(np.abs(arr.imag)))
            max_real = float(np.max(np.abs(arr.real)))
        else:
            max_imag = 0.0
            max_real = 0.0
        limit = max(float(abs_tol), float(rel_tol) * max(max_real, 1.0))
        if max_imag > limit:
            packed = np.empty(arr.shape + (2,), dtype=np.float64)
            packed[..., 0] = arr.real
            packed[..., 1] = arr.imag
            return np.ascontiguousarray(packed), "complex_split_last_axis"
        arr = arr.real
    return np.ascontiguousarray(np.asarray(arr, dtype=np.float64)), "real"


def _complex_split_array(data: Any) -> np.ndarray:
    """Pack a complex array as float64 ``[..., real/imag]`` components."""
    arr = np.asarray(data, dtype=np.complex128)
    packed = np.empty(arr.shape + (2,), dtype=np.float64)
    packed[..., 0] = arr.real
    packed[..., 1] = arr.imag
    return np.ascontiguousarray(packed)

# Element symbols, Z = 0..118.  Mirrors _ELEMENT_SYMBOLS in trajectory.py.
_ELEMENT_SYMBOLS = (
    "X",  # 0 -- placeholder / ghost atom
    "H",
    "He",
    "Li",
    "Be",
    "B",
    "C",
    "N",
    "O",
    "F",
    "Ne",
    "Na",
    "Mg",
    "Al",
    "Si",
    "P",
    "S",
    "Cl",
    "Ar",
    "K",
    "Ca",
    "Sc",
    "Ti",
    "V",
    "Cr",
    "Mn",
    "Fe",
    "Co",
    "Ni",
    "Cu",
    "Zn",
    "Ga",
    "Ge",
    "As",
    "Se",
    "Br",
    "Kr",
    "Rb",
    "Sr",
    "Y",
    "Zr",
    "Nb",
    "Mo",
    "Tc",
    "Ru",
    "Rh",
    "Pd",
    "Ag",
    "Cd",
    "In",
    "Sn",
    "Sb",
    "Te",
    "I",
    "Xe",
    "Cs",
    "Ba",
    "La",
    "Ce",
    "Pr",
    "Nd",
    "Pm",
    "Sm",
    "Eu",
    "Gd",
    "Tb",
    "Dy",
    "Ho",
    "Er",
    "Tm",
    "Yb",
    "Lu",
    "Hf",
    "Ta",
    "W",
    "Re",
    "Os",
    "Ir",
    "Pt",
    "Au",
    "Hg",
    "Tl",
    "Pb",
    "Bi",
    "Po",
    "At",
    "Rn",
    "Fr",
    "Ra",
    "Ac",
    "Th",
    "Pa",
    "U",
    "Np",
    "Pu",
    "Am",
    "Cm",
    "Bk",
    "Cf",
    "Es",
    "Fm",
    "Md",
    "No",
    "Lr",
    "Rf",
    "Db",
    "Sg",
    "Bh",
    "Hs",
    "Mt",
    "Ds",
    "Rg",
    "Cn",
    "Nh",
    "Fl",
    "Mc",
    "Lv",
    "Ts",
    "Og",
)


def _symbol(z: int) -> str:
    if 0 <= z < len(_ELEMENT_SYMBOLS):
        return _ELEMENT_SYMBOLS[z]
    return "X"


# Canonical section kinds that the writer can emit. This list must
# stay in lock-step with the ``Section.oneOf`` branches in
# qvf_manifest.schema.json -- tests/test_qvf_round_trip.py carries the
# schema-drift guard. ``provenance`` and ``viewer_defaults`` are root
# keys, not section kinds; they intentionally don't appear here.
_IMPLEMENTED_KINDS = frozenset(
    {
        "structure",
        "volume.density",
        "volume.orbital",
        "volume.spin",
        "volume.elf",
        "volume.difference",
        "volume.generic",
        "volume.potential",
        "volume.rdg",
        "fermi_surface",
        "phonon_bands",
        "phonon_dos",
        "equation_of_state",
        "basis.ao",
        "wavefunction.gto",
        "atom_properties",
        "trajectory",
        "reaction.path",
        "reaction.waypoints",
        "scan.surface",
        "vibrations",
        "spectra.ir",
        "spectra.raman",
        "spectra.uvvis",
        "spectra.ecd",
        "spectra.vcd",
        "spectra.nmr",
        "spectra.epr",
        "spectra.generic",
        "bands",
        "structure.symmetry",
        "bonds",
        "scf_history",
        "citations",
        "dos.total",
        "dos.projected",
        "bond_orders",
        "topology.qtaim",
        "dos.coop",
        "dos.cohp",
    }
)

# Kinds reserved in the design doc but not yet implemented in the
# writer. The validator accepts them (so a vendor producer can ship
# them ahead of the canonical writer) but the writer never emits one.
#
# `basis` was reserved before `wavefunction.gto` landed -- back then
# we anticipated a separate kind that would carry just the AO basis
# shells. `wavefunction.gto` now ships basis shells + MO coefficients
# in a single section, so a standalone `basis` kind is dead weight
# and has been removed from the registry.
_RESERVED_KINDS = frozenset(
    {
        "volume.orbital_projection",
        "topology.elf_basins",
        "projections.lcao",
    }
)


# ---------------------------------------------------------------------------
# Grid-evaluation convenience helpers
# ---------------------------------------------------------------------------
#
# These produce pre-packaged data dicts that can be passed directly to
# write_qvf() as ``volume_data=`` and ``mo_data=``.  They call the
# existing grid evaluators in vibeqc.cube and repackage the results.


def qvf_density_data(
    result: Any,
    basis: Any,
    molecule: Any,
    *,
    spacing: float = 0.25,
    padding: float = 4.0,
    label: str = "Electron density",
) -> dict[str, tuple]:
    """Evaluate total electron density on a uniform grid and return a
    dict suitable for ``write_qvf(..., volume_data=...)``.

    Parameters
    ----------
    result
        Converged SCF result with a ``.density`` attribute (RHF/RKS)
        or ``.density_alpha`` + ``.density_beta`` (UHF/UKS).
    basis
        :class:`BasisSet` used in the calculation.
    molecule
        :class:`Molecule` defining the atomic positions.
    spacing
        Voxel spacing in bohr (default 0.25).
    padding
        Extra headroom around the molecular bounding box in bohr.
    label
        Human-readable label for the density section.

    Returns
    -------
    dict
        ``{label: (data_3d, origin_3, span_3x3)}`` -- pass as
        ``volume_data=`` to :func:`write_qvf`.
    """
    from vibeqc.cube import (
        CubeGrid,
        _density_on_grid,
        make_uniform_grid,
    )

    grid: CubeGrid = make_uniform_grid(
        molecule,
        spacing=spacing,
        padding=padding,
    )
    # Build density matrix.
    if hasattr(result, "density_alpha"):
        D = np.asarray(result.density_alpha) + np.asarray(result.density_beta)
    else:
        D = np.asarray(result.density)
    try:
        from ...properties import _real_if_hermitian
    except ImportError:
        from vibeqc.properties import _real_if_hermitian  # type: ignore[no-redef]
    D = _real_if_hermitian(D, what="QVF density matrix")

    rho = _density_on_grid(D, basis, grid)
    origin = np.asarray(grid.origin, dtype=np.float64)
    # Per-voxel step vectors (matches 'voxel_vectors' in the QVF schema).
    span = np.diag(np.asarray(grid.spacing, dtype=np.float64))
    return {label: (rho, origin, span)}


def qvf_mo_data(
    result: Any,
    basis: Any,
    molecule: Any,
    indices: list[int],
    *,
    spacing: float = 0.25,
    padding: float = 4.0,
    component: str = "real",
) -> list[dict[str, Any]]:
    """Evaluate MO wavefunctions on a uniform grid and return a list
    suitable for ``write_qvf(..., mo_data=...)``.

    Parameters
    ----------
    result
        Converged SCF result with ``.mo_coefficients`` (RHF/RKS) or
        ``.mo_coefficients_alpha`` + ``.mo_coefficients_beta`` (UHF/UKS).
    basis
        :class:`BasisSet` used in the calculation.
    molecule
        :class:`Molecule` defining the atomic positions.
    indices
        0-based MO indices to evaluate. Must be a list of plain
        ``int`` values; the tuple form returned by
        :func:`vibeqc.output.formats.cube.requested_mo_indices`
        (``[(index, name), ...]``) must be unpacked at the call site.
    spacing
        Voxel spacing in bohr (default 0.25).
    padding
        Extra headroom in bohr.
    component
        ``"real"`` (default), ``"imag"``, ``"abs"``, or ``"density"``.

    Returns
    -------
    list[dict]
        Each dict has keys ``label``, ``data``, ``origin``, ``span``,
        ``band_index``, ``energy_eh``, ``occupation``, ``spin``,
        ``component``.  Pass as ``mo_data=`` to :func:`write_qvf`.
    """
    from vibeqc.cube import CubeGrid, _mo_on_grid, make_uniform_grid

    grid: CubeGrid = make_uniform_grid(
        molecule,
        spacing=spacing,
        padding=padding,
    )
    origin = np.asarray(grid.origin, dtype=np.float64)
    # Per-voxel step vectors (matches 'voxel_vectors' in the QVF schema).
    span = np.diag(np.asarray(grid.spacing, dtype=np.float64))

    # MO coefficients.
    if hasattr(result, "mo_coeffs"):
        C = np.asarray(result.mo_coeffs)
        spin = 0
    elif hasattr(result, "mo_coefficients"):
        C = np.asarray(result.mo_coefficients)
        spin = 0
    elif hasattr(result, "mo_coeffs_alpha"):
        C = np.asarray(result.mo_coeffs_alpha)
        spin = 0
    else:
        raise ValueError(
            "qvf_mo_data: result has no mo_coeffs or mo_coefficients attribute"
        )

    # MO energies.
    if hasattr(result, "mo_energies"):
        energies = np.asarray(result.mo_energies)
    elif hasattr(result, "mo_energies_alpha"):
        energies = np.asarray(result.mo_energies_alpha)
    else:
        energies = np.zeros(C.shape[1])

    # Occupations.
    n_occ = getattr(result, "n_occ", None)
    if n_occ is None:
        n_elec = getattr(molecule, "n_electrons", None)
        if callable(n_elec):
            n_elec = n_elec()
        n_occ = int(n_elec // 2) if n_elec else 0

    out: list[dict[str, Any]] = []
    for idx in indices:
        C_col = C[:, idx]
        mo = _mo_on_grid(C_col, basis, grid)
        if component == "abs":
            mo = np.abs(mo)
        elif component == "density":
            mo = mo**2
        occ = 2.0 if idx < n_occ else 0.0
        out.append(
            {
                "label": f"MO_{idx}",
                "data": mo,
                "origin": origin,
                "span": span,
                "band_index": idx,
                "energy_eh": float(energies[idx]),
                "occupation": occ,
                "spin": spin,
                "component": component,
            }
        )
    return out


def qvf_ao_data(
    basis: Any,
    molecule: Any,
    *,
    grid: Any = None,
    spacing: float = 0.15,
    padding: float = 8.0,
    ao_indices: list[int] | None = None,
    primitive_indices: list[tuple[int, int]] | None = None,
    include_contracted: bool = True,
    include_primitives: bool = True,
    basis_label: str | None = None,
) -> list[dict[str, Any]]:
    """Build per-AO grid data for :func:`write_qvf` ``ao_data=``.

    Evaluates selected atomic orbitals (contracted shells and/or
    individual primitives) on a uniform grid and returns a list of
    dicts ready for the ``basis.ao`` section writer.

    Parameters
    ----------
    basis
        :class:`BasisSet` carrying AO shells.
    molecule
        :class:`Molecule` defining atom centers.
    grid
        Pre-built :class:`CubeGrid`.  When ``None``, one is built via
        :func:`make_uniform_grid` with the given ``spacing`` and
        ``padding``.
    spacing
        Voxel spacing in bohr (default 0.15 -- finer than the 0.25
        density default to resolve compact primitives).
    padding
        Extra headroom around the bounding box in bohr (default 8.0).
    ao_indices
        List of global AO indices for contracted shells to evaluate.
        When ``None`` and ``primitive_indices`` is also ``None``,
        evaluates ALL contracted AOs.
    primitive_indices
        List of ``(shell_idx, prim_idx)`` tuples for individual
        primitives to evaluate.  Each builds a temp single-primitive
        basis and evaluates it.
    include_contracted
        When ``True`` and ``ao_indices`` is ``None``, include every
        contracted AO.  Ignored when ``ao_indices`` is explicit.
    include_primitives
        When ``True`` and ``primitive_indices`` is ``None``, include
        every primitive in every shell.  Ignored when
        ``primitive_indices`` is explicit.
    basis_label
        Human-readable basis-set name (e.g. ``"pob-TZVP"``), written
        into each section's ``ao_metadata.basis_label``.

    Returns
    -------
    list[dict]
        One dict per AO.  Each dict has keys: ``label``, ``data``,
        ``origin``, ``span``, ``ao_metadata``, and ``viewer_hints``.
        Pass the list as ``ao_data=`` to :func:`write_qvf`.
    """
    from vibeqc.cube import (
        CubeGrid,
        _ao_ranges,
        evaluate_ao_on_grid,
        evaluate_single_primitive_on_grid,
        make_uniform_grid,
    )

    # --- grid -----------------------------------------------------------
    if grid is None:
        grid = make_uniform_grid(molecule, spacing=spacing, padding=padding)
    origin = np.asarray(grid.origin, dtype=np.float64)
    span = np.diag(np.asarray(grid.spacing, dtype=np.float64))

    # --- shell metadata --------------------------------------------------
    shells = list(basis.shells())
    ao_ranges = _ao_ranges(basis)
    name = getattr(basis, "name", None)
    blabel = basis_label or (str(name) if name and name != "<custom>" else None)

    # Atom symbol lookup.
    atoms = list(molecule.atoms)
    _Z_to_sym: dict[int, str] = {}
    for ai, a in enumerate(atoms):
        _Z_to_sym[ai] = _symbol(int(a.Z))

    results: list[dict[str, Any]] = []

    # --- contracted AOs --------------------------------------------------
    if include_contracted or ao_indices is not None:
        idxs: list[int]
        if ao_indices is not None:
            idxs = sorted(set(ao_indices))
        else:
            idxs = list(range(int(basis.nbasis)))
        grids = evaluate_ao_on_grid(basis, grid, idxs)
        for ao_idx, data_3d in zip(idxs, grids):
            # Find which shell this AO belongs to.
            shell_idx = -1
            for si, (lo, hi) in enumerate(ao_ranges):
                if lo <= ao_idx < hi:
                    shell_idx = si
                    break
            s = shells[shell_idx]
            ao_local = ao_idx - ao_ranges[shell_idx][0]
            l = int(s.l)
            m_val = ao_local - l  # spherical: m = -l ... +l
            atom_idx = int(s.atom_index)
            sym = _Z_to_sym.get(atom_idx, "?")

            label = _ao_label(sym, shell_idx, l, m_val, prim_idx=None, contracted=True)
            section_id = _ao_section_id(
                sym, shell_idx, l, m_val, prim_idx=None, contracted=True
            )

            meta: dict[str, Any] = {
                "atom_index": atom_idx,
                "atom_symbol": sym,
                "shell_index": shell_idx,
                "primitive_index": 0,
                "angular_momentum": [l, m_val],
                "shell_type": _l_to_shell_type(l),
                "exponent": float(s.exponents[0]),
                "coefficient": float(s.coefficients[0]),
                "is_primitive": False,
                "is_contracted": True,
                "ao_index": ao_idx,
            }
            if blabel:
                meta["basis_label"] = blabel

            results.append(
                {
                    "label": label,
                    "data": data_3d,
                    "origin": origin,
                    "span": span,
                    "ao_metadata": meta,
                    "section_id": section_id,
                }
            )

    # --- individual primitives -------------------------------------------
    if include_primitives or primitive_indices is not None:
        pairs: list[tuple[int, int]]
        if primitive_indices is not None:
            pairs = sorted(set(primitive_indices))
        else:
            pairs = [
                (si, pi)
                for si, s in enumerate(shells)
                for pi in range(len(s.exponents))
            ]
        for shell_idx, prim_idx in pairs:
            s = shells[shell_idx]
            l = int(s.l)
            atom_idx = int(s.atom_index)
            sym = _Z_to_sym.get(atom_idx, "?")

            # Use m=0 label for primitives (representative).
            label = _ao_label(sym, shell_idx, l, 0, prim_idx=prim_idx, contracted=False)
            section_id = _ao_section_id(
                sym, shell_idx, l, 0, prim_idx=prim_idx, contracted=False
            )

            data_3d = evaluate_single_primitive_on_grid(
                basis,
                molecule,
                shell_idx,
                prim_idx,
                grid,
            )

            meta = {
                "atom_index": atom_idx,
                "atom_symbol": sym,
                "shell_index": shell_idx,
                "primitive_index": prim_idx,
                "angular_momentum": [l, 0],
                "shell_type": _l_to_shell_type(l),
                "exponent": float(s.exponents[prim_idx]),
                "coefficient": float(s.coefficients[prim_idx]),
                "is_primitive": True,
                "is_contracted": False,
                "ao_index": ao_ranges[shell_idx][0],
            }
            if blabel:
                meta["basis_label"] = blabel

            results.append(
                {
                    "label": label,
                    "data": data_3d,
                    "origin": origin,
                    "span": span,
                    "ao_metadata": meta,
                    "section_id": section_id,
                }
            )

    return results


# -- AO label / id helpers -------------------------------------------------

_L_LABELS: dict[int, str] = {0: "s", 1: "p", 2: "d", 3: "f", 4: "g", 5: "h"}


def _l_to_shell_type(l: int) -> str:
    return _L_LABELS.get(l, f"l{l}")


def _ao_label(
    sym: str,
    shell_idx: int,
    l: int,
    m: int,
    *,
    prim_idx: int | None = None,
    contracted: bool = False,
) -> str:
    """Human-readable AO label, e.g. 'O 2p_z  exp=3.50'."""
    lt = _L_LABELS.get(l, f"l={l}")
    if contracted:
        return f"{sym} {lt} (contracted)  sh={shell_idx}"
    return f"{sym} {lt}  sh={shell_idx}  p{prim_idx}"


def _ao_section_id(
    sym: str,
    shell_idx: int,
    l: int,
    m: int,
    *,
    prim_idx: int | None = None,
    contracted: bool = False,
) -> str:
    """Stable section id for an AO, e.g. 'ao_O_p_s2_p0'."""
    lt = _L_LABELS.get(l, f"l{l}")
    if contracted:
        return f"ao_{sym}_{lt}_s{shell_idx}_contracted"
    return f"ao_{sym}_{lt}_s{shell_idx}_p{prim_idx}"


def _ao_isovalue_default(exponent: float) -> float:
    """Heuristic isovalue based on Gaussian exponent a.

    Diffuse functions (a < 0.1) have low peak amplitude -- a high
    isovalue shows nothing.  Tight functions (a >= 10) have high peak
    amplitude at the nucleus.
    """
    if exponent < 0.1:
        return 0.005
    if exponent < 1.0:
        return 0.02
    if exponent < 10.0:
        return 0.05
    return 0.10


def _primitive_norm(alpha: float, l: int) -> float:
    """libint2 primitive normalisation -- the factor by which libint scales
    contraction coefficients so they multiply *un-normalised* primitives.

    For a normalised Cartesian (lx, ly, lz) Gaussian primitive
        g(r) = (x-Ax)^lx (y-Ay)^ly (z-Az)^lz exp(-a r^2)
    the normalisation is: N = (2a/pi)^(3/4) . (4a)^(l/2) / √((2l-1)!!)
    where l = lx + ly + lz.

    libint's ``shell.coefficients`` already include this factor (they are
    "libint-normalised"). The QVF spec Sec. 4.6 requires coefficients that
    apply to *normalised* primitives, so the writer must divide libint's
    stored coefficients by this factor on the way out.
    """
    radial = (2.0 * alpha / math.pi) ** 0.75
    angular = (4.0 * alpha) ** (l / 2.0)
    df = 1.0
    for k in range(1, 2 * l, 2):
        df *= k
    return radial * angular / math.sqrt(df)


def _basis_shell_payload(basis: Any) -> tuple[list[dict[str, Any]], bool, int] | None:
    """Return QVF shell JSON, top-level purity, and AO count for ``basis``."""
    try:
        shells_native = list(basis.shells())
    except AttributeError:
        return None

    # libint stores per-shell `pure` flags; vibe-qc forces set_pure(true)
    # on every BasisSet (see symmetry_core.py), so `pure` is uniformly
    # True at the QVF top level. We still emit per-shell `pure` to
    # match the design exactly and stay correct if that ever changes.
    pure_top = all(bool(sh.pure) for sh in shells_native) if shells_native else True

    shell_list: list[dict[str, Any]] = []
    n_ao = 0
    for sh in shells_native:
        l = int(sh.l)
        shell_pure = bool(sh.pure)
        shell_list.append(
            {
                "center": int(sh.atom_index),
                "l": l,
                "exponents": [float(x) for x in sh.exponents],
                "coefficients": [
                    float(c) / _primitive_norm(float(a), l)
                    for a, c in zip(sh.exponents, sh.coefficients)
                ],
                "pure": shell_pure,
            }
        )
        n_ao += (2 * l + 1) if shell_pure else ((l + 1) * (l + 2) // 2)
    return shell_list, pure_top, n_ao


def qvf_wf_data(
    result: Any,
    basis: Any,
    molecule: Any,
    *,
    structure_ref: str = "structure",
    orbital_kind: str = "canonical",
    k_point: "list[float] | None" = None,
) -> dict[str, Any] | None:
    """Package basis shells + MO coefficients + per-orbital metadata into
    a dict suitable for ``write_qvf(..., wf_data=...)``.

    Lets a re-sampling viewer (vibe-view, moltui) evaluate any orbital
    on a grid of its own choosing -- the Molden-style separation of
    concerns described in design Sec. 1.5.

    Parameters
    ----------
    result
        Converged SCF result. RHF/RKS: ``.mo_coeffs`` + ``.mo_energies``.
        UHF/UKS: ``.mo_coeffs_alpha`` + ``_beta`` + matching energies.
    basis
        :class:`BasisSet` carrying the AO shells used in the calculation.
    molecule
        :class:`Molecule` defining atom centers (referenced by ``center``
        indices in the basis shells).
    structure_ref
        ``id`` of the structure section the shell centers refer to
        (default ``"structure"`` -- matches what
        :func:`_write_structure_section` emits).
    orbital_kind
        ``"canonical"`` | ``"natural"`` | ``"localized"`` -- written
        verbatim into the mo_metadata.
    k_point
        Optional fractional reciprocal-space coordinate the MO coefficients
        describe. Pass ``[0.0, 0.0, 0.0]`` for the Γ-point of a periodic
        system; leave ``None`` for molecular wavefunctions. Recorded in
        ``mo_metadata["k_point"]`` so the archive is self-describing.
        Non-Gamma Bloch coefficients are stored as real/imag component pairs.

    Returns
    -------
    dict | None
        A wf_data dict, or ``None`` if neither restricted nor
        unrestricted MO coefficients can be found on ``result`` (e.g.
        a DFTB result that exposes a different attribute layout).
        The shape matches what :func:`_write_wavefunction_gto_section`
        consumes.
    """
    # --- shells -------------------------------------------------------
    basis_payload = _basis_shell_payload(basis)
    if basis_payload is None:
        return None
    shell_list, pure_top, n_ao = basis_payload

    # --- MO coefficients + metadata -----------------------------------
    # vibe-qc convention: mo_coeffs has shape [n_ao, n_mo] (columns are
    # MOs). The QVF format wants [n_mo, n_ao] (rows are MOs) so we
    # transpose on the way out.
    def _as_rowmo(arr: np.ndarray) -> tuple[np.ndarray, str]:
        row = np.asarray(arr).T
        coeff, encoding = _mo_coefficients_for_artifact(
            row,
            label="wavefunction.gto MO coefficients",
        )
        return coeff, encoding

    # Restricted (RHF / RKS).
    if hasattr(result, "mo_coeffs") and not hasattr(result, "mo_coeffs_alpha"):
        C, encoding = _as_rowmo(result.mo_coeffs)
        n_mo = int(C.shape[0])
        energies = (
            np.asarray(result.mo_energies, dtype=np.float64).tolist()
            if hasattr(result, "mo_energies")
            else [0.0] * n_mo
        )
        # Restricted occupations: 2.0 for the first n_elec/2 MOs.
        n_elec = molecule.n_electrons()
        n_doubly = int(n_elec // 2)
        occupations = [2.0 if i < n_doubly else 0.0 for i in range(n_mo)]
        mo_metadata: dict[str, Any] = {
            "n_mo": n_mo,
            "n_ao": n_ao,
            "spin": "restricted",
            "orbital_kind": orbital_kind,
            "energies": energies,
            "occupations": occupations,
        }
        if k_point is not None:
            mo_metadata["k_point"] = [float(x) for x in k_point]
        if encoding != "real":
            mo_metadata["coefficient_encoding"] = encoding
            mo_metadata["coefficient_components"] = ["real", "imag"]
        return {
            "basis": shell_list,
            "structure_ref": structure_ref,
            "pure": pure_top,
            "n_ao": n_ao,
            "mo_metadata": mo_metadata,
            "mo_coefficients": C,
        }

    # Unrestricted (UHF / UKS).
    if hasattr(result, "mo_coeffs_alpha") and hasattr(result, "mo_coeffs_beta"):
        Ca, enc_alpha = _as_rowmo(result.mo_coeffs_alpha)
        Cb, enc_beta = _as_rowmo(result.mo_coeffs_beta)
        n_elec = molecule.n_electrons()
        mult = int(getattr(molecule, "multiplicity", 1))
        n_alpha = (n_elec + mult - 1) // 2
        n_beta = (n_elec - mult + 1) // 2
        e_alpha = (
            np.asarray(result.mo_energies_alpha, dtype=np.float64).tolist()
            if hasattr(result, "mo_energies_alpha")
            else [0.0] * int(Ca.shape[0])
        )
        e_beta = (
            np.asarray(result.mo_energies_beta, dtype=np.float64).tolist()
            if hasattr(result, "mo_energies_beta")
            else [0.0] * int(Cb.shape[0])
        )
        mo_metadata = {
            "n_ao": n_ao,
            "spin": "unrestricted",
            "orbital_kind": orbital_kind,
            "alpha": {
                "n_mo": int(Ca.shape[0]),
                "energies": e_alpha,
                "occupations": [
                    1.0 if i < n_alpha else 0.0 for i in range(int(Ca.shape[0]))
                ],
            },
            "beta": {
                "n_mo": int(Cb.shape[0]),
                "energies": e_beta,
                "occupations": [
                    1.0 if i < n_beta else 0.0 for i in range(int(Cb.shape[0]))
                ],
            },
        }
        if k_point is not None:
            mo_metadata["k_point"] = [float(x) for x in k_point]
        if enc_alpha != "real":
            mo_metadata["alpha"]["coefficient_encoding"] = enc_alpha
            mo_metadata["alpha"]["coefficient_components"] = ["real", "imag"]
        if enc_beta != "real":
            mo_metadata["beta"]["coefficient_encoding"] = enc_beta
            mo_metadata["beta"]["coefficient_components"] = ["real", "imag"]
        return {
            "basis": shell_list,
            "structure_ref": structure_ref,
            "pure": pure_top,
            "n_ao": n_ao,
            "mo_metadata": mo_metadata,
            "mo_coefficients_alpha": Ca,
            "mo_coefficients_beta": Cb,
        }

    return None


def _multik_sequence(obj: Any, names: Sequence[str]) -> list[Any] | None:
    for name in names:
        value = getattr(obj, name, None)
        if isinstance(value, (list, tuple)) and len(value) > 0:
            return list(value)
    return None


def _single_or_multik_coefficients(obj: Any) -> list[Any] | None:
    coeffs = _multik_sequence(obj, ("mo_coeffs_k", "mo_coeffs"))
    if coeffs is not None:
        return coeffs
    value = getattr(obj, "mo_coeffs", None)
    if value is not None and not hasattr(obj, "mo_coeffs_alpha"):
        return [value]
    return None


def _single_or_multik_energies(obj: Any) -> list[Any] | None:
    energies = _multik_sequence(obj, ("mo_energies_k", "mo_energies"))
    if energies is not None:
        return energies
    value = getattr(obj, "mo_energies", None)
    if value is not None:
        return [value]
    return None


def _single_or_multik_occupations(
    obj: Any,
    coeffs: Sequence[Any],
    molecule: Any | None,
) -> list[Any] | None:
    occs = _multik_sequence(obj, ("occupations_k", "occupations"))
    if occs is not None:
        return occs
    value = getattr(obj, "occupations", None)
    if value is not None:
        return [value]
    if len(coeffs) == 1 and molecule is not None and hasattr(molecule, "n_electrons"):
        C = np.asarray(coeffs[0])
        if C.ndim != 2:
            return None
        n_mo = int(C.shape[1])
        n_doubly = int(molecule.n_electrons() // 2)
        occ = np.zeros(n_mo, dtype=np.float64)
        occ[: min(n_doubly, n_mo)] = 2.0
        return [occ]
    return None


def qvf_bloch_wf_data(
    result: Any,
    basis: Any,
    molecule: Any | None = None,
    *,
    k_points: Sequence[Sequence[float]],
    structure_ref: str = "structure",
    orbital_kind: str = "canonical",
) -> dict[str, Any] | None:
    """Package all-k closed-shell Bloch orbitals for QVF READ restarts.

    This is a vibe-qc vendor extension, not the visualization-oriented
    ``wavefunction.gto`` contract.  Coefficients are stored per k-point as
    MO-major arrays ``[n_mo, n_ao, 2]`` with the last axis equal to
    ``[real, imag]``.  Per-k members are deliberately ragged so restart
    archives remain valid when canonical orthogonalisation prunes a different
    number of near-null directions at different k-points.
    """
    basis_payload = _basis_shell_payload(basis)
    if basis_payload is None:
        return None
    shell_list, pure_top, n_ao = basis_payload

    coeffs = _single_or_multik_coefficients(result)
    occs = (
        _single_or_multik_occupations(result, coeffs, molecule)
        if coeffs is not None
        else None
    )
    if coeffs is None or occs is None:
        return None
    if len(coeffs) != len(occs):
        raise ValueError(
            "qvf_bloch_wf_data: coefficient and occupation k-block counts "
            f"differ ({len(coeffs)} vs {len(occs)})."
        )

    kpts = np.asarray(k_points, dtype=np.float64).reshape(-1, 3)
    if kpts.shape[0] != len(coeffs):
        raise ValueError(
            "qvf_bloch_wf_data: k-point metadata count does not match "
            f"coefficient blocks ({kpts.shape[0]} vs {len(coeffs)})."
        )

    energies = _single_or_multik_energies(result)
    if energies is not None and len(energies) != len(coeffs):
        raise ValueError(
            "qvf_bloch_wf_data: energy and coefficient k-block counts "
            f"differ ({len(energies)} vs {len(coeffs)})."
        )

    coeff_blocks: list[np.ndarray] = []
    occ_blocks: list[np.ndarray] = []
    energy_blocks: list[np.ndarray] = []
    block_meta: list[dict[str, Any]] = []
    for ik, (C_raw, occ_raw) in enumerate(zip(coeffs, occs)):
        C = np.asarray(C_raw, dtype=np.complex128)
        occ = np.asarray(occ_raw, dtype=np.float64)
        if C.ndim != 2:
            raise ValueError(
                f"qvf_bloch_wf_data: coefficient block {ik} must be 2-D; "
                f"got shape {C.shape}."
            )
        if C.shape[0] != n_ao:
            raise ValueError(
                f"qvf_bloch_wf_data: coefficient block {ik} has {C.shape[0]} "
                f"AO rows but the basis has {n_ao} functions."
            )
        if occ.ndim != 1:
            raise ValueError(
                f"qvf_bloch_wf_data: occupation block {ik} must be 1-D; "
                f"got shape {occ.shape}."
            )
        if C.shape[1] != occ.shape[0]:
            raise ValueError(
                f"qvf_bloch_wf_data: block {ik} has {C.shape[1]} orbitals "
                f"but {occ.shape[0]} occupations."
            )
        if energies is None:
            eps = np.zeros(C.shape[1], dtype=np.float64)
        else:
            eps = np.asarray(energies[ik], dtype=np.float64)
        if eps.ndim != 1 or eps.shape[0] != C.shape[1]:
            raise ValueError(
                f"qvf_bloch_wf_data: energy block {ik} must have shape "
                f"({C.shape[1]},); got {eps.shape}."
            )

        coeff_name = f"mo_coefficients_k{ik}"
        occ_name = f"occupations_k{ik}"
        energy_name = f"mo_energies_k{ik}"
        coeff_blocks.append(_complex_split_array(C.T))
        occ_blocks.append(np.ascontiguousarray(occ))
        energy_blocks.append(np.ascontiguousarray(eps))
        block_meta.append(
            {
                "k_index": ik,
                "k_point": [float(x) for x in kpts[ik]],
                "n_mo": int(C.shape[1]),
                "n_ao": int(n_ao),
                "mo_coefficients": coeff_name,
                "occupations": occ_name,
                "mo_energies": energy_name,
            }
        )

    mo_metadata: dict[str, Any] = {
        "schema_version": "1.0",
        "restart_kind": "closed_shell_bloch_kpoints",
        "spin": "restricted",
        "orbital_kind": orbital_kind,
        "n_kpoints": len(coeff_blocks),
        "n_ao": int(n_ao),
        "k_point_units": "fractional_reciprocal",
        "coefficient_encoding": "complex_split_last_axis",
        "coefficient_components": ["real", "imag"],
        "blocks": block_meta,
    }
    return {
        "basis": shell_list,
        "structure_ref": structure_ref,
        "pure": pure_top,
        "n_ao": n_ao,
        "kpoints": np.ascontiguousarray(kpts),
        "mo_metadata": mo_metadata,
        "mo_coefficients": coeff_blocks,
        "occupations": occ_blocks,
        "mo_energies": energy_blocks,
    }


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _sha256_hex(data: bytes) -> str:
    """Return the sha256 hex digest of ``data``."""
    return hashlib.sha256(data).hexdigest()


def _slug(label: str, *, fallback: str = "section") -> str:
    """Reduce a user-supplied label to a zip-path-safe slug.

    Keeps ASCII ``[A-Za-z0-9._-]``, replaces every other character with
    ``_``, collapses runs of ``_``, and trims leading/trailing ``_./-``.
    Returns ``fallback`` if the result is empty after trimming. Used
    for the label-derived components of zip paths (volume / orbital /
    spin / elf / difference) so that a label like
    ``"r(product) - r(reactant)"`` or a path-traversal attempt like
    ``"../etc/passwd"`` cannot reach the zip writer as-is.
    """
    if not label:
        return fallback
    _SAFE = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-")
    out_chars: list[str] = []
    prev_us = False
    for ch in label:
        if ch in _SAFE:
            out_chars.append(ch)
            prev_us = False
        elif not prev_us:
            out_chars.append("_")
            prev_us = True
    slug = "".join(out_chars).strip("._-")
    return slug or fallback


def _require_3d_volume(data: np.ndarray, kind: str, label: str) -> None:
    """Raise ValueError if ``data`` is not a 3-D array.

    The volume writers previously skipped malformed sections silently
    (``if vol.ndim != 3: continue``), which let bad caller-supplied
    data ship as a QVF missing the section. We'd rather fail the write
    than produce an archive that quietly drops user data.
    """
    if data.ndim != 3:
        raise ValueError(
            f"{kind}[{label!r}]: data must be 3-D (got ndim={data.ndim}, "
            f"shape={tuple(data.shape)})"
        )


def _binary_array_entry(
    path_in_zip: str,
    data: np.ndarray,
) -> tuple[bytes, dict[str, Any]]:
    """Serialize ``data`` as raw little-endian bytes and build the
    matching manifest member-entry dict.

    Returns ``(raw_bytes, member_spec)``.  The caller writes
    ``raw_bytes`` into the zip and embeds ``member_spec`` in the
    appropriate section's ``members`` dict.

    The member spec has keys ``path``, ``format`` ("binary"),
    ``sha256``, and optionally ``dtype``, ``shape``.
    """
    if not data.flags["C_CONTIGUOUS"]:
        data = np.ascontiguousarray(data)
    raw = data.tobytes()
    dtype_name = np.dtype(data.dtype).name
    member = {
        "path": path_in_zip,
        "format": "binary",
        "dtype": dtype_name,
        "shape": list(data.shape),
        "sha256": _sha256_hex(raw),
    }
    return raw, member


def _write_binary_to_zip(
    zf: zipfile.ZipFile,
    path_in_zip: str,
    data: np.ndarray,
) -> dict[str, Any]:
    """Convenience: serialise ``data``, write it into ``zf``, and return
    the manifest member-spec dict."""
    raw, member = _binary_array_entry(path_in_zip, data)
    zf.writestr(path_in_zip, raw)
    return member


def _now_iso() -> str:
    return _dt.datetime.now().astimezone().isoformat(timespec="seconds")


def _is_vendor_kind(kind: str) -> bool:
    return kind.startswith("x_")


# ---------------------------------------------------------------------------
# Main writer
# ---------------------------------------------------------------------------


def write_qvf(
    stem: os.PathLike | str,
    plan: OutputPlan,
    *,
    compression: int = zipfile.ZIP_DEFLATED,
    volume_dtype: str = "float32",
    atomic: bool = False,
    **context: Any,
) -> Path:
    """Write ``{stem}.qvf``.

    Parameters
    ----------
    stem
        Path stem; ``.qvf`` suffix is appended.
    plan
        :class:`OutputPlan` declaring what artefacts are expected.
    compression
        ``zipfile.ZIP_DEFLATED`` (default), ``zipfile.ZIP_STORED``, or
        the zstd constant if ``zipfile-zstd`` is importable.
    volume_dtype
        ``"float32"`` (default) or ``"float64"`` for volumetric grids.
    atomic
        When ``True``, write the archive to a temporary sibling file and
        ``os.replace`` it onto ``{stem}.qvf`` only after write-time
        validation passes. A concurrent reader then sees either the
        previous complete archive or the new complete archive -- never a
        half-written zip. Used by the live-checkpoint path so vibe-view
        can hot-reload a running job's QVF safely. Default ``False``
        preserves the historical in-place write.
    **context
        Data objects the section writers need.  Typical keys:

        * ``run_status`` -- ``"running"`` | ``"converged"`` | ``"failed"``;
          emitted at ``provenance.run_status``. Lets a live consumer tell
          a mid-run checkpoint from the settled final archive.
        * ``checkpoint`` -- dict with ``seq`` (monotonic int),
          ``wall_time_s`` (float), ``written_at`` (ISO-8601 str), and
          optional ``scf_iteration`` / ``energy_eh`` running hints;
          emitted at ``provenance.checkpoint``.
        * ``partial_sections`` -- ``True`` to mark every section
          ``partial: true``, or an iterable of section ids / ``kind``
          strings (e.g. ``{"trajectory"}``) to flag only the still-growing
          ones in a checkpoint snapshot.
        * ``biomolecule_data`` -- optional dict adding biomolecule
          metadata to the ``structure`` section for ribbon/cartoon
          rendering: ``chains`` (list[str]), ``residues`` (list of
          ``{name, seq, chain, atom_indices}``, 0-based atom indices), and
          ``secondary_structure`` (list of ``{type, chain, start_seq,
          end_seq}``). Additive peer keys on the section object; all three
          independently optional. See :func:`_normalize_biomolecule`.

        * ``molecule`` / ``system`` -- :class:`Molecule` or
          :class:`PeriodicSystem`
        * ``structure_lattice_bohr`` -- optional 3x3 lattice override for
          the ``structure`` section, using the same bohr column-vector
          convention as ``PeriodicSystem.lattice``. Periodic CCM callers use
          this to emit the full BvK torus cell while keeping the primitive
          input ``system`` intact.
        * ``result`` -- converged SCF result object
        * ``basis`` -- :class:`BasisSet`
        * ``population_summary`` -- :class:`PopulationSummary`
        * ``hessian_result`` -- :class:`HessianResult`
        * ``band_structure`` -- :class:`BandStructure`
        * ``trajectory_frames`` -- list of :class:`Molecule`
        * ``trajectory_energies`` -- list of float (Hartree)
        * ``trajectory_rms_grad`` -- list of float (optional)
        * ``bibtex_content`` -- str, the full BibTeX file body
        * ``bond_orders_data`` -- dict with keys ``method`` (str, e.g.
          ``"mayer"``) and ``pairs`` (list of dicts each with ``i``, ``j``,
          ``order`` and optional ``distance_ang``, ``symbol_i``, ``symbol_j``).
          Emitted as a ``bond_orders`` section.
        * ``volume_data`` -- dict of ``{label: (data_3d, origin, span)}``
        * ``mo_data`` -- list of dicts with keys ``label``, ``data``,
          ``origin``, ``span``, ``band_index``, ``energy_eh``, ``occupation``,
          ``spin``, ``component``
        * ``spin_data`` -- dict of ``{label: (data_3d, origin, span)}``
        * ``elf_data`` -- dict of ``{label: (data_3d, origin, span)}``
        * ``generic_volume_data`` -- dict of ``{label: (data_3d, origin,
          span)}`` for ``volume.generic`` (escape hatch for any scalar
          field that doesn't fit density/orbital/spin/elf/difference)
        * ``potential_data`` -- dict of ``{label: (data_3d, origin, span)}``
          for ``volume.potential`` (electrostatic potential grid; same
          member structure as ``volume.density``, QVF spec Sec. 4.10)
        * ``rdg_data`` -- dict of ``{label: (data_3d, origin, span)}``
          for ``volume.rdg`` (reduced density gradient for NCI analysis;
          same member structure, QVF spec Sec. 4.11)
        * ``diff_data`` -- dict of ``{label: spec}`` for difference
          density (e.g. r(product) - r(reactant)). ``spec`` is either
          a 3-tuple ``(data_3d, origin, span)`` for an unannotated
          difference, or a dict with keys ``data``, ``origin``, ``span``,
          and optionally ``operand_a`` (str, section id of minuend),
          ``operand_b`` (str, section id of subtrahend), ``description``.
        * ``reaction_path`` -- dict ``{frames, waypoints, energies?,
          reaction_coordinate?}`` for a self-contained ``reaction.path``
          section. ``waypoints`` is a list of
          ``{frame_index, label, kind, energy_eh?}`` records where
          ``kind`` is one of ``"reactant" | "transition_state" |
          "intermediate" | "product" | "point"``.
        * ``reaction_waypoints`` -- dict ``{trajectory_ref, waypoints,
          reaction_coordinate?}`` for a lightweight
          ``reaction.waypoints`` annotation over an already-emitted
          ``trajectory`` section. ``trajectory_ref`` must name a
          trajectory section emitted in the same archive; the writer
          raises if it doesn't resolve.
        * ``viewer_defaults`` -- dict written verbatim to the manifest
          root. Recognised keys: ``auto_open`` (list of section ids),
          per-section render hints, and ``bookmarks`` (ordered list of
          ``{name, camera}`` records using the VTK camera model).
        * ``thermochemistry_data`` -- dict with keys ``zpve_eh``,
          ``enthalpy_eh``, ``entropy_cal_mol_k``, ``gibbs_free_energy_eh``,
          ``temperature_k``, ``pressure_atm`` for a root
          ``thermochemistry`` field (QVF spec Sec. 4.7).
        * ``dipole_moment_data`` -- dict with keys ``total_debye``,
          ``vector_debye`` (3-vector), ``origin`` (str) for a root
          ``dipole_moment`` field (QVF spec Sec. 4.7).
        * ``constraints_data`` -- dict with keys ``frozen_atoms`` (list
          of int), ``distance_constraints`` (list of ``{atoms, target_angstrom}``)
          for a root ``constraints`` field (QVF spec Sec. 4.7).
        * ``extensions`` -- dict of ``{vendor_ns: {version, schema_uri?,
          critical?}}`` for the root ``extensions`` governance block
          (QVF spec Sec. 5.4).
        * ``vendor_json_sections`` -- list of first- or third-party vendor
          JSON sections. Each entry is ``{id, kind, payload}`` with optional
          ``member`` (default ``"data"``), ``label``, and ``critical``. The
          kind must live in the ``x_<vendor>.*`` namespace.
        * ``eos_data`` -- dict with keys ``volumes`` (float64 [n_points]),
          ``energies`` (float64 [n_points]), ``fit`` (dict with ``model``,
          ``V0``, ``E0``, ``B0``, ``B0_prime``, etc.) for an
          ``equation_of_state`` section (QVF spec Sec. 4.14).
        * ``fermi_surface_data`` -- dict with keys ``nk1``, ``nk2``, ``nk3``
          (int), ``energies`` (float64 [nk1, nk2, nk3, n_bands]),
          ``band_indices`` (list of int), ``lattice_vectors`` (3x3),
          ``fermi_energy_ev`` (float), and optional ``n_spin`` (int,
          default 1) for a ``fermi_surface`` section (QVF spec Sec. 4.12).
        * ``wf_data`` -- dict with keys ``basis`` (list of shell dicts),
          ``mo_metadata`` (dict), ``mo_coefficients`` (2D || `[n_mo, n_ao]`),
          and optionally ``mo_coefficients_alpha`` / ``mo_coefficients_beta``
          for unrestricted. Emitted as ``wavefunction.gto`` with id ``"wf"``.
        * ``bloch_wf_data`` -- dict from :func:`qvf_bloch_wf_data` carrying
          all-k closed-shell Bloch coefficients, occupations, and k-point
          metadata for QVF-backed periodic READ restarts. Emitted as the
          first-party vendor section ``x_vibeqc.bloch_wavefunction``.
        * ``wf_localized_data`` -- same shape as ``wf_data`` but emitted as a
          second ``wavefunction.gto`` section with id ``"wf_localized"``.
          ``mo_metadata["orbital_kind"]`` should be ``"localized"``.
        * ``wf_nto_hole_data`` -- same shape as ``wf_data``, emitted as
          ``wavefunction.gto`` with id ``"wf_nto_hole"`` and
          ``orbital_kind="natural"``.  The "hole" side of a single
          NTO pair (simplest post-TD-DFT path).
        * ``wf_nto_electron_data`` -- same shape as ``wf_data``, emitted as
          ``wavefunction.gto`` with id ``"wf_nto_electron"`` and
          ``orbital_kind="natural"``.  The "electron" side of a single
          NTO pair.
        * ``nto_data`` -- list of ``{"hole": wf_dict, "electron": wf_dict,
          "state_index": int, "excitation_energy_ev": float}`` records, one per
          excited state. Each emits two ``wavefunction.gto`` sections with
          ``orbital_kind="natural"`` and ids ``"wf_nto_S{n}_hole"`` /
          ``"wf_nto_S{n}_electron"``. Intended for Natural Transition Orbitals
          (post-TD-DFT).
        * ``ao_data`` -- list of dicts from :func:`qvf_ao_data`, each with
          keys ``label``, ``data`` (3-D array), ``origin``, ``span``,
          ``ao_metadata``, ``section_id``.  Emitted as ``basis.ao`` sections.
        * ``coop_data`` -- dict with keys ``energies`` (float64 [n_points]
          in eV), ``projections`` (float64 [n_pairs, n_points] or
          [n_spin, n_pairs, n_points]), ``integrated`` (float64 [n_pairs]),
          ``energies_units``, ``n_spin``, ``fermi_energy_ev``, ``sigma_ev``,
          ``pairs`` for a ``dos.coop`` section (QVF spec Sec. 4.8b).
        * ``cohp_data`` -- dict with keys ``energies``, ``projections``,
          ``integrated``, ``energies_units``, ``n_spin``, ``fermi_energy_ev``,
          ``sigma_ev``, ``pairs`` for a ``dos.cohp`` section
          (QVF spec Sec. 4.8c).

    Returns
    -------
    pathlib.Path
        The on-disk ``{stem}.qvf`` path.
    """
    stem = Path(os.fspath(stem))
    target = stem.with_suffix(".qvf")
    target.parent.mkdir(parents=True, exist_ok=True)

    # --- guardrails -----------------------------------------------------
    if not isinstance(plan, OutputPlan):
        raise TypeError(
            f"write_qvf: 'plan' must be an OutputPlan, got {type(plan).__name__}"
        )

    mol_or_sys = context.get("molecule") or context.get("system")

    # Volume size check.
    vol_data = context.get("volume_data")
    if vol_data:
        for label, (data, _o, _s) in vol_data.items():
            nv = int(np.prod(data.shape)) if hasattr(data, "shape") else 0
            if nv > _MAX_VOXELS:
                raise ValueError(
                    f"volume_data[{label!r}]: {nv:_d} voxels exceeds "
                    f"max {_MAX_VOXELS:_d}. Reduce grid or use a separate "
                    f".cube/.xsf file."
                )
    mo_data = context.get("mo_data")
    if mo_data:
        for mo in mo_data:
            data = mo.get("data")
            if data is not None and hasattr(data, "shape"):
                nv = int(np.prod(data.shape))
                if nv > _MAX_VOXELS:
                    raise ValueError(
                        f"mo_data[{mo.get('label', '?')!r}]: {nv:_d} voxels "
                        f"exceeds max {_MAX_VOXELS:_d}."
                    )

    # Warn (don't crash) on missing structure -- a QVF with no structure
    # section is valid but unusual.
    if mol_or_sys is None:
        import warnings

        warnings.warn(
            "write_qvf: no 'molecule' or 'system' in context -- the QVF "
            "will have no structure section. Pass molecule=<Mol> or "
            "system=<PeriodicSystem> for a complete archive.",
            UserWarning,
            stacklevel=2,
        )

    # Resolve compression.  Default deflate; try zstd if available.
    _compression = compression
    if compression == zipfile.ZIP_DEFLATED:
        try:
            from zipfile_zstd import ZIP_ZSTANDARD  # noqa: F811

            _compression = ZIP_ZSTANDARD
        except ImportError:
            pass

    volume_dt = np.dtype(volume_dtype)

    # --- build manifest skeleton ----------------------------------------
    _version = _resolve_version()
    manifest: dict[str, Any] = {
        "qvf_version": QVF_FORMAT_VERSION,
        "schema_uri": _SCHEMA_URI,
        "source": {
            "program": _PRODUCER_NAME,
            "version": _version,
            "calculation": (
                f"{context.get('method', '?')}/{context.get('basis', '?')}"
            ),
        },
        "sections": [],
    }
    sections: list[dict[str, Any]] = manifest["sections"]

    # --- provenance (manifest root) --------------------------------------
    manifest["provenance"] = _build_provenance(context)

    # --- viewer_defaults (manifest root, optional) -----------------------
    vd = context.get("viewer_defaults")
    if vd is not None:
        manifest["viewer_defaults"] = dict(vd)

    # --- thermochemistry (manifest root, optional) -----------------------
    thermo = context.get("thermochemistry_data")
    if thermo is not None:
        manifest["thermochemistry"] = dict(thermo)

    # --- dipole_moment (manifest root, optional) -------------------------
    dipole = context.get("dipole_moment_data")
    if dipole is not None:
        manifest["dipole_moment"] = dict(dipole)

    # --- constraints (manifest root, optional) ---------------------------
    constraints = context.get("constraints_data")
    if constraints is not None:
        manifest["constraints"] = dict(constraints)

    # --- extensions (manifest root, optional) ----------------------------
    ext_block = context.get("extensions")
    if ext_block is not None:
        manifest["extensions"] = dict(ext_block)

    # Atomic mode writes to a temp sibling on the same filesystem and
    # ``os.replace``s it onto ``target`` only after validation passes, so
    # a concurrent reader never observes a partially-written zip. The
    # historical (default) path writes ``target`` in place.
    if atomic:
        fd, _tmp_name = tempfile.mkstemp(
            dir=str(target.parent),
            prefix=f".{target.name}.",
            suffix=".tmp",
        )
        os.close(fd)
        write_target = Path(_tmp_name)
    else:
        write_target = target

    try:
        with zipfile.ZipFile(write_target, "w", _compression) as zf:
            _emit_qvf_into_zip(
                zf,
                manifest=manifest,
                sections=sections,
                context=context,
                mol_or_sys=mol_or_sys,
                volume_dt=volume_dt,
            )

        # --- write-time validation gate ---------------------------------
        # Validate the freshly-written archive against the canonical
        # schema before returning. This is the producer-side enforcement
        # of the SSOT: write_qvf() never returns a path to an invalid
        # archive. Skipping the gate would let writer regressions ship
        # to consumers -- the exact failure mode the SSOT work fixes.
        report = validate_qvf(write_target)
        if not report["valid"]:
            raise ValueError(
                f"write_qvf produced an archive that fails canonical "
                f"validation -- this is a writer bug. Errors:\n  - "
                + "\n  - ".join(report["errors"][:8])
            )
    except BaseException:
        # Wipe the bad/partial output so a caller cannot mistake a stale
        # invalid file for a successful write. In atomic mode this only
        # touches the temp file, leaving any prior ``target`` intact.
        try:
            write_target.unlink()
        except OSError:
            pass
        raise

    if atomic:
        # POSIX ``os.replace`` is atomic within a filesystem; the temp
        # file and target share ``target.parent`` so the rename cannot
        # cross a filesystem boundary.
        os.replace(write_target, target)
    return target


def _emit_qvf_into_zip(
    zf: zipfile.ZipFile,
    *,
    manifest: dict[str, Any],
    sections: list[dict[str, Any]],
    context: dict[str, Any],
    mol_or_sys: Any,
    volume_dt: np.dtype,
) -> None:
    """Write every QVF section into an open zipfile + finalize the manifest.

    Factored out of :func:`write_qvf` so the in-memory helper
    :func:`qvf_bytes` shares the same emission pipeline.
    """
    # --- structure ----------------------------------------------
    if mol_or_sys is not None:
        is_periodic = bool(
            context.get("system") is not None and context.get("molecule") is None
        )
        _write_structure_section(
            zf,
            mol_or_sys,
            sections,
            periodic=is_periodic,
            biomolecule_data=context.get("biomolecule_data"),
            structure_lattice_bohr=context.get("structure_lattice_bohr"),
        )

    # --- volume.density -----------------------------------------
    vol_data = context.get("volume_data")
    if vol_data:
        _write_volume_density_section(zf, vol_data, sections, volume_dtype=volume_dt)

    # --- volume.orbital -----------------------------------------
    mo_data = context.get("mo_data")
    if mo_data:
        _write_volume_orbital_section(zf, mo_data, sections, volume_dtype=volume_dt)

    # --- volume.spin --------------------------------------------
    spin_data = context.get("spin_data")
    if spin_data:
        _write_volume_spin_section(zf, spin_data, sections, volume_dtype=volume_dt)

    # --- volume.elf ---------------------------------------------
    elf_data = context.get("elf_data")
    if elf_data:
        _write_volume_elf_section(zf, elf_data, sections, volume_dtype=volume_dt)

    # --- volume.difference --------------------------------------
    diff_data = context.get("diff_data")
    if diff_data:
        _write_volume_difference_section(
            zf, diff_data, sections, volume_dtype=volume_dt
        )

    # --- volume.generic -----------------------------------------
    gen_vol_data = context.get("generic_volume_data")
    if gen_vol_data:
        _write_volume_generic_section(
            zf, gen_vol_data, sections, volume_dtype=volume_dt
        )

    # --- volume.potential --------------------------------------
    potential_data = context.get("potential_data")
    if potential_data:
        _write_volume_potential_section(
            zf, potential_data, sections, volume_dtype=volume_dt
        )

    # --- volume.rdg -------------------------------------------
    rdg_data = context.get("rdg_data")
    if rdg_data:
        _write_volume_rdg_section(zf, rdg_data, sections, volume_dtype=volume_dt)

    # --- wavefunction.gto (canonical) ---------------------------
    wf_data = context.get("wf_data")
    if wf_data:
        _write_wavefunction_gto_section(zf, wf_data, sections)

    # --- x_vibeqc.bloch_wavefunction (all-k READ restart) ------
    bloch_wf_data = context.get("bloch_wf_data")
    if bloch_wf_data:
        _write_bloch_wavefunction_section(zf, bloch_wf_data, sections)

    # --- wavefunction.gto (localized) ----------------------------
    wf_localized_data = context.get("wf_localized_data")
    if wf_localized_data:
        _write_wavefunction_gto_section(
            zf, wf_localized_data, sections, section_id="wf_localized"
        )

    # --- wavefunction.gto (NTO hole / electron pairs) --------------
    # Standalone keys: a single hole/electron pair (simplest path).
    # Emitted as sections "wf_nto_hole" and "wf_nto_electron".
    # The mo_metadata["orbital_kind"] should be "natural".
    wf_nto_hole_data = context.get("wf_nto_hole_data")
    if wf_nto_hole_data:
        wf_nto_hole_data.setdefault("mo_metadata", {})
        wf_nto_hole_data["mo_metadata"].setdefault("orbital_kind", "natural")
        _write_wavefunction_gto_section(
            zf, wf_nto_hole_data, sections, section_id="wf_nto_hole"
        )
    wf_nto_electron_data = context.get("wf_nto_electron_data")
    if wf_nto_electron_data:
        wf_nto_electron_data.setdefault("mo_metadata", {})
        wf_nto_electron_data["mo_metadata"].setdefault("orbital_kind", "natural")
        _write_wavefunction_gto_section(
            zf, wf_nto_electron_data, sections, section_id="wf_nto_electron"
        )

    # --- wavefunction.gto (NTO hole / electron pairs, multi-state) --
    # Each entry is {"hole": wf_data_dict, "electron": wf_data_dict,
    # "state_index": int, "excitation_energy_ev": float,
    # optional "spin": "alpha"|"beta"}.
    # Emitted as two sections per state: wf_nto_S{n}_hole and
    # wf_nto_S{n}_electron (or wf_nto_S{n}_{spin}_hole etc. for UHF).
    # The mo_metadata["orbital_kind"] should be "natural".
    nto_states = context.get("nto_data")
    if nto_states:
        for state in nto_states:
            si = state.get("state_index", 0)
            e_ev = state.get("excitation_energy_ev")
            _spin = state.get("spin", "")
            _spin_tag = f"_{_spin}" if _spin else ""
            for role in ("hole", "electron"):
                wf = state.get(role)
                if wf is None:
                    continue
                # Ensure orbital_kind is set.
                wf.setdefault("mo_metadata", {})
                wf["mo_metadata"].setdefault("orbital_kind", "natural")
                if e_ev is not None:
                    wf["mo_metadata"].setdefault("excitation_energy_ev", float(e_ev))
                    wf["mo_metadata"].setdefault("excitation_index", int(si))
                sec_id = f"wf_nto_S{si}{_spin_tag}_{role}"
                _write_wavefunction_gto_section(zf, wf, sections, section_id=sec_id)

    # --- basis.ao -----------------------------------------------
    ao_data = context.get("ao_data")
    if ao_data:
        _write_basis_ao_section(zf, ao_data, sections, volume_dtype=volume_dt)

    # --- atom_properties ----------------------------------------
    pop = context.get("population_summary")
    if pop is not None:
        _write_atom_properties_section(zf, pop, sections)

    # --- trajectory ---------------------------------------------
    traj_frames = context.get("trajectory_frames")
    if traj_frames:
        _write_trajectory_section(
            zf,
            traj_frames,
            sections,
            energies=context.get("trajectory_energies"),
            rms_grad=context.get("trajectory_rms_grad"),
            trajectory_type=context.get(
                "trajectory_type",
                "geometry_optimization",
            ),
        )

    # --- reaction.path (self-contained) -------------------------
    rxn_path = context.get("reaction_path")
    if rxn_path:
        # A reaction path whose frames are PeriodicSystem instances carries the
        # per-frame `lattice` member + `dim` metadata that vibe-view needs to
        # render the cell and wrap atoms across periodic boundaries. Both are
        # *optional* additions, so the archive stays qvf_version=1: a consumer
        # detects a periodic path from the presence of the `lattice` member,
        # never from the manifest version.
        #
        # vibe-qc v0.10.0-v0.15.x bumped to qvf_version=2 here. That bump was
        # withdrawn by the governance ruling of 2026-07-10 (an optional member
        # is the additive case and must not bump the version, and the bump
        # obliged conforming v1-only consumers to refuse archives we routinely
        # wrote). See qvf-writer/GOVERNANCE.md, "Version history".
        _write_reaction_path_section(
            zf,
            rxn_path["frames"],
            rxn_path["waypoints"],
            sections,
            energies=rxn_path.get("energies"),
            reaction_coordinate=rxn_path.get("reaction_coordinate"),
            reaction_coordinate_label=rxn_path.get("reaction_coordinate_label"),
            reaction_coordinate_unit=rxn_path.get("reaction_coordinate_unit"),
            frame_volumes=rxn_path.get("frame_volumes"),
            volume_grid=rxn_path.get("volume_grid"),
            volume_frame_index=rxn_path.get("volume_frame_index"),
            volume_label=rxn_path.get("volume_label", "Electron density"),
            volume_isovalue=rxn_path.get("volume_isovalue"),
        )

    # --- reaction.waypoints (annotation over a trajectory) ------
    rxn_wps = context.get("reaction_waypoints")
    if rxn_wps:
        traj_ref = rxn_wps["trajectory_ref"]
        traj_section = next(
            (s for s in sections if s.get("id") == traj_ref),
            None,
        )
        if traj_section is None or traj_section.get("kind") != "trajectory":
            raise ValueError(
                f"reaction.waypoints: trajectory_ref={traj_ref!r} does "
                "not name a trajectory section emitted in this archive. "
                "Producers MUST emit the referenced trajectory first."
            )
        n_traj_frames = int(traj_section["members"]["coords"]["shape"][0])
        _write_reaction_waypoints_section(
            zf,
            traj_ref,
            rxn_wps["waypoints"],
            n_traj_frames,
            sections,
            reaction_coordinate=rxn_wps.get("reaction_coordinate"),
        )

    # --- scan.surface (2D relaxed-scan energy grid) -------------
    scan_surf = context.get("scan_surface")
    if scan_surf:
        _write_scan_surface_section(
            zf,
            sections,
            axis_a=scan_surf["axis_a"],
            axis_b=scan_surf["axis_b"],
            energies=scan_surf["energies"],
            coordinate_a_label=scan_surf["coordinate_a_label"],
            coordinate_a_unit=scan_surf["coordinate_a_unit"],
            coordinate_b_label=scan_surf["coordinate_b_label"],
            coordinate_b_unit=scan_surf["coordinate_b_unit"],
            geometries=scan_surf.get("geometries"),
            atoms=scan_surf.get("atoms"),
        )

    # --- vibrations ---------------------------------------------
    hess = context.get("hessian_result")
    if hess is not None:
        mol = context.get("molecule")
        syms = None
        if mol is not None:
            syms = [_symbol(int(a.Z)) for a in mol.atoms]
        _write_vibrations_section(zf, hess, sections, atom_symbols=syms, molecule=mol)

    # --- spectra.ir ---------------------------------------------
    if hess is not None:
        _write_spectra_ir_section(zf, hess, sections)

    # --- bands --------------------------------------------------
    bs = context.get("band_structure")
    if bs is not None:
        _write_bands_section(zf, bs, sections)

    # --- citations ----------------------------------------------
    bib = context.get("bibtex_content")
    if bib:
        _write_citations_section(zf, bib, sections)

    # --- dos.total ----------------------------------------------
    dos_data = context.get("dos_data")
    if dos_data:
        _write_dos_total_section(zf, dos_data, sections)

    # --- dos.projected ------------------------------------------
    pdos_data = context.get("pdos_data")
    if pdos_data:
        _write_dos_projected_section(zf, pdos_data, sections)

    # --- dos.coop ----------------------------------------------
    coop_data = context.get("coop_data")
    if coop_data:
        _write_dos_coop_section(zf, coop_data, sections)

    # --- dos.cohp ----------------------------------------------
    cohp_data = context.get("cohp_data")
    if cohp_data:
        _write_dos_cohp_section(zf, cohp_data, sections)

    # --- spectra.raman ------------------------------------------
    raman = context.get("raman_data")
    if raman:
        _write_spectra_raman_section(zf, raman, sections)

    # --- spectra.uvvis ------------------------------------------
    uvvis = context.get("uvvis_data")
    if uvvis:
        _write_spectra_uvvis_section(zf, uvvis, sections)

    # --- spectra.ecd --------------------------------------------
    ecd = context.get("ecd_data")
    if ecd:
        _write_spectra_ecd_section(zf, ecd, sections)

    # --- spectra.vcd --------------------------------------------
    vcd = context.get("vcd_data")
    if vcd:
        _write_spectra_vcd_section(zf, vcd, sections)

    # --- spectra.nmr --------------------------------------------
    nmr = context.get("nmr_data")
    if nmr:
        _write_spectra_nmr_section(zf, nmr, sections)

    # --- spectra.epr --------------------------------------------
    epr = context.get("epr_data")
    if epr:
        _write_spectra_epr_section(zf, epr, sections)

    # --- spectra.generic ----------------------------------------
    generic_spec = context.get("generic_spectrum_data")
    if generic_spec:
        _write_spectra_generic_section(zf, generic_spec, sections)

    # --- structure.symmetry -------------------------------------
    sym = context.get("symmetry_data")
    if sym:
        _write_symmetry_section(zf, sym, sections)

    # --- bonds --------------------------------------------------
    bonds = context.get("bonds_data")
    if bonds:
        _write_bonds_section(zf, bonds, sections)

    # --- bond_orders ---------------------------------------------
    bo_data = context.get("bond_orders_data")
    if bo_data:
        _write_bond_orders_section(zf, bo_data["method"], bo_data["pairs"], sections)

    # --- scf_history --------------------------------------------
    scf_hist = context.get("scf_history_data")
    if scf_hist:
        _write_scf_history_section(zf, scf_hist, sections)

    # --- equation_of_state --------------------------------------
    eos_data = context.get("eos_data")
    if eos_data:
        _write_equation_of_state_section(zf, eos_data, sections)

    # --- fermi_surface -----------------------------------------
    fermi_data = context.get("fermi_surface_data")
    if fermi_data:
        _write_fermi_surface_section(zf, fermi_data, sections)

    # --- phonon_bands ------------------------------------------
    phonon_bands_data = context.get("phonon_bands_data")
    if phonon_bands_data:
        _write_phonon_bands_section(zf, phonon_bands_data, sections)

    # --- phonon_dos --------------------------------------------
    phonon_dos_data = context.get("phonon_dos_data")
    if phonon_dos_data:
        _write_phonon_dos_section(zf, phonon_dos_data, sections)

    # --- topology.qtaim -----------------------------------------
    qtaim = context.get("qtaim_data")
    if qtaim:
        _write_topology_qtaim_section(
            zf,
            qtaim["critical_points"],
            bond_paths=qtaim.get("bond_paths"),
            sections=sections,
        )

    # --- x_<vendor> JSON sections -----------------------------------
    vendor_json_sections = context.get("vendor_json_sections")
    if vendor_json_sections:
        _write_vendor_json_sections(zf, vendor_json_sections, sections)

    # --- partial-section marking (streaming checkpoints) --------
    # A checkpoint snapshot flags every still-growing section (e.g. an
    # optimization ``trajectory`` or an SCF-history section not yet at
    # its final point) with ``partial: true`` so a live consumer knows
    # not to treat it as final. Sections carry no ``additionalProperties:
    # false`` in the schema, so the extra key validates under v1.
    _mark_partial_sections(sections, context.get("partial_sections"))

    # --- manifest.json (ZIP_STORED, always last by convention) ---
    manifest_bytes = safe_json_bytes(manifest, indent=2)
    zf.writestr(
        zipfile.ZipInfo("manifest.json"),
        manifest_bytes,
        compress_type=zipfile.ZIP_STORED,
    )


def _mark_partial_sections(
    sections: list[dict[str, Any]],
    partial_sections: Any,
) -> None:
    """Set ``partial=True`` on emitted sections named by ``partial_sections``.

    ``partial_sections`` may be ``None`` (no-op), ``True`` (mark every
    section partial -- the whole snapshot is mid-flight), or an iterable of
    section ids and/or ``kind`` strings. Matching is by section ``id`` first,
    then ``kind``, so a caller can say ``{"trajectory"}`` to flag every
    trajectory section without knowing the generated ids.
    """
    if not partial_sections:
        return
    if partial_sections is True:
        for sec in sections:
            sec["partial"] = True
        return
    wanted = {str(x) for x in partial_sections}
    for sec in sections:
        if sec.get("id") in wanted or sec.get("kind") in wanted:
            sec["partial"] = True


def _write_vendor_json_sections(
    zf: zipfile.ZipFile,
    vendor_sections: Sequence[dict[str, Any]],
    sections: list[dict[str, Any]],
) -> None:
    """Write caller-supplied JSON sections in an ``x_<vendor>`` namespace."""
    for idx, spec in enumerate(vendor_sections):
        kind = str(spec.get("kind", ""))
        if not _is_vendor_kind(kind):
            raise ValueError(
                "vendor_json_sections entries must use an x_<vendor> kind; "
                f"got {kind!r}"
            )
        if "payload" not in spec:
            raise ValueError(
                "vendor_json_sections entries require a JSON 'payload' value"
            )

        section_id = str(spec.get("id") or f"vendor_json_{idx}")
        member_name = str(spec.get("member", "data"))
        section_slug = _slug(section_id, fallback=f"vendor_json_{idx}")
        member_slug = _slug(member_name, fallback="data")
        path_in_zip = str(
            spec.get("path") or f"vendor/{section_slug}/{member_slug}.json"
        )
        payload_json = safe_json_bytes(spec["payload"], indent=2)
        zf.writestr(path_in_zip, payload_json)

        section: dict[str, Any] = {
            "id": section_id,
            "kind": kind,
            "members": {
                member_name: {
                    "path": path_in_zip,
                    "format": "json",
                    "sha256": _sha256_hex(payload_json),
                },
            },
        }
        if "label" in spec:
            section["label"] = str(spec["label"])
        if "critical" in spec:
            section["critical"] = bool(spec["critical"])
        sections.append(section)


def qvf_bytes(
    plan: "OutputPlan",  # noqa: F821 -- same forward ref as write_qvf
    *,
    compression: int = zipfile.ZIP_DEFLATED,
    volume_dtype: str = "float32",
    **context: Any,
) -> bytes:
    """Build a QVF archive in memory and return the raw zip bytes.

    Mirrors :func:`write_qvf` -- same ``plan`` and ``**context`` surface,
    same emission pipeline, same canonical post-build validation gate
    -- but never touches the filesystem. Use this when vibe-qc wants to
    hand a freshly built QVF directly to ``vibe-view`` (see
    :func:`vibeview.launch_qvf`) without a temporary file.

    Returns
    -------
    bytes
        The complete .qvf archive bytes. ``QVFReader(<bytes>)`` opens
        them directly.

    Raises
    ------
    ValueError
        If the in-memory archive fails the SSOT validation gate. The
        bytes are *not* returned in that case -- same behaviour as
        :func:`write_qvf` unlinking the on-disk artefact.
    """
    if not isinstance(plan, OutputPlan):
        raise TypeError(
            f"qvf_bytes: 'plan' must be an OutputPlan, got {type(plan).__name__}"
        )

    mol_or_sys = context.get("molecule") or context.get("system")

    # Shared voxel size guard (single source = _MAX_VOXELS module const).
    vol_data = context.get("volume_data")
    if vol_data:
        for label, (data, _o, _s) in vol_data.items():
            nv = int(np.prod(data.shape)) if hasattr(data, "shape") else 0
            if nv > _MAX_VOXELS:
                raise ValueError(
                    f"volume_data[{label!r}]: {nv:_d} voxels exceeds "
                    f"max {_MAX_VOXELS:_d}."
                )
    mo_data = context.get("mo_data")
    if mo_data:
        for mo in mo_data:
            data = mo.get("data")
            if data is not None and hasattr(data, "shape"):
                nv = int(np.prod(data.shape))
                if nv > _MAX_VOXELS:
                    raise ValueError(
                        f"mo_data[{mo.get('label', '?')!r}]: {nv:_d} voxels "
                        f"exceeds max {_MAX_VOXELS:_d}."
                    )

    if mol_or_sys is None:
        import warnings

        warnings.warn(
            "qvf_bytes: no 'molecule' or 'system' in context -- the QVF "
            "will have no structure section.",
            UserWarning,
            stacklevel=2,
        )

    volume_dt = np.dtype(volume_dtype)
    _version = _resolve_version()
    manifest: dict[str, Any] = {
        "qvf_version": QVF_FORMAT_VERSION,
        "schema_uri": _SCHEMA_URI,
        "source": {
            "program": _PRODUCER_NAME,
            "version": _version,
            "calculation": (
                f"{context.get('method', '?')}/{context.get('basis', '?')}"
            ),
        },
        "sections": [],
    }
    sections: list[dict[str, Any]] = manifest["sections"]
    manifest["provenance"] = _build_provenance(context)
    vd = context.get("viewer_defaults")
    if vd is not None:
        manifest["viewer_defaults"] = dict(vd)

    buf = io.BytesIO()
    with zipfile.ZipFile(buf, "w", compression) as zf:
        _emit_qvf_into_zip(
            zf,
            manifest=manifest,
            sections=sections,
            context=context,
            mol_or_sys=mol_or_sys,
            volume_dt=volume_dt,
        )

    # SSOT validation gate -- re-open the in-memory archive read-only
    # and run the canonical validator. Same blast radius as write_qvf:
    # raise rather than return bad bytes.
    buf.seek(0)
    with zipfile.ZipFile(buf, "r") as zf_ro:
        report = validate_qvf(zf_ro)
    if not report["valid"]:
        raise ValueError(
            "qvf_bytes produced an archive that fails canonical "
            "validation -- this is a writer bug. Errors:\n  - "
            + "\n  - ".join(report["errors"][:8])
        )
    return buf.getvalue()


def _resolve_version() -> str:
    """Try to import vibe-qc's version string; fall back gracefully."""
    try:
        from ...banner import VIBEQC_VERSION

        return str(VIBEQC_VERSION)
    except Exception:
        return "0.0.0"


# ---------------------------------------------------------------------------
# Section writers -- each adds its entry to the ``sections`` list
# ---------------------------------------------------------------------------

# -- structure ------------------------------------------------------------


def _normalize_biomolecule(bio: Any) -> dict[str, Any]:
    """Coerce a ``biomolecule_data`` dict to plain JSON-safe values.

    Returns a dict with only the fields that were supplied — all three
    are independently optional. numpy ints/arrays are coerced to plain
    ``int``/``list`` so :func:`safe_json_bytes` and the write-time
    :func:`validate_qvf` gate stay clean. Malformed entries are dropped
    rather than raising, mirroring the fail-soft writer policy.

    Fields (peer keys on the ``structure`` section object, additive —
    no ``qvf_version`` bump, riding the open Section schema):

      * ``chains`` -- list[str] of chain ids, e.g. ``["A", "B"]``.
      * ``residues`` -- list of ``{name, seq, chain, atom_indices}``;
        ``atom_indices`` are 0-based into the structure payload atoms.
      * ``secondary_structure`` -- list of
        ``{type, chain, start_seq, end_seq}`` with ``type`` in
        ``{"helix", "sheet", "coil"}``.
    """
    if not isinstance(bio, dict):
        return {}
    out: dict[str, Any] = {}

    chains = bio.get("chains")
    if chains is not None:
        out["chains"] = [str(c) for c in chains]

    residues = bio.get("residues")
    if residues is not None:
        norm_res = []
        for r in residues:
            if not isinstance(r, dict):
                continue
            # Explicit None check, not ``or []`` — a numpy-array
            # atom_indices would raise "truth value ambiguous".
            ai = r.get("atom_indices")
            ai = [] if ai is None else list(ai)
            norm_res.append(
                {
                    "name": str(r.get("name", "")),
                    "seq": int(r.get("seq", 0)),
                    "chain": str(r.get("chain", "")),
                    "atom_indices": [int(i) for i in ai],
                }
            )
        out["residues"] = norm_res

    ss = bio.get("secondary_structure")
    if ss is not None:
        norm_ss = []
        for s in ss:
            if not isinstance(s, dict):
                continue
            norm_ss.append(
                {
                    "type": str(s.get("type", "coil")),
                    "chain": str(s.get("chain", "")),
                    "start_seq": int(s.get("start_seq", 0)),
                    "end_seq": int(s.get("end_seq", 0)),
                }
            )
        out["secondary_structure"] = norm_ss

    return out


def _write_structure_section(
    zf: zipfile.ZipFile,
    mol_or_sys: Any,
    sections: list[dict[str, Any]],
    *,
    periodic: bool = False,
    biomolecule_data: Any = None,
    structure_lattice_bohr: Any = None,
) -> None:
    """Write the ``structure`` section.

    Molecules: ``Molecule.atoms``. Periodic systems: the bound
    ``PeriodicSystem`` exposes ``unit_cell`` (not ``atoms``) plus
    ``dim`` in {1,2,3} and ``lattice`` (Cartesian column vectors in
    bohr). Tries ``.atoms`` first for back-compat with molecular
    callers and any test stubs that still use the old name.
    """
    # PeriodicSystem (the bound C++ type) carries atoms under
    # ``unit_cell``; Molecule carries them under ``atoms``. Pick the
    # populated one. Some stubs and the molecular path use ``atoms``;
    # the periodic path uses ``unit_cell``.
    atoms = list(getattr(mol_or_sys, "atoms", []) or [])
    if not atoms:
        atoms = list(getattr(mol_or_sys, "unit_cell", []) or [])
    if not atoms:
        import warnings

        warnings.warn(
            "_write_structure_section: molecule/system has no atoms "
            "(.atoms / .unit_cell both empty) -- skipping structure "
            "section.",
            UserWarning,
            stacklevel=2,
        )
        return

    # Build atoms array for JSON.
    atom_list = []
    for a in atoms:
        x, y, z = a.xyz
        atom_list.append(
            {
                "symbol": _symbol(int(a.Z)),
                "position": [
                    float(x) * _BOHR_TO_ANGSTROM,
                    float(y) * _BOHR_TO_ANGSTROM,
                    float(z) * _BOHR_TO_ANGSTROM,
                ],
                "atomic_number": int(a.Z),
            }
        )

    payload: dict[str, Any] = {
        "atoms": atom_list,
        "pbc": [False, False, False],
    }

    if periodic:
        lattice_source = (
            structure_lattice_bohr
            if structure_lattice_bohr is not None
            else mol_or_sys.lattice
        )
        L_bohr = np.asarray(lattice_source, dtype=np.float64)
        if L_bohr.shape != (3, 3):
            raise ValueError(
                "structure_lattice_bohr must be a 3x3 bohr column-vector lattice"
            )
        L_ang = (L_bohr * _BOHR_TO_ANGSTROM).T.tolist()  # row vectors for JSON
        # PeriodicSystem exposes ``dim`` in {1,2,3}; molecular stubs may
        # use ``dimensionality``. Default to fully periodic.
        dim = int(
            getattr(mol_or_sys, "dim", None)
            or getattr(mol_or_sys, "dimensionality", 3)
            or 3
        )
        dim = max(1, min(3, dim))
        payload["pbc"] = [i < dim for i in range(3)]
        payload["dimensionality"] = dim
        payload["lattice_vectors"] = [
            [float(L_ang[0][0]), float(L_ang[0][1]), float(L_ang[0][2])],
            [float(L_ang[1][0]), float(L_ang[1][1]), float(L_ang[1][2])],
            [float(L_ang[2][0]), float(L_ang[2][1]), float(L_ang[2][2])],
        ]

    struct_json = safe_json_bytes(payload, indent=2)
    path_in_zip = "structure/structure.json"
    zf.writestr(path_in_zip, struct_json)

    section: dict[str, Any] = {
        "id": "structure",
        "kind": "structure",
        "members": {
            "structure": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(struct_json),
            },
        },
    }
    # Biomolecule metadata (Ask B) — optional, additive peer keys on the
    # structure Section object (NOT inside structure.json), riding the open
    # Section schema like the checkpoint fields; no qvf_version bump.
    if biomolecule_data is not None:
        section.update(_normalize_biomolecule(biomolecule_data))
    sections.append(section)


# -- volume.density / volume.orbital helpers ------------------------------


def _grid_descriptor(
    data: np.ndarray,
    origin: np.ndarray,
    span: np.ndarray,
) -> dict[str, Any]:
    """Build the ``grid`` member dict for a volumetric section."""
    ox, oy, oz = float(origin[0]), float(origin[1]), float(origin[2])
    nx, ny, nz = data.shape
    grid: dict[str, Any] = {
        "origin": [ox, oy, oz],
        "voxel_vectors": [
            [float(span[0, 0]), float(span[0, 1]), float(span[0, 2])],
            [float(span[1, 0]), float(span[1, 1]), float(span[1, 2])],
            [float(span[2, 0]), float(span[2, 1]), float(span[2, 2])],
        ],
        "shape": [int(nx), int(ny), int(nz)],
    }
    return grid


def _write_volume_density_section(
    zf: zipfile.ZipFile,
    vol_data: dict[str, tuple],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``volume.density`` sections.

    ``vol_data`` is ``{label: (data_3d, origin_bohr, span_bohr)}``.
    Origin and span are in bohr; we write the grid descriptor in bohr.
    """
    for idx, (label, (data, origin, span)) in enumerate(vol_data.items()):
        vol = _real_array_for_artifact(
            data,
            dtype=volume_dtype,
            label=f"volume.density[{label!r}]",
        )
        _require_3d_volume(vol, "volume.density", label)
        section_id = f"vol_dens_{idx}"
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"volumes/{slug}.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)
        origin_arr = np.asarray(origin, dtype=np.float64)
        span_arr = np.asarray(span, dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"volumes/{slug}_grid.json"
        zf.writestr(grid_path, grid_json)
        section: dict[str, Any] = {
            "id": section_id,
            "kind": "volume.density",
            "label": label,
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        sections.append(section)


def _write_volume_orbital_section(
    zf: zipfile.ZipFile,
    mo_data: list[dict[str, Any]],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``volume.orbital`` sections.

    Each item in ``mo_data`` is a dict with keys: ``label``, ``data``
    (3D array), ``origin`` (3-vector, bohr), ``span`` (3x3, bohr),
    ``band_index`` (int), ``energy_eh`` (float), optional
    ``occupation`` (float, default 2.0 for restricted), ``spin``
    (int, default 0), ``component`` (str, "real" default).
    """
    for idx, mo in enumerate(mo_data):
        label = mo.get("label", f"MO_{idx}")
        vol = _real_array_for_artifact(
            mo["data"],
            dtype=volume_dtype,
            label=f"volume.orbital[{label!r}]",
        )
        _require_3d_volume(vol, "volume.orbital", label)
        section_id = f"vol_mo_{idx}"
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"volumes/{slug}.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)
        comp = mo.get("component", "real")
        origin_arr = np.asarray(mo["origin"], dtype=np.float64)
        span_arr = np.asarray(mo["span"], dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"volumes/{slug}_grid.json"
        zf.writestr(grid_path, grid_json)
        section: dict[str, Any] = {
            "id": section_id,
            "kind": "volume.orbital",
            "label": label,
            "component": comp,
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        sections.append(section)


# -- volume.spin -----------------------------------------------------------


def _write_volume_spin_section(
    zf: zipfile.ZipFile,
    spin_data: dict[str, tuple],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``volume.spin`` sections.

    ``spin_data`` is ``{label: (data_3d, origin_bohr, span_bohr)}``.
    Same shape as ``volume.density``, different kind string.
    """
    for idx, (label, (data, origin, span)) in enumerate(spin_data.items()):
        vol = _real_array_for_artifact(
            data,
            dtype=volume_dtype,
            label=f"volume.spin[{label!r}]",
        )
        _require_3d_volume(vol, "volume.spin", label)
        section_id = f"vol_spin_{idx}"
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"volumes/{slug}_spin.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)
        origin_arr = np.asarray(origin, dtype=np.float64)
        span_arr = np.asarray(span, dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"volumes/{slug}_spin_grid.json"
        zf.writestr(grid_path, grid_json)
        section: dict[str, Any] = {
            "id": section_id,
            "kind": "volume.spin",
            "label": label,
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        sections.append(section)


# -- volume.elf -------------------------------------------------------------


def _write_volume_elf_section(
    zf: zipfile.ZipFile,
    elf_data: dict[str, tuple],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``volume.elf`` sections (electron localisation function)."""
    for idx, (label, (data, origin, span)) in enumerate(elf_data.items()):
        vol = _real_array_for_artifact(
            data,
            dtype=volume_dtype,
            label=f"volume.elf[{label!r}]",
        )
        _require_3d_volume(vol, "volume.elf", label)
        section_id = f"vol_elf_{idx}"
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"volumes/{slug}_elf.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)
        origin_arr = np.asarray(origin, dtype=np.float64)
        span_arr = np.asarray(span, dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"volumes/{slug}_elf_grid.json"
        zf.writestr(grid_path, grid_json)
        section: dict[str, Any] = {
            "id": section_id,
            "kind": "volume.elf",
            "label": label,
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        sections.append(section)


# -- volume.generic -------------------------------------------------------


def _write_volume_generic_section(
    zf: zipfile.ZipFile,
    gen_data: dict[str, tuple],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``volume.generic`` sections.

    Escape hatch for any scalar field that doesn't fit one of the
    purpose-built kinds (density / orbital / spin / elf / difference).
    Producers should prefer a more specific kind when one applies; the
    viewer renders this with the same isosurface machinery but cannot
    apply kind-specific defaults (colormap, sign convention, etc.).

    ``gen_data`` is ``{label: (data_3d, origin_bohr, span_bohr)}`` --
    same shape as ``volume.density``.
    """
    for idx, (label, (data, origin, span)) in enumerate(gen_data.items()):
        vol = _real_array_for_artifact(
            data,
            dtype=volume_dtype,
            label=f"volume.generic[{label!r}]",
        )
        _require_3d_volume(vol, "volume.generic", label)
        section_id = f"vol_gen_{idx}"
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"volumes/{slug}_generic.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)
        origin_arr = np.asarray(origin, dtype=np.float64)
        span_arr = np.asarray(span, dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"volumes/{slug}_generic_grid.json"
        zf.writestr(grid_path, grid_json)
        section: dict[str, Any] = {
            "id": section_id,
            "kind": "volume.generic",
            "label": label,
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        sections.append(section)


# -- volume.difference ----------------------------------------------------


def _write_volume_difference_section(
    zf: zipfile.ZipFile,
    diff_data: dict[str, Any],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``volume.difference`` sections.

    ``diff_data`` is ``{label: spec}``, where ``spec`` is either:

    * ``(data_3d, origin_bohr, span_bohr)`` -- no operand metadata.
    * a dict with keys ``data``, ``origin``, ``span``, and optionally
      ``operand_a`` (str, section id of the minuend), ``operand_b``
      (str, section id of the subtrahend), ``description`` (str). If
      one operand is given the other is required (schema's
      ``dependentRequired``).

    Sign convention: ``data = r(operand_a) - r(operand_b)``.
    """
    for idx, (label, spec) in enumerate(diff_data.items()):
        if isinstance(spec, dict):
            data = spec["data"]
            origin = spec["origin"]
            span = spec["span"]
            operand_a = spec.get("operand_a")
            operand_b = spec.get("operand_b")
            description = spec.get("description")
        else:
            data, origin, span = spec
            operand_a = operand_b = description = None

        vol = _real_array_for_artifact(
            data,
            dtype=volume_dtype,
            label=f"volume.difference[{label!r}]",
        )
        _require_3d_volume(vol, "volume.difference", label)
        section_id = f"vol_diff_{idx}"
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"volumes/{slug}_diff.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)
        origin_arr = np.asarray(origin, dtype=np.float64)
        span_arr = np.asarray(span, dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"volumes/{slug}_diff_grid.json"
        zf.writestr(grid_path, grid_json)
        section: dict[str, Any] = {
            "id": section_id,
            "kind": "volume.difference",
            "label": label,
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        if operand_a is not None:
            section["operand_a"] = str(operand_a)
        if operand_b is not None:
            section["operand_b"] = str(operand_b)
        if description is not None:
            section["description"] = str(description)
        sections.append(section)


# -- volume.potential ----------------------------------------------------


def _write_volume_potential_section(
    zf: zipfile.ZipFile,
    potential_data: dict[str, tuple],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``volume.potential`` sections (QVF spec 4.10).

    Member structure is identical to ``volume.density``:
    ``{label: (data_3d, origin_bohr, span_bohr)}``.
    Origin and span are in bohr; grid descriptor in bohr.
    """
    for idx, (label, (data, origin, span)) in enumerate(potential_data.items()):
        vol = _real_array_for_artifact(
            data,
            dtype=volume_dtype,
            label=f"volume.potential[{label!r}]",
        )
        _require_3d_volume(vol, "volume.potential", label)
        section_id = f"vol_esp_{idx}"
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"volumes/{slug}.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)
        origin_arr = np.asarray(origin, dtype=np.float64)
        span_arr = np.asarray(span, dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"volumes/{slug}_grid.json"
        zf.writestr(grid_path, grid_json)
        section: dict[str, Any] = {
            "id": section_id,
            "kind": "volume.potential",
            "label": label,
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        sections.append(section)


# -- volume.rdg ----------------------------------------------------------


def _write_volume_rdg_section(
    zf: zipfile.ZipFile,
    rdg_data: dict[str, tuple],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``volume.rdg`` sections (QVF spec 4.11).

    Member structure is identical to ``volume.density``:
    ``{label: (data_3d, origin_bohr, span_bohr)}``.
    Data is dimensionless RDG = |grad rho| / (2*(3*pi^2)^(1/3) * rho^(4/3)).
    """
    for idx, (label, (data, origin, span)) in enumerate(rdg_data.items()):
        vol = _real_array_for_artifact(
            data,
            dtype=volume_dtype,
            label=f"volume.rdg[{label!r}]",
        )
        _require_3d_volume(vol, "volume.rdg", label)
        section_id = f"vol_rdg_{idx}"
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"volumes/{slug}.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)
        origin_arr = np.asarray(origin, dtype=np.float64)
        span_arr = np.asarray(span, dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"volumes/{slug}_grid.json"
        zf.writestr(grid_path, grid_json)
        section: dict[str, Any] = {
            "id": section_id,
            "kind": "volume.rdg",
            "label": label,
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        sections.append(section)


# -- wavefunction.gto -----------------------------------------------------


def _write_wavefunction_gto_section(
    zf: zipfile.ZipFile,
    wf_data: dict[str, Any],
    sections: list[dict[str, Any]],
    *,
    section_id: str = "wf",
) -> None:
    """Write the ``wavefunction.gto`` section.

    ``wf_data`` is a dict with keys:

    * ``basis`` -- list of shell dicts, each with ``center`` (int, 0-based),
      ``l`` (int), ``exponents`` (list of float), ``coefficients``
      (list of list of float, [n_prim, n_gen] for general contraction).
    * ``mo_metadata`` -- dict with ``spin`` ("restricted" or "unrestricted"),
      ``orbital_kind``, ``n_ao``, and either top-level ``energies``,
      ``occupations`` (restricted) or ``alpha``/``beta`` sub-dicts
      (unrestricted).
    * ``mo_coefficients`` -- 2D ||(`[n_mo, n_ao]`, float64) for restricted,
      or 3D ||(`[n_mo, n_ao, 2]`, float64) with last axis `[real, imag]`.
    * ``mo_coefficients_alpha``, ``mo_coefficients_beta`` -- for unrestricted.
    * ``structure_ref`` -- str, section id of the structure (default
      ``"structure"``).
    * ``pure`` -- bool, whether spherical harmonics are used (default True).

    ``section_id`` overrides the section id in the manifest (default ``"wf"``).
    Use ``"wf_localized"`` for a second wavefunction section carrying localized
    orbitals beside the canonical set.

    Covers molecular wavefunctions, Γ crystalline orbitals, and one selected
    complex Bloch k-point. The optional ``mo_metadata["k_point"]`` records the
    reciprocal fractional coordinate; complex coefficients are stored as
    real/imag component pairs.
    """
    # Derive a subdirectory from the section id so two wavefunction
    # sections (canonical + localized) don't collide on zip paths.
    subdir = "wavefunction" if section_id == "wf" else f"wavefunction_{section_id}"

    # --- basis JSON ----------------------------------------------------
    basis_shells = wf_data.get("basis", [])
    structure_ref = wf_data.get("structure_ref", "structure")
    pure = wf_data.get("pure", True)
    if "n_ao" in wf_data:
        n_ao = int(wf_data["n_ao"])
    else:
        n_ao = sum(
            (2 * sh["l"] + 1) if pure else ((sh["l"] + 1) * (sh["l"] + 2) // 2)
            for sh in basis_shells
        )

    basis_dict: dict[str, Any] = {
        "structure_ref": structure_ref,
        "pure": pure,
        "n_ao": n_ao,
        "shells": basis_shells,
    }
    basis_json = safe_json_bytes(basis_dict, indent=2)
    basis_path = f"{subdir}/basis.json"
    zf.writestr(basis_path, basis_json)

    # --- mo_metadata JSON -----------------------------------------------
    mo_meta = dict(wf_data.get("mo_metadata", {}))
    mo_meta.setdefault("n_ao", n_ao)
    mo_meta_json = safe_json_bytes(mo_meta, indent=2)
    mo_meta_path = f"{subdir}/mo_metadata.json"
    zf.writestr(mo_meta_path, mo_meta_json)

    # --- mo_coefficients binary -----------------------------------------
    members: dict[str, Any] = {
        "basis": {
            "path": basis_path,
            "format": "json",
            "sha256": _sha256_hex(basis_json),
        },
        "mo_metadata": {
            "path": mo_meta_path,
            "format": "json",
            "sha256": _sha256_hex(mo_meta_json),
        },
    }

    spin = mo_meta.get("spin", "restricted")
    if spin == "unrestricted":
        for tag in ("mo_coefficients_alpha", "mo_coefficients_beta"):
            coeff = wf_data.get(tag)
            if coeff is not None:
                arr, _encoding = _mo_coefficients_for_artifact(
                    coeff,
                    label=f"wavefunction.gto {tag}",
                )
                if arr.ndim in (2, 3):
                    path = f"{subdir}/{tag}.dat"
                    members[tag] = _write_binary_to_zip(zf, path, arr)
    else:
        coeff = wf_data.get("mo_coefficients")
        if coeff is not None:
            arr, _encoding = _mo_coefficients_for_artifact(
                coeff,
                label="wavefunction.gto mo_coefficients",
            )
            if arr.ndim in (2, 3):
                path = f"{subdir}/mo_coefficients.dat"
                members["mo_coefficients"] = _write_binary_to_zip(zf, path, arr)

    section: dict[str, Any] = {
        "id": section_id,
        "kind": "wavefunction.gto",
        "members": members,
    }
    sections.append(section)


def _write_bloch_wavefunction_section(
    zf: zipfile.ZipFile,
    wf_data: dict[str, Any],
    sections: list[dict[str, Any]],
    *,
    section_id: str = "wf_bloch_kpoints",
) -> None:
    """Write the vibe-qc all-k Bloch wavefunction restart section."""
    subdir = "wavefunction_bloch"

    basis_shells = wf_data.get("basis", [])
    structure_ref = wf_data.get("structure_ref", "structure")
    pure = wf_data.get("pure", True)
    n_ao = int(
        wf_data.get(
            "n_ao",
            sum(
                (2 * sh["l"] + 1)
                if bool(sh.get("pure", pure))
                else ((sh["l"] + 1) * (sh["l"] + 2) // 2)
                for sh in basis_shells
            ),
        )
    )
    basis_dict: dict[str, Any] = {
        "structure_ref": structure_ref,
        "pure": pure,
        "n_ao": n_ao,
        "shells": basis_shells,
    }
    basis_json = safe_json_bytes(basis_dict, indent=2)
    basis_path = f"{subdir}/basis.json"
    zf.writestr(basis_path, basis_json)

    mo_meta = dict(wf_data.get("mo_metadata", {}))
    mo_meta.setdefault("n_ao", n_ao)
    mo_meta_json = safe_json_bytes(mo_meta, indent=2)
    mo_meta_path = f"{subdir}/mo_metadata.json"
    zf.writestr(mo_meta_path, mo_meta_json)

    members: dict[str, Any] = {
        "basis": {
            "path": basis_path,
            "format": "json",
            "sha256": _sha256_hex(basis_json),
        },
        "mo_metadata": {
            "path": mo_meta_path,
            "format": "json",
            "sha256": _sha256_hex(mo_meta_json),
        },
    }

    if "kpoints" in wf_data:
        kpts = np.ascontiguousarray(np.asarray(wf_data["kpoints"], dtype=np.float64))
        members["kpoints"] = _write_binary_to_zip(
            zf,
            f"{subdir}/kpoints.dat",
            kpts,
        )

    coeff_blocks = list(wf_data.get("mo_coefficients", []))
    occ_blocks = list(wf_data.get("occupations", []))
    energy_blocks = list(wf_data.get("mo_energies", []))
    if not (len(coeff_blocks) == len(occ_blocks) == len(energy_blocks)):
        raise ValueError(
            "_write_bloch_wavefunction_section: coefficient, occupation, and "
            "energy block counts must match."
        )
    blocks = mo_meta.get("blocks", [])
    if len(blocks) != len(coeff_blocks):
        raise ValueError(
            "_write_bloch_wavefunction_section: metadata block count does not "
            f"match payload blocks ({len(blocks)} vs {len(coeff_blocks)})."
        )

    for ik, block in enumerate(blocks):
        coeff_name = str(block.get("mo_coefficients", f"mo_coefficients_k{ik}"))
        occ_name = str(block.get("occupations", f"occupations_k{ik}"))
        energy_name = str(block.get("mo_energies", f"mo_energies_k{ik}"))

        coeff = np.ascontiguousarray(
            np.asarray(coeff_blocks[ik], dtype=np.float64)
        )
        if coeff.ndim != 3 or coeff.shape[-1] != 2:
            raise ValueError(
                "_write_bloch_wavefunction_section: coefficient blocks must "
                f"have shape [n_mo, n_ao, 2]; block {ik} has {coeff.shape}."
            )
        members[coeff_name] = _write_binary_to_zip(
            zf,
            f"{subdir}/{coeff_name}.dat",
            coeff,
        )
        members[occ_name] = _write_binary_to_zip(
            zf,
            f"{subdir}/{occ_name}.dat",
            np.ascontiguousarray(np.asarray(occ_blocks[ik], dtype=np.float64)),
        )
        members[energy_name] = _write_binary_to_zip(
            zf,
            f"{subdir}/{energy_name}.dat",
            np.ascontiguousarray(np.asarray(energy_blocks[ik], dtype=np.float64)),
        )

    sections.append(
        {
            "id": section_id,
            "kind": QVF_BLOCH_WF_KIND,
            "members": members,
        }
    )


# -- spectra.raman ---------------------------------------------------------


def _write_spectra_raman_section(
    zf: zipfile.ZipFile,
    raman: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``spectra.raman`` section.

    ``raman`` is a dict with keys like ``frequencies_cm1``,
    ``intensities``, ``broadening``, ``units_x``, ``units_y``, etc.
    Mirrors the IR spectrum JSON format.
    """
    spec = dict(raman)
    spec.setdefault("kind", "spectra.raman")
    spec.setdefault("version", "1.0")
    # Consumer expects {frequencies, intensities}.
    if "frequencies" not in spec:
        spec["frequencies"] = spec.pop("frequencies_cm1", [])
    spec.pop("kind", None)
    spec.pop("version", None)
    spec.pop("units_x", None)
    spec.pop("units_y", None)
    spec.pop("label_x", None)
    spec.pop("label_y", None)
    spec.pop("broadening", None)
    spec_json = safe_json_bytes(spec, indent=2)
    path_in_zip = "spectra/raman.json"
    zf.writestr(path_in_zip, spec_json)
    section: dict[str, Any] = {
        "id": "raman_spec",
        "kind": "spectra.raman",
        "label": "Raman spectrum",
        "members": {
            "spectrum": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(spec_json),
            },
        },
    }
    sections.append(section)


# -- spectra.uvvis ---------------------------------------------------------


def _write_spectra_uvvis_section(
    zf: zipfile.ZipFile,
    uvvis_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``spectra.uvvis`` section.

    ``uvvis_data`` is a dict with keys:

    * ``energies_ev`` -- list of float, excitation energies (eV)
    * ``intensities`` -- list of float, oscillator strengths
    * optionally ``wavelength_nm``, ``broadening``, ``units_x``, ``units_y``
    """
    spec = dict(uvvis_data)
    spec.setdefault("kind", "spectra.uvvis")
    spec.setdefault("version", "1.0")
    # Consumer expects {frequencies, intensities} -- we store energies as
    # "frequencies" in eV (the consumer can convert to nm).
    if "frequencies" not in spec and "energies_ev" in spec:
        spec["frequencies"] = spec.pop("energies_ev")
    if "frequencies" not in spec and "wavelength_nm" in spec:
        import numpy as _np

        wl = _np.asarray(spec.pop("wavelength_nm"), dtype=float)
        # eV ≈ 1240 / l(nm)
        spec["frequencies"] = (1240.0 / wl).tolist()
    spec.pop("kind", None)
    spec.pop("version", None)
    spec.pop("units_x", None)
    spec.pop("units_y", None)
    spec.pop("broadening", None)
    spec_json = safe_json_bytes(spec, indent=2)
    path_in_zip = "spectra/uvvis.json"
    zf.writestr(path_in_zip, spec_json)
    section: dict[str, Any] = {
        "id": "uvvis_spec",
        "kind": "spectra.uvvis",
        "label": "UV-Vis spectrum",
        "members": {
            "spectrum": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(spec_json),
            },
        },
    }
    sections.append(section)


# -- spectra.ecd -----------------------------------------------------------


def _write_spectra_ecd_section(
    zf: zipfile.ZipFile,
    ecd: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``spectra.ecd`` section (electronic circular dichroism)."""
    spec = dict(ecd)
    spec.setdefault("kind", "spectra.ecd")
    spec.setdefault("version", "1.0")
    if "frequencies" not in spec and "energies_ev" in spec:
        spec["frequencies"] = spec.pop("energies_ev")
    spec.pop("kind", None)
    spec.pop("version", None)
    spec.pop("units_x", None)
    spec.pop("units_y", None)
    spec.pop("broadening", None)
    spec_json = safe_json_bytes(spec, indent=2)
    path_in_zip = "spectra/ecd.json"
    zf.writestr(path_in_zip, spec_json)
    section: dict[str, Any] = {
        "id": "ecd_spec",
        "kind": "spectra.ecd",
        "label": "ECD spectrum",
        "members": {
            "spectrum": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(spec_json),
            },
        },
    }
    sections.append(section)


# -- spectra.vcd -----------------------------------------------------------


def _write_spectra_vcd_section(
    zf: zipfile.ZipFile,
    vcd: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``spectra.vcd`` section (vibrational circular dichroism)."""
    spec = dict(vcd)
    spec.setdefault("kind", "spectra.vcd")
    spec.setdefault("version", "1.0")
    if "frequencies" not in spec and "frequencies_cm1" in spec:
        spec["frequencies"] = spec.pop("frequencies_cm1")
    spec.pop("kind", None)
    spec.pop("version", None)
    spec.pop("units_x", None)
    spec.pop("units_y", None)
    spec.pop("broadening", None)
    spec_json = safe_json_bytes(spec, indent=2)
    path_in_zip = "spectra/vcd.json"
    zf.writestr(path_in_zip, spec_json)
    section: dict[str, Any] = {
        "id": "vcd_spec",
        "kind": "spectra.vcd",
        "label": "VCD spectrum",
        "members": {
            "spectrum": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(spec_json),
            },
        },
    }
    sections.append(section)


# -- spectra.nmr -----------------------------------------------------------


def _write_spectra_nmr_section(
    zf: zipfile.ZipFile,
    nmr: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``spectra.nmr`` section.

    ``nmr`` is a dict with keys like ``chemical_shifts``, ``shielding_tensors``,
    ``j_couplings``, ``isotope``, ``reference``, ``solvent``.
    """
    spec = dict(nmr)
    spec.setdefault("kind", "spectra.nmr")
    spec.setdefault("version", "1.0")
    spec.pop("kind", None)
    spec.pop("version", None)
    spec_json = safe_json_bytes(spec, indent=2)
    path_in_zip = "spectra/nmr.json"
    zf.writestr(path_in_zip, spec_json)
    section: dict[str, Any] = {
        "id": "nmr_spec",
        "kind": "spectra.nmr",
        "label": "NMR spectrum",
        "members": {
            "spectrum": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(spec_json),
            },
        },
    }
    sections.append(section)


# -- spectra.epr -----------------------------------------------------------


def _write_spectra_epr_section(
    zf: zipfile.ZipFile,
    epr: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``spectra.epr`` section (QVF spec § 4.4).

    ``epr`` is a dict carrying EPR parameters, e.g. ``g_tensor`` (with
    ``matrix`` / ``isotropic`` / ``principal``), ``hyperfine`` (list of
    per-nucleus ``a_tensor_mhz`` / ``a_iso_mhz``), and
    ``zero_field_splitting`` (``d_mhz`` / ``e_mhz``). Payload shape is
    intentionally loose in v1, mirroring ``spectra.nmr``. vibe-qc does not
    yet compute EPR; this is a dormant writer (like the phonon / EOS writers)
    driven by the ``epr_data`` context key, present so an external producer's
    format and vibe-view's renderer share one contract.
    """
    spec = dict(epr)
    spec.pop("kind", None)
    spec.pop("version", None)
    spec_json = safe_json_bytes(spec, indent=2)
    path_in_zip = "spectra/epr.json"
    zf.writestr(path_in_zip, spec_json)
    section: dict[str, Any] = {
        "id": "epr_spec",
        "kind": "spectra.epr",
        "label": "EPR spectrum",
        "members": {
            "spectrum": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(spec_json),
            },
        },
    }
    sections.append(section)


# -- spectra.generic -------------------------------------------------------


def _write_spectra_generic_section(
    zf: zipfile.ZipFile,
    generic: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write a ``spectra.generic`` section.

    ``generic`` is a dict with at least ``frequencies`` and
    ``intensities``, plus any user-defined metadata keys.
    """
    spec = dict(generic)
    spec.setdefault("kind", "spectra.generic")
    spec.setdefault("version", "1.0")
    label = spec.pop("label", "Generic spectrum")
    spec_id = spec.pop("section_id", "gen_spec")
    spec.pop("kind", None)
    spec.pop("version", None)
    spec_json = safe_json_bytes(spec, indent=2)
    # Slug the caller-supplied section_id before it becomes a zip member
    # name -- like the volume labels above -- so a bidi/control char or a
    # path-traversal attempt cannot reach the archive path. See _slug.
    path_in_zip = f"spectra/{_slug(spec_id, fallback='gen_spec')}.json"
    zf.writestr(path_in_zip, spec_json)
    section: dict[str, Any] = {
        "id": spec_id,
        "kind": "spectra.generic",
        "label": label,
        "members": {
            "spectrum": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(spec_json),
            },
        },
    }
    sections.append(section)


# -- structure.symmetry ----------------------------------------------------


def _write_symmetry_section(
    zf: zipfile.ZipFile,
    sym: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``structure.symmetry`` section.

    ``sym`` is a dict with keys like ``space_group_number``,
    ``space_group_symbol``, ``point_group``, etc. (spglib output).
    """
    payload = dict(sym)
    payload.setdefault("kind", "structure.symmetry")
    payload.setdefault("version", "1.0")
    sym_json = safe_json_bytes(payload, indent=2)
    path_in_zip = "structure/symmetry.json"
    zf.writestr(path_in_zip, sym_json)
    section: dict[str, Any] = {
        "id": "sym0",
        "kind": "structure.symmetry",
        "members": {
            "data": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(sym_json),
            },
        },
    }
    sections.append(section)


# -- bonds ------------------------------------------------------------------


def _write_bonds_section(
    zf: zipfile.ZipFile,
    bonds: list[tuple[int, int, float]],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``bonds`` section.

    ``bonds`` is a list of ``(i, j, order)`` tuples. Emitted as one
    JSON member ``bonds`` carrying ``{"pairs": [{"i", "j", "order"}, ...]}``
    -- a small table that doesn't justify a binary blob, and that
    round-trips through every consumer without a custom struct format.
    """
    payload = {
        "pairs": [
            {"i": int(i), "j": int(j), "order": float(order)} for (i, j, order) in bonds
        ],
    }
    bonds_json = safe_json_bytes(payload)
    path_in_zip = "bonds/connectivity.json"
    zf.writestr(path_in_zip, bonds_json)
    section: dict[str, Any] = {
        "id": "bonds0",
        "kind": "bonds",
        "label": "Bond connectivity",
        "members": {
            "bonds": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(bonds_json),
            },
        },
    }
    sections.append(section)


# -- bond_orders ------------------------------------------------------------


def _write_bond_orders_section(
    zf: zipfile.ZipFile,
    method: str,
    pairs: list[dict[str, Any]],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``bond_orders`` section.

    ``method`` is the bond-order definition (``"mayer"``, ``"wiberg"``, etc.).
    ``pairs`` is a list of dicts each with ``i``, ``j``, ``order`` and
    optional ``distance_ang``, ``symbol_i``, ``symbol_j``.
    """
    payload: dict[str, Any] = {
        "method": method,
        "pairs": [{k: v for k, v in p.items()} for p in pairs],
    }
    bo_json = safe_json_bytes(payload, indent=2)
    path_in_zip = "bonds/orders.json"
    zf.writestr(path_in_zip, bo_json)
    section: dict[str, Any] = {
        "id": "bond_orders",
        "kind": "bond_orders",
        "members": {
            "bond_orders": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(bo_json),
            },
        },
    }
    sections.append(section)


# -- topology.qtaim ---------------------------------------------------------


def _write_topology_qtaim_section(
    zf: zipfile.ZipFile,
    critical_points: list[dict[str, Any]],
    *,
    bond_paths: list[dict[str, Any]] | None = None,
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``topology.qtaim`` section.

    ``critical_points`` is a list of dicts each with at minimum ``type``
    (``"bcp"``, ``"rcp"``, ``"ccp"``), ``position`` (3-vector in Angstrom),
    ``rho`` (e/Å³), and ``laplacian`` (e/Å⁵). Optional fields:
    ``ellipticity``, ``atom_pair`` (for BCPs).

    ``bond_paths`` is an optional list of ``{atoms: [i, j], path: [[x,y,z],...]}``
    records for gradient-path polylines.

    The compute module (critical-point search, Hessian evaluation,
    gradient-path tracing) is not yet in vibe-qc; the writer accepts
    pre-computed data so it is usable as soon as the compute side lands.
    """
    payload: dict[str, Any] = {"points": list(critical_points)}
    if bond_paths:
        payload["bond_paths"] = list(bond_paths)

    cp_json = safe_json_bytes(payload, indent=2)
    path_in_zip = "topology/critical_points.json"
    zf.writestr(path_in_zip, cp_json)
    section: dict[str, Any] = {
        "id": "qtaim",
        "kind": "topology.qtaim",
        "members": {
            "critical_points": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(cp_json),
            },
        },
    }
    sections.append(section)


# -- scf_history ------------------------------------------------------------


def _write_scf_history_section(
    zf: zipfile.ZipFile,
    history: list[dict[str, Any]],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``scf_history`` section.

    ``history`` is a list of per-iteration records with keys like
    ``iter``, ``energy_eh``, ``delta_e``, ``diis_error``. Emitted as a
    JSON document carrying ``{"iterations": [...]}`` (not JSONL -- the
    canonical schema declares this member's format as ``json`` and a
    JSONL payload isn't a valid JSON document).
    """
    payload = {"iterations": list(history)}
    hist_json = safe_json_bytes(payload)
    path_in_zip = "scf_history/iterations.json"
    zf.writestr(path_in_zip, hist_json)
    section: dict[str, Any] = {
        "id": "scf_hist0",
        "kind": "scf_history",
        "members": {
            "iterations": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(hist_json),
            },
        },
    }
    sections.append(section)


# -- atom_properties ------------------------------------------------------


def _write_atom_properties_section(
    zf: zipfile.ZipFile,
    pop: Any,
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``atom_properties`` section.

    ``pop`` is a :class:`PopulationSummary` with fields
    ``mulliken_atoms``, ``loewdin_atoms``, ``mayer_bonds``,
    ``dipole``, ``errors``.
    """
    section: dict[str, Any] = {
        "id": "props0",
        "kind": "atom_properties",
        "members": {},
    }

    if pop.mulliken_atoms:
        charges = np.array([row[3] for row in pop.mulliken_atoms], dtype=np.float64)
        member = _write_binary_to_zip(
            zf,
            "atom_properties/mulliken_charge.bin",
            charges,
        )
        section["members"]["mulliken_charge"] = member

    if pop.loewdin_atoms:
        charges = np.array([row[3] for row in pop.loewdin_atoms], dtype=np.float64)
        member = _write_binary_to_zip(
            zf,
            "atom_properties/loewdin_charge.bin",
            charges,
        )
        section["members"]["loewdin_charge"] = member

    if section["members"]:
        sections.append(section)


# -- trajectory -----------------------------------------------------------


def _write_trajectory_section(
    zf: zipfile.ZipFile,
    frames: Sequence[Any],
    sections: list[dict[str, Any]],
    *,
    energies: Optional[Sequence[float]] = None,
    rms_grad: Optional[Sequence[float]] = None,
    trajectory_type: str = "geometry_optimization",
) -> None:
    """Write the ``trajectory`` section."""
    n_steps = len(frames)
    # Metadata JSON
    first_atoms = list(frames[0].atoms)
    meta_atoms = [
        {
            "symbol": _symbol(int(a.Z)),
            "position": [0.0, 0.0, 0.0],
            "atomic_number": int(a.Z),
        }
        for a in first_atoms
    ]
    meta = {"atoms": meta_atoms}
    if energies is not None:
        meta["energies"] = [float(e) for e in energies]
    meta_json = safe_json_bytes(meta)
    meta_path = "trajectory/metadata.json"
    zf.writestr(meta_path, meta_json)

    # Coords binary: (n_steps, n_atoms, 3) float64 in Å.
    n_atoms_f = len(first_atoms)
    coords = np.zeros((n_steps, n_atoms_f, 3), dtype=np.float64)
    for i, mol in enumerate(frames):
        for j, a in enumerate(mol.atoms):
            coords[i, j, 0] = float(a.xyz[0]) * _BOHR_TO_ANGSTROM
            coords[i, j, 1] = float(a.xyz[1]) * _BOHR_TO_ANGSTROM
            coords[i, j, 2] = float(a.xyz[2]) * _BOHR_TO_ANGSTROM
    coords_member = _write_binary_to_zip(
        zf,
        "trajectory/coords.bin",
        coords,
    )

    section: dict[str, Any] = {
        "id": "traj0",
        "kind": "trajectory",
        "members": {
            "metadata": {
                "path": meta_path,
                "format": "json",
                "sha256": _sha256_hex(meta_json),
            },
            "coords": coords_member,
        },
    }
    sections.append(section)


# -- reaction.path / reaction.waypoints -----------------------------------


_WAYPOINT_KINDS = frozenset(
    {"reactant", "transition_state", "intermediate", "product", "point"}
)


def _validate_waypoints(
    waypoints: Sequence[dict[str, Any]],
    n_frames: int,
    *,
    context_label: str,
) -> list[dict[str, Any]]:
    """Sanity-check a waypoint list and return a normalised copy.

    Raises ``ValueError`` if a waypoint is missing required fields, the
    ``kind`` is outside the registry, or ``frame_index`` is out of
    ``[0, n_frames)``.
    """
    if not waypoints:
        raise ValueError(
            f"{context_label}: at least one waypoint is required "
            "(reaction.path / reaction.waypoints both need them)"
        )
    out: list[dict[str, Any]] = []
    for i, wp in enumerate(waypoints):
        if "frame_index" not in wp or "label" not in wp or "kind" not in wp:
            raise ValueError(
                f"{context_label}: waypoint #{i} must carry "
                "'frame_index', 'label', and 'kind'"
            )
        fi = int(wp["frame_index"])
        if not 0 <= fi < n_frames:
            raise ValueError(
                f"{context_label}: waypoint #{i} frame_index={fi} "
                f"is outside [0, {n_frames})"
            )
        kind = str(wp["kind"])
        if kind not in _WAYPOINT_KINDS:
            raise ValueError(
                f"{context_label}: waypoint #{i} kind={kind!r} is not "
                f"one of {sorted(_WAYPOINT_KINDS)}"
            )
        rec: dict[str, Any] = {
            "frame_index": fi,
            "label": str(wp["label"]),
            "kind": kind,
        }
        if "energy_eh" in wp:
            rec["energy_eh"] = float(wp["energy_eh"])
        out.append(rec)
    return out


def _frame_atoms(frame: Any) -> list[Any]:
    """Pick the populated atom collection on ``frame``.

    Molecule exposes atoms under ``.atoms``; PeriodicSystem under
    ``.unit_cell``. Mirrors the dispatch in the structure-section
    writer so reaction.path frames can be either type.
    """
    atoms = list(getattr(frame, "atoms", []) or [])
    if not atoms:
        atoms = list(getattr(frame, "unit_cell", []) or [])
    return atoms


def _frame_is_periodic(frame: Any) -> bool:
    """A reaction.path frame is periodic iff it carries a lattice."""
    return getattr(frame, "lattice", None) is not None and bool(
        list(getattr(frame, "unit_cell", []) or [])
    )


def _reaction_path_is_periodic(frames: Sequence[Any]) -> bool:
    """True iff every frame in ``frames`` is a periodic system.

    Raises ValueError on a mixed reaction path (molecular + periodic
    interleaved) -- that's malformed input, not a silent fall-through.
    """
    flags = [_frame_is_periodic(f) for f in frames]
    if all(flags):
        return True
    if not any(flags):
        return False
    raise ValueError(
        "reaction.path: mixed molecular/periodic frames are not "
        "supported -- every frame must be the same system type"
    )


def _write_reaction_path_section(
    zf: zipfile.ZipFile,
    frames: Sequence[Any],
    waypoints: Sequence[dict[str, Any]],
    sections: list[dict[str, Any]],
    *,
    energies: Optional[Sequence[float]] = None,
    reaction_coordinate: Optional[Sequence[float]] = None,
    reaction_coordinate_label: Optional[str] = None,
    reaction_coordinate_unit: Optional[str] = None,
    frame_volumes: Optional[np.ndarray] = None,
    volume_grid: Optional[dict[str, Any]] = None,
    volume_frame_index: Optional[Sequence[int]] = None,
    volume_label: str = "Electron density",
    volume_isovalue: Optional[float] = None,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write a self-contained ``reaction.path`` section.

    Binary layout matches ``trajectory`` (coords float64 [n_frames,
    n_atoms, 3] in Å) so the same readers decode coords. The waypoint
    annotations live in the JSON metadata member.

    Frames may be ``Molecule`` (atoms under ``.atoms``) or
    ``PeriodicSystem`` (atoms under ``.unit_cell``; lattice + dim
    available). Periodic reaction paths add a binary ``lattice``
    member (float64, columns = a, b, c, in bohr -- matching
    ``PeriodicSystem.lattice``) and a ``dim`` integer in the metadata
    JSON. A shared lattice across all frames is stored once as shape
    [3, 3]; per-frame lattices (forward-compat with variable-cell)
    are stored as shape [n_frames, 3, 3]. Mixed molecular/periodic
    reaction paths are rejected at write time.
    """
    n_frames = len(frames)
    if n_frames == 0:
        raise ValueError("reaction.path: frames is empty")

    is_periodic = _reaction_path_is_periodic(frames)

    norm_wps = _validate_waypoints(waypoints, n_frames, context_label="reaction.path")

    first_atoms = _frame_atoms(frames[0])
    if not first_atoms:
        raise ValueError("reaction.path: first frame has no atoms")
    meta_atoms = [
        {
            "symbol": _symbol(int(a.Z)),
            "position": [0.0, 0.0, 0.0],
            "atomic_number": int(a.Z),
        }
        for a in first_atoms
    ]
    meta: dict[str, Any] = {"atoms": meta_atoms, "waypoints": norm_wps}
    if energies is not None:
        meta["energies"] = [float(e) for e in energies]
    if reaction_coordinate is not None:
        meta["reaction_coordinate"] = [float(x) for x in reaction_coordinate]
    # Optional human-readable axis annotations for the energy plot.
    # Additive + optional: v1/v2 readers that don't know them ignore
    # them, so this needs no qvf_version bump.
    if reaction_coordinate_label is not None:
        meta["reaction_coordinate_label"] = str(reaction_coordinate_label)
    if reaction_coordinate_unit is not None:
        meta["reaction_coordinate_unit"] = str(reaction_coordinate_unit)

    # v2: pull lattice + dim off periodic frames before writing the
    # metadata JSON so its sha256 includes them.
    lattice_array: Optional[np.ndarray] = None
    if is_periodic:
        lattices = [np.asarray(f.lattice, dtype=np.float64) for f in frames]
        for L in lattices:
            if L.shape != (3, 3):
                raise ValueError(
                    "reaction.path: PeriodicSystem.lattice must be "
                    f"3x3; got shape {L.shape}"
                )
        all_equal = all(np.allclose(L, lattices[0]) for L in lattices[1:])
        if all_equal:
            lattice_array = lattices[0]
        else:
            lattice_array = np.stack(lattices, axis=0)
        dims = [int(getattr(f, "dim", 3) or 3) for f in frames]
        if len(set(dims)) == 1:
            meta["dim"] = dims[0]
        else:
            meta["dim_per_frame"] = dims

    # Optional per-frame volumetric data (W1). A 4D array
    # [n_emitted, nx, ny, nz] of scalar field values morphing along the
    # path, plus a shared grid descriptor and an index map saying which
    # path frame each emitted slab corresponds to (so callers can
    # decimate -- emit every Nth frame's cube without claiming a cube for
    # every frame). Validated here so the sha256 includes the metadata.
    vol_array: Optional[np.ndarray] = None
    if frame_volumes is not None:
        vol_array = _real_array_for_artifact(
            frame_volumes,
            dtype=volume_dtype,
            label="reaction.path frame_volumes",
        )
        if vol_array.ndim != 4:
            raise ValueError(
                "reaction.path: frame_volumes must be 4D "
                f"[n_emitted, nx, ny, nz]; got shape {vol_array.shape}"
            )
        if volume_grid is None:
            raise ValueError(
                "reaction.path: frame_volumes requires volume_grid "
                "(an {origin, voxel_vectors, shape} descriptor)"
            )
        n_emit = vol_array.shape[0]
        if volume_frame_index is None:
            vfi = list(range(n_emit))
        else:
            vfi = [int(i) for i in volume_frame_index]
        if len(vfi) != n_emit:
            raise ValueError(
                "reaction.path: volume_frame_index length "
                f"({len(vfi)}) must equal n_emitted ({n_emit})"
            )
        for i in vfi:
            if not 0 <= i < n_frames:
                raise ValueError(
                    f"reaction.path: volume_frame_index entry {i} out of "
                    f"range [0, {n_frames})"
                )
        grid_shape = list(int(x) for x in volume_grid["shape"])
        if list(vol_array.shape[1:]) != grid_shape:
            raise ValueError(
                "reaction.path: frame_volumes grid dims "
                f"{list(vol_array.shape[1:])} disagree with volume_grid "
                f"shape {grid_shape}"
            )
        meta["volume_frame_index"] = vfi
        meta["volume_label"] = str(volume_label)
        if volume_isovalue is not None:
            meta["volume_isovalue"] = float(volume_isovalue)

    meta_json = safe_json_bytes(meta)
    meta_path = "reaction/metadata.json"
    zf.writestr(meta_path, meta_json)

    n_atoms_f = len(first_atoms)
    coords = np.zeros((n_frames, n_atoms_f, 3), dtype=np.float64)
    for i, frame in enumerate(frames):
        frame_atoms = _frame_atoms(frame)
        if len(frame_atoms) != n_atoms_f:
            raise ValueError(
                f"reaction.path: frame {i} has {len(frame_atoms)} "
                f"atoms; expected {n_atoms_f} (matching frame 0)"
            )
        for j, a in enumerate(frame_atoms):
            coords[i, j, 0] = float(a.xyz[0]) * _BOHR_TO_ANGSTROM
            coords[i, j, 1] = float(a.xyz[1]) * _BOHR_TO_ANGSTROM
            coords[i, j, 2] = float(a.xyz[2]) * _BOHR_TO_ANGSTROM
    coords_member = _write_binary_to_zip(zf, "reaction/coords.bin", coords)

    members: dict[str, Any] = {
        "metadata": {
            "path": meta_path,
            "format": "json",
            "sha256": _sha256_hex(meta_json),
        },
        "coords": coords_member,
    }
    if lattice_array is not None:
        members["lattice"] = _write_binary_to_zip(
            zf, "reaction/lattice.bin", lattice_array
        )
    if vol_array is not None:
        assert volume_grid is not None  # guarded above
        members["frame_volumes"] = _write_binary_to_zip(
            zf, "reaction/frame_volumes.bin", vol_array
        )
        grid_json = safe_json_bytes(volume_grid)
        grid_path = "reaction/volume_grid.json"
        zf.writestr(grid_path, grid_json)
        members["volume_grid"] = {
            "path": grid_path,
            "format": "json",
            "sha256": _sha256_hex(grid_json),
        }

    section: dict[str, Any] = {
        "id": "rxn0",
        "kind": "reaction.path",
        "members": members,
    }
    sections.append(section)


def write_reaction_path_qvf(
    stem: "os.PathLike | str",
    *,
    frames: Sequence[Any],
    energies: Sequence[float],
    waypoints: Sequence[dict[str, Any]],
    reaction_coordinate: Optional[Sequence[float]] = None,
    reaction_coordinate_label: Optional[str] = None,
    reaction_coordinate_unit: Optional[str] = None,
    frame_volumes: Optional[np.ndarray] = None,
    volume_grid: Optional[dict[str, Any]] = None,
    volume_frame_index: Optional[Sequence[int]] = None,
    volume_label: str = "Electron density",
    volume_isovalue: Optional[float] = None,
    method: str,
    basis: str,
    functional: Optional[str] = None,
    extra_assemble_kwargs: Optional[dict[str, Any]] = None,
    compression: Optional[int] = None,
) -> Path:
    """High-level helper: emit a vibe-view reaction.path archive.

    Wraps :func:`write_qvf` for the common pattern shared by
    :meth:`vibeqc.NEBResult.write_qvf` and
    :meth:`vibeqc.ScanResult.write_qvf`: a structure section
    (reactant geometry -- first frame), a reaction.path section
    (frames + waypoints + energies + reaction coordinate), and a
    citations section (BibTeX assembled with the caller's flags).

    Periodic vs molecular dispatch is automatic: if
    ``frames[0]`` is a :class:`PeriodicSystem` the writer detects
    it (via ``_reaction_path_is_periodic``) and bumps the manifest
    to QVF v2 with the per-frame lattice + dim -- no extra knobs
    needed.

    Parameters
    ----------
    stem
        Path stem; ``.qvf`` is appended.
    frames
        Sequence of :class:`Molecule` or :class:`PeriodicSystem`,
        one per image. All frames must be the same type; mixed
        molecular/periodic raises in the lower-level writer.
    energies
        Per-frame energies in Hartree, ``len == len(frames)``.
    waypoints
        Iterable of
        ``{frame_index, label, kind, energy_eh?}`` dicts.
        ``kind`` is one of ``"reactant" | "transition_state" |
        "intermediate" | "product" | "point"``.
    reaction_coordinate
        Per-frame coordinate values; whatever the caller wants
        the x-axis of the energy plot to show -- arc length for
        NEB (normalised 0-1), bond length / angle for a relaxed
        scan, etc. ``None`` => the plot uses frame indices.
    reaction_coordinate_label, reaction_coordinate_unit
        Optional human-readable name + unit for the reaction
        coordinate (e.g. ``"O-H distance"`` / ``"bohr"``). The
        viewer labels the energy-plot x-axis ``"{label} ({unit})"``
        when present, else falls back to ``"Reaction coordinate"``.
        Additive optional metadata -- no ``qvf_version`` bump.
    method, basis, functional
        SCF flavour for the OutputPlan + citation routing.
        ``functional`` is None for HF methods.
    extra_assemble_kwargs
        Forwarded to ``CitationDatabase.assemble`` --
        e.g. ``{"uses_neb": True, "uses_ci_neb": True}`` for a
        climbing-image NEB run. Use this to fire driver-specific
        citation routes; the per-image SCF citations (method /
        basis / functional) fire automatically from the args
        above.
    compression
        Optional ``zipfile`` compression constant; ``None`` =>
        the writer's default.

    Returns
    -------
    pathlib.Path
        The on-disk ``{stem}.qvf`` path.
    """
    from ..citations.bibtex import format_bibtex
    from ..citations.registry import load_default_database
    from ..plan import OutputPlan as _OutputPlan

    frames_list = list(frames)
    if not frames_list:
        raise ValueError("write_reaction_path_qvf: frames is empty")

    is_periodic = _reaction_path_is_periodic(frames_list)
    first = frames_list[0]

    rc: Optional[list[float]] = None
    if reaction_coordinate is not None:
        rc = [float(v) for v in reaction_coordinate]
        if len(rc) != len(frames_list):
            raise ValueError(
                f"write_reaction_path_qvf: reaction_coordinate length "
                f"({len(rc)}) must equal n_frames ({len(frames_list)})"
            )

    energy_list = [float(e) for e in energies]
    if len(energy_list) != len(frames_list):
        raise ValueError(
            f"write_reaction_path_qvf: energies length "
            f"({len(energy_list)}) must equal n_frames "
            f"({len(frames_list)})"
        )

    # OutputPlan needs a string method/basis. Caller is responsible
    # for not passing None there -- citations + provenance get
    # garbled otherwise.
    plan = _OutputPlan.from_run_job_kwargs(
        output=stem,
        method=method,
        basis=basis,
        functional=functional,
        job_kind="periodic_scf" if is_periodic else "molecular_scf",
        output_qvf=True,
        citations=True,
        write_xyz=False,
        write_molden_file=False,
        write_population=False,
    )

    # Stub SCF result carrying the first frame's energy -- enough
    # for the structure-section writer; the reaction.path section
    # carries its own per-image energies.
    stub_result = type(
        "_ReactionPathStubResult",
        (),
        {
            "converged": True,
            "energy": energy_list[0],
        },
    )()

    # Assemble citations. The method / basis / functional routes
    # always fire; extra_assemble_kwargs fires driver-level routes
    # (e.g. uses_neb / uses_ci_neb).
    db = load_default_database()
    asm_kwargs: dict[str, Any] = dict(extra_assemble_kwargs or {})
    asm_kwargs.setdefault("method", method.lower())
    asm_kwargs.setdefault("basis", basis)
    if functional is not None:
        asm_kwargs.setdefault("functional", functional)
    asm_kwargs.setdefault("periodic", is_periodic)
    assembled = db.assemble(**asm_kwargs)
    bibtex_content = format_bibtex(assembled)

    context: dict[str, Any] = {
        "method": method,
        "basis": basis,
        "functional": functional,
        "result": stub_result,
        "reaction_path": {
            "frames": frames_list,
            "waypoints": list(waypoints),
            "energies": energy_list,
        },
        "bibtex_content": bibtex_content,
    }
    if rc is not None:
        context["reaction_path"]["reaction_coordinate"] = rc
    if reaction_coordinate_label is not None:
        context["reaction_path"]["reaction_coordinate_label"] = str(
            reaction_coordinate_label
        )
    if reaction_coordinate_unit is not None:
        context["reaction_path"]["reaction_coordinate_unit"] = str(
            reaction_coordinate_unit
        )
    if frame_volumes is not None:
        context["reaction_path"]["frame_volumes"] = frame_volumes
        context["reaction_path"]["volume_grid"] = volume_grid
        context["reaction_path"]["volume_frame_index"] = volume_frame_index
        context["reaction_path"]["volume_label"] = volume_label
        if volume_isovalue is not None:
            context["reaction_path"]["volume_isovalue"] = volume_isovalue
    if is_periodic:
        context["system"] = first
    else:
        context["molecule"] = first

    kwargs: dict[str, Any] = {}
    if compression is not None:
        kwargs["compression"] = compression
    return write_qvf(stem, plan, **context, **kwargs)


def _write_scan_surface_section(
    zf: zipfile.ZipFile,
    sections: list[dict[str, Any]],
    *,
    axis_a: Sequence[float],
    axis_b: Sequence[float],
    energies: np.ndarray,
    coordinate_a_label: str,
    coordinate_a_unit: str,
    coordinate_b_label: str,
    coordinate_b_unit: str,
    geometries: Optional[np.ndarray] = None,
    atoms: Optional[Sequence[dict[str, Any]]] = None,
) -> None:
    """Write a self-contained ``scan.surface`` section (2D energy grid).

    ``energies`` is ``[nA, nB]`` (Hartree); ``axis_a`` / ``axis_b`` are
    the 1D driven-coordinate values. Optional ``geometries`` is the
    relaxed structure at each node, flattened ``[nA*nB, n_atoms, 3]``
    in Å, row-major over ``(a, b)``.
    """
    e = np.asarray(energies, dtype=np.float64)
    if e.ndim != 2:
        raise ValueError(f"scan.surface: energies must be 2D; got {e.shape}")
    n_a, n_b = e.shape
    a_arr = np.asarray(list(axis_a), dtype=np.float64)
    b_arr = np.asarray(list(axis_b), dtype=np.float64)
    if a_arr.shape != (n_a,):
        raise ValueError(f"scan.surface: axis_a length {a_arr.shape} != nA={n_a}")
    if b_arr.shape != (n_b,):
        raise ValueError(f"scan.surface: axis_b length {b_arr.shape} != nB={n_b}")

    meta: dict[str, Any] = {
        "shape": [int(n_a), int(n_b)],
        "coordinate_a_label": str(coordinate_a_label),
        "coordinate_a_unit": str(coordinate_a_unit),
        "coordinate_b_label": str(coordinate_b_label),
        "coordinate_b_unit": str(coordinate_b_unit),
    }
    if geometries is not None and atoms is not None:
        meta["atoms"] = list(atoms)

    meta_json = safe_json_bytes(meta)
    meta_path = "scan_surface/metadata.json"
    zf.writestr(meta_path, meta_json)

    members: dict[str, Any] = {
        "metadata": {
            "path": meta_path,
            "format": "json",
            "sha256": _sha256_hex(meta_json),
        },
        "axis_a": _write_binary_to_zip(zf, "scan_surface/axis_a.bin", a_arr),
        "axis_b": _write_binary_to_zip(zf, "scan_surface/axis_b.bin", b_arr),
        "energies": _write_binary_to_zip(zf, "scan_surface/energies.bin", e),
    }
    if geometries is not None and atoms is not None:
        g = np.asarray(geometries, dtype=np.float64)
        if g.ndim != 3 or g.shape[0] != n_a * n_b:
            raise ValueError(
                "scan.surface: geometries must be "
                f"[nA*nB, n_atoms, 3]; got {g.shape} (nA*nB={n_a * n_b})"
            )
        members["geometries"] = _write_binary_to_zip(
            zf, "scan_surface/geometries.bin", g
        )

    sections.append({"id": "scan0", "kind": "scan.surface", "members": members})


def write_scan_surface_qvf(
    stem: "os.PathLike | str",
    *,
    axis_a: Sequence[float],
    axis_b: Sequence[float],
    energies: np.ndarray,
    coordinate_a_label: str,
    coordinate_a_unit: str,
    coordinate_b_label: str,
    coordinate_b_unit: str,
    structure: Any,
    geometries: Optional[np.ndarray] = None,
    method: str,
    basis: str,
    functional: Optional[str] = None,
    compression: Optional[int] = None,
) -> Path:
    """High-level helper: emit a vibe-view ``scan.surface`` archive for
    a 2D relaxed scan -- a structure section (the reference geometry), a
    scan.surface section (the energy grid + axes), and a citations
    section. Mirrors :func:`write_reaction_path_qvf`.
    """
    from ..citations.bibtex import format_bibtex
    from ..citations.registry import load_default_database
    from ..plan import OutputPlan as _OutputPlan

    is_periodic = _frame_is_periodic(structure)
    ref_mol = structure.unit_cell_molecule() if is_periodic else structure

    atoms_meta = None
    if geometries is not None:
        atoms_meta = [
            {"symbol": _symbol(int(a.Z)), "atomic_number": int(a.Z)}
            for a in _frame_atoms(structure)
        ]

    plan = _OutputPlan.from_run_job_kwargs(
        output=stem,
        method=method,
        basis=basis,
        functional=functional,
        job_kind="periodic_scf" if is_periodic else "molecular_scf",
        output_qvf=True,
        citations=True,
        write_xyz=False,
        write_molden_file=False,
        write_population=False,
    )
    e0 = float(np.asarray(energies, dtype=np.float64).flat[0])
    stub_result = type(
        "_ScanSurfaceStubResult",
        (),
        {"converged": True, "energy": e0},
    )()

    db = load_default_database()
    asm_kwargs: dict[str, Any] = {
        "method": method.lower(),
        "basis": basis,
        "periodic": is_periodic,
    }
    if functional is not None:
        asm_kwargs["functional"] = functional
    bibtex_content = format_bibtex(db.assemble(**asm_kwargs))

    context: dict[str, Any] = {
        "method": method,
        "basis": basis,
        "functional": functional,
        "result": stub_result,
        "bibtex_content": bibtex_content,
        "scan_surface": {
            "axis_a": list(axis_a),
            "axis_b": list(axis_b),
            "energies": energies,
            "coordinate_a_label": coordinate_a_label,
            "coordinate_a_unit": coordinate_a_unit,
            "coordinate_b_label": coordinate_b_label,
            "coordinate_b_unit": coordinate_b_unit,
            "geometries": geometries,
            "atoms": atoms_meta,
        },
    }
    if is_periodic:
        context["system"] = structure
    else:
        context["molecule"] = ref_mol

    kwargs: dict[str, Any] = {}
    if compression is not None:
        kwargs["compression"] = compression
    return write_qvf(stem, plan, **context, **kwargs)


def _write_reaction_waypoints_section(
    zf: zipfile.ZipFile,
    trajectory_ref: str,
    waypoints: Sequence[dict[str, Any]],
    n_trajectory_frames: int,
    sections: list[dict[str, Any]],
    *,
    reaction_coordinate: Optional[Sequence[float]] = None,
) -> None:
    """Write a ``reaction.waypoints`` section pointing at an existing
    ``trajectory`` section.

    No coords are emitted -- they live in the referenced trajectory.
    The producer is responsible for ensuring ``trajectory_ref`` names
    a section actually present in this archive; the validator checks
    that cross-reference at write time.
    """
    norm_wps = _validate_waypoints(
        waypoints,
        n_trajectory_frames,
        context_label=f"reaction.waypoints(trajectory_ref={trajectory_ref!r})",
    )
    payload: dict[str, Any] = {"waypoints": norm_wps}
    if reaction_coordinate is not None:
        if len(reaction_coordinate) != n_trajectory_frames:
            raise ValueError(
                "reaction.waypoints: reaction_coordinate length "
                f"({len(reaction_coordinate)}) must equal "
                f"n_trajectory_frames ({n_trajectory_frames})"
            )
        payload["reaction_coordinate"] = [float(x) for x in reaction_coordinate]
    wps_json = safe_json_bytes(payload)
    path_in_zip = "reaction/waypoints.json"
    zf.writestr(path_in_zip, wps_json)
    section: dict[str, Any] = {
        "id": "rxn_wp0",
        "kind": "reaction.waypoints",
        "trajectory_ref": str(trajectory_ref),
        "members": {
            "waypoints": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(wps_json),
            },
        },
    }
    sections.append(section)


# -- vibrations -----------------------------------------------------------


def _write_vibrations_section(
    zf: zipfile.ZipFile,
    hess: Any,
    sections: list[dict[str, Any]],
    *,
    atom_symbols: list[str] | None = None,
    molecule: Any | None = None,
) -> None:
    """Write the ``vibrations`` section.

    ``hess`` is a :class:`HessianResult` with ``frequencies_cm1``,
    ``normal_modes`` (and optionally ``masses_amu``).
    ``atom_symbols`` provides element symbols; ``molecule`` provides the
    equilibrium geometry + atomic numbers for the metadata atoms array.

    Two correctness fixes vs. the original implementation (audit findings
    A5-01/A5-02/A5-05):

    * The metadata atoms array now carries the **real equilibrium
      positions** (Å) and atomic numbers, not ``[0,0,0]`` / ``Z=0``.
      Without this the viewer animated every atom collapsed at the origin.
    * The displacements are now **un-mass-weighted Cartesian** patterns.
      ``HessianResult.normal_modes`` are orthonormal *mass-weighted*
      eigenvectors; the Cartesian motion of atom ``i`` is
      ``modes[3i:3i+3, k] / sqrt(M_i)``. Shipping the raw mass-weighted
      vector made heavy atoms over-move and light atoms under-move
      (a C-H stretch showed the C moving as much as the H). Each mode is
      then normalized to unit maximum atomic displacement so the viewer's
      amplitude slider gives a consistent, visible scale while preserving
      the (now correct) relative per-atom amplitudes.
    """
    freqs = np.asarray(hess.frequencies_cm1, dtype=np.float64)
    n_modes = freqs.shape[0]
    n_atoms = n_modes // 3
    modes = np.asarray(hess.normal_modes, dtype=np.float64)

    # Equilibrium geometry (bohr -> Å) + atomic numbers from the molecule.
    positions_ang: list[list[float]] | None = None
    atomic_numbers: list[int] | None = None
    if molecule is not None:
        try:
            positions_ang = [
                [
                    float(a.xyz[0]) * _BOHR_TO_ANGSTROM,
                    float(a.xyz[1]) * _BOHR_TO_ANGSTROM,
                    float(a.xyz[2]) * _BOHR_TO_ANGSTROM,
                ]
                for a in molecule.atoms
            ]
            atomic_numbers = [int(a.Z) for a in molecule.atoms]
        except Exception:  # noqa: BLE001 -- degrade to zeros if molecule API differs
            positions_ang = None
            atomic_numbers = None

    syms = atom_symbols or ["?"] * n_atoms
    atoms_list = []
    for a_idx in range(n_atoms):
        sym = syms[a_idx] if a_idx < len(syms) else "?"
        pos = (
            positions_ang[a_idx]
            if positions_ang is not None and a_idx < len(positions_ang)
            else [0.0, 0.0, 0.0]
        )
        z = (
            atomic_numbers[a_idx]
            if atomic_numbers is not None and a_idx < len(atomic_numbers)
            else 0
        )
        atoms_list.append({"symbol": sym, "position": pos, "atomic_number": z})
    meta = {
        "frequencies": [float(freqs[p]) for p in range(n_modes)],
        "atoms": atoms_list,
    }
    meta_json = safe_json_bytes(meta)
    meta_path = "vibrations/metadata.json"
    zf.writestr(meta_path, meta_json)

    # Un-mass-weighting factor 1/sqrt(M_i) per atom (electron-mass units, to
    # match HessianResult.normal_modes). Falls back to 1 when masses are
    # unavailable (e.g. minimal test stubs) so we never crash.
    masses_amu = getattr(hess, "masses_amu", None)
    inv_sqrt_m: np.ndarray | None = None
    if masses_amu is not None:
        m = np.asarray(masses_amu, dtype=np.float64)
        if m.shape == (n_atoms,) and np.all(m > 0):
            _AMU_TO_E = 1822.8884862
            inv_sqrt_m = 1.0 / np.sqrt(m * _AMU_TO_E)

    # Displacements (n_modes, n_atoms, 3): un-mass-weighted Cartesian
    # patterns, each mode normalized to unit max atomic displacement.
    disp = np.zeros((n_modes, n_atoms, 3), dtype=np.float64)
    for p in range(n_modes):
        block = modes[:, p].reshape(n_atoms, 3)
        if inv_sqrt_m is not None:
            block = block * inv_sqrt_m[:, None]
        max_norm = float(np.max(np.linalg.norm(block, axis=1))) if n_atoms else 0.0
        if max_norm > 1e-12:
            block = block / max_norm
        disp[p] = block

    disp_member = _write_binary_to_zip(
        zf,
        "vibrations/displacements.bin",
        disp,
    )

    section: dict[str, Any] = {
        "id": "vib0",
        "kind": "vibrations",
        "members": {
            "metadata": {
                "path": meta_path,
                "format": "json",
                "sha256": _sha256_hex(meta_json),
            },
            "displacements": disp_member,
        },
    }
    sections.append(section)


# -- spectra.ir -----------------------------------------------------------


def _write_spectra_ir_section(
    zf: zipfile.ZipFile,
    hess: Any,
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``spectra.ir`` section."""
    try:
        from ...hessian import ir_intensities

        intensities = np.asarray(ir_intensities(hess), dtype=np.float64)
    except Exception:
        return

    freqs = np.asarray(hess.frequencies_cm1, dtype=np.float64)
    # Only positive (real) frequencies -- skip imaginary and trans/rot.
    mask = freqs > 1.0
    spec = {
        "frequencies": freqs[mask].tolist(),
        "intensities": intensities[mask].tolist(),
    }
    spec_json = safe_json_bytes(spec, indent=2)
    path_in_zip = "spectra/ir.json"
    zf.writestr(path_in_zip, spec_json)
    section: dict[str, Any] = {
        "id": "ir_spec",
        "kind": "spectra.ir",
        "label": "IR spectrum",
        "members": {
            "spectrum": {
                "path": path_in_zip,
                "format": "json",
                "sha256": _sha256_hex(spec_json),
            },
        },
    }
    sections.append(section)


# -- basis.ao -------------------------------------------------------------


def _write_basis_ao_section(
    zf: zipfile.ZipFile,
    ao_data: list[dict[str, Any]],
    sections: list[dict[str, Any]],
    *,
    volume_dtype: np.dtype = np.dtype("float32"),
) -> None:
    """Write ``basis.ao`` sections -- one per atomic orbital.

    ``ao_data`` is a list of dicts from :func:`qvf_ao_data`.  Each
    dict has keys: ``label``, ``data`` (3-D array), ``origin`` (bohr),
    ``span`` (3x3 bohr), ``ao_metadata``, ``section_id``.

    The grid+data member layout is identical to ``volume.orbital``;
    the ``ao_metadata`` is embedded at the section root.
    """
    for idx, ao in enumerate(ao_data):
        label = ao.get("label", f"AO_{idx}")
        vol = _real_array_for_artifact(
            ao["data"],
            dtype=volume_dtype,
            label=f"basis.ao[{label!r}]",
        )
        _require_3d_volume(vol, "basis.ao", label)
        section_id = ao.get("section_id", f"ao_{idx}")
        slug = _slug(label, fallback=section_id)
        path_in_zip = f"basis_ao/{slug}.dat"
        file_member = _write_binary_to_zip(zf, path_in_zip, vol)

        origin_arr = np.asarray(ao["origin"], dtype=np.float64)
        span_arr = np.asarray(ao["span"], dtype=np.float64).reshape(3, 3)
        grid = _grid_descriptor(vol, origin_arr, span_arr)
        grid_json = safe_json_bytes(grid)
        grid_path = f"basis_ao/{slug}_grid.json"
        zf.writestr(grid_path, grid_json)

        meta = ao.get("ao_metadata", {})

        section: dict[str, Any] = {
            "id": section_id,
            "kind": "basis.ao",
            "label": label,
            "ao_metadata": {
                "atom_index": int(meta.get("atom_index", 0)),
                "atom_symbol": str(meta.get("atom_symbol", "?")),
                "shell_index": int(meta.get("shell_index", 0)),
                "primitive_index": int(meta.get("primitive_index", 0)),
                "angular_momentum": [
                    int(meta["angular_momentum"][0]),
                    int(meta["angular_momentum"][1]),
                ],
                "shell_type": str(meta.get("shell_type", "s")),
                "exponent": float(meta.get("exponent", 0.0)),
                "coefficient": float(meta.get("coefficient", 0.0)),
                "is_primitive": bool(meta.get("is_primitive", False)),
                "is_contracted": bool(meta.get("is_contracted", True)),
                "ao_index": int(meta.get("ao_index", 0)),
            },
            "members": {
                "data": file_member,
                "grid": {
                    "path": grid_path,
                    "format": "json",
                    "sha256": _sha256_hex(grid_json),
                },
            },
        }
        bl = meta.get("basis_label")
        if bl:
            section["ao_metadata"]["basis_label"] = str(bl)

        sections.append(section)


# -- bands ----------------------------------------------------------------


def _write_bands_section(
    zf: zipfile.ZipFile,
    bs: Any,
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``bands`` section.

    ``bs`` is a :class:`BandStructure` with ``kpath``, ``energies``
    (n_points, n_bands, Hartree), and optional ``e_fermi``,
    ``projections``, ``channels``.
    """
    kp = bs.kpath
    energies = np.asarray(bs.energies, dtype=np.float64)
    n_points, n_bands = energies.shape
    e_fermi_eh = bs.e_fermi if bs.e_fermi is not None else 0.0
    e_fermi_ev = float(e_fermi_eh) * _HARTREE_TO_EV

    # Eigenvalues as (1, n_k, n_bands) float64, eV.
    energies_ev = energies * _HARTREE_TO_EV
    data = energies_ev.reshape(1, n_points, n_bands)
    data_member = _write_binary_to_zip(
        zf,
        "bands/eigenvalues.bin",
        data,
    )

    # k-path JSON
    segs: list[dict[str, Any]] = []
    kpath_json_data: dict[str, Any] = {
        "kind": "bands",
        "version": "1.0",
        "n_kpoints": int(n_points),
        "n_bands": int(n_bands),
        "n_spin": 1,
        "fermi": float(e_fermi_ev),
        "fermi_energy_ev": float(e_fermi_ev),
        "reciprocal_space": True,
        "segments": [],
    }
    # Build segments from label pairs.
    labels = kp.labels if hasattr(kp, "labels") else []
    if len(labels) >= 2:
        for i in range(len(labels) - 1):
            d_start, name_start = labels[i]
            d_end, name_end = labels[i + 1]
            seg_mask = (kp.distances >= d_start) & (kp.distances <= d_end)
            n_pts = int(np.sum(seg_mask))
            n_pts = max(n_pts, 2)
            segs.append(
                {
                    "label_start": name_start,
                    "label_end": name_end,
                    "k_start": kp.kpoints_frac[
                        int(np.argmin(np.abs(kp.distances - d_start)))
                    ].tolist(),
                    "k_end": kp.kpoints_frac[
                        int(np.argmin(np.abs(kp.distances - d_end)))
                    ].tolist(),
                    "n_points": n_pts,
                }
            )
    kpath_json_data["segments"] = segs

    # --- band-character channels (fat bands) ------------------------------
    projections = getattr(bs, "projections", None)
    channels = getattr(bs, "channels", None)
    if projections is not None:
        proj = np.asarray(projections, dtype=np.float64)
        if proj.ndim != 3:
            raise ValueError(
                f"_write_bands_section: projections must be rank-3, got "
                f"ndim={proj.ndim}"
            )
        if proj.shape[0] != n_points or proj.shape[1] != n_bands:
            raise ValueError(
                f"_write_bands_section: projections shape {proj.shape} "
                f"mismatches eigenvalues ({n_points}, {n_bands})"
            )
        n_channels = proj.shape[2]
        if channels is not None and len(channels) != n_channels:
            raise ValueError(
                f"_write_bands_section: channels length {len(channels)} "
                f"mismatches projections n_channels={n_channels}"
            )
        # Write channels list into the kpath JSON.
        kpath_json_data["channels"] = [str(c) for c in channels] if channels else []

    kpath_json = safe_json_bytes(kpath_json_data, indent=2)
    kpath_path = "bands/kpath.json"
    zf.writestr(kpath_path, kpath_json)

    members: dict[str, Any] = {
        "kpath": {
            "path": kpath_path,
            "format": "json",
            "sha256": _sha256_hex(kpath_json),
        },
        "eigenvalues": data_member,
    }

    if projections is not None:
        proj_member = _write_binary_to_zip(zf, "bands/projections.bin", proj)
        members["projections"] = proj_member

    section: dict[str, Any] = {
        "id": "bands0",
        "kind": "bands",
        "members": members,
    }
    sections.append(section)


# -- citations ------------------------------------------------------------


def _write_citations_section(
    zf: zipfile.ZipFile,
    bibtex_content: str,
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``citations`` section (embedded BibTeX).

    BibTeX is utf-8 bytes; the manifest format is ``binary`` (not
    ``json``) and carries a sha256 like every other binary member.
    Consumers decode the bytes as utf-8 when they want the text.
    """
    bib_bytes = bibtex_content.encode("utf-8")
    path_in_zip = "citations/references.bib"
    zf.writestr(path_in_zip, bib_bytes)
    section: dict[str, Any] = {
        "id": "citations0",
        "kind": "citations",
        "members": {
            "references": {
                "path": path_in_zip,
                "format": "binary",
                "sha256": _sha256_hex(bib_bytes),
            },
        },
    }
    sections.append(section)


# -- dos.total --------------------------------------------------------------


def _write_dos_total_section(
    zf: zipfile.ZipFile,
    dos_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``dos.total`` section (QVF spec Sec.4.8).

    ``dos_data`` is a dict with keys:

    * ``energies`` -- float64 `[n_points]` in eV, Fermi = 0
    * ``dos`` -- float64 `[n_points]` (restricted) or `[2, n_points]`
      (spin-polarized), states / eV / cell
    * ``smearing`` -- float, broadening width in eV
    * ``smearing_type`` -- str, e.g. ``"gaussian"``
    * ``fermi_energy_ev`` -- float, absolute Fermi level in eV
    * ``n_electrons`` -- float, integrated electron count
    * ``n_spin`` -- int, 1 (restricted) or 2 (spin-polarized)
    """
    energies = np.asarray(dos_data["energies"], dtype=np.float64)
    dos_arr = np.asarray(dos_data["dos"], dtype=np.float64)
    n_spin = int(dos_data.get("n_spin", 1))

    if n_spin == 1:
        if dos_arr.ndim != 1:
            raise ValueError(
                f"dos.total: for n_spin=1, dos must be 1-D; got shape {dos_arr.shape}"
            )
        dos_shape = list(dos_arr.shape)
    else:
        if dos_arr.ndim != 2 or dos_arr.shape[0] != 2:
            raise ValueError(
                "dos.total: for n_spin=2, dos must be shape [2, n_points]; "
                f"got shape {dos_arr.shape}"
            )
        dos_shape = list(dos_arr.shape)

    # Binary payloads.
    energies_member = _write_binary_to_zip(
        zf,
        "dos/energies.bin",
        energies,
    )
    dos_member = _write_binary_to_zip(
        zf,
        "dos/total.bin",
        dos_arr,
    )

    # Metadata JSON (per-spec "role meta").
    smearing_ev = float(dos_data.get("smearing", 0.05))
    smearing_type = str(dos_data.get("smearing_type", "gaussian"))
    fermi_ev = float(dos_data.get("fermi_energy_ev", 0.0))
    n_elec = float(dos_data.get("n_electrons", 0.0))

    section: dict[str, Any] = {
        "id": "dos_total",
        "kind": "dos.total",
        "members": {
            "energies": energies_member,
            "dos": dos_member,
        },
        # Per the spec, metadata is embedded at the section level as
        # optional JSON keys (not as a separate member JSON).
        "smearing": smearing_ev,
        "smearing_type": smearing_type,
        "fermi_energy_ev": fermi_ev,
        "n_electrons": n_elec,
        "n_spin": n_spin,
    }
    sections.append(section)


# -- dos.projected ----------------------------------------------------------


def _write_dos_projected_section(
    zf: zipfile.ZipFile,
    pdos_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``dos.projected`` section (QVF spec Sec.4.9).

    ``pdos_data`` is a dict with keys:

    * ``energies`` -- float64 `[n_points]` in eV, Fermi = 0
    * ``projections`` -- float64 `[n_channels, n_points]` (restricted) or
      `[n_spin, n_channels, n_points]` (spin-polarized), states / eV / cell
    * ``energies_units`` -- str, ``"eV"``
    * ``n_spin`` -- int, 1 or 2
    * ``fermi_energy_ev`` -- float, absolute Fermi level in eV
    * ``channels`` -- list of dicts, each with ``atom_index``, ``symbol``,
      ``l``, ``label``
    """
    energies = np.asarray(pdos_data["energies"], dtype=np.float64)
    projections = np.asarray(pdos_data["projections"], dtype=np.float64)
    n_spin = int(pdos_data.get("n_spin", 1))

    if n_spin == 1:
        if projections.ndim != 2:
            raise ValueError(
                "dos.projected: for n_spin=1, projections must be "
                f"[n_channels, n_points]; got shape {projections.shape}"
            )
    else:
        if projections.ndim != 3 or projections.shape[0] != 2:
            raise ValueError(
                "dos.projected: for n_spin=2, projections must be "
                f"[2, n_channels, n_points]; got shape {projections.shape}"
            )

    # Binary payloads. Use a projected-specific energies path so a
    # dos.projected section can coexist with a dos.total section in the
    # same archive -- both used to write "dos/energies.bin", which
    # produced a duplicate zip member (the energy grid member path is
    # per-section, so the consumer resolves each independently).
    energies_member = _write_binary_to_zip(
        zf,
        "dos/projected_energies.bin",
        energies,
    )
    proj_member = _write_binary_to_zip(
        zf,
        "dos/projections.bin",
        projections,
    )

    # Channel metadata.
    channels: list[dict[str, Any]] = list(pdos_data.get("channels", []))
    energies_units = str(pdos_data.get("energies_units", "eV"))
    fermi_ev = float(pdos_data.get("fermi_energy_ev", 0.0))

    section: dict[str, Any] = {
        "id": "dos_pdos",
        "kind": "dos.projected",
        "members": {
            "energies": energies_member,
            "projections": proj_member,
        },
        # Metadata at section level per spec.  Note: energies_units is
        # a separate meta key to distinguish from dos.total's units layout.
        "energies_units": energies_units,
        "n_spin": n_spin,
        "fermi_energy_ev": fermi_ev,
        "channels": channels,
    }
    sections.append(section)


# -- dos.coop --------------------------------------------------------------


def _write_dos_coop_section(
    zf: zipfile.ZipFile,
    coop_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``dos.coop`` section (QVF spec Sec.4.8b).

    ``coop_data`` is a dict with keys:

    * ``energies`` -- float64 ``[n_points]`` in eV, Fermi = 0
    * ``projections`` -- float64 ``[n_pairs, n_points]`` (restricted) or
      ``[n_spin, n_pairs, n_points]`` (spin-polarized), COOP(E)
    * ``integrated`` -- float64 ``[n_pairs]``, ICOOP integrated to E_F
    * ``energies_units`` -- str, ``"eV"``
    * ``n_spin`` -- int, 1 or 2
    * ``fermi_energy_ev`` -- float, absolute Fermi level in eV
    * ``sigma_ev`` -- float, Gaussian broadening in eV
    * ``pairs`` -- list of dicts, each with ``i``, ``j``, ``symbol_i``,
      ``symbol_j``, ``distance_ang``
    """
    energies = np.asarray(coop_data["energies"], dtype=np.float64)
    projections = np.asarray(coop_data["projections"], dtype=np.float64)
    integrated = np.asarray(coop_data["integrated"], dtype=np.float64)
    n_spin = int(coop_data.get("n_spin", 1))

    if n_spin == 1:
        if projections.ndim != 2:
            raise ValueError(
                "dos.coop: for n_spin=1, projections must be "
                f"[n_pairs, n_points]; got shape {projections.shape}"
            )
        if integrated.ndim != 1:
            raise ValueError(
                "dos.coop: for n_spin=1, integrated must be "
                f"[n_pairs]; got shape {integrated.shape}"
            )
    else:
        if projections.ndim != 3 or projections.shape[0] != 2:
            raise ValueError(
                "dos.coop: for n_spin=2, projections must be "
                f"[2, n_pairs, n_points]; got shape {projections.shape}"
            )
        if integrated.ndim != 2 or integrated.shape[0] != 2:
            raise ValueError(
                "dos.coop: for n_spin=2, integrated must be "
                f"[2, n_pairs]; got shape {integrated.shape}"
            )

    energies_member = _write_binary_to_zip(zf, "dos/coop_energies.bin", energies)
    proj_member = _write_binary_to_zip(zf, "dos/coop_projections.bin", projections)
    integ_member = _write_binary_to_zip(zf, "dos/coop_integrated.bin", integrated)

    # Pair metadata as JSON
    pairs: list[dict[str, Any]] = list(coop_data.get("pairs", []))
    meta = {
        "method": "coop",
        "fermi_energy_ev": float(coop_data.get("fermi_energy_ev", 0.0)),
        "sigma_ev": float(coop_data.get("sigma_ev", 0.27)),
        "n_spin": n_spin,
        "pairs": pairs,
    }
    meta_json = safe_json_bytes(meta)
    meta_path = "dos/coop_meta.json"
    zf.writestr(meta_path, meta_json)

    section: dict[str, Any] = {
        "id": "coop0",
        "kind": "dos.coop",
        "members": {
            "energies": energies_member,
            "projections": proj_member,
            "integrated": integ_member,
            "meta": {
                "path": meta_path,
                "format": "json",
                "sha256": _sha256_hex(meta_json),
            },
        },
        "energies_units": str(coop_data.get("energies_units", "eV")),
        "n_spin": n_spin,
        "fermi_energy_ev": float(coop_data.get("fermi_energy_ev", 0.0)),
        "sigma_ev": float(coop_data.get("sigma_ev", 0.27)),
        "pairs": pairs,
    }
    sections.append(section)


# -- dos.cohp --------------------------------------------------------------


def _write_dos_cohp_section(
    zf: zipfile.ZipFile,
    cohp_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``dos.cohp`` section (QVF spec Sec.4.8c).

    ``cohp_data`` is a dict with keys:

    * ``energies`` -- float64 ``[n_points]`` in eV, Fermi = 0
    * ``projections`` -- float64 ``[n_pairs, n_points]`` (restricted) or
      ``[n_spin, n_pairs, n_points]`` (spin-polarized), -COHP(E) with
      bonding positive
    * ``integrated`` -- float64 ``[n_pairs]``, -ICOHP
    * ``energies_units`` -- str, ``"eV"``
    * ``n_spin`` -- int, 1 or 2
    * ``fermi_energy_ev`` -- float
    * ``sigma_ev`` -- float
    * ``pairs`` -- list of dicts
    """
    energies = np.asarray(cohp_data["energies"], dtype=np.float64)
    projections = np.asarray(cohp_data["projections"], dtype=np.float64)
    integrated = np.asarray(cohp_data["integrated"], dtype=np.float64)
    n_spin = int(cohp_data.get("n_spin", 1))

    if n_spin == 1:
        if projections.ndim != 2:
            raise ValueError(
                "dos.cohp: for n_spin=1, projections must be "
                f"[n_pairs, n_points]; got shape {projections.shape}"
            )
        if integrated.ndim != 1:
            raise ValueError(
                "dos.cohp: for n_spin=1, integrated must be "
                f"[n_pairs]; got shape {integrated.shape}"
            )
    else:
        if projections.ndim != 3 or projections.shape[0] != 2:
            raise ValueError(
                "dos.cohp: for n_spin=2, projections must be "
                f"[2, n_pairs, n_points]; got shape {projections.shape}"
            )
        if integrated.ndim != 2 or integrated.shape[0] != 2:
            raise ValueError(
                "dos.cohp: for n_spin=2, integrated must be "
                f"[2, n_pairs]; got shape {integrated.shape}"
            )

    energies_member = _write_binary_to_zip(zf, "dos/cohp_energies.bin", energies)
    proj_member = _write_binary_to_zip(zf, "dos/cohp_projections.bin", projections)
    integ_member = _write_binary_to_zip(zf, "dos/cohp_integrated.bin", integrated)

    pairs: list[dict[str, Any]] = list(cohp_data.get("pairs", []))
    meta = {
        "method": "cohp",
        "fermi_energy_ev": float(cohp_data.get("fermi_energy_ev", 0.0)),
        "sigma_ev": float(cohp_data.get("sigma_ev", 0.27)),
        "n_spin": n_spin,
        "pairs": pairs,
    }
    meta_json = safe_json_bytes(meta)
    meta_path = "dos/cohp_meta.json"
    zf.writestr(meta_path, meta_json)

    section: dict[str, Any] = {
        "id": "cohp0",
        "kind": "dos.cohp",
        "members": {
            "energies": energies_member,
            "projections": proj_member,
            "integrated": integ_member,
            "meta": {
                "path": meta_path,
                "format": "json",
                "sha256": _sha256_hex(meta_json),
            },
        },
        "energies_units": str(cohp_data.get("energies_units", "eV")),
        "n_spin": n_spin,
        "fermi_energy_ev": float(cohp_data.get("fermi_energy_ev", 0.0)),
        "sigma_ev": float(cohp_data.get("sigma_ev", 0.27)),
        "pairs": pairs,
    }
    sections.append(section)


# -- equation_of_state -----------------------------------------------------


def _write_equation_of_state_section(
    zf: zipfile.ZipFile,
    eos_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``equation_of_state`` section (QVF spec Sec. 4.14).

    ``eos_data`` is a dict with keys:

    * ``volumes`` -- float64 `[n_points]`, unit-cell volumes
    * ``energies`` -- float64 `[n_points]`, total energies
    * ``fit`` -- dict with ``model``, ``V0``, ``E0``, ``B0``, ``B0_prime``,
      ``energy_unit``, ``volume_unit``, ``pressure_unit``, ``residual_rms``,
      and optional ``pressures_gpa``.
    """
    volumes = np.asarray(eos_data["volumes"], dtype=np.float64)
    energies = np.asarray(eos_data["energies"], dtype=np.float64)
    if volumes.shape != energies.shape:
        raise ValueError(
            "equation_of_state: volumes and energies must have the same shape; "
            f"got {volumes.shape} vs {energies.shape}"
        )

    vols_member = _write_binary_to_zip(zf, "eos/volumes.bin", volumes)
    energies_member = _write_binary_to_zip(zf, "eos/energies.bin", energies)

    fit = dict(eos_data.get("fit", {}))
    fit_json = safe_json_bytes(fit)
    fit_path = "eos/fit.json"
    zf.writestr(fit_path, fit_json)

    section: dict[str, Any] = {
        "id": "eos",
        "kind": "equation_of_state",
        "members": {
            "volumes": vols_member,
            "energies": energies_member,
            "fit": {
                "path": fit_path,
                "format": "json",
                "sha256": _sha256_hex(fit_json),
            },
        },
    }
    sections.append(section)


# -- fermi_surface --------------------------------------------------------


def _write_fermi_surface_section(
    zf: zipfile.ZipFile,
    fermi_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``fermi_surface`` section (QVF spec Sec. 4.12).

    ``fermi_data`` is a dict with keys:

    * ``nk1``, ``nk2``, ``nk3`` -- int, Monkhorst-Pack mesh dimensions
    * ``energies`` -- float64 `[nk1, nk2, nk3, n_bands]`, signed distance
      from E_F in eV for bands near the Fermi level
    * ``fermi_energy_ev`` -- float, absolute Fermi level in eV
    * ``band_indices`` -- list of int, which bands are included
    * ``lattice_vectors`` -- 3x3 array, real-space lattice in Angstrom
    * ``n_spin`` -- int, 1 (default) or 2
    """
    nk1 = int(fermi_data["nk1"])
    nk2 = int(fermi_data["nk2"])
    nk3 = int(fermi_data["nk3"])
    energies = np.asarray(fermi_data["energies"], dtype=np.float64)
    if energies.ndim != 4:
        raise ValueError(
            "fermi_surface: energies must be 4-D [nk1, nk2, nk3, n_bands]; "
            f"got shape {energies.shape}"
        )
    fermi_ev = float(fermi_data.get("fermi_energy_ev", 0.0))
    band_indices = [int(b) for b in fermi_data.get("band_indices", [])]
    lattice = np.asarray(fermi_data["lattice_vectors"], dtype=np.float64)
    n_spin = int(fermi_data.get("n_spin", 1))

    # Mesh descriptor (JSON).
    mesh = {
        "nk1": nk1,
        "nk2": nk2,
        "nk3": nk3,
        "n_spin": n_spin,
        "fermi_energy_ev": fermi_ev,
        "band_indices": band_indices,
        "lattice_vectors": lattice.tolist(),
    }
    mesh_json = safe_json_bytes(mesh)
    mesh_path = "fermi/mesh.json"
    zf.writestr(mesh_path, mesh_json)

    # Binary payload.
    energies_member = _write_binary_to_zip(zf, "fermi/energies.bin", energies)

    section: dict[str, Any] = {
        "id": "fermi0",
        "kind": "fermi_surface",
        "members": {
            "mesh": {
                "path": mesh_path,
                "format": "json",
                "sha256": _sha256_hex(mesh_json),
            },
            "energies": energies_member,
        },
    }
    sections.append(section)


# -- phonon_bands ---------------------------------------------------------


def _write_phonon_bands_section(
    zf: zipfile.ZipFile,
    bands_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``phonon_bands`` section (QVF spec Sec. 4.13).

    ``bands_data`` is a dict with keys:

    * ``qpath`` -- dict with ``n_atoms``, ``n_modes``,
      ``has_eigenvectors`` (bool), ``segments`` (list of dicts with
      ``label_start``, ``label_end``, ``k_start``, ``k_end``, ``n_points``)
    * ``frequencies`` -- float64 `[n_qpts, n_modes]`, in cm^-1
    * ``eigenvectors`` -- optional float64 `[n_qpts, n_modes, n_atoms, 3]`
    """
    qpath = dict(bands_data["qpath"])
    qpath_json = safe_json_bytes(qpath)
    qpath_path = "phonons/qpath.json"
    zf.writestr(qpath_path, qpath_json)

    freq = np.asarray(bands_data["frequencies"], dtype=np.float64)
    freq_member = _write_binary_to_zip(zf, "phonons/frequencies.bin", freq)

    members: dict[str, Any] = {
        "qpath": {
            "path": qpath_path,
            "format": "json",
            "sha256": _sha256_hex(qpath_json),
        },
        "frequencies": freq_member,
    }

    eig = bands_data.get("eigenvectors")
    if eig is not None:
        eig_arr = np.asarray(eig, dtype=np.float64)
        members["eigenvectors"] = _write_binary_to_zip(
            zf, "phonons/eigenvectors.bin", eig_arr
        )

    section: dict[str, Any] = {
        "id": "phonon_bands",
        "kind": "phonon_bands",
        "members": members,
    }
    sections.append(section)


# -- phonon_dos -----------------------------------------------------------


def _write_phonon_dos_section(
    zf: zipfile.ZipFile,
    dos_data: dict[str, Any],
    sections: list[dict[str, Any]],
) -> None:
    """Write the ``phonon_dos`` section (QVF spec Sec. 4.13).

    ``dos_data`` is a dict with keys:

    * ``frequencies`` -- float64 `[n_points]`, frequency grid in cm^-1
    * ``dos`` -- float64 `[n_points]`, phonon DOS in states / cm^-1
    * ``meta`` -- optional dict with ``smearing``, ``smearing_type``,
      ``n_atoms``, ``n_modes``
    * ``projected`` -- optional float64 `[n_atoms, n_points]`
    """
    freq = np.asarray(dos_data["frequencies"], dtype=np.float64)
    dos_arr = np.asarray(dos_data["dos"], dtype=np.float64)
    if freq.shape != dos_arr.shape:
        raise ValueError(
            "phonon_dos: frequencies and dos must have the same shape; "
            f"got {freq.shape} vs {dos_arr.shape}"
        )

    meta = dict(dos_data.get("meta", {}))
    meta_json = safe_json_bytes(meta)
    meta_path = "phonons/dos_meta.json"
    zf.writestr(meta_path, meta_json)

    freq_member = _write_binary_to_zip(zf, "phonons/dos_freq.bin", freq)
    dos_member = _write_binary_to_zip(zf, "phonons/dos_total.bin", dos_arr)

    members: dict[str, Any] = {
        "meta": {
            "path": meta_path,
            "format": "json",
            "sha256": _sha256_hex(meta_json),
        },
        "frequencies": freq_member,
        "dos": dos_member,
    }

    proj = dos_data.get("projected")
    if proj is not None:
        proj_arr = np.asarray(proj, dtype=np.float64)
        members["projected"] = _write_binary_to_zip(
            zf, "phonons/dos_projected.bin", proj_arr
        )

    section: dict[str, Any] = {
        "id": "phonon_dos",
        "kind": "phonon_dos",
        "members": members,
    }
    sections.append(section)


# -- scf history extraction -----------------------------------------------


def scf_history_from_result(result: Any) -> list[dict[str, Any]] | None:
    """Extract a QVF ``scf_history`` payload from an SCF result object.

    Reads ``result.scf_trace`` -- the canonical per-iteration record both
    the molecular (C++ ``SolverResult``) and periodic (e.g.
    ``GpwScfResult``) solvers carry. Molecular trace steps are objects
    with ``.iter`` / ``.energy`` / ``.delta_e`` attributes; periodic
    steps are dicts with the same keys. Returns a list of records shaped
    for :func:`_write_scf_history_section` (``iter`` / ``energy_eh`` /
    ``delta_e``), or ``None`` when no per-iteration trace is available.

    ``delta_e`` is omitted for the first iteration, mirroring the SCF
    log / perf-tracker convention (no meaningful energy delta exists
    before the second cycle).
    """
    trace = getattr(result, "scf_trace", None)
    if not trace:
        return None
    history: list[dict[str, Any]] = []
    for step in trace:
        if isinstance(step, dict):
            it = step.get("iter")
            energy = step.get("energy")
            delta_e = step.get("delta_e")
        else:
            it = getattr(step, "iter", None)
            energy = getattr(step, "energy", None)
            delta_e = getattr(step, "delta_e", None)
        if it is None or energy is None:
            continue
        it = int(it)
        record: dict[str, Any] = {"iter": it, "energy_eh": float(energy)}
        if delta_e is not None and it > 1:
            record["delta_e"] = float(delta_e)
        history.append(record)
    return history or None


# -- provenance -----------------------------------------------------------


def _build_provenance(context: dict[str, Any]) -> dict[str, Any]:
    """Build the ``provenance`` block for the manifest root."""
    result = context.get("result")
    provenance: dict[str, Any] = {}
    if context.get("method"):
        provenance["method"] = str(context["method"])
    if context.get("functional"):
        provenance["functional"] = str(context["functional"])
    if context.get("basis"):
        provenance["basis"] = str(context["basis"])
    if context.get("jk_method"):
        provenance["jk_method"] = str(context["jk_method"])
    if context.get("jk_method_resolved"):
        provenance["jk_method_resolved"] = str(context["jk_method_resolved"])
    if context.get("jk_method_executed"):
        provenance["jk_method_executed"] = str(context["jk_method_executed"])
    if "dft_plus_u" in context:
        provenance["dft_plus_u"] = bool(context["dft_plus_u"])
    if context.get("dft_plus_u_route"):
        provenance["dft_plus_u_route"] = str(context["dft_plus_u_route"])
    mol_or_sys = context.get("molecule") or context.get("system")
    if mol_or_sys is not None:
        provenance["charge"] = int(getattr(mol_or_sys, "charge", 0))
        provenance["multiplicity"] = int(
            getattr(mol_or_sys, "multiplicity", 1),
        )
        n_elec = getattr(mol_or_sys, "n_electrons", None)
        if n_elec is not None:
            if callable(n_elec):
                n_elec = n_elec()
            provenance["n_electrons"] = int(n_elec)

    if result is not None:
        provenance["scf_converged"] = bool(
            getattr(result, "converged", False),
        )
        # SCF iteration count. Both the molecular ``SolverResult`` and
        # the periodic solver results carry ``n_iter``; fall back to the
        # length of the per-iteration trace when the count isn't a field.
        n_iter = getattr(result, "n_iter", None)
        if n_iter is None:
            _trace = getattr(result, "scf_trace", None)
            if _trace:
                n_iter = len(_trace)
        if n_iter is not None:
            provenance["n_scf_iterations"] = int(n_iter)
        energy = getattr(result, "energy", None)
        if energy is not None:
            provenance["scf_energy"] = {
                "value": float(energy),
                "units": "Eh",
            }
        fermi = getattr(result, "fermi_energy", None)
        if fermi is not None:
            provenance["fermi_energy"] = {
                "value": float(fermi),
                "units": "Eh",
            }

    if context.get("wall_seconds") is not None:
        provenance["wall_seconds"] = float(context["wall_seconds"])

    try:
        import socket

        provenance["hostname"] = socket.gethostname()
    except Exception:
        provenance["hostname"] = "unknown"

    if mol_or_sys is not None:
        # The bound PeriodicSystem exposes ``dim`` in {1,2,3}; molecular
        # stubs may carry ``dimensionality``; a bare Molecule has neither
        # (0-D). Reading only ``dimensionality`` used to record 0 for
        # every real periodic system.
        dim = getattr(mol_or_sys, "dim", None)
        if dim is None:
            dim = getattr(mol_or_sys, "dimensionality", None)
        if dim is None:
            dim = 3 if context.get("system") is not None else 0
        provenance["dimensionality"] = int(dim)

    # --- streaming / checkpoint fields (Ask-1 / Ask-2) -------------------
    # ``run_status`` and ``checkpoint`` let a live consumer (vibe-view)
    # tell a running snapshot from the settled final one, and order a
    # sequence of snapshots by ``checkpoint.seq`` without diffing bytes.
    # Both live under the ``provenance`` block, which the schema declares
    # ``additionalProperties: true`` -- so these validate against the
    # current QVF v1 manifest schema with no v3 bump required.
    run_status = context.get("run_status")
    if run_status is not None:
        rs = str(run_status)
        if rs not in ("running", "converged", "failed"):
            raise ValueError(
                "write_qvf: run_status must be one of "
                f"'running' | 'converged' | 'failed', got {rs!r}."
            )
        provenance["run_status"] = rs
    checkpoint = context.get("checkpoint")
    if checkpoint is not None:
        # Copy defensively; the caller's dict must not be mutated and any
        # non-JSON-native values (e.g. numpy floats) are coerced here.
        ckpt: dict[str, Any] = {}
        if "seq" in checkpoint:
            ckpt["seq"] = int(checkpoint["seq"])
        if "wall_time_s" in checkpoint:
            ckpt["wall_time_s"] = float(checkpoint["wall_time_s"])
        if checkpoint.get("written_at") is not None:
            ckpt["written_at"] = str(checkpoint["written_at"])
        # Optional running-state hints (additive; provenance is open).
        if checkpoint.get("scf_iteration") is not None:
            ckpt["scf_iteration"] = int(checkpoint["scf_iteration"])
        if checkpoint.get("energy_eh") is not None:
            ckpt["energy_eh"] = float(checkpoint["energy_eh"])
        provenance["checkpoint"] = ckpt

    return provenance


# ---------------------------------------------------------------------------
# Validation tool
# ---------------------------------------------------------------------------
#
# validate_qvf() drives off the canonical schema at
# qvf_manifest.schema.json -- the SSOT. The hand-rolled per-kind checks
# that used to live in this function are gone; what remains is the
# cross-cutting work that JSON Schema can't express: sha256 of every
# member matches the bytes on disk, every referenced zip path exists,
# binary dtype/shape add up to the byte count on disk, operand_a /
# operand_b / trajectory_ref cross-references resolve to existing
# sections.

_SCHEMA_PATH_V1 = Path(__file__).parent / "qvf_manifest.schema.json"
_SCHEMA_PATH_V2 = Path(__file__).parent / "qvf_manifest_v2.schema.json"
# Back-compat alias for any caller still reaching for ``_SCHEMA_PATH``;
# defaults to the v1 path (the only version that existed before v2).
_SCHEMA_PATH = _SCHEMA_PATH_V1
_SCHEMA_CACHE: dict[int, dict[str, Any]] = {}

# ---------------------------------------------------------------------------
# v2 = v1 + the periodic-reaction-path delta, derived — never forked.
#
# v2 began life as a hand-copied snapshot of v1 and then fell behind it:
# every section kind added to v1 afterwards (bond_orders, spectra.epr,
# volume.rdg, volume.potential, basis.ao, fermi_surface, phonon.bands,
# phonon.dos, equation_of_state, topology.qtaim), the root
# thermochemistry / dipole_moment / constraints / extensions blocks, the
# Section ``critical`` flag, and the ``bands`` fat-band ``projections``
# member were all absent from the fork. Because the manifest root sets
# ``additionalProperties: false`` and ``Section`` is a closed ``oneOf``,
# a periodic reaction.path -- which forces qvf_version=2 -- carrying any
# of them tripped the write-time validation gate in :func:`write_qvf`.
#
# Deriving v2 from v1 makes the delta below the *only* way the two can
# differ, so a section added to v1 tomorrow lands in v2 for free.
# ``scripts/gen_qvf_v2_schema.py`` renders this derivation to
# ``qvf_manifest_v2.schema.json`` for external validators;
# ``tests/test_qvf_v2_schema_drift.py`` pins that file to the derivation.
# ---------------------------------------------------------------------------

_V2_TITLE = "QVF Manifest (v2)"

_V2_DESCRIPTION = (
    "DEPRECATED. Canonical JSON Schema for manifest.json inside a .qvf "
    "archive that declares qvf_version=2. GENERATED FILE -- do not "
    "hand-edit; regenerate with scripts/gen_qvf_v2_schema.py. "
    "qvf_version=2 was WITHDRAWN by the governance ruling of 2026-07-10 "
    "(qvf-writer/GOVERNANCE.md, Version history): its only delta over v1 "
    "was the optional SectionReactionPath `lattice` member, which is the "
    "additive case and must not bump the version. `lattice` is now an "
    "optional member of v1, and a periodic reaction path is detected by "
    "its presence. This schema is therefore v1 with a different "
    "qvf_version const, retained only so that archives written by "
    "vibe-qc v0.10.0-v0.15.x (which are v1-compatible) still validate and "
    "so anything resolving its $id keeps working. Producers MUST NOT emit "
    "qvf_version=2; consumers SHOULD accept it and treat it as 1."
)

_V2_QVF_VERSION_DESCRIPTION = (
    "QVF format version. DEPRECATED: 2 was withdrawn (see the schema "
    "description); it is v1 in every respect but this const. Producers "
    "must emit 1."
)

def _derive_v2_schema(v1_schema: dict[str, Any]) -> dict[str, Any]:
    """Return the (deprecated) v2 manifest schema derived from ``v1_schema``.

    ``qvf_version: 2`` was withdrawn by the governance ruling of 2026-07-10
    (``qvf-writer/GOVERNANCE.md``, "Version history"): its only delta over v1
    was the optional ``SectionReactionPath.lattice`` member, which is the
    *additive* case and must not bump the version. ``lattice`` now lives in the
    v1 schema, so the v2 delta has collapsed to pure identity metadata:
    ``$id``, ``title``, ``description``, and ``properties.qvf_version``
    (const + description). Everything else is v1 verbatim.

    v2 is therefore v1 with a different ``qvf_version`` const. It is retained
    only so archives written by vibe-qc v0.10.0-v0.15.x still validate, and so
    anything resolving the v2 ``$id`` keeps working. Producers must emit 1.
    """
    schema = copy.deepcopy(v1_schema)
    schema["$id"] = _SCHEMA_URI_V2
    schema["title"] = _V2_TITLE
    schema["description"] = _V2_DESCRIPTION

    version_prop = schema["properties"]["qvf_version"]
    version_prop["const"] = QVF_FORMAT_VERSION_V2
    version_prop["description"] = _V2_QVF_VERSION_DESCRIPTION
    return schema

def _load_canonical_schema(qvf_version: int = 1) -> dict[str, Any]:
    """Return the canonical QVF manifest schema for ``qvf_version``.

    v1 is loaded from ``qvf_manifest.schema.json``, the single source of
    truth. v2 is *derived* from it by :func:`_derive_v2_schema` rather
    than read from disk, so the on-disk v2 copy can never drift out from
    under the validator. v2 extends only ``SectionReactionPath``, with an
    optional ``lattice`` binary member + ``dim`` carried in metadata, so
    that periodic reaction paths (slabs, surfaces, NEB) round-trip
    cleanly through vibe-view. Every other section is v1's.
    """
    cached = _SCHEMA_CACHE.get(qvf_version)
    if cached is not None:
        return cached
    if qvf_version not in (1, 2):
        raise ValueError(f"unknown qvf_version {qvf_version!r}; supported: 1, 2")

    with open(_SCHEMA_PATH_V1, encoding="utf-8") as f:
        schema = json.load(f)
    _SCHEMA_CACHE[1] = schema
    if qvf_version == 2:
        schema = _derive_v2_schema(schema)
        _SCHEMA_CACHE[2] = schema
    return schema


def validate_qvf(
    source: "os.PathLike | str | zipfile.ZipFile",
) -> dict[str, Any]:
    """Validate a QVF against the canonical SSOT schema.

    ``source`` may be either a filesystem path to a ``.qvf`` file or an
    already-open :class:`zipfile.ZipFile`. The latter form lets
    :func:`qvf_bytes` validate an in-memory archive without
    round-tripping through disk.

    Returns a dict with keys ``valid`` (bool), ``summary`` (list of
    per-section result strs), and ``errors`` (list of error strs).

    Checks performed:

    * The archive is a valid zip, no member exceeds the zip-bomb cap.
    * ``manifest.json`` exists and parses as JSON.
    * The manifest validates against
      :data:`_SCHEMA_PATH` (the canonical schema) -- this catches per-kind
      member shape, dtype, format, and unknown kinds.
    * Every member's declared zip path exists in the archive.
    * Every member's declared sha256 matches the bytes on disk.
    * Every binary member's ``len(bytes) == np.dtype(dtype).itemsize *
      product(shape)`` (no silent under/over-sized buffer).
    * On ``volume.difference``: both ``operand_a`` and ``operand_b``
      (if present) resolve to section ids that exist in the archive.
    * On ``reaction.waypoints``: ``trajectory_ref`` resolves to a
      section in the archive whose kind is ``trajectory``.

    Sections whose kind is in :data:`_RESERVED_KINDS` are not
    shape-validated (no schema branch yet); their file refs are still
    checked. Vendor (``x_*``) sections must conform to the
    ``SectionVendor`` schema branch (members must be valid Json/Binary
    members) but the shape of those members is unconstrained.
    """
    report: dict[str, Any] = {"valid": True, "summary": [], "errors": []}
    summary: list[str] = []
    errors: list[str] = []

    # Accept either a path-like (open + close here) or an already-open
    # ZipFile (do not close -- caller owns the handle).
    own_zf = True
    if isinstance(source, zipfile.ZipFile):
        zf = source
        own_zf = False
    else:
        path = Path(os.fspath(source))
        if not path.is_file():
            report.update(valid=False, errors=[f"file not found: {path}"])
            return report
        try:
            zf = zipfile.ZipFile(path, "r")
        except zipfile.BadZipFile as exc:
            report.update(valid=False, errors=[f"not a valid zip file: {exc}"])
            return report

    # --- zip-bomb guard: per-member uncompressed-size ceiling ----------
    # Aligned with the writer's _MAX_VOXELS payload guard (see module
    # constants) so a valid write_qvf output cannot subsequently fail
    # validation as "too large". Compressed bytes on disk are typically
    # far smaller; this guard reads `file_size` which is the
    # uncompressed length encoded in the zip central directory.
    try:
        for _info in zf.infolist():
            if _info.file_size > _MAX_MEMBER_UNCOMPRESSED_BYTES:
                errors.append(
                    f"member {_info.filename!r}: uncompressed size "
                    f"{_info.file_size:_d} bytes exceeds max "
                    f"{_MAX_MEMBER_UNCOMPRESSED_BYTES:_d}; possible zip bomb"
                )
                report["valid"] = False
    except Exception as exc:
        if own_zf:
            zf.close()
        report.update(valid=False, errors=[f"cannot read zip directory: {exc}"])
        return report

    try:
        # --- manifest.json -------------------------------------------------
        try:
            manifest_bytes = zf.read("manifest.json")
        except KeyError:
            report.update(
                valid=False, errors=errors + ["manifest.json missing from archive"]
            )
            return report

        try:
            manifest = json.loads(manifest_bytes.decode("utf-8"))
        except (json.JSONDecodeError, UnicodeDecodeError) as exc:
            report.update(
                valid=False, errors=errors + [f"manifest.json is not valid JSON: {exc}"]
            )
            return report

        # --- jsonschema validation against the canonical SSOT --------------
        #
        # Sections with reserved kinds have no schema branch yet -- temporarily
        # remove them from the to-be-validated manifest and shape-skip them.
        # Their file refs are still verified below.
        try:
            import jsonschema  # noqa: F401  (soft import; required dep)
        except ImportError:
            errors.append(
                "validate_qvf requires the 'jsonschema' package "
                "(`pip install jsonschema`) -- the canonical schema "
                "cannot be enforced without it."
            )
            report.update(valid=False, errors=errors)
            return report

        # Dispatch by the manifest's qvf_version: v1 archives validate
        # against the v1 schema; v2 archives (periodic reaction.path)
        # against the v2 schema. An unknown version falls through to
        # the v1 schema's `const: 1` rule, which produces the natural
        # "qvf_version was X, expected 1" validation error.
        try:
            manifest_qvf_version = int(manifest.get("qvf_version", 1))
        except (TypeError, ValueError):
            manifest_qvf_version = 1
        schema_version = manifest_qvf_version if manifest_qvf_version in (1, 2) else 1
        schema = _load_canonical_schema(schema_version)

        # Build a "check manifest" that mirrors the input but with
        # reserved-kind sections pulled out (they have no schema branch
        # yet; the validator still file-ref-checks them below). If
        # `sections` was absent from the input we leave it absent so
        # the schema's required-field rule fires.
        check_manifest = dict(manifest)
        raw_sections = manifest.get("sections")
        if isinstance(raw_sections, list):
            shape_check_secs = [
                s
                for s in raw_sections
                if not (isinstance(s, dict) and s.get("kind") in _RESERVED_KINDS)
            ]
            check_manifest["sections"] = shape_check_secs
        # else: `sections` is missing or not a list -- leave the manifest
        # as-is so the schema validator reports it.

        validator = jsonschema.Draft202012Validator(schema)
        schema_errors = sorted(
            validator.iter_errors(check_manifest), key=lambda e: list(e.absolute_path)
        )
        for e in schema_errors:
            loc = "/".join(str(p) for p in e.absolute_path) or "<root>"
            errors.append(f"schema: {loc}: {e.message}")
            report["valid"] = False

        fmt_ver = manifest.get("qvf_version", "?")
        n_secs = len(raw_sections) if isinstance(raw_sections, list) else 0
        summary.append(
            f"manifest.json: qvf_version={fmt_ver}, "
            f"{n_secs} section(s); "
            f"{'schema OK' if not schema_errors else f'{len(schema_errors)} schema error(s)'}"
        )

        # If sections is missing/malformed, the per-section loop is a
        # no-op -- the schema error already covered it.
        if not isinstance(raw_sections, list):
            return report

        # --- critical-flag enforcement (QVF spec 5.4-5.5) -----------
        # Per-section critical: warn if a section is marked critical
        # but its kind is reserved (no schema branch) or an unknown
        # non-vendor kind -- no consumer can render it, so a critical
        # flag is a contract violation.
        if isinstance(raw_sections, list):
            for sec in raw_sections:
                if not isinstance(sec, dict):
                    continue
                if sec.get("critical") is True:
                    kind = sec.get("kind", "")
                    sec_id = sec.get("id", "?")
                    if kind in _RESERVED_KINDS:
                        errors.append(
                            f"section {sec_id} ({kind}): critical=true but "
                            f"kind is reserved (no consumer renders it)"
                        )
                        report["valid"] = False
                    elif not _is_vendor_kind(kind) and kind not in _IMPLEMENTED_KINDS:
                        errors.append(
                            f"section {sec_id} ({kind}): critical=true but "
                            f"kind is not a known canonical or vendor kind"
                        )
                        report["valid"] = False

        # Root extensions block: warn if a critical extension is declared
        # but no sections use its vendor namespace.
        ext_block = manifest.get("extensions")
        if isinstance(ext_block, dict) and isinstance(raw_sections, list):
            section_kinds = {
                s["kind"]
                for s in raw_sections
                if isinstance(s, dict) and isinstance(s.get("kind"), str)
            }
            for ns, ext_info in ext_block.items():
                if not isinstance(ext_info, dict):
                    continue
                if ext_info.get("critical") is True:
                    prefix = ns if ns.endswith(".") else ns + "."
                    used = any(k.startswith(prefix) for k in section_kinds)
                    if not used:
                        summary.append(
                            f"extensions: {ns} declares critical=true but "
                            f"no section uses its namespace"
                        )

        # --- per-section cross-checks --------------------------------------
        zip_names = set(zf.namelist())
        section_ids = {s.get("id") for s in raw_sections if isinstance(s, dict)}
        seen_section_ids: set[str] = set()
        for sec in raw_sections:
            if not isinstance(sec, dict):
                continue
            sec_id = sec.get("id")
            if not isinstance(sec_id, str):
                continue
            if sec_id in seen_section_ids:
                errors.append(f"manifest: duplicate section id {sec_id!r}")
                report["valid"] = False
            seen_section_ids.add(sec_id)

        for sec in raw_sections:
            if not isinstance(sec, dict):
                continue
            sec_id = sec.get("id", "?")
            kind = sec.get("kind", "")

            if _is_vendor_kind(kind):
                file_refs = _collect_file_refs(sec)
                payload_ok = _validate_file_refs(
                    zf, file_refs, zip_names, sec_id, errors
                )
                summary.append(
                    f"section {sec_id} ({kind}): vendor "
                    f"({'refs OK' if payload_ok else 'refs FAILED'}); "
                    "member shape not validated"
                )
                continue

            if kind in _RESERVED_KINDS:
                file_refs = _collect_file_refs(sec)
                payload_ok = _validate_file_refs(
                    zf, file_refs, zip_names, sec_id, errors
                )
                summary.append(
                    f"section {sec_id} ({kind}): reserved, not yet "
                    f"implemented; refs {'OK' if payload_ok else 'FAILED'}"
                )
                continue

            # Implemented (canonical) kind: file refs + dtype/shape match.
            file_refs = _collect_file_refs(sec)
            refs_ok = _validate_file_refs(zf, file_refs, zip_names, sec_id, errors)
            sizes_ok = _validate_binary_shapes(zf, file_refs, sec_id, errors)

            # Cross-reference checks for the kinds that carry them.
            xref_ok = True
            if kind == "volume.difference":
                for key in ("operand_a", "operand_b"):
                    ref = sec.get(key)
                    if ref is not None and ref not in section_ids:
                        errors.append(
                            f"section {sec_id} (volume.difference): {key}="
                            f"{ref!r} does not name a section in this archive"
                        )
                        xref_ok = False
            if kind == "reaction.waypoints":
                ref = sec.get("trajectory_ref")
                target_sec = next(
                    (
                        s
                        for s in raw_sections
                        if isinstance(s, dict) and s.get("id") == ref
                    ),
                    None,
                )
                if target_sec is None:
                    errors.append(
                        f"section {sec_id} (reaction.waypoints): "
                        f"trajectory_ref={ref!r} does not name a section "
                        "in this archive"
                    )
                    xref_ok = False
                elif target_sec.get("kind") != "trajectory":
                    errors.append(
                        f"section {sec_id} (reaction.waypoints): "
                        f"trajectory_ref={ref!r} resolves to a section "
                        f"of kind {target_sec.get('kind')!r}, expected "
                        "'trajectory'"
                    )
                    xref_ok = False

            status_bits = []
            if refs_ok and sizes_ok and xref_ok:
                status_bits.append("OK")
            else:
                if not refs_ok:
                    status_bits.append("refs FAILED")
                if not sizes_ok:
                    status_bits.append("sizes FAILED")
                if not xref_ok:
                    status_bits.append("xref FAILED")
            summary.append(f"section {sec_id} ({kind}): {', '.join(status_bits)}")
            if not (refs_ok and sizes_ok and xref_ok):
                report["valid"] = False

    finally:
        if own_zf:
            zf.close()

    report["summary"] = summary
    report["errors"] = errors
    if errors:
        report["valid"] = False
    return report


def _collect_file_refs(section: dict[str, Any]) -> list[dict[str, Any]]:
    """Walk a section dict and return every file-like sub-dict that has
    a ``path`` key and (optionally) a ``sha256`` key."""
    refs: list[dict[str, Any]] = []

    def _recurse(obj: Any) -> None:
        if isinstance(obj, dict):
            if "path" in obj and isinstance(obj["path"], str):
                refs.append(obj)
            for v in obj.values():
                _recurse(v)
        elif isinstance(obj, list):
            for item in obj:
                _recurse(item)

    _recurse(section)
    return refs


def _validate_binary_shapes(
    zf: zipfile.ZipFile,
    file_refs: list[dict[str, Any]],
    sec_id: str,
    errors: list[str],
) -> bool:
    """Check that every binary member's dtype x shape matches the byte
    length of the member on disk. Catches dtype-shape lies the schema
    cannot express. Returns True if all pass, False otherwise.
    """
    ok = True
    for ref in file_refs:
        if ref.get("format") != "binary":
            continue
        dtype = ref.get("dtype")
        shape = ref.get("shape")
        if dtype is None or shape is None:
            # schema-required fields were missing; the schema error
            # already covers this.
            continue
        try:
            itemsize = int(np.dtype(dtype).itemsize)
        except TypeError:
            errors.append(
                f"section {sec_id}: member {ref['path']!r} declares "
                f"dtype={dtype!r} which numpy cannot resolve"
            )
            ok = False
            continue
        expected = itemsize
        for d in shape:
            expected *= int(d)
        try:
            actual = zf.getinfo(ref["path"]).file_size
        except KeyError:
            # missing-file error already reported by _validate_file_refs.
            continue
        if actual != expected:
            errors.append(
                f"section {sec_id}: member {ref['path']!r} byte length "
                f"{actual} != dtype({dtype}).itemsize x prod({shape}) "
                f"= {expected}"
            )
            ok = False
    return ok


def _validate_file_refs(
    zf: zipfile.ZipFile,
    file_refs: list[dict[str, Any]],
    zip_names: set[str],
    sec_id: str,
    errors: list[str],
) -> bool:
    """Validate sha256 and existence for a list of file reference
    dicts.  Returns True if all pass, False otherwise."""
    ok = True
    for ref in file_refs:
        path_in_zip = ref["path"]
        fmt = ref.get("format", "binary")
        if fmt not in ("json", "binary"):
            errors.append(
                f"section {sec_id}: member {path_in_zip!r} has invalid "
                f"format {fmt!r} (expected 'json' or 'binary')",
            )
            ok = False
            continue

        if path_in_zip not in zip_names:
            errors.append(
                f"section {sec_id}: file {path_in_zip!r} "
                "declared in manifest but missing from zip",
            )
            ok = False
            continue

        expected_sha = ref.get("sha256")
        data: bytes | None = None
        if expected_sha is not None or fmt == "json":
            try:
                data = zf.read(path_in_zip)
            except Exception as exc:
                errors.append(
                    f"section {sec_id}: cannot read {path_in_zip!r}: {exc}",
                )
                ok = False
                continue
            actual = _sha256_hex(data)
            if actual != expected_sha:
                errors.append(
                    f"section {sec_id}: sha256 mismatch for {path_in_zip!r} "
                    f"(expected {expected_sha[:12]}..., got {actual[:12]}...)",
                )
                ok = False

        if fmt == "json" and data is not None:
            try:
                json.loads(data.decode("utf-8"))
            except (UnicodeDecodeError, json.JSONDecodeError) as exc:
                errors.append(
                    f"section {sec_id}: JSON member {path_in_zip!r} "
                    f"does not parse: {exc}"
                )
                ok = False
    return ok


def _print_validate_report(report: dict[str, Any]) -> str:
    """Print-friendly summary of a :func:`validate_qvf` report."""
    lines: list[str] = []
    for s in report["summary"]:
        if "vendor" in s.lower():
            prefix = "⚠"
        elif "OK" in s or "format " in s:
            prefix = "✓"
        else:
            prefix = "✗"
        lines.append(f"{prefix} {s}")
    for e in report["errors"]:
        lines.append(f"✗ ERROR: {e}")
    if report["valid"]:
        lines.append("\n✓ QVF file is valid.")
    else:
        lines.append("\n✗ QVF file has validation errors.")
    return "\n".join(lines)


# ---------------------------------------------------------------------------
# CLI entry point for qvf-validate
# ---------------------------------------------------------------------------


def _qvf_validate_cli() -> None:
    """CLI: ``python -m vibeqc.output.formats.qvf <path.qvf>``."""
    import sys

    if len(sys.argv) < 2:
        print("usage: python -m vibeqc.output.formats.qvf <path.qvf>", file=sys.stderr)
        sys.exit(2)
    report = validate_qvf(sys.argv[1])
    print(_print_validate_report(report))
    sys.exit(0 if report["valid"] else 1)


if __name__ == "__main__":
    _qvf_validate_cli()
