#!/usr/bin/env python3
"""aria-taxonomist — Write-Time Filing Helper für den Aria-Brain-Vault.

Gibt anhand von Titel + Inhalt eine Empfehlung für:
  - Folder (z.B. 02-Wissen)
  - frontmatter type
  - Rationale

Heuristik-basiert (deterministisch, v1). Kein LLM-Call by default.
Mit --llm wird ein optionaler Claude-API-Call ausgeführt.

Usage:
    aria-taxonomist.py --title "Meeting-Notes BMW" --content "Heute besprochen..."
    aria-taxonomist.py --title "Goose Audit" --content-file /tmp/draft.md
    aria-taxonomist.py --file /root/aria/brain/00-Inbox/foo.md
    aria-taxonomist.py --title "Kurztitel" --content "..." --llm

Exit: 0 immer (ist ein Empfehlungs-Tool, kein Blocker).
"""
from __future__ import annotations

import argparse
import json
import os
import re
import sys
from dataclasses import dataclass, field, asdict
from pathlib import Path

VAULT_ROOT = Path("/root/aria/brain")
SCHEMA_PACK_PATH = Path("/root/aria/brain/schema-pack.yaml")

# ─── Suggestion Dataclass ─────────────────────────────────────────────────────

@dataclass
class Suggestion:
    type: str
    folder: str
    confidence: float        # 0.0 – 1.0
    rationale: str
    filename_hint: str = ""  # optional: empfohlener Dateiname
    signals: list[str] = field(default_factory=list)


# ─── Heuristik-Regeln ─────────────────────────────────────────────────────────

# Reihenfolge: erste passende Regel gewinnt (höchste Priorität oben).
# Jede Regel: (match_fn(title, content, folder) -> bool, type, folder, confidence, rationale)

_TITLE_LC = ""   # wird im Matching dynamisch gesetzt

def _kw(text: str, *words: str) -> bool:
    """True wenn mindestens eines der Wörter im Text enthalten ist (case-insensitive)."""
    t = text.lower()
    return any(w.lower() in t for w in words)


def _classify(title: str, content: str, source_path: str | None) -> Suggestion:
    t = title.lower()
    c = content.lower() if content else ""
    combined = t + " " + c

    signals: list[str] = []

    # ── Folder-Herkunft: wenn Pfad bekannt ────────────────────────────────────
    folder_hint: str | None = None
    if source_path:
        rel = Path(source_path).as_posix()
        for folder_prefix in ["00-Inbox", "01-Projekte", "02-Wissen", "03-Recherchen",
                               "04-Feedback", "05-Referenzen", "06-Daily"]:
            if folder_prefix.lower() in rel.lower():
                folder_hint = folder_prefix
                signals.append(f"Quelle-Pfad → {folder_prefix}")
                break
        if not folder_hint and re.search(r"06-Daily", rel, re.IGNORECASE):
            folder_hint = "06-Daily"

    # ── Rule 1: Daily Log ──────────────────────────────────────────────────────
    date_in_title = bool(re.search(r"\d{4}-\d{2}-\d{2}", title))
    daily_kw = _kw(combined, "daily", "tages-log", "session-start", "wrap-up",
                   "morgen-routine", "abend-routine", "journal", "tagebuch",
                   "daily log", "heute", "session ende", "wrap up")
    if date_in_title and (daily_kw or folder_hint == "06-Daily"):
        signals.append("Datum im Titel + Daily-Keywords")
        fname = re.search(r"\d{4}-\d{2}-\d{2}", title)
        return Suggestion(
            type="daily", folder="06-Daily", confidence=0.92,
            rationale="Datum im Titel + Daily-Log-Muster erkannt.",
            filename_hint=f"{fname.group(0)}.md" if fname else "",
            signals=signals,
        )
    if folder_hint == "06-Daily":
        signals.append("Datei aus 06-Daily")
        return Suggestion(
            type="daily", folder="06-Daily", confidence=0.85,
            rationale="Datei liegt in 06-Daily → Daily-Typ.",
            signals=signals,
        )

    # ── Rule 2: Inbox ──────────────────────────────────────────────────────────
    inbox_kw = _kw(combined, "inbox", "unsortiert", "triage", "vorsortierung", "pending")
    if inbox_kw or folder_hint == "00-Inbox":
        signals.append("Inbox-Keyword oder Pfad")
        return Suggestion(
            type="inbox", folder="00-Inbox", confidence=0.80,
            rationale="Inbox-Keyword erkannt — noch nicht klassifiziert.",
            signals=signals,
        )

    # ── Rule 3: Identity / Persona ────────────────────────────────────────────
    identity_kw = _kw(combined, "identity", "soul", "persona", "werte", "identität",
                       "aria identity", "user profile", "kais profil")
    if identity_kw:
        signals.append("Identity-Keywords")
        return Suggestion(
            type="identity", folder="root",
            confidence=0.85,
            rationale="Identity/Persona-Keywords gefunden → root identity-File.",
            signals=signals,
        )

    # ── Rule 4: Schema / Konvention ───────────────────────────────────────────
    schema_kw = _kw(combined, "schema", "konvention", "schema-pack", "vault-schema",
                    "claude.md", "frontmatter", "typing system", "page type")
    if schema_kw:
        signals.append("Schema-Keywords")
        return Suggestion(
            type="schema", folder="02-Wissen",
            confidence=0.88,
            rationale="Schema/Konventions-Keywords → Vault-Meta-File in 02-Wissen.",
            signals=signals,
        )

    # ── Rule 5: Template ──────────────────────────────────────────────────────
    template_kw = _kw(combined, "template", "vorlage", "boilerplate", "muster",
                      "prompt-vorlage", "note-template")
    if template_kw:
        signals.append("Template-Keywords")
        return Suggestion(
            type="template", folder="05-Referenzen",
            confidence=0.85,
            rationale="Template/Vorlage-Keywords → 05-Referenzen.",
            signals=signals,
        )

    # ── Precompute remaining keyword signals ─────────────────────────────────
    # Precompute to allow cross-rule priority decisions
    audit_kw = _kw(combined, "audit", "review", "sicherheitscheck",
                   "security review", "code review", "prompt injection scan",
                   "tool audit", "repo audit", "analyse-bericht")
    audit_in_title = _kw(t, "audit", "review", "analyse")

    project_kw = _kw(combined, "projekt", "project", "roadmap", "sprint",
                     "milestone", "deliverable", "kar-", "kadi-v2", "feature",
                     "initiative", "epic", "status-update", "plan", "planung",
                     "implementation plan", "spec")
    project_is_active = _kw(combined, "aktiv", "laufend", "in progress")

    learnings_kw = _kw(combined, "learning", "lrn-", "korrektur", "correction",
                       "erkenntnis", "lessons", "feedback", "standing order",
                       "lern-regel", "pattern erkannt")

    research_in_title = _kw(t, "recherche", "research", "finding", "studie",
                            "paper", "deep dive", "deep-dive", "benchmark",
                            "comparison", "vergleich", "survey", "investigation")
    research_in_content = _kw(c, "quellen", "source", "literatur", "studie",
                              "cross-report", "synthese", "key finding")
    research_kw = research_in_title or (research_in_content and not project_kw)

    # ── Rule 6: Research — vor Audit+Project, wenn im Titel explizit ─────────
    if research_in_title:
        signals.append("Research im Titel (hohe Prio)")
        folder = "03-Recherchen" if _kw(combined, "tiefe", "deep", "source") else "02-Wissen"
        return Suggestion(
            type="research", folder=folder,
            confidence=0.85,
            rationale="'Research/Finding/Studie/...' explizit im Titel → research-Typ.",
            signals=signals,
        )

    # ── Rule 7: Audit ─────────────────────────────────────────────────────────
    if audit_kw:
        signals.append("Audit-Keywords")
        return Suggestion(
            type="audit", folder="02-Wissen",
            confidence=0.87,
            rationale="Audit/Review/Analyse-Keywords → 02-Wissen, type audit.",
            signals=signals,
        )

    # ── Rule 8: Project ───────────────────────────────────────────────────────
    if project_kw:
        signals.append("Projekt-Keywords")
        folder = "01-Projekte" if project_is_active else "02-Wissen"
        return Suggestion(
            type="project", folder=folder,
            confidence=0.78,
            rationale="Projekt-Keywords erkannt. Bei aktivem Deliverable → 01-Projekte, sonst 02-Wissen.",
            signals=signals,
        )

    # ── Rule 9: Learnings / Korrekturen ──────────────────────────────────────
    if learnings_kw:
        signals.append("Learnings-Keywords")
        folder = "04-Feedback" if _kw(combined, "feedback") else "root"
        return Suggestion(
            type="learnings", folder=folder,
            confidence=0.82,
            rationale="Learnings/Feedback-Keywords → learnings-Typ.",
            signals=signals,
        )

    # ── Rule 10 (prev 9): Research via Content ────────────────────────────────
    if research_kw:
        signals.append("Research-Keywords im Inhalt")
        folder = "03-Recherchen" if _kw(combined, "tiefe", "deep", "source") else "02-Wissen"
        return Suggestion(
            type="research", folder=folder,
            confidence=0.75,
            rationale="Recherche/Research-Keywords im Inhalt → research-Typ.",
            signals=signals,
        )

    # ── Rule 10: Reference ────────────────────────────────────────────────────
    ref_kw = _kw(combined, "referenz", "reference", "cheatsheet", "cheat sheet",
                 "config", "konfiguration", "anleitung", "guide", "howto",
                 "how-to", "snapshot", "backup", "log-datei", "subscription",
                 "rss", "kanal", "channel")
    if ref_kw:
        signals.append("Reference-Keywords")
        return Suggestion(
            type="reference", folder="05-Referenzen",
            confidence=0.75,
            rationale="Referenz/Config/Anleitung-Keywords → 05-Referenzen, type reference.",
            signals=signals,
        )

    # ── Rule 11: System ───────────────────────────────────────────────────────
    system_kw = _kw(combined, "systemd", "infra", "stack", "service", "cron",
                    "hook", "deployment", "docker", "server", "infrastruktur",
                    "mcp", "health", "monitoring", "api-key", "credential",
                    "tech stack", "system design")
    if system_kw:
        signals.append("System/Infra-Keywords")
        return Suggestion(
            type="system", folder="02-Wissen",
            confidence=0.76,
            rationale="System/Infra-Keywords erkannt → system-Typ in 02-Wissen.",
            signals=signals,
        )

    # ── Fallback ──────────────────────────────────────────────────────────────
    signals.append("Keine klaren Keywords → Fallback inbox")
    return Suggestion(
        type="inbox", folder="00-Inbox",
        confidence=0.45,
        rationale="Kein eindeutiger Typ erkennbar — vorerst Inbox, manuell sortieren.",
        signals=signals,
    )


# ─── LLM-Fallback (optional) ─────────────────────────────────────────────────

_ARIA_CONFIG_PATH = Path("/root/aria/brain/youtube/.config.yaml")

def _get_taxonomist_model() -> str:
    """Liest das Modell aus ARIA_TAXONOMIST_MODEL env, dann .config.yaml models.taxonomist_model,
    dann fällt zurück auf claude-sonnet-4-5."""
    env_val = os.environ.get("ARIA_TAXONOMIST_MODEL")
    if env_val:
        return env_val
    try:
        import yaml as _yaml  # type: ignore
        cfg = _yaml.safe_load(_ARIA_CONFIG_PATH.read_text(encoding="utf-8"))
        model = cfg.get("models", {}).get("taxonomist_model")
        if model:
            return model
    except Exception:
        pass
    return "claude-sonnet-4-5"


def classify_with_llm(title: str, content: str, allowed_types: list[str]) -> Suggestion:
    """Fragt Claude API für bessere Klassifikation. Nur mit --llm Flag."""
    try:
        import anthropic  # type: ignore
    except ImportError:
        sys.stderr.write("anthropic SDK nicht installiert: pip install anthropic\n")
        return _classify(title, content, None)

    client = anthropic.Anthropic()
    model = _get_taxonomist_model()

    type_list = "\n".join(f"- {t}" for t in allowed_types)
    system_prompt = "Du bist ein Aria-Brain Filing-Assistent. Antworte NUR mit einem JSON-Objekt."
    user_prompt = f"""Klassifiziere diese Note:

Titel: {title}

Inhalt (Auszug):
{content[:500]}

Erlaubte Types:
{type_list}

Antworte NUR mit einem JSON-Objekt:
{{
  "type": "<type>",
  "folder": "<z.B. 02-Wissen>",
  "confidence": <0.0-1.0>,
  "rationale": "<1-2 Sätze>"
}}"""

    try:
        msg = client.messages.create(
            model=model,
            max_tokens=256,
            system=[
                {
                    "type": "text",
                    "text": system_prompt,
                    "cache_control": {"type": "ephemeral"},
                }
            ],
            messages=[{"role": "user", "content": user_prompt}],
        )
        raw = msg.content[0].text.strip()
        # JSON extrahieren
        m = re.search(r"\{.*\}", raw, re.DOTALL)
        if m:
            data = json.loads(m.group(0))
            return Suggestion(
                type=data.get("type", "inbox"),
                folder=data.get("folder", "00-Inbox"),
                confidence=float(data.get("confidence", 0.5)),
                rationale=data.get("rationale", "LLM-Klassifikation"),
                signals=["llm-classification"],
            )
    except Exception as e:
        sys.stderr.write(f"LLM-Fehler: {e}\n")

    return _classify(title, content, None)


# ─── Schema Pack ─────────────────────────────────────────────────────────────

def load_allowed_types() -> list[str]:
    if not SCHEMA_PACK_PATH.exists():
        return list(["project", "reference", "research", "daily", "identity",
                      "system", "learnings", "template", "inbox", "audit", "schema"])
    try:
        import yaml  # type: ignore
        pack = yaml.safe_load(SCHEMA_PACK_PATH.read_text(encoding="utf-8"))
        if isinstance(pack, dict) and "page_types" in pack:
            return list(pack["page_types"].keys())
    except Exception:
        pass
    return ["project", "reference", "research", "daily", "identity",
            "system", "learnings", "template", "inbox", "audit", "schema"]


# ─── Frontmatter Generator ────────────────────────────────────────────────────

def generate_frontmatter(title: str, suggestion: Suggestion, date: str) -> str:
    lines = [
        "---",
        f"title: {title}",
        f"type: {suggestion.type}",
        f"tags: []",
        f"date: {date}",
        f"status: draft",
        "---",
    ]
    return "\n".join(lines)


# ─── Main ─────────────────────────────────────────────────────────────────────

def main() -> int:
    ap = argparse.ArgumentParser(
        description="Aria Write-Time Filing Helper — empfiehlt Folder + Type für neue Notes"
    )
    group = ap.add_mutually_exclusive_group()
    group.add_argument("--file", type=Path,
                       help="Existierende Markdown-Datei analysieren")
    group.add_argument("--content", type=str,
                       help="Inhalt als direkter String")
    ap.add_argument("--title", type=str, default="",
                    help="Titel der Note")
    ap.add_argument("--content-file", type=Path,
                    help="Inhalt aus Datei lesen (mit --title)")
    ap.add_argument("--json", action="store_true",
                    help="JSON-Output")
    ap.add_argument("--llm", action="store_true",
                    help="Claude API für Klassifikation nutzen (optional)")
    ap.add_argument("--show-frontmatter", action="store_true",
                    help="Vorgeschlagenes Frontmatter ausgeben")
    args = ap.parse_args()

    try:
        import yaml  # noqa: F401
    except ImportError:
        sys.stderr.write("WARN: pyyaml nicht installiert — schema-pack nicht ladbar\n")

    # ── Input vorbereiten ─────────────────────────────────────────────────────
    title = args.title or ""
    content = ""
    source_path: str | None = None

    if args.file:
        source_path = str(args.file)
        try:
            text = args.file.read_text(encoding="utf-8", errors="replace")
        except Exception as e:
            sys.stderr.write(f"Datei nicht lesbar: {e}\n")
            return 1
        # Frontmatter extrahieren
        parts = text.split("---", 2)
        if len(parts) >= 3 and not parts[0].strip():
            fm_raw = parts[1]
            body = parts[2]
            try:
                import yaml
                fm_raw_norm = re.sub(
                    r"^(\s*(?:related|tags):\s*)(\[\[.+?)$",
                    lambda m: f'{m.group(1)}"{m.group(2).replace(chr(34), chr(39))}"',
                    fm_raw, flags=re.MULTILINE,
                )
                fm = yaml.safe_load(fm_raw_norm)
                if isinstance(fm, dict):
                    if not title and fm.get("title"):
                        title = str(fm["title"])
            except Exception:
                body = text
        else:
            body = text
        content = body[:1000]
        if not title:
            title = args.file.stem.replace("-", " ").replace("_", " ").title()

    elif args.content_file:
        try:
            content = args.content_file.read_text(encoding="utf-8", errors="replace")[:1000]
        except Exception as e:
            sys.stderr.write(f"content-file nicht lesbar: {e}\n")
            return 1
    elif args.content:
        content = args.content

    if not title and not content:
        ap.print_help()
        return 1

    # ── Klassifikation ────────────────────────────────────────────────────────
    allowed_types = load_allowed_types()

    if args.llm:
        suggestion = classify_with_llm(title, content, allowed_types)
    else:
        suggestion = _classify(title, content, source_path)

    # ── Output ────────────────────────────────────────────────────────────────
    import datetime
    today = datetime.date.today().isoformat()

    if args.json:
        out = {
            "input_title": title,
            "suggestion": asdict(suggestion),
            "source_path": source_path,
        }
        if args.show_frontmatter:
            out["frontmatter"] = generate_frontmatter(title, suggestion, today)
        print(json.dumps(out, ensure_ascii=False, indent=2))
    else:
        print(f"Titel   : {title or '(nicht angegeben)'}")
        print(f"Type    : {suggestion.type}")
        print(f"Folder  : {suggestion.folder}/")
        conf_pct = f"{suggestion.confidence:.0%}"
        print(f"Konfidenz: {conf_pct}")
        print(f"Rationale: {suggestion.rationale}")
        if suggestion.signals:
            print(f"Signale : {', '.join(suggestion.signals)}")
        if suggestion.filename_hint:
            print(f"Dateiname: {suggestion.filename_hint}")
        if args.show_frontmatter:
            print("\n── Vorgeschlagenes Frontmatter ──────────────────────────────")
            print(generate_frontmatter(title, suggestion, today))

    return 0


if __name__ == "__main__":
    sys.exit(main())
