#!/usr/bin/env python3
"""Lint committed docs against .agents/shared/writing-style.md.

Checks:
  1. Banned vocabulary (significance puffers, analytical verbs, poetic nouns,
     promotional adjectives, opening crutches).
  2. Broken relative markdown links.
  3. Plan status consistency (status vs location vs index).

Prose-voice rules are not machine-checkable; this covers the parts that are.
Run from the repo root: python3 .agents/skills/docs-lint/lint.py [paths...]
Exit 1 if any violation is found.
"""
import os, re, sys, glob

BANNED = [
    "pivotal", "crucial", "vital", "groundbreaking", "transformative", "testament",
    "paramount", "invaluable", "delve", "leverage", "utilize", "facilitate", "foster",
    "showcase", "underscore", "streamline", "harness", "tapestry", "realm", "paradigm",
    "nexus", "cornerstone", "robust", "seamless", "innovative", "cutting-edge",
    "meticulous", "holistic", "comprehensive", "in today's world",
    "it's worth noting", "it is important to note",
]
BAN_RE = re.compile(r'(?<![\w-])(' + "|".join(re.escape(w) for w in BANNED) + r')(?![\w-])', re.I)
LINK = re.compile(r'\]\(([^)]+)\)')

def iter_md(paths):
    for p in paths:
        if os.path.isfile(p) and p.endswith(".md"):
            yield p
        for root, dirs, files in os.walk(p):
            dirs[:] = [d for d in dirs if d not in (".git", "node_modules")]
            for f in files:
                if f.endswith(".md"):
                    yield os.path.join(root, f)

def check_plans():
    """Check plan status consistency: active plans with 'Done' status, files
    missing from index, dangling index entries, done files with wrong status."""
    REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    plans_dir = os.path.join(REPO, "plans")
    done_dir = os.path.join(REPO, "plans", "done")
    index_path = os.path.join(plans_dir, "index.md")

    if not os.path.exists(index_path):
        return 0

    violations = 0
    STATUS_RE = re.compile(r'^\*\*Status:\*\*\s*(.+)', re.I)

    # Parse index.md for active and done entries
    active_files = set()
    done_files = set()
    current_section = None
    with open(index_path) as f:
        for line in f:
            if line.startswith("## Active"):
                current_section = "active"
                continue
            if line.startswith("## Done"):
                current_section = "done"
                continue
            if current_section == "active":
                m = re.search(r'\]\(([^)]+)\)', line)
                if m:
                    active_files.add(m.group(1))
            elif current_section == "done":
                m = re.search(r'\]\(([^)]+)\)', line)
                if m:
                    done_files.add(m.group(1))

    # Active plans on disk (not in done/, not index.md)
    disk_active = set()
    for f in glob.glob(os.path.join(plans_dir, "*.md")):
        name = os.path.basename(f)
        if name == "index.md":
            continue
        disk_active.add(name)

    # Done plans on disk
    disk_done = set()
    if os.path.isdir(done_dir):
        for f in glob.glob(os.path.join(done_dir, "*.md")):
            disk_done.add("done/" + os.path.basename(f))

    # Check 1: active plans on disk whose internal status is Done/Implemented/Complete
    for name in disk_active:
        fpath = os.path.join(plans_dir, name)
        with open(fpath) as f:
            for line_num, line in enumerate(f, 1):
                if line_num > 5:
                    break
                m = STATUS_RE.match(line)
                if m:
                    status = m.group(1).strip().lower()
                    done_keywords = ["done", "implemented", "complete", "completed"]
                    if any(status.startswith(kw) for kw in done_keywords):
                        print(f"{fpath}:{line_num}: status '{m.group(1).strip()}' — file is in plans/ but appears done; move to done/")
                        violations += 1
                    break

    # Check 2: active plans on disk not in index
    for name in sorted(disk_active):
        if name not in active_files:
            fpath = os.path.join(plans_dir, name)
            print(f"{fpath}:1: not listed in plans/index.md Active table")
            violations += 1

    # Check 3: done plans on disk not in index
    for name in sorted(disk_done):
        if name not in done_files:
            fpath = os.path.join(REPO, "plans", name)
            print(f"{fpath}:1: not listed in plans/index.md Done table")
            violations += 1

    # Check 4: index entries with no file on disk
    for name in sorted(active_files):
        if name not in disk_active:
            print(f"plans/index.md: active entry '{name}' — file not found on disk")
            violations += 1

    for name in sorted(done_files):
        if name not in disk_done:
            print(f"plans/index.md: done entry '{name}' — file not found on disk")
            violations += 1

    # Check 5: files in done/ whose internal status doesn't say Done
    for name in disk_done:
        fpath = os.path.join(REPO, "plans", name)
        with open(fpath) as f:
            found_status = False
            for line_num, line in enumerate(f, 1):
                if line_num > 5:
                    break
                m = STATUS_RE.match(line)
                if m:
                    found_status = True
                    status = m.group(1).strip().lower()
                    if not status.startswith("done"):
                        print(f"{fpath}:{line_num}: status '{m.group(1).strip()}' — file is in done/ but status is not 'Done'")
                        violations += 1
                    break
            if not found_status:
                print(f"{fpath}:1: file is in done/ but has no Status header")
                violations += 1

    return violations

def main(argv):
    paths = argv or ["knowledge", ".agents", "operations", "investigations", "plans"]
    violations = 0

    if "plans" in paths or any(p.startswith("plans") for p in paths):
        violations += check_plans()

    # The style guide and this skill enumerate the banned words by definition.
    ban_exempt = ("shared/writing-style.md", "skills/docs-lint/")
    for f in sorted(set(iter_md(paths))):
        check_banned = not any(x in f for x in ban_exempt)
        fence = False
        with open(f) as fh:
            for ln, line in enumerate(fh, 1):
                if line.lstrip().startswith("```"):
                    fence = not fence; continue
                if fence:
                    continue
                if check_banned:
                    for m in BAN_RE.finditer(line):
                        print(f"{f}:{ln}: banned word '{m.group(1)}'")
                        violations += 1
                for m in LINK.finditer(line):
                    link = m.group(1)
                    if re.match(r'^(https?:|mailto:|#|/)', link):
                        continue
                    path = re.split(r'[#?]', link)[0]
                    if not path:
                        continue
                    tgt = os.path.normpath(os.path.join(os.path.dirname(f), path))
                    if not os.path.exists(tgt):
                        print(f"{f}:{ln}: broken link -> {link}")
                        violations += 1
    print(f"\n{violations} violation(s)")
    return 1 if violations else 0

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
