"""Full static-site audit for META 2027.

Checks, in order: stale-2026 content presented as 2027; broken internal links and anchors; nav and
footer consistency; canonical/og URL agreement; JSON-LD still carrying 2026 facts; title/desc
presence. Historical references are allowed where the page is explicitly archival — the plenary
archive, the proceedings page, the footer link to the META 2026 site — so those contexts are
whitelisted by page+pattern rather than globally.
"""

import glob
import json
import re

# ---------------------------------------------------------------- archive photo blocks
# Les photos d'archive portent légitimement « META 2026 », « Dublin » et « Trinity » dans leur
# alt et leur légende. On ne désactive rien globalement : on calcule l'étendue exacte de chaque
# <figure> dont la <figcaption> annonce explicitement l'édition 2026, et on ne tolère ces termes
# QUE dans ces intervalles. Une mention hors figure, ou dans une figure sans légende datée,
# reste signalée — c'est précisément le cas qui ferait croire que Dublin concerne 2027.
def archive_spans(s):
    spans = []
    for m in re.finditer(r'<figure\b[^>]*>.*?</figure>', s, re.S):
        block = m.group(0)
        cap = re.search(r'<figcaption\b[^>]*>(.*?)</figcaption>', block, re.S)
        if cap and re.search(r'META\s?2026', cap.group(1)):
            spans.append((m.start(), m.end()))
    # Second marqueur, explicite et volontaire : une <section data-archive> est une section
    # entièrement consacrée à une édition passée. L'attribut doit être posé à la main, section
    # par section — il n'y a pas de laissez-passer implicite. Les <section> ne s'imbriquent pas
    # sur ce site, le balayage ci-dessous s'appuie sur cette propriété.
    for m in re.finditer(r'<section\b[^>]*\bdata-archive\b[^>]*>', s):
        end = s.find('</section>', m.end())
        if end != -1:
            spans.append((m.start(), end + len('</section>')))
    return spans


def in_archive(spans, i):
    return any(a <= i < b for a, b in spans)


PAGES = [f for f in sorted(glob.glob('*.html')) if f != 'meta26_program_inject.html']
issues = []

# ---------------------------------------------------------------- stale 2026 content
STALE = ['Dublin', 'Ireland', 'Trinity', '14–17 July 2026', '14-17 July 2026', '14 to 17 July 2026',
         'Okamoto', '9 March 2026', '24 April 2026', '7 May 2026', '6 June 2026', '26 June 2026']
# Contexts where a 2026 mention is legitimately historical. Every entry below was read in
# context before being whitelisted — none is a blanket pass:
#   speakers.html    JSON-LD of *past* plenary lectures (superEvent: META 2014..2026)
#   committees.html  Ortwin Hess's real affiliation is Trinity College Dublin; the committee
#                    carries an explicit "shown as of META 2026" disclaimer
#   workshop.html    Ortwin Hess's verified institutional affiliation is Trinity College Dublin
#   about.html       the list of past host cities (Paris · Singapore · … · Dublin)
#   home.html        "from Lisbon to Dublin", a venues-history sentence
#   proceedings.html past proceedings
ARCHIVE_OK = {
    'speakers.html': ['Dublin', 'Ireland', 'Trinity'],
    'proceedings.html': ['Dublin', 'Ireland'],
    'about.html': ['Dublin', 'Ireland', 'Trinity'],
    'committees.html': ['Dublin', 'Ireland', 'Trinity'],
    'workshop.html': ['Dublin', 'Ireland', 'Trinity'],
    'home.html': ['Dublin'],
}

for f in PAGES:
    s = open(f, encoding='utf-8').read()
    spans = archive_spans(s)
    for w in STALE:
        hits = [m.start() for m in re.finditer(re.escape(w), s)]
        hits = [i for i in hits if not in_archive(spans, i)]   # photos datées : légitimes
        n = len(hits)
        if not n:
            continue
        if w in ARCHIVE_OK.get(f, []):
            # verify each occurrence sits near a past-edition marker
            ok = all(re.search(r'(META\s*20(1[4-9]|2[0-6])|[Pp]ast|[Aa]rchive|[Pp]revious|superEvent|'
                               r'startDate|Trinity College Dublin|from Lisbon|Torremolinos|'
                               r'20(1[4-9]|2[0-6])\s*[·—-])',
                               s[max(0, i - 320):i + 320])
                     for i in hits)
            if ok:
                continue
        issues.append('%s : « %s » ×%d hors contexte archive' % (f, w, n))

# "META 2026"/"META26" allowed only as explicit history or the footer link
for f in PAGES:
    s = open(f, encoding='utf-8').read()
    spans = archive_spans(s)
    for m in re.finditer(r'META\s?20?26', s):
        if in_archive(spans, m.start()):
            continue
        ctx = s[max(0, m.start() - 200):m.start() + 200]
        if re.search(r'(footer|Past|past|[Aa]rchive|[Pp]revious|history|Marrakesh|editions?|'
                     r'shown as of|shown for reference|to be confirmed|superEvent|Contributions at|metaconferences\.org/META26)', ctx):
            continue
        issues.append('%s : « %s » à %d sans marqueur d\'archive' % (f, m.group(0), m.start()))

# ---------------------------------------------------------------- links and anchors
ids = {f: set(re.findall(r'id="([^"]+)"', open(f, encoding='utf-8').read())) for f in PAGES}
for f in PAGES:
    s = open(f, encoding='utf-8').read()
    for href in re.findall(r'href="([^"#][^"]*)"', s):
        if href.startswith(('http', 'mailto:', 'tel:')):
            continue
        target = href.split('#')[0].split('?')[0]
        frag = href.split('#')[1] if '#' in href else None
        if target and not target.startswith('files/') and target not in ids and target not in (
                './',) and not target.endswith(('.css', '.png', '.jpg', '.webp', '.ico', '.pdf', '.zip', '.docx')):
            issues.append('%s : lien cassé → %s' % (f, href))
        elif frag and target in ids and frag not in ids[target]:
            issues.append('%s : ancre inexistante → %s' % (f, href))
    for href in re.findall(r'href="#([^"]+)"', s):
        if href not in ids[f]:
            issues.append('%s : ancre locale inexistante → #%s' % (f, href))

# files/ links: existence checked on the server side by the caller
file_links = set()
for f in PAGES:
    file_links |= set(re.findall(r'href="(files/[^"]+)"', open(f, encoding='utf-8').read()))
print('LIENS_FILES=%s' % ' '.join(sorted(file_links)))

# ---------------------------------------------------------------- nav and footer consistency
ref_nav = ref_foot = None
for f in PAGES:
    s = open(f, encoding='utf-8').read()
    nav = re.search(r'<nav\b.*?</nav>', s, re.S).group(0)
    nav = re.sub(r'\s*class="active"', '', nav)
    nav = re.sub(r'<nav\b[^>]*>', '', nav)
    nav = re.sub(r'href="(#|\./)"', 'href="@"', nav)
    nav = re.sub(r'\s+', ' ', nav)
    foot = re.search(r'<footer\b.*?</footer>', s, re.S)
    foot = re.sub(r'\s+', ' ', foot.group(0)) if foot else '(aucun)'
    if ref_nav is None:
        ref_nav, ref_foot = nav, foot
    if nav != ref_nav:
        issues.append('%s : menu divergent' % f)
    if foot != ref_foot:
        issues.append('%s : pied de page divergent' % f)

# ---------------------------------------------------------------- metadata
for f in PAGES:
    s = open(f, encoding='utf-8').read()
    t = re.search(r'<title>(.*?)</title>', s, re.S)
    if not t or not t.group(1).strip():
        issues.append('%s : <title> vide' % f)
    canon = re.search(r'rel="canonical" href="([^"]+)"', s)
    if canon and not canon.group(1).endswith('/' + f) and not (f == 'home.html'):
        issues.append('%s : canonical → %s' % (f, canon.group(1)))
    for m in re.finditer(r'<script type="application/ld\+json">(.*?)</script>', s, re.S):
        try:
            data = json.loads(m.group(1))
        except ValueError:
            issues.append('%s : JSON-LD invalide' % f)
            continue
        blob = json.dumps(data)
        # "1 September 2026" and "Autumn 2026" belong to the 2027 cycle; only venue facts and the
        # 2026 conference dates are stale.
        for w in ['Dublin', 'Ireland', 'July 14', '14 July 2026', 'July 2026']:
            if w in blob and f not in ('proceedings.html', 'speakers.html', 'about.html'):
                issues.append('%s : JSON-LD contient « %s »' % (f, w))

print('\n%d problème(s)' % len(issues))
for i in issues:
    print('  ✗', i)
