#!/usr/bin/env python3
"""
Team email-loop state (off-repo, leaf module)
=============================================
Shared store tying together the team-digest send/receive loop. Imported by the SEND side
(team_digest.py records each brief's Message-ID, keyed PER MEMBER, so a reply can be proven
to thread off a digest that member was actually sent), the RECEIVE side (team_inbox.py:
which reply Message-IDs are already processed — idempotency), and the owner's own brief (the
Core digest engine drains the "pending for the owner" queue into their morning digest).

No dependencies of its own, so every consumer can import it without a cycle. Defaults to a
location OUTSIDE the vault checkout (so it never syncs to the repo): it holds reply
Message-IDs and a raw-decision audit trail that must never be committed. The directory is
configurable via ALEXANDRIA_STATE_DIR (or the individual *_STATE / *_AUDIT vars). Writes are
best-effort — a missing/unwritable store degrades gracefully rather than breaking a send.

sent_digest_ids is a {member_key: [ids]} map (not a flat list), so multiple members' reply
paths stay isolated.
"""
import os
import json
from pathlib import Path


def _state_dir():
    d = os.environ.get("ALEXANDRIA_STATE_DIR")
    return Path(d).expanduser() if d else (Path.home() / ".alexandria")


STATE_PATH = Path(os.environ.get(
    "TEAM_INBOX_STATE", str(_state_dir() / "team-inbox-state.json")))
AUDIT_PATH = Path(os.environ.get(
    "TEAM_INBOX_AUDIT", str(_state_dir() / "team-inbox-audit.jsonl")))
_KEEP_IDS = 500   # cap each rolling id list so the state file can't grow without bound


def load():
    try:
        return json.loads(STATE_PATH.read_text())
    except Exception:
        return {}


def save(state):
    try:
        STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
        STATE_PATH.write_text(json.dumps(state, indent=2))
    except Exception:
        pass


def _push(seq, value):
    seq = [x for x in (seq or []) if x != value]
    seq.append(value)
    return seq[-_KEEP_IDS:]


def record_sent_digest_id(member_key, msg_id):
    """Remember a digest Message-ID we just sent `member_key`, so their reply can be
    proven to thread off a real brief they received."""
    if not msg_id:
        return
    s = load()
    m = s.get("sent_digest_ids", {})
    if not isinstance(m, dict):                      # migrate an old flat list, if any
        m = {}
    m[member_key] = _push(m.get(member_key, []), msg_id)
    s["sent_digest_ids"] = m
    save(s)


def sent_digest_ids(member_key):
    m = load().get("sent_digest_ids", {})
    return m.get(member_key, []) if isinstance(m, dict) else []


def record_sent_message(to_addr, msg_id):
    """Remember a Message-ID the agent sent TO `to_addr` (keyed by recipient address), so a
    reply FROM that address can later be proven to thread off a genuine agent email — the
    inbound paths' threading gate. Recorded for EVERY outbound send (digests, confirmations,
    one-offs) from the Core digest engine's send_email, the single send chokepoint.
    Best-effort."""
    if not to_addr or not msg_id:
        return
    a = to_addr.strip().lower()
    s = load()
    m = s.get("sent_message_ids", {})
    if not isinstance(m, dict):
        m = {}
    m[a] = _push(m.get(a, []), msg_id)
    s["sent_message_ids"] = m
    save(s)


def sent_message_ids(*to_addrs):
    """All Message-IDs the agent sent to any of `to_addrs` (case-insensitive). Pass one or
    more recipient addresses (e.g. all of the owner's addresses) → the union of their
    recorded ids."""
    m = load().get("sent_message_ids", {})
    if not isinstance(m, dict):
        return []
    out = []
    for a in to_addrs:
        out += m.get((a or "").strip().lower(), [])
    return out


def is_processed_reply(msg_id):
    return bool(msg_id) and msg_id in set(load().get("processed_reply_ids", []))


def mark_processed_reply(msg_id):
    if not msg_id:
        return
    s = load()
    s["processed_reply_ids"] = _push(s.get("processed_reply_ids", []), msg_id)
    save(s)


def add_pending_for_owner(update):
    """Queue an applied update (carrying `who` = the member who reported it) for the owner's
    next digest."""
    s = load()
    q = s.get("pending_for_owner", [])
    q.append(update)
    s["pending_for_owner"] = q[-200:]
    save(s)


def get_pending_for_owner():
    return load().get("pending_for_owner", [])


def clear_pending_for_owner():
    s = load()
    if s.get("pending_for_owner"):
        s["pending_for_owner"] = []
        save(s)


# --- Compatibility shims for the Core digest engine -------------------------
# The shipped Core engine (Agent/scripts/daily-digest/digest.py) drains the
# owner's pending queue via these exact symbol names. They are kept as thin aliases
def append_audit(record):
    """Append one decision record (raw email excerpt + what was applied) to the off-repo
    JSONL audit trail — the human-readable counterpart to the git history of vault edits."""
    try:
        AUDIT_PATH.parent.mkdir(parents=True, exist_ok=True)
        with open(AUDIT_PATH, "a", encoding="utf-8") as f:
            f.write(json.dumps(record, ensure_ascii=False) + "\n")
    except Exception:
        pass
