"""Work out what a document is from its contents.

The distinction that matters: what a document **is** versus what it **mentions**.
AGM minutes discuss budgets and insurance at length; a board candidate's
biography lists their committee service and by-law experience. Counting
mentions files both as "financials", so evidence is weighted by *where* it
appears — a document announces what it is in its opening lines and its title,
and only discusses other subjects further down.
"""
import re
from collections import Counter

HEADER_CHARS = 500

# What a document declares itself to be, near the top. These decide.
DECLARATIONS = [
    ("minutes", re.compile(r"\bminutes\s+of\b|\bmeeting\s+minutes\b|"
                           r"\bannual\s+general\s+meeting\b|\bcalled\s+to\s+order\b", re.I)),
    ("insurance", re.compile(r"\bcertificate\s+of\s+insurance\b|\bpolicy\s+(?:no|number)\b|"
                             r"\breinsurance\b|\blineslip\b|\bsum\s+insured\b|"
                             r"\bunderwrit(?:er|ten)\b", re.I)),
    ("financials", re.compile(r"\bfinancial\s+statements?\b|\bbalance\s+sheet\b|"
                              r"\bstatement\s+of\s+operations\b|\bincome\s+statement\b|"
                              r"\bbudget\b|\bvariance\b|\bcapex\b|\breserve\s+fund\s+study\b", re.I)),
    ("governance", re.compile(r"\bby[-\s]?laws?\b|\bunit\s+entitlement\b|"
                              r"\bmanagement\s+agreement\b|\bform\s+of\s+proxy\b|"
                              r"\brules\s+and\s+regulations\b|\bdeclaration\b", re.I)),
    ("newsletters", re.compile(r"\bnewsletter\b|\bdear\s+(?:owners|proprietors)\b|"
                               r"\bowners['’]?\s+update\b", re.I)),
    ("board-and-people", re.compile(r"\bcurriculum\s+vitae\b|\bbiograph(?:y|ical)\b|"
                                    r"\bcandidate\s+(?:for|statement)\b|\bnominee\b", re.I)),
]

# Weaker corroboration, worth something anywhere in the document.
MENTIONS = {
    "minutes": ["moved and seconded", "quorum", "resolved that", "in attendance", "agenda"],
    "financials": ["auditor", "cash flow", "depreciation", "expenditure", "accounts payable"],
    "insurance": ["premium", "deductible", "coverage", "insured value", "broker"],
    "governance": ["proprietors", "resolution", "amendment"],
    "newsletters": ["season", "we are pleased"],
    "board-and-people": ["education", "employment", "career", "he has served", "she has served"],
}

# A title that is only a person's name, or announces a candidate.
PERSON_TITLE = re.compile(r"^[A-Z][a-zA-Z'\-]+(?:\s+[A-Z]\.?)?(?:\s+[A-Z][a-zA-Z'\-]+){1,2}$")
CANDIDATE_TITLE = re.compile(r"\bcandidate\b|\bbio\b|\bresume\b|\bcv\b", re.I)
NOT_A_NAME = {
    "strata", "palms", "sands", "shore", "club", "resort", "management", "plan",
    "board", "committee", "annual", "general", "meeting", "financial", "budget",
    "insurance", "certificate", "policy", "statement", "documents", "document",
    "update", "newsletter", "agm", "owners", "presentation", "report", "minutes",
}

YEAR = re.compile(r"\b(20[0-3]\d)\b")
LONG_DATE = re.compile(
    r"\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+(\d{1,2})[,\s]+(20\d{2})", re.I)
MONTHS = {m: i for i, m in enumerate(
    ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"], 1)}


def title_is_a_person(title):
    stripped = (title or "").strip()
    if CANDIDATE_TITLE.search(stripped):
        return True
    if not PERSON_TITLE.match(stripped):
        return False
    return not any(w.strip(".,'").lower() in NOT_A_NAME for w in stripped.split())


def classify_text(text, title=""):
    """Return (category_key, confidence 0-1, evidence).

    A person's document is decided by its title; everything else is decided by
    what the opening of the document declares, with the body only corroborating.
    """
    if title_is_a_person(title):
        return "board-and-people", 0.9, ["title is a person"]

    text = text or ""
    header = text[:HEADER_CHARS]
    scores, evidence = Counter(), {}

    for key, pattern in DECLARATIONS:
        found = pattern.search(header)
        if found:
            scores[key] += 10
            evidence.setdefault(key, []).append(f"header: {found.group(0)[:26]}")
        # The same declaration deeper in is much weaker — minutes quote the
        # financial statements they received.
        elif pattern.search(text):
            scores[key] += 2
            evidence.setdefault(key, []).append("mentioned below")

    lowered_title = (title or "").lower()
    for key, pattern in DECLARATIONS:
        if pattern.search(lowered_title):
            scores[key] += 8
            evidence.setdefault(key, []).append("title says so")

    for key, phrases in MENTIONS.items():
        hits = sum(1 for phrase in phrases if phrase in text.lower())
        if hits:
            scores[key] += min(hits, 3)
            evidence.setdefault(key, []).append(f"{hits} supporting terms")

    if not scores:
        return None, 0.0, []

    best, top = scores.most_common(1)[0]
    runner_up = scores.most_common(2)[1][1] if len(scores) > 1 else 0
    margin = top - runner_up
    # Confidence needs a clear winner, not merely a high score.
    confidence = min(1.0, (top / 20) * 0.5 + (margin / 12) * 0.5)
    if top < 6 or margin < 2:
        confidence = min(confidence, 0.24)
    return best, round(confidence, 2), evidence.get(best, [])[:3]


def subject_year(title="", text=""):
    """The year a document is *about*.

    The title is trusted first: "2026 Board Presentation" is about 2026 even
    though the workbook inside it cites five other years.
    """
    in_title = YEAR.findall(title or "")
    if in_title:
        return int(in_title[0])
    in_header = YEAR.findall((text or "")[:HEADER_CHARS])
    if in_header:
        return int(Counter(in_header).most_common(1)[0][0])
    return None


def content_years(text, title=""):
    counts = Counter(int(y) for y in YEAR.findall(f"{title} {text}"))
    return [y for y, _ in counts.most_common() if 2000 <= y <= 2035]


def content_date(text):
    match = LONG_DATE.search(text or "")
    if not match:
        return None
    month = MONTHS.get(match.group(1)[:3].lower())
    if not month:
        return None
    from datetime import date
    try:
        return date(int(match.group(3)), month, int(match.group(2)))
    except ValueError:
        return None


def suggest_title(text, title, kind=""):
    """A corrected title, or "" to keep the existing one.

    Deliberately narrow: only fixes titles that plainly contradict the file.
    Renaming documents people already recognise costs more than it gains.
    """
    if not (title or "").strip():
        key, _c, _e = classify_text(text)
        year = subject_year("", text)
        label = {"minutes": "Minutes", "financials": "Financial document",
                 "insurance": "Insurance document", "governance": "Governance document",
                 "newsletters": "Newsletter"}.get(key, "Document")
        return f"{year or ''} {label}".strip()

    # A spreadsheet of budget variance is not a "presentation" — but keep the
    # year that is already in the title, which is the year it is about.
    lowered = title.lower()
    if kind == "xls" and "presentation" in lowered:
        body = (text or "").lower()
        if "variance" in body or "var " in body[:600] or "budget" in body:
            year = subject_year(title, text)
            return f"{year} Budget Variance".strip() if year else "Budget Variance"
    return ""
