"""Corpus policy — which paths are not brain content.

Distinct from the fs jail (:mod:`vault_core.fs`), which is a *boundary*: the jail
says what a seat may never read, this says what is not worth indexing as a note.
Generated artifacts, engine scripts, test fixtures — present in the folder, not
part of the graph.

**Config is the authority, and that is the point.** This used to read only
Obsidian's ``.obsidian/app.json`` ``userIgnoreFilters``. A hosted brain has no
Obsidian and no local folder, so that file never exists there and the policy
silently fell back — the same vault answered graph questions differently served
than opened locally. ``Agent/config.json`` exists on every vault, hosted or not,
so the answer is now portable.

Resolution order:

1. ``vault.ignore_paths`` in the vault config — wins outright when set.
2. ``.obsidian/app.json`` → ``userIgnoreFilters`` — read only when config is
   silent, so existing local vaults keep the behavior they had.
3. :data:`FALLBACK` — recurring generated artifacts.
"""
import json
import os
import re
from pathlib import Path

from .config import load_config

# Recurring non-content: the engine's own scripts, the machine state dir, and the
# federation outbox (the packaged notes behind pushes to peer brains).
FALLBACK = ("Agent/scripts/", ".state/", "Agent/Federation/outbox/")

# The federation outbox: every push to a peer is written as one packaged note here
# (`alexandria push --file`), kept as the wire-identical record of what was sent. It
# is a shipment record, not knowledge — the durable facts stay in their home notes —
# so it is ignored whatever `vault.ignore_paths` says, like test fixtures.
OUTBOX_PREFIX = "Agent/Federation/outbox/"

# Test fixtures living inside a vault are never brain content.
FIXTURE_PREFIX = "Agent/scripts/tests/fixtures"


def _obsidian_filters(root: Path):
    try:
        filters = json.loads((Path(root) / ".obsidian" / "app.json")
                             .read_text(encoding="utf-8")).get("userIgnoreFilters")
    except Exception:
        return None
    return tuple(filters) if filters is not None else None


def ignore_prefixes(root) -> tuple:
    """The ignore patterns for this vault (see module docstring for precedence)."""
    cfg = load_config(root) or {}
    configured = ((cfg.get("vault") or {}).get("ignore_paths"))
    if configured:
        return tuple(str(p) for p in configured)
    return _obsidian_filters(root) or FALLBACK


def is_ignored(rel: str, patterns) -> bool:
    """Is this vault-relative path ignored?

    Patterns are path prefixes. Obsidian also accepts ``/regex/`` entries, which
    the previous prefix-only check matched against nothing at all — a filter the
    user wrote and that silently did nothing. Those are honored here.
    """
    rel = str(rel).replace(os.sep, "/")
    if rel.startswith(FIXTURE_PREFIX) or rel.startswith(OUTBOX_PREFIX):
        return True
    for pat in patterns or ():
        pat = str(pat)
        if len(pat) > 1 and pat.startswith("/") and pat.endswith("/"):
            try:
                if re.search(pat[1:-1], rel):
                    return True
            except re.error:      # a malformed filter ignores nothing, never raises
                continue
        elif rel.startswith(pat):
            return True
    return False
