#!/usr/bin/env python3
"""7개 cell/m1 파일의 하드코딩 제거 마이그레이션 스크립트.

두 그룹을 처리:
  Group A (cell1,cell2,m1_1,m1_2,m1_3): .format() 호환 ({{}} CSS), {bg_url} placeholder
  Group B (cell7,cell8): raw string ({} CSS), no .format()
"""
import re
import sys
from pathlib import Path

DIR = Path(__file__).parent

# ── DQ 규칙 매핑 ──
DQ_MAP = {
    40: ("_CTA_PX", "CTA_MIN_PX"),
    64: ("_SUBHEAD_PX", "SUBHEAD_MIN_PX"),
    84: ("_HEADLINE_PX", "HEADLINE_MIN_PX"),
    96: ("_METRIC_PX", "CORE_METRIC_MIN_PX"),
}

LH_RATIO_MAP = {
    "1.3": ("_LH_RATIO", "HEAD_SUB_RATIO"),
}

# ── 파일 그룹 정의 ──
GROUP_A = [
    "gen_cell1_incar_fair_banners.py",
    "gen_cell2_incar_leader_banners.py",
    "gen_m1_1_fair_banners.py",
    "gen_m1_2_leader_banners.py",
    "gen_m1_3_support_banners.py",
]

GROUP_B = [
    "gen_cell7_snu_fair_banners.py",
    "gen_cell8_snu_leader_banners.py",
]

FONT_PATH_HARD = "file:///home/jay/.local/share/fonts/Pretendard"
WORKSPACE_HARD = "/home/jay/workspace/"


def find_template_ranges(source: str):
    """HTML 템플릿 문자열의 (start, end) 범위 반환."""
    ranges = []
    # Pattern: VAR = """\  or VAR = """<!DOCTYPE
    for m in re.finditer(r'= (""")(\\?\s*<!DOCTYPE|\\?\n<!DOCTYPE|<!DOCTYPE)', source):
        start = m.start(1)
        # Find closing """
        end = source.find('"""', start + 3)
        if end != -1:
            end += 3
            ranges.append((start, end))
    return ranges


def collect_font_sizes(template: str) -> set:
    """템플릿 내 font-size: Npx 값 수집."""
    return {int(m.group(1)) for m in re.finditer(r'font-size:\s*(\d+)px', template)}


def collect_line_heights(template: str) -> dict:
    """템플릿 내 line-height 값 수집. {원본문자열: 값}."""
    result = {}
    # px values: line-height: 48px
    for m in re.finditer(r'line-height:\s*(\d+)px', template):
        result[m.group(0)] = ("px", int(m.group(1)))
    # ratio values: line-height: 1.25
    for m in re.finditer(r'line-height:\s*(\d+\.\d+)', template):
        if "px" not in m.group(0):
            result[m.group(0)] = ("ratio", m.group(1))
    return result


def var_name_for_size(px: int) -> str:
    if px in DQ_MAP:
        return DQ_MAP[px][0]
    return f"_SIZE_{px}PX"


def var_def_for_size(px: int) -> str:
    if px in DQ_MAP:
        return f"{DQ_MAP[px][0]} = {DQ_MAP[px][1]}"
    return f"_SIZE_{px}PX = {px}"


def var_name_for_lh(kind: str, val) -> str:
    if kind == "px":
        return f"_LH_{val}PX"
    # ratio
    if str(val) in LH_RATIO_MAP:
        return LH_RATIO_MAP[str(val)][0]
    safe = str(val).replace(".", "_")
    return f"_LH_{safe}"


def var_def_for_lh(kind: str, val) -> str:
    if kind == "px":
        return f"_LH_{val}PX = {val}"
    if str(val) in LH_RATIO_MAP:
        name, ref = LH_RATIO_MAP[str(val)]
        return f"{name} = {ref}"
    safe = str(val).replace(".", "_")
    return f"_LH_{safe} = {val}"


def needed_imports(sizes: set, lh_dict: dict, has_font_path: bool, has_workspace_path: bool) -> tuple:
    """필요한 gen_config import 심볼 목록과 변수 정의 반환."""
    symbols = set()
    if has_workspace_path:
        symbols.add("WORKSPACE_ROOT")
    if has_font_path:
        symbols.add("FONT_DIR")

    var_defs = []
    for px in sorted(sizes):
        if px in DQ_MAP:
            symbols.add(DQ_MAP[px][1])
        var_defs.append(var_def_for_size(px))

    for original, (kind, val) in sorted(lh_dict.items(), key=lambda x: str(x[1])):
        if kind == "ratio" and str(val) in LH_RATIO_MAP:
            symbols.add(LH_RATIO_MAP[str(val)][1])
        var_defs.append(var_def_for_lh(kind, val))

    # Deduplicate var_defs
    seen = set()
    unique_defs = []
    for d in var_defs:
        name = d.split("=")[0].strip()
        if name not in seen:
            seen.add(name)
            unique_defs.append(d)

    return sorted(symbols), unique_defs


def convert_template_group_a(template: str, sizes: set, lh_dict: dict) -> str:
    """Group A: {{}} CSS + {bg_url} 패턴 f-string 변환."""
    # 1. Escape {bg_url} → {{bg_url}}
    template = re.sub(r'\{(bg_url)\}', r'{{\1}}', template)

    # 2. Replace font-size values
    for px in sizes:
        vname = var_name_for_size(px)
        template = re.sub(
            rf'font-size:\s*{px}px',
            f'font-size: {{{vname}}}px',
            template
        )

    # 3. Replace line-height values
    for original, (kind, val) in lh_dict.items():
        vname = var_name_for_lh(kind, val)
        if kind == "px":
            template = template.replace(
                f'line-height: {val}px',
                f'line-height: {{{vname}}}px'
            )
            # Also handle compact form: line-height: Npx;
            template = template.replace(
                f'line-height:{val}px',
                f'line-height:{{{vname}}}px'
            )
        else:
            template = template.replace(
                f'line-height: {val}',
                f'line-height: {{{vname}}}'
            )

    # 4. Replace font path
    template = template.replace(FONT_PATH_HARD, f'file://{{FONT_DIR}}')

    # 5. Add f prefix: """ → f"""
    template = "f" + template

    return template


def convert_template_group_b(template: str, sizes: set, lh_dict: dict) -> str:
    """Group B: raw {} CSS (no .format()) f-string 변환."""
    # Remove the opening """ and closing """
    prefix = template[:3]  # """
    suffix = template[-3:]  # """

    inner = template[3:-3]

    # 1. Double all { → {{ and } → }}
    inner = inner.replace("{", "{{").replace("}", "}}")

    # 2. Replace font-size values (now inside doubled braces)
    for px in sizes:
        vname = var_name_for_size(px)
        inner = re.sub(
            rf'font-size:\s*{px}px',
            f'font-size: {{{vname}}}px',
            inner
        )

    # 3. Replace line-height values
    for original, (kind, val) in lh_dict.items():
        vname = var_name_for_lh(kind, val)
        if kind == "px":
            inner = inner.replace(
                f'line-height: {val}px',
                f'line-height: {{{vname}}}px'
            )
            inner = inner.replace(
                f'line-height:{val}px',
                f'line-height:{{{vname}}}px'
            )
        else:
            inner = inner.replace(
                f'line-height: {val}',
                f'line-height: {{{vname}}}'
            )

    # 4. Replace font path (was doubled by step 1, now undo for FONT_DIR ref)
    doubled_font_path = FONT_PATH_HARD.replace("{", "{{").replace("}", "}}")
    inner = inner.replace(doubled_font_path, f'file://{{FONT_DIR}}')

    # 5. Reconstruct with f prefix
    return f'f{prefix}{inner}{suffix}'


def add_imports_and_tokens(source: str, symbols: list, var_defs: list, filename: str) -> str:
    """Import와 디자인 토큰 삽입."""
    if not symbols:
        return source

    import_line = f"from gen_config import {', '.join(symbols)}"

    # Find insertion point: after last TOP-LEVEL import (no indentation)
    lines = source.split('\n')
    insert_idx = 0
    for i, line in enumerate(lines):
        stripped = line.strip()
        # Only consider top-level statements (not indented)
        if line and line[0] != ' ' and line[0] != '\t':
            if stripped.startswith('import ') or stripped.startswith('from '):
                insert_idx = i + 1
            elif stripped.startswith('sys.path.insert'):
                insert_idx = i + 1
        # Also handle gcloud_auth at any indentation level
        if line and line[0] != ' ' and line[0] != '\t' and 'gcloud_auth' in stripped:
            insert_idx = i + 1

    # Build insertion block
    block = [import_line, ""]
    if var_defs:
        block.append("# ── 디자인 토큰 ──")
        block.extend(var_defs)
        block.append("")

    lines[insert_idx:insert_idx] = block
    return '\n'.join(lines)


def replace_workspace_paths(source: str) -> str:
    """Path("/home/jay/workspace/...") → WORKSPACE_ROOT / "..." 치환."""
    # Pattern: Path("/home/jay/workspace/xxx")
    source = re.sub(
        r'Path\("' + re.escape(WORKSPACE_HARD) + r'([^"]+)"\)',
        r'WORKSPACE_ROOT / "\1"',
        source
    )
    # Pattern: "/home/jay/workspace/xxx" (standalone string, not in f-string)
    # Be careful not to replace in docstrings or comments
    return source


def process_file(filename: str, group: str) -> dict:
    """파일 처리."""
    filepath = DIR / filename
    source = filepath.read_text(encoding="utf-8")
    original = source

    # 1. Find template ranges
    ranges = find_template_ranges(source)
    if not ranges:
        return {"file": filename, "status": "SKIP", "reason": "no templates found"}

    # 2. Collect all font-size and line-height values from templates
    all_sizes = set()
    all_lh = {}
    has_font_path = FONT_PATH_HARD in source
    has_workspace_path = WORKSPACE_HARD in source

    for start, end in ranges:
        template = source[start:end]
        all_sizes |= collect_font_sizes(template)
        all_lh.update(collect_line_heights(template))

    # 3. Convert templates (process from end to start to preserve positions)
    for start, end in reversed(ranges):
        template = source[start:end]
        if group == "A":
            converted = convert_template_group_a(template, all_sizes, all_lh)
        else:
            converted = convert_template_group_b(template, all_sizes, all_lh)
        source = source[:start] + converted + source[end:]

    # 4. Replace workspace paths
    source = replace_workspace_paths(source)

    # 5. Add imports and token definitions
    symbols, var_defs = needed_imports(all_sizes, all_lh, has_font_path, has_workspace_path)
    source = add_imports_and_tokens(source, symbols, var_defs, filename)

    # 6. Write
    filepath.write_text(source, encoding="utf-8")

    return {
        "file": filename,
        "status": "OK",
        "sizes": sorted(all_sizes),
        "lh_count": len(all_lh),
        "symbols": symbols,
    }


def main():
    print("=" * 60)
    print("7개 파일 마이그레이션 시작")
    print("=" * 60)

    results = []
    for f in GROUP_A:
        r = process_file(f, "A")
        results.append(r)
        print(f"  [{r['status']}] {f} — sizes={r.get('sizes','')}, lh={r.get('lh_count','')}")

    for f in GROUP_B:
        r = process_file(f, "B")
        results.append(r)
        print(f"  [{r['status']}] {f} — sizes={r.get('sizes','')}, lh={r.get('lh_count','')}")

    # Verify
    print("\n" + "=" * 60)
    print("검증")
    print("=" * 60)

    errors = 0
    for f in GROUP_A + GROUP_B:
        filepath = DIR / f
        content = filepath.read_text()
        # Check font-size hardcoding
        fs_count = len(re.findall(r'font-size:\s*\d+px', content))
        lh_px_count = len(re.findall(r'line-height:\s*\d+px', content))
        lh_ratio_count = len(re.findall(r'line-height:\s*\d+\.\d+[^p]', content))
        font_path = content.count(FONT_PATH_HARD)
        ws_path = len(re.findall(r'Path\("' + re.escape(WORKSPACE_HARD), content))

        issues = []
        if fs_count: issues.append(f"font-size:{fs_count}")
        if lh_px_count: issues.append(f"lh-px:{lh_px_count}")
        # lh-ratio in templates only (not in variable defs)
        if font_path: issues.append(f"font-path:{font_path}")
        if ws_path: issues.append(f"ws-path:{ws_path}")

        if issues:
            print(f"  [FAIL] {f}: {', '.join(issues)}")
            errors += 1
        else:
            print(f"  [OK] {f}")

    # AST check
    print("\nAST 파싱 검증:")
    import ast
    for f in GROUP_A + GROUP_B:
        filepath = DIR / f
        try:
            ast.parse(filepath.read_text())
            print(f"  [OK] {f}")
        except SyntaxError as e:
            print(f"  [FAIL] {f}: {e}")
            errors += 1

    if errors:
        print(f"\n총 {errors}건 에러 발견")
        sys.exit(1)
    else:
        print("\n모든 검증 통과!")


if __name__ == "__main__":
    main()
