#!/usr/bin/env python3
"""가독성/인터랙션 검증기 — Playwright로 실제 렌더링을 열어 눈으로 보이는 문제를 기계적으로 잡는다.

사용:
  python3 verify_readability.py <url 또는 파일경로>
  python3 verify_readability.py https://programmer-park.tistory.com/75
  python3 verify_readability.py file:///home/dtsli/.../content.html

검사 항목:
  1. .book-root 내부 텍스트 보유 요소 전수 — 전경/배경 대비 WCAG AA(4.5:1) 미달 검출
     (스킨 기본색이 새어나와 글자가 안 보이는 케이스 — 이번 사고 원인 — 를 정확히 잡음)
  2. details.acc(아코디언) 전수 클릭 — open 상태가 실제로 토글되는지 인터랙션 검증

실패 시 exit code 1 + 문제 목록 출력. 발행 파이프라인에서 --publish 뒤에 항상 이걸 통과해야
"완료"로 보고한다 (박씨 2026-07-14 지시: 가독성 체크 플래그 상시 의무화).
"""
import asyncio
import sys

MIN_CONTRAST = 4.5  # WCAG AA, 일반 텍스트 기준


def _luminance(rgb):
    def chan(c):
        c = c / 255
        return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
    r, g, b = rgb
    return 0.2126 * chan(r) + 0.7152 * chan(g) + 0.0722 * chan(b)


def _contrast(rgb1, rgb2):
    l1, l2 = _luminance(rgb1) + 0.05, _luminance(rgb2) + 0.05
    return max(l1, l2) / min(l1, l2)


async def verify(url: str, screenshot_path: str = "/tmp/verify_readability.png") -> list[str]:
    from playwright.async_api import async_playwright

    fails: list[str] = []
    async with async_playwright() as p:
        b = await p.chromium.launch(headless=True)
        page = await b.new_page(viewport={"width": 1280, "height": 1000})
        await page.goto(url, wait_until="networkidle", timeout=30000)
        await page.wait_for_timeout(1200)

        # 1) 대비 검사 — 배경은 조상 체인을 실제 알파 합성해서 계산 (반투명 오버레이를
        #    "배경 그 자체"로 오인해 오탐 내는 문제 방지). 이모지 단독 텍스트는 색상 검사 제외.
        results = await page.evaluate(
            """() => {
                const root = document.querySelector('.book-root');
                if (!root) return {error: 'book-root not found'};
                function parseRGBA(s) {
                    const m = (s || '').match(/rgba?\\(([^)]+)\\)/);
                    if (!m) return [255,255,255,1];
                    const p = m[1].split(',').map(x => parseFloat(x));
                    return [p[0], p[1], p[2], p.length > 3 ? p[3] : 1];
                }
                function effectiveBg(el) {
                    const chain = [];
                    let cur = el;
                    while (cur) { chain.push(parseRGBA(getComputedStyle(cur).backgroundColor)); cur = cur.parentElement; }
                    let result = [255, 255, 255];
                    for (let i = chain.length - 1; i >= 0; i--) {
                        const [r, g, b, a] = chain[i];
                        if (a === 0) continue;
                        result = [r*a + result[0]*(1-a), g*a + result[1]*(1-a), b*a + result[2]*(1-a)];
                    }
                    return result;
                }
                const out = [];
                const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
                let node;
                while (node = walker.nextNode()) {
                    const text = Array.from(node.childNodes)
                        .filter(n => n.nodeType === 3).map(n => n.textContent).join('').trim();
                    if (!text) continue;
                    if (!/[a-zA-Z0-9\\u3131-\\uD79D]/.test(text)) continue;  // 이모지/기호만 있는 텍스트 제외
                    const cs = getComputedStyle(node);
                    out.push({tag: node.tagName, cls: node.className || '',
                              fg: parseRGBA(cs.color).slice(0,3), bg: effectiveBg(node),
                              text: text.slice(0, 40)});
                }
                return out;
            }"""
        )
        if isinstance(results, dict) and results.get("error"):
            fails.append(results["error"])
        else:
            for r in results:
                try:
                    ratio = _contrast(r["fg"], r["bg"])
                    if ratio < MIN_CONTRAST:
                        fails.append(
                            f"대비 부족 {ratio:.2f}:1 <{r['tag'].lower()} class='{r['cls']}'> \"{r['text']}\""
                        )
                except Exception:
                    pass

        # 2) 아코디언 인터랙션 검사
        accs = page.locator("details.acc")
        n = await accs.count()
        for i in range(n):
            acc = accs.nth(i)
            before = await acc.get_attribute("open")
            await acc.locator("summary").click()
            await page.wait_for_timeout(200)
            after = await acc.get_attribute("open")
            if before == after:
                fails.append(f"아코디언 #{i} 클릭해도 open 상태 안 바뀜 (open={after})")

        await page.screenshot(path=screenshot_path, full_page=True)
        await b.close()

    return fails


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("사용법: python3 verify_readability.py <url>")
        sys.exit(2)
    target = sys.argv[1]
    problems = asyncio.run(verify(target))
    if problems:
        print(f"❌ 가독성/인터랙션 검증 실패 ({len(problems)}건) — {target}")
        for p in problems:
            print(" -", p)
        sys.exit(1)
    print(f"✅ 가독성/인터랙션 검증 통과 — {target}")
    sys.exit(0)
