#!/usr/bin/env python3
"""Validate final/social-posts.md — the mechanical half of the social gate.

Structure is enforced (hard fail); voice is the reviewer's job. Checks:
sections present, each with a fenced post; X post ≤280 chars; balanced
fences; no chat-gutter artifacts (the paste-from-chat failure); the video
path named; the film's verdict language present (warn only).

Usage: check_social.py [<project-dir>] [<pack-path>]
Env:   BRAND_PROJECT, BRAND_PACK (used when the args are omitted, as the runner
       does). Runnable standalone so copy can be checked while it is written,
       not only at the gate. Exit 0 ok, 1 fail.
"""
import os
import re
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from packlib import parse_pack  # noqa: E402

_args = sys.argv[1:]
PROJ = _args[0] if _args else os.environ.get("BRAND_PROJECT")
_pack = _args[1] if len(_args) > 1 else os.environ.get("BRAND_PACK")
if not PROJ or not _pack:
    sys.exit("social: need a project dir + pack (args, or BRAND_PROJECT/BRAND_PACK)")
os.environ.setdefault("BRAND_PROJECT", PROJ)
PACK = parse_pack(_pack)
SLUG = os.path.basename(PROJ.rstrip("/"))
PATH = os.path.join(PROJ, "final", "social-posts.md")

errors = []
warns = []

if not os.path.isfile(PATH):
    sys.exit(f"social: {PATH} missing")
s = open(PATH, encoding="utf-8").read()

if s.count("```") % 2:
    errors.append("unbalanced ``` fences")
if "▎" in s:
    errors.append("chat-gutter artifact (▎) — copy came from a chat UI, not this file")
if f"final/videos/{SLUG}.mp4" not in s:
    errors.append(f"video path final/videos/{SLUG}.mp4 not named in the header")

posts = {}
for sec in ("LinkedIn", "X", "Instagram"):
    if f"## {sec}" not in s:
        errors.append(f"missing section ## {sec}")
        continue
    chunks = s.split(f"## {sec}")[1].split("```")
    if len(chunks) < 3 or not chunks[1].strip():
        errors.append(f"## {sec} has no fenced post block")
        continue
    posts[sec] = chunks[1].strip()

# the X section carries the post AND any self-reply blocks; label them for the
# length check below
x_blocks = []
if "## X" in s:
    _xsec = s.split("## X", 1)[1].split("\n## ", 1)[0]
    _fenced = [b.strip() for i, b in enumerate(_xsec.split("```")) if i % 2 == 1 and b.strip()]
    for i, b in enumerate(_fenced):
        x_blocks.append(("post" if i == 0 else f"reply {i}", b))

# X counts any URL as 23 characters however long it is, so a naive len() both
# over-counts a link-bearing post and under-counts nothing — measure it X's way.
_URL_RE = re.compile(r"\b(?:https?://)?[a-z0-9.-]+\.[a-z]{2,}(?:/\S*)?", re.I)


def xlen(text):
    return len(_URL_RE.sub("x" * 23, text))


# EVERY fenced block under ## X is a post, not just the first. The reply is where
# the source link and the hashtags live, so it is the block most likely to grow
# past the limit — and it was the one nobody measured: a corrected caveat pushed
# one reply to 306 characters and this file still said OK.
for label, text in x_blocks:
    n = xlen(text)
    if n > 280:
        errors.append(
            f"X {label} is {n} chars as X counts them (limit 280; a URL counts as 23 "
            f"however long). Cut {n - 280}."
        )
if "LinkedIn" in posts:
    w = len(posts["LinkedIn"].split())
    if not 80 <= w <= 320:
        warns.append(f"LinkedIn post is {w} words (target ~150-250)")

verdict = PACK.get("MYTAKE_VERDICT_1", "").rstrip(",.")
if verdict and not any(verdict.lower() in p.lower() for p in posts.values()):
    warns.append(f'verdict language "{verdict}" not found in any post — copy should mirror the film')

# The copy is authored ONCE, at a gate, and then the pack keeps moving. The opus5
# copy was written at 11:47 and validated clean twice while three pack facts moved
# under it at 12:30 — including the caveat framing, which the copy then contradicted
# in all three posts. Structure was fine; agreement was not, and no wording check
# can see a framing contradiction. What IS decidable: whether the copy is older than
# the pack it is supposed to mirror.
pack_mtime = os.path.getmtime(_pack)
copy_mtime = os.path.getmtime(PATH)
if pack_mtime > copy_mtime + 1:
    from datetime import datetime as _dt

    def _t(ts):
        return _dt.fromtimestamp(ts).strftime("%H:%M:%S")

    errors.append(
        f"the pack changed AFTER the copy was written ({os.path.basename(_pack)} "
        f"{_t(pack_mtime)} > social-posts.md {_t(copy_mtime)}). The copy mirrors the "
        f"film's claims and verdict, so a pack edit can silently put the two out of "
        f"agreement — read {PATH} against the current pack, fix what moved, and save "
        f"it (that alone clears this)."
    )

for w in warns:
    print(f"social warn: {w}")
if errors:
    for e in errors:
        print(f"social FAIL: {e}", file=sys.stderr)
    sys.exit(1)
print(f"social OK: {', '.join(f'{k} {len(v)}ch' for k, v in posts.items())}")
