"""design-md → satori 변수 자동 매칭.

resources/design-md/{brand}/DESIGN.md 파싱 결과를 Satori 템플릿에서 사용 가능한
디자인 토큰 dict로 변환한다. 외부 API/LLM 호출은 일절 하지 않으며, 정규식 + 키워드
매칭만 사용한다.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import TypedDict

DESIGN_MD_ROOT = Path("/home/jay/workspace/resources/design-md")

# 한글 폰트 fallback 차단을 위해 항상 우선순위 폰트로 강제
_DEFAULT_FONT = "Pretendard"
KOREAN_FALLBACK = "Pretendard, 'Noto Sans KR'"
"""한글 폰트 스택 (외부 import용 — system fallback 절대 미포함)."""


class DesignTokens(TypedDict):
    primary: str
    secondary: str
    accent: str
    background: str
    text_primary: str
    text_secondary: str
    font_display: str
    font_body: str
    spacing: dict[str, int]
    border_radius: str
    shadow: list[str]


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


def load_design_md(brand: str) -> DesignTokens:
    """``resources/design-md/{brand}/DESIGN.md`` 파싱 → satori 변수 dict 반환.

    Raises:
        FileNotFoundError: 브랜드 미존재 시
        ValueError: 필수 토큰 추출 실패 시
    """
    path = DESIGN_MD_ROOT / brand / "DESIGN.md"
    if not path.exists():
        raise FileNotFoundError(f"DESIGN.md not found: {brand}")

    text = path.read_text(encoding="utf-8")

    hex_colors = _extract_hex_colors(text)
    if not hex_colors:
        raise ValueError(f"no hex colors found for brand: {brand}")

    fonts = _extract_fonts(text)

    primary = (hex_colors + ["#0f0f0f"])[0]
    secondary_candidates = hex_colors + ["#171717", "#171717"]
    secondary = secondary_candidates[1] if len(hex_colors) > 1 else secondary_candidates[0]

    return {
        "primary": primary,
        "secondary": secondary,
        "accent": _find_accent(hex_colors, text),
        "background": _find_bg(hex_colors, text),
        "text_primary": _find_text(hex_colors, text),
        "text_secondary": "#898989",
        "font_display": fonts[0] if fonts else _DEFAULT_FONT,
        "font_body": fonts[1] if len(fonts) > 1 else (fonts[0] if fonts else _DEFAULT_FONT),
        "spacing": {"sm": 8, "md": 16, "lg": 32, "xl": 64},
        "border_radius": _find_radius(text),
        "shadow": _find_shadows(text),
    }


def list_brands() -> list[str]:
    """사용 가능한 모든 brand 디렉토리 반환."""
    if not DESIGN_MD_ROOT.exists():
        return []
    return sorted(
        p.name
        for p in DESIGN_MD_ROOT.iterdir()
        if p.is_dir() and (p / "DESIGN.md").exists()
    )


def fallback_tokens() -> DesignTokens:
    """잘못된 design-md 입력 시 사용할 fallback 디자인 토큰."""
    return {
        "primary": "#0f0f0f",
        "secondary": "#171717",
        "accent": "#3ecf8e",
        "background": "#ffffff",
        "text_primary": "#111111",
        "text_secondary": "#666666",
        "font_display": _DEFAULT_FONT,
        "font_body": _DEFAULT_FONT,
        "spacing": {"sm": 8, "md": 16, "lg": 32, "xl": 64},
        "border_radius": "8px",
        "shadow": [],
    }


# ---------------------------------------------------------------------------
# Internal helpers (정규식 / 키워드 매칭)
# ---------------------------------------------------------------------------


_HEX_PATTERN = re.compile(r"`?(#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?)`?")
RGBA_PATTERN = re.compile(
    r"`?(rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}(?:\s*,\s*[\d.]+)?\s*\))`?"
)
"""rgba 색상 매처 (외부 import 가능)."""


def _extract_hex_colors(text: str) -> list[str]:
    """텍스트에서 #RRGGBB / #RRGGBBAA 색상 추출 (등장 순서 유지, 중복 제거)."""
    seen: set[str] = set()
    ordered: list[str] = []
    for match in _HEX_PATTERN.finditer(text):
        color = match.group(1).lower()
        if color not in seen:
            seen.add(color)
            ordered.append(color)
    return ordered


def _extract_fonts(text: str) -> list[str]:
    """`Primary` / `font` / `typeface` 키워드 근처에서 폰트명 추출.

    Markdown 인용 부호로 감싼 폰트명을 우선 추출하며 한글 1순위 Pretendard 보장.
    """
    fonts: list[str] = []

    # Primary/Monospace/Body/Display 라벨 라인에서 backtick 폰트명 추출
    label_re = re.compile(
        r"\*\*(Primary|Monospace|Body|Display|Heading|Secondary)\*\*\s*:?\s*`([^`]+)`",
        re.IGNORECASE,
    )
    for match in label_re.finditer(text):
        font_name = _clean_font_name(match.group(2))
        if font_name and font_name not in fonts:
            fonts.append(font_name)

    # font-family / typeface 명시 라인
    family_re = re.compile(r"font[- ]family\s*:?\s*`?([A-Za-z][A-Za-z0-9 ,'\"-]+)`?", re.IGNORECASE)
    for match in family_re.finditer(text):
        font_name = _clean_font_name(match.group(1))
        if font_name and font_name not in fonts:
            fonts.append(font_name)

    # Pretendard / Noto Sans KR이 본문 어딘가에 있다면 우선 채택
    if "Pretendard" in text and _DEFAULT_FONT not in fonts:
        fonts.insert(0, _DEFAULT_FONT)

    return fonts or [_DEFAULT_FONT]


def _clean_font_name(raw: str) -> str:
    """폰트명에서 fallback 체인을 잘라내고 첫 번째 패밀리만 반환."""
    cleaned = raw.strip().strip("`'\"")
    # 첫 콤마 앞까지만 사용
    head = cleaned.split(",")[0].strip().strip("'\"")
    # 너무 긴 키워드(설명문) 차단
    if len(head) > 40 or not head:
        return ""
    return head


def _find_accent(hex_colors: list[str], text: str) -> str:
    """`accent` / `brand` 키워드 근처 색상을 우선 반환."""
    keyword_color = _find_color_near_keywords(
        text, ("accent", "brand", "primary brand", "highlight", "link")
    )
    if keyword_color:
        return keyword_color
    if len(hex_colors) >= 3:
        return hex_colors[2]
    if hex_colors:
        return hex_colors[-1]
    return "#3ecf8e"


def _find_bg(hex_colors: list[str], text: str) -> str:
    """`background` / `canvas` / `surface` 키워드 근처 색상 반환."""
    keyword_color = _find_color_near_keywords(
        text, ("background", "canvas", "page background", "surface")
    )
    if keyword_color:
        return keyword_color
    # dark-mode 특성: 첫 색상이 어두우면 배경 후보
    if hex_colors:
        return hex_colors[0]
    return "#ffffff"


def _find_text(hex_colors: list[str], text: str) -> str:
    """`text` / `foreground` / `body` 키워드 근처 색상 반환."""
    keyword_color = _find_color_near_keywords(
        text, ("text primary", "primary text", "foreground", "body text", "text")
    )
    if keyword_color:
        return keyword_color
    if len(hex_colors) >= 4:
        return hex_colors[3]
    return "#111111"


def _find_radius(text: str) -> str:
    """border-radius 토큰 추출. 기본 ``8px``."""
    radius_re = re.compile(r"border[- ]radius\s*:?\s*`?(\d{1,4}(?:px|rem|em|%)?)`?", re.IGNORECASE)
    match = radius_re.search(text)
    if match:
        value = match.group(1)
        if value.isdigit():
            value = f"{value}px"
        return value
    # `radius (8px)` 같은 패턴
    paren_re = re.compile(r"radius[^\d`]*`?(\d{1,4})\s*px`?", re.IGNORECASE)
    match = paren_re.search(text)
    if match:
        return f"{match.group(1)}px"
    return "8px"


def _find_shadows(text: str) -> list[str]:
    """``box-shadow`` / ``shadow`` 정의 라인에서 그림자 값 추출."""
    shadows: list[str] = []
    shadow_re = re.compile(r"`((?:rgba?\([^)]+\)|\d[^`]*?)\s*(?:px|em|rem)[^`]*)`", re.IGNORECASE)
    for line in text.splitlines():
        if "shadow" not in line.lower():
            continue
        for match in shadow_re.finditer(line):
            value = match.group(1).strip()
            if value and value not in shadows and len(value) < 200:
                shadows.append(value)
    return shadows[:5]


def _find_color_near_keywords(text: str, keywords: tuple[str, ...]) -> str | None:
    """키워드가 포함된 라인에서 가장 먼저 등장하는 hex 색상을 반환."""
    lowered_keywords = [kw.lower() for kw in keywords]
    for line in text.splitlines():
        line_lower = line.lower()
        if not any(kw in line_lower for kw in lowered_keywords):
            continue
        match = _HEX_PATTERN.search(line)
        if match:
            return match.group(1).lower()
    return None
