"""Read/write helpers for Mitch's Obsidian vault, shared with the /pa skill.

Design contract (so Jessica never corrupts /pa's structured files):

- Anything messy or structured (notes, tasks, reminders, vitals, golf) goes to a
  dedicated voice-capture inbox (``Jessica Inbox.md``) as tagged, timestamped
  bullets. The /pa skill triages that inbox into the kanban board, vitals log,
  etc. on its morning/evening runs.
- Only simple, low-risk list appends write directly: grocery, household, and the
  pantry file (which Jessica owns outright).
- Everything else is read-only.

The vault path is read from ``$MT_VAULT`` and defaults to the known location.
"""

import os
import re
from datetime import datetime
from pathlib import Path

VAULT = Path(os.environ.get("MT_VAULT", "/Users/mitch_tango/Obsidian/MT_Vault"))

INBOX = "Jessica Inbox.md"
GROCERY = "Tasks/Grocery List.md"
HOUSEHOLD = "Tasks/Household.md"
TASK_BOARD = "Tasks/Task Board.md"
PANTRY = "Food/Pantry.md"

# Canonical grocery sections (match the headings already used in Grocery List.md).
_GROCERY_SECTIONS = {
    "Produce": ["lettuce", "onion", "garlic", "tomato", "pepper", "carrot", "celery", "apple", "banana", "lemon", "lime", "herb", "parsley", "cilantro", "basil", "spinach", "kale", "potato", "avocado", "broccoli", "asparagus", "mushroom", "cucumber", "zucchini", "fruit", "vegetable", "scallion", "shallot", "ginger"],
    "Meat / Protein": ["chicken", "beef", "pork", "bacon", "sausage", "turkey", "fish", "salmon", "shrimp", "egg", "eggs", "tofu", "prosciutto", "steak", "lamb", "ground"],
    "Dairy": ["milk", "cheese", "butter", "yogurt", "cream", "parmesan", "mozzarella", "feta"],
    "Bakery / Prepared": ["bread", "bun", "bagel", "tortilla", "croissant", "eclair", "roll", "baguette"],
    "Frozen": ["frozen", "icecream"],
    "Beverages": ["water", "juice", "soda", "wine", "beer", "coffee", "tea", "seltzer"],
    "Household": ["towel", "detergent", "soap", "foil", "wrap", "napkin", "sponge", "trash", "bag", "cleaner", "toilet"],
    "Pantry": ["flour", "sugar", "salt", "oil", "vinegar", "rice", "pasta", "bulgur", "stock", "broth", "bean", "spice", "sauce", "baking", "cumin", "paprika", "oregano", "cornstarch", "honey", "syrup", "mustard", "ketchup", "mayo", "can", "tomatoes", "nuts", "oats", "cereal"],
}


def _safe_path(relpath: str) -> Path:
    """Resolve a vault-relative path, refusing anything that escapes the vault."""
    root = VAULT.resolve()
    p = (VAULT / relpath).resolve()
    if p != root and root not in p.parents:
        raise ValueError(f"path escapes vault: {relpath}")
    return p


def read_text(relpath: str) -> str | None:
    p = _safe_path(relpath)
    return p.read_text() if p.exists() else None


def daily_note_relpath(dt: datetime | None = None) -> str:
    dt = dt or datetime.now()
    return f"Daily Notes/{dt:%Y.%m.%d}.md"


def append_under_heading(relpath: str, heading: str, line: str) -> None:
    """Append ``line`` under the markdown ``heading`` (e.g. ``## Produce``).

    Inserts after the last existing item in that section. If the heading is
    absent, a new section is appended at the end of the file. Creates the file
    if needed.
    """
    p = _safe_path(relpath)
    p.parent.mkdir(parents=True, exist_ok=True)
    text = p.read_text() if p.exists() else ""
    lines = text.splitlines()

    hidx = next(
        (i for i, ln in enumerate(lines) if ln.strip().lower() == heading.strip().lower()),
        None,
    )
    if hidx is None:
        prefix = (text.rstrip("\n") + "\n\n") if text.strip() else ""
        p.write_text(f"{prefix}{heading}\n{line}\n")
        return

    end = next(
        (j for j in range(hidx + 1, len(lines)) if lines[j].lstrip().startswith("## ")),
        len(lines),
    )
    insert_at = end
    while insert_at - 1 > hidx and not lines[insert_at - 1].strip():
        insert_at -= 1
    lines.insert(insert_at, line)
    p.write_text("\n".join(lines) + "\n")


def capture(kind: str, text: str) -> str:
    """Append a tagged, timestamped bullet to the voice inbox for /pa to triage."""
    now = datetime.now()
    day_heading = f"## {now:%Y.%m.%d}"
    line = f"- [ ] [{now:%H:%M}] {kind.upper()}: {text.strip()}"
    p = _safe_path(INBOX)
    if not p.exists():
        p.write_text(
            "# Jessica Inbox\n\n"
            "Voice captures from Jessica, for /pa to triage on its morning/evening runs.\n\n"
            f"{day_heading}\n{line}\n"
        )
        return "Captured."
    append_under_heading(INBOX, day_heading, line)
    return "Captured."


def categorize_grocery(item: str) -> str:
    """Best-effort map of a grocery item to a store section. Defaults to Pantry."""
    raw = re.findall(r"[a-z]+", item.lower())
    # Match against both the word and a singularized form (so "carrots" matches
    # "carrot" while singular "asparagus" still matches itself).
    words = set(raw)
    for w in raw:
        if len(w) > 3 and w.endswith("s"):
            words.add(w[:-1])
        if len(w) > 4 and w.endswith("es"):
            words.add(w[:-2])
    for section, keywords in _GROCERY_SECTIONS.items():
        if any(w in keywords for w in words):
            return section
    return "Pantry"


def add_grocery(item: str, section: str | None = None) -> str:
    """Add a grocery item under its (auto-detected) store section."""
    section = section or categorize_grocery(item)
    append_under_heading(GROCERY, f"## {section}", f"- [ ] {item.strip()}")
    return section


def add_household(item: str, tier: str = "When I Get To It") -> str:
    append_under_heading(HOUSEHOLD, f"## {tier}", f"- [ ] {item.strip()}")
    return tier


def read_today_focus() -> list[str]:
    """Return the task lines under the Task Board's 'Today / Focus' column."""
    text = read_text(TASK_BOARD)
    if not text:
        return []
    lines = text.splitlines()
    start = next(
        (
            i
            for i, ln in enumerate(lines)
            if ln.lstrip().startswith("## ") and "today" in ln.lower() and "focus" in ln.lower()
        ),
        None,
    )
    if start is None:
        return []
    items: list[str] = []
    for ln in lines[start + 1 :]:
        if ln.lstrip().startswith("## "):
            break
        m = re.match(r"\s*- \[[ xX]\]\s*(.+)", ln)
        if m:
            items.append(m.group(1).strip())
    return items


# --- Pantry: Jessica owns this file outright, so direct edits are fine. ---


def read_pantry() -> list[str]:
    text = read_text(PANTRY)
    if not text:
        return []
    return [
        m.group(1).strip()
        for ln in text.splitlines()
        if (m := re.match(r"\s*- \[[ xX]\]\s*(.+)", ln))
    ]


def add_pantry(item: str) -> None:
    p = _safe_path(PANTRY)
    p.parent.mkdir(parents=True, exist_ok=True)
    if not p.exists():
        p.write_text("# Pantry\n\nWhat's on hand. Maintained by Jessica.\n\n")
    append_under_heading(PANTRY, "# Pantry", f"- [ ] {item.strip()}")


def remove_pantry(item: str) -> bool:
    """Remove the first pantry entry matching ``item`` (case-insensitive substring)."""
    p = _safe_path(PANTRY)
    if not p.exists():
        return False
    needle = item.strip().lower()
    out, removed = [], False
    for ln in p.read_text().splitlines():
        m = re.match(r"\s*- \[[ xX]\]\s*(.+)", ln)
        if not removed and m and needle in m.group(1).strip().lower():
            removed = True
            continue
        out.append(ln)
    if removed:
        p.write_text("\n".join(out) + "\n")
    return removed
