"""Resolve the [elements] declarations directive from the manifest (s040 op).

WHY:
    Whether a repository has authored element declarations is a DECLARED fact,
    not something inferred from the filesystem. An implementation repository
    (such as accountable-record-py itself) legitimately has no authored
    elements; it says so explicitly via [elements].declarations = "none",
    mirroring the existing [traceability] identifier_map = "none" opt-out
    pattern. s040 must honor that declaration rather than hard-failing on a
    missing data/elements/ directory.

OBS (three cases):
    - declarations = "none"   -> repo declares no authored elements; s040
                                 succeeds with an empty registry.
    - declarations = "<path>" -> require that directory and validate quartets
                                 beneath it (default path is data/elements).
    - [elements] absent       -> no opt-out: fall back to requiring the default
                                 data/elements/ directory, preserving the prior
                                 hard-failure behavior when it is missing.

OBS (reuse the manifest idiom):
    Uses stage 010's load_manifest_data(repo_root) -> (filename, table) so the
    manifest filename resolution (owned by se-manifest-schema) is not
    duplicated here. A missing manifest is treated as "no opt-out declared",
    which keeps s040 independent of identity-stage failures (those are s010's
    to report).
"""

from dataclasses import dataclass
from pathlib import Path

from accountable_record.boundary.toml import is_table
from accountable_record.stage_010_identity.manifest import load_manifest_data
from accountable_record.stage_040_elements.base import constants


@dataclass(frozen=True)
class ElementsDirective:
    """The resolved [elements].declarations directive.

    Attributes:
        opted_out: True when declarations = "none"; the repo declares no
            authored element source quartets.
        declarations_path: The repository-relative directory to validate when
            not opted out. Defaults to data/elements when [elements] is absent
            or declares no explicit path.
    """

    opted_out: bool
    declarations_path: str


def resolve_elements_directive(repo_root: Path) -> ElementsDirective:
    """Resolve the [elements].declarations directive for a repository.

    Args:
        repo_root: The target repository root.

    Returns:
        An :class:`ElementsDirective`. When no manifest or no [elements] block
        is present, returns the default-path directive (not opted out), which
        preserves the prior behavior of requiring data/elements/.
    """
    table = _load_manifest_or_none(repo_root)
    if table is None:
        return ElementsDirective(
            opted_out=False,
            declarations_path=constants.ELEMENTS_DIRNAME,
        )

    elements = table.get(constants.MANIFEST_ELEMENTS_TABLE)
    if not is_table(elements):
        return ElementsDirective(
            opted_out=False,
            declarations_path=constants.ELEMENTS_DIRNAME,
        )

    declarations = elements.get(constants.MANIFEST_DECLARATIONS_KEY)

    if declarations == constants.DECLARATIONS_NONE:
        return ElementsDirective(opted_out=True, declarations_path="")

    if isinstance(declarations, str) and declarations.strip():
        return ElementsDirective(
            opted_out=False,
            declarations_path=declarations.strip(),
        )

    # [elements] present but no usable declarations value: fall back to default.
    return ElementsDirective(
        opted_out=False,
        declarations_path=constants.ELEMENTS_DIRNAME,
    )


def _load_manifest_or_none(repo_root: Path) -> dict[str, object] | None:
    """Load the manifest table, or None when no manifest is present.

    OBS:
        A missing manifest is not s040's error to raise (s010 owns manifest
        presence). Treating it as "no opt-out" lets s040 fall back to the
        default-path behavior cleanly.
    """
    try:
        _filename, table = load_manifest_data(repo_root)
    except (FileNotFoundError, TypeError):
        return None

    if not is_table(table):
        return None

    return table
