#!/usr/bin/env python3
"""
lint-voice.py — deterministic voice linter for Oleg's texts.

Checks for AI/corporate artifacts, wrong typography, and banned patterns
that make text sound like a model rather than Oleg.

Usage:
    python lint-voice.py "text to check"
    echo "text" | python lint-voice.py
    python lint-voice.py < input.txt

Exit codes:
    0 — clean (no ERRORs; WARNs alone do not fail)
    1 — ERRORs found (agent must fix and re-run)
    2 — usage error
"""

import re
import sys


# (level, id, pattern, message)
# level: "ERROR" = must fix before delivery; "WARN" = check in context, may be OK
CHECKS = [
    # ── Typography ────────────────────────────────────────────────────────────
    ("ERROR", "EM_DASH",
     r"—",
     "Em-dash (—). Replace with colon, comma, or rephrase as separate sentence."),

    ("ERROR", "SMART_QUOTES",
     r'[""„«»]',
     'Smart/typographic double quote. Use straight " or rephrase.'),

    ("WARN", "SMART_APOSTROPHE",
     r"[''`]",
     "Smart/curly apostrophe or backtick. Use straight apostrophe '."),

    # ── AI / corporate words ──────────────────────────────────────────────────
    ("ERROR", "AI_LEVERAGE",
     r"(?i)\bleverag(e|es|ed|ing)\b",
     '"leverage" — AI/corporate word. Replace with plain verb (use, apply, build on).'),

    ("ERROR", "AI_ROBUST",
     r"(?i)\brobust\b",
     '"robust" — AI/corporate word. Replace with specific description.'),

    ("ERROR", "AI_SEAMLESS",
     r"(?i)\bseamless(ly)?\b",
     '"seamless" — AI/corporate word. Cut or replace with plain description.'),

    ("ERROR", "AI_PASSIONATE",
     r"(?i)\bpassionat(e|ely)\b|\bmy passion\b",
     '"passionate/passion" — AI/corporate word. Replace with direct statement.'),

    ("ERROR", "AI_THRILLED",
     r"(?i)\bthrilled\b",
     '"thrilled" — AI word. Replace with direct statement.'),

    ("ERROR", "AI_DELVE",
     r"(?i)\bdelv(e|ing|ed)\b",
     '"delve" — AI word. Replace with explore / look into / dig.'),

    ("ERROR", "AI_TAPESTRY",
     r"(?i)\btapestry\b",
     '"tapestry" — AI word. Remove or replace.'),

    ("ERROR", "AI_EMPOWER",
     r"(?i)\bempower(s|ed|ing)?\b",
     '"empower" — AI/corporate word. Replace with plain verb.'),

    ("ERROR", "AI_STREAMLINE",
     r"(?i)\bstreamline[ds]?\b|\bstreamlining\b",
     '"streamline" — AI/corporate word. Replace with plain verb.'),

    ("ERROR", "AI_ACTIONABLE",
     r"(?i)\bactionable\b",
     '"actionable" — AI/corporate word. Replace with plain description.'),

    ("ERROR", "AI_UNLOCK",
     r"(?i)\bunlock(s|ed|ing)?\b",
     '"unlock" — AI/corporate word. Replace with plain verb.'),

    ("ERROR", "AI_CUTTING_EDGE",
     r"(?i)cutting[- ]edge",
     '"cutting-edge" — AI/corporate phrase. Replace with specific description.'),

    ("WARN", "AI_INNOVATIVE",
     r"(?i)\binnovativ(e|ely)\b|\binnovation\b",
     '"innovative/innovation" — often AI/corporate filler. Replace with specific if used generically.'),

    # ── Sentence openers (capitalised opener form) ────────────────────────────
    # Pattern: word at line start OR after terminal punctuation + whitespace
    ("ERROR", "OPENER_FURTHERMORE",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)Furthermore[,\s]",
     '"Furthermore" opener. Cut — connection should come from content, not connective.'),

    ("ERROR", "OPENER_MOREOVER",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)Moreover[,\s]",
     '"Moreover" opener. Cut — restructure.'),

    ("ERROR", "OPENER_ADDITIONALLY",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)Additionally[,\s]",
     '"Additionally" opener. Cut — restructure without connective.'),

    ("ERROR", "OPENER_ULTIMATELY",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)Ultimately[,\s]",
     '"Ultimately" opener. Cut — restructure.'),

    ("ERROR", "OPENER_HOWEVER",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)However[,\s]",
     '"However" opener. Cut — use contrast from content, not a gluing connective.'),

    ("ERROR", "OPENER_IMPORTANTLY",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)Importantly[,\s]",
     '"Importantly" opener — filler frame. Cut.'),

    ("ERROR", "OPENER_THAT_SAID",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)That said[,\s]",
     '"That said" opener. Cut — restructure.'),

    ("ERROR", "OPENER_IN_CONCLUSION",
     r"(?i)in conclusion[,\s]",
     '"In conclusion" — empty closing summary. Cut entirely.'),

    ("ERROR", "OPENER_OVERALL",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)Overall[,\s]",
     '"Overall" opener — empty summary signal. Cut.'),

    ("WARN", "OPENER_SO_COMMA",
     r"(?m)(?:^[ \t]*|(?<=[.!?])[ \t]+)So[,\s]",
     '"So," opener. Oleg cuts these — connection should come from content.'),

    # ── Filler frames ─────────────────────────────────────────────────────────
    ("ERROR", "FILLER_WORTH_NOTING",
     r"(?i)it'?s worth noting",
     '"It\'s worth noting" — filler frame. Cut, say the thing directly.'),

    ("ERROR", "FILLER_IMPORTANT_REMEMBER",
     r"(?i)it'?s important to remember",
     '"It\'s important to remember" — filler frame. Cut.'),

    ("ERROR", "FILLER_I_AM_WRITING",
     r"(?i)\bI am writing to\b",
     '"I am writing to" — generic opener. Replace with direct lead.'),

    ("ERROR", "FILLER_IN_TODAYS",
     r"(?i)in today'?s\b",
     '"In today\'s..." — AI/generic opener. Cut.'),

    # ── AI structural patterns ────────────────────────────────────────────────
    ("ERROR", "PATTERN_ITS_NOT_JUST",
     r"(?i)it'?s not just",
     '"it\'s not just X, it\'s Y" construction. Rephrase as direct statement.'),

    ("WARN", "PATTERN_NOT_X_BUT",
     r"(?i)\bnot [\w]+(?:\s+[\w]+){0,3},?\s+but\b",
     '"not X, but Y" — often AI phrasing. Rephrase if rhetorical.'),

    ("ERROR", "PATTERN_HONEST_FRAMING",
     r"(?i)honest framing|one thing to flag|I want to be honest about",
     '"honest framing / one thing to flag" template. Not Oleg\'s pattern — remove.'),

    # ── AI closings ───────────────────────────────────────────────────────────
    ("ERROR", "CLOSING_LOOKING_FORWARD",
     r"(?i)looking forward to",
     '"Looking forward to" closing. Replace e.g. "Open to chat if it\'s a fit."'),

    ("ERROR", "CLOSING_HAPPY_TO",
     r"(?i)\bhappy to\b",
     '"happy to" — AI phrasing. Replace with plain statement.'),

    # ── Warn-only borderline cases ────────────────────────────────────────────
    ("WARN", "WARN_NAVIGATE",
     r"(?i)\bnavigat(e|ing|ed)\b",
     '"navigate" — check if metaphorical AI-speak ("navigate challenges"). OK if literal.'),

    ("WARN", "WARN_LANDSCAPE",
     r"(?i)\blandscape\b",
     '"landscape" — check if metaphorical AI-speak. OK if literal/technical.'),
]


def get_context(text: str, match: re.Match, window: int = 60) -> str:
    start = max(0, match.start() - window)
    end = min(len(text), match.end() + window)
    snippet = text[start:end]
    snippet = " ".join(snippet.split())
    prefix = "…" if start > 0 else ""
    suffix = "…" if end < len(text) else ""
    return f"{prefix}{snippet}{suffix}"


def line_of(text: str, pos: int) -> int:
    return text[:pos].count("\n") + 1


def run_checks(text: str) -> list[dict]:
    findings = []
    for level, check_id, pattern, message in CHECKS:
        for m in re.finditer(pattern, text):
            findings.append({
                "level": level,
                "id": check_id,
                "line": line_of(text, m.start()),
                "context": get_context(text, m),
                "message": message,
            })
    findings.sort(key=lambda f: (f["line"], f["id"]))
    return findings


def main() -> None:
    if len(sys.argv) > 1:
        text = " ".join(sys.argv[1:])
    elif not sys.stdin.isatty():
        text = sys.stdin.read()
    else:
        print("Usage: python lint-voice.py \"text\" OR pipe text via stdin", file=sys.stderr)
        sys.exit(2)

    if not text.strip():
        print("✓ CLEAN — empty input.", file=sys.stderr)
        sys.exit(0)

    findings = run_checks(text)
    errors = [f for f in findings if f["level"] == "ERROR"]
    warns  = [f for f in findings if f["level"] == "WARN"]

    if not findings:
        print("✓ CLEAN — no issues found.")
        sys.exit(0)

    print(f"Found {len(findings)} issue(s): {len(errors)} ERROR, {len(warns)} WARN\n")

    for f in findings:
        label = f"[{f['level']}]"
        action = "Fix" if f["level"] == "ERROR" else "Note"
        print(f"{label} {f['id']} — line {f['line']}")
        print(f'  Context: "{f["context"]}"')
        print(f"  {action}: {f['message']}")
        print()

    print("---")
    if errors:
        print(f"EXIT 1 — {len(errors)} error(s) present. Fix and re-run.")
        sys.exit(1)
    else:
        print("EXIT 0 — no errors (warnings only). Review WARNs if applicable.")
        sys.exit(0)


if __name__ == "__main__":
    main()
