#!/usr/bin/env python3
"""디자인 브리프 폰트 규칙 검증 스크립트.

마케팅팀이 디자인팀에 전달하는 브리프의 폰트 사이즈가
DQ 규칙(단일 소스: memory/specs/dq-rules.json)을 준수하는지 자동 검증한다.

사용법:
    python3 tools/validate-design-brief.py <브리프파일.md>
    python3 tools/validate-design-brief.py <브리프파일.md> --json

★ 모든 규칙은 memory/specs/dq-rules.json에서 로드됨.
  규칙 변경 시 dq-rules.json만 수정하면 이 스크립트에 자동 반영.
"""

import re
import sys
import json
from pathlib import Path

# ── DQ 규칙 단일 소스에서 로드 ──
from dq_rules import (
    DQ_RULES,
    ABSOLUTE_MIN_PX,
    MIN_FONT_FAMILIES,
    BANNED_FONTS,
    get_font_rule,
    get_all_font_rules,
    rules_version,
)

# get_all_font_rules()에서 역할별 규칙을 가져와 FONT_SIZE_RULES 구성
FONT_SIZE_RULES = get_all_font_rules()

KNOWN_FONTS = {
    "Pretendard", "Noto Sans KR", "Black Han Sans", "나눔명조",
    "나눔스퀘어", "나눔스퀘어 Neo", "에스코어드림", "마루부리",
    "GMarket Sans", "카카오 큰글씨", "카카오 작은글씨", "어그로체",
    "본명조",
}

# ── 파서 ──

# px 값 추출: "44px", "44-48px", "최소 36px" 등
PX_PATTERN = re.compile(r'(\d+)\s*(?:~|[-–])\s*(\d+)\s*px|(\d+)\s*px', re.IGNORECASE)

# 폰트가 아닌 px 값 컨텍스트 (오탐 방지)
NON_FONT_CONTEXTS = re.compile(
    r'(구분선|border|라운드|radius|패딩|padding|margin|간격|spacing|gap|'
    r'line-height|행간|라인\s*간격|터치\s*영역|그리드|grid|'
    r'max-width|min-width|max-height|min-height|width|height|'
    r'벗어나|사이즈\s*정확|해상도|dpi|안전\s*영역|blur|opacity|shadow|'
    r'검수|PASS|FAIL|0건|기준|'
    r'dq-rules|단일\s*소스|font_sizes|font_pairing|font_ratio)',
    re.IGNORECASE
)

# font-weight 추출: "font-weight 800", "Bold", "ExtraBold" 등
WEIGHT_MAP = {
    "thin": 100, "hairline": 100,
    "extralight": 200, "ultra-light": 200,
    "light": 300,
    "regular": 400, "normal": 400,
    "medium": 500,
    "semibold": 600, "semi-bold": 600, "demibold": 600,
    "bold": 700,
    "extrabold": 800, "extra-bold": 800, "ultra-bold": 800,
    "black": 900, "heavy": 900,
}


def extract_px_values(text: str) -> list[int]:
    """텍스트에서 모든 px 값을 추출."""
    values = []
    for match in PX_PATTERN.finditer(text):
        if match.group(1) and match.group(2):
            values.append(int(match.group(1)))
            values.append(int(match.group(2)))
        elif match.group(3):
            values.append(int(match.group(3)))
    return values


def detect_role(line: str) -> str | None:
    """라인에서 텍스트 역할(헤드라인/서브/CTA 등)을 감지."""
    lower = line.lower()
    normalized = line.replace(" ", "")

    role_patterns = [
        (["핵심 수치", "핵심수치", "숫자/강조", "숫자 강조", "수치 강조"], "핵심 수치"),
        (["헤드라인", "headline", "메인 카피", "메인카피", "main copy"], "헤드라인"),
        (["서브헤드", "subhead", "서브 카피", "서브카피", "sub copy", "본문"], "서브헤드"),
        (["cta", "버튼", "button"], "CTA"),
        (["면책", "disclaimer", "최소"], "면책"),
    ]
    for keywords, role in role_patterns:
        for kw in keywords:
            if kw.lower() in lower or kw.lower() in normalized.lower():
                return role
    return None


def detect_fonts(content: str) -> set[str]:
    """브리프에서 사용된 폰트 이름을 추출."""
    found = set()
    for font in KNOWN_FONTS:
        if font in content:
            found.add(font)
    for font in BANNED_FONTS:
        if font in content:
            found.add(font)
    return found


def validate_brief(filepath: str) -> dict:
    """브리프 파일을 검증하고 결과를 반환."""
    path = Path(filepath)
    if not path.exists():
        return {"status": "error", "message": f"파일 없음: {filepath}"}

    content = path.read_text(encoding="utf-8")
    lines = content.split("\n")

    errors = []
    warnings = []
    info = []

    # ── 1. 폰트 사이즈 검증 ──
    all_px_values = []

    for i, line in enumerate(lines, 1):
        px_values = extract_px_values(line)
        if not px_values:
            continue

        # 폰트가 아닌 px 값(border, padding, radius, 간격 등) 스킵
        if NON_FONT_CONTEXTS.search(line):
            continue

        all_px_values.extend(px_values)
        role = detect_role(line)

        # 절대 최소 위반 체크
        for px in px_values:
            if px < ABSOLUTE_MIN_PX:
                errors.append({
                    "line": i,
                    "type": "FONT_SIZE_VIOLATION",
                    "severity": "CRITICAL",
                    "message": f"40px 미만 절대 금지 위반: {px}px (라인: {line.strip()[:80]})",
                    "fix": f"{px}px → {ABSOLUTE_MIN_PX}px 이상으로 변경",
                })

        # 역할별 최소 사이즈 체크
        if role and role in FONT_SIZE_RULES:
            rule = FONT_SIZE_RULES[role]
            min_px_in_line = min(px_values)
            if min_px_in_line < rule["min_px"]:
                errors.append({
                    "line": i,
                    "type": "FONT_SIZE_VIOLATION",
                    "severity": "ERROR",
                    "message": f"{role} 최소 {rule['min_px']}px 위반: {min_px_in_line}px",
                    "fix": f"{min_px_in_line}px → {rule['min_px']}px 이상으로 변경",
                })

    # ── 2. 폰트 종류 검증 ──
    fonts = detect_fonts(content)
    banned_found = fonts & BANNED_FONTS
    valid_fonts = fonts - BANNED_FONTS

    # 금지 폰트가 "금지" 맥락에서만 언급된 경우(예: "금지 폰트: 궁서체") 제외
    # 같은 라인 또는 직전 5줄 내 "금지" 키워드가 있으면 허용 컨텍스트로 판단
    actually_banned = set()
    for font in banned_found:
        for i, line in enumerate(lines):
            if font in line:
                context_lines = lines[max(0, i - 5):i + 1]
                context_text = " ".join(context_lines)
                if not re.search(r'금지|banned|불가|사용하지|쓰지', context_text, re.IGNORECASE):
                    actually_banned.add(font)
                    break

    if actually_banned:
        errors.append({
            "type": "BANNED_FONT",
            "severity": "ERROR",
            "message": f"금지 폰트 사용: {', '.join(actually_banned)}",
            "fix": "Pretendard, Noto Sans KR, Black Han Sans 등 허용 폰트로 교체",
        })

    if len(valid_fonts) < MIN_FONT_FAMILIES:
        warnings.append({
            "type": "FONT_DIVERSITY",
            "severity": "WARNING",
            "message": f"폰트 {len(valid_fonts)}종 감지 — 최소 {MIN_FONT_FAMILIES}종 필수 "
                       f"(감지된 폰트: {', '.join(valid_fonts) if valid_fonts else '없음'})",
            "fix": "메인(Black Han Sans/나눔명조) + 서브(Pretendard/Noto Sans KR) 조합 사용",
        })

    # ── 3. 텍스트 최소화 검증 ──
    slide_sections = re.findall(r'###\s*슬라이드\s*\d+', content)
    if slide_sections:
        info.append(f"슬라이드 {len(slide_sections)}개 감지")

    # ── 4. 3초 가독성 테스트 언급 체크 ──
    if "3초" not in content and "가독성" not in content:
        warnings.append({
            "type": "READABILITY_CHECK",
            "severity": "WARNING",
            "message": "3초 가독성 테스트 언급 없음 — 디자인팀 QC 기준 포함 권장",
        })

    # ── 결과 집계 ──
    critical_count = sum(1 for e in errors if e.get("severity") == "CRITICAL")
    error_count = sum(1 for e in errors if e.get("severity") == "ERROR")
    warning_count = len(warnings)

    passed = critical_count == 0 and error_count == 0

    return {
        "status": "PASS" if passed else "FAIL",
        "file": str(path),
        "summary": {
            "critical": critical_count,
            "errors": error_count,
            "warnings": warning_count,
            "total_px_values_found": len(all_px_values),
            "fonts_detected": sorted(list(fonts)),
        },
        "errors": errors,
        "warnings": warnings,
        "info": info,
    }


def format_result(result: dict) -> str:
    """결과를 읽기 좋은 텍스트로 포맷."""
    lines = []
    status = result["status"]
    icon = "✅" if status == "PASS" else "❌"

    lines.append(f"{icon} 디자인 브리프 검증: {status}")
    lines.append(f"   파일: {result['file']}")
    lines.append("")

    s = result["summary"]
    lines.append(f"   폰트 사이즈 값 {s['total_px_values_found']}개 감지")
    lines.append(f"   폰트 종류: {', '.join(s['fonts_detected']) if s['fonts_detected'] else '감지 안됨'}")
    lines.append("")

    if result["errors"]:
        lines.append("── 오류 ──")
        for e in result["errors"]:
            sev = e.get("severity", "ERROR")
            line_info = f" (line {e['line']})" if "line" in e else ""
            lines.append(f"   [{sev}]{line_info} {e['message']}")
            if "fix" in e:
                lines.append(f"           → 수정: {e['fix']}")
        lines.append("")

    if result["warnings"]:
        lines.append("── 경고 ──")
        for w in result["warnings"]:
            lines.append(f"   [WARNING] {w['message']}")
            if "fix" in w:
                lines.append(f"           → 수정: {w['fix']}")
        lines.append("")

    if status == "FAIL":
        lines.append("★ 위 오류를 수정한 후 다시 검증하세요.")
        lines.append("  참고: teams/design/CLAUDE.md Section 9 (타이포그래피 계층 구조 규칙)")

    return "\n".join(lines)


def main():
    if len(sys.argv) < 2:
        print("사용법: python3 validate-design-brief.py <브리프파일.md> [--json]")
        print("")
        print("디자인 브리프의 폰트 규칙 준수 여부를 검증합니다.")
        print("")
        print("디자인팀 확정 규칙:")
        print("  핵심 수치:    96px+  (weight 900)")
        print("  헤드라인:     84px+  (weight 800-900)")
        print("  서브헤드:     64px+  (weight 600-700)")
        print("  CTA:          40px+  (weight 700-800)")
        print("  면책/최소:    40px   (weight 400-500)")
        print("  40px 미만:    절대 금지")
        print("  폰트:         최소 2종 필수")
        sys.exit(1)

    filepath = sys.argv[1]
    output_json = "--json" in sys.argv

    result = validate_brief(filepath)

    if output_json:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print(format_result(result))

    sys.exit(0 if result["status"] == "PASS" else 1)


if __name__ == "__main__":
    main()
