"""Code generator — takes a Recommendation + Brief and produces frontend code.

This is intentionally a thin layer. The Recommendation has already done the
hard reasoning (industry → style → palette → type → motion → components +
guardrails). The generator's job is to:

1. Emit a design-tokens file (CSS variables or TS export) reflecting the
   recommendation.
2. Emit per-component skeletons for the picked components, in the requested
   stack.
3. Embed the guardrail comment block at the top of every generated file so
   anyone editing later sees the rules.

For v2.0.0-alpha, the generator emits stack-agnostic CSS tokens + a manifest
JSON the calling command (in Claude Code) can use to drive its own code-gen.
Stack-specific emitters land in v2.0.0-beta.

Public surface
--------------
``generate(recommendation, brief, out_dir) -> GeneratedBundle``
"""
from __future__ import annotations

import json
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Any, Dict, List, Optional

from engine.recommender.core import Brief, Recommendation


@dataclass
class GeneratedBundle:
    out_dir: str
    files_written: List[str] = field(default_factory=list)
    summary: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)


def _emit_tokens_css(rec: Recommendation) -> str:
    palette = (rec.palette or {}).get("colors", {})
    type_pair = rec.type_pair or {}
    lines = [
        "/* ux-skill v2 — generated design tokens */",
        "/* DO NOT EDIT this file directly. Re-run `ux generate` to refresh. */",
        ":root {",
    ]
    for key, value in palette.items():
        if isinstance(value, str):
            lines.append(f"  --color-{key.replace('_', '-')}: {value};")

    display = type_pair.get("display", {}).get("family")
    body = type_pair.get("body", {}).get("family")
    mono = type_pair.get("mono", {}).get("family")
    if display:
        lines.append(f"  --font-display: \"{display}\", serif;")
    if body:
        lines.append(f"  --font-body: \"{body}\", system-ui, sans-serif;")
    if mono:
        lines.append(f"  --font-mono: \"{mono}\", ui-monospace, monospace;")

    lines.append("}")
    return "\n".join(lines) + "\n"


def _emit_guardrail_header(rec: Recommendation) -> str:
    header = ["/* ux-skill v2 — generated. Active guardrails:"]
    for g in rec.guardrails[:10]:
        header.append(f" * - [{g.get('severity')}] {g.get('name')}")
    if len(rec.guardrails) > 10:
        header.append(f" * ...and {len(rec.guardrails) - 10} more in data/anti-patterns.json")
    header.append(" */")
    return "\n".join(header) + "\n\n"


def _emit_manifest(rec: Recommendation, brief: Brief) -> str:
    payload: Dict[str, Any] = {
        "brief": asdict(brief),
        "picks": {
            "style": (rec.style or {}).get("id"),
            "palette": (rec.palette or {}).get("id"),
            "type_pair": (rec.type_pair or {}).get("id"),
            "motion_presets": [m.get("id") for m in rec.motion],
            "components": [c.get("id") for c in rec.components],
            "brand_exemplars": [b.get("id") for b in rec.brand_exemplars],
        },
        "guardrails_active": len(rec.guardrails),
        "rationale": rec.rationale,
    }
    return json.dumps(payload, indent=2, ensure_ascii=False) + "\n"


def generate(
    recommendation: Recommendation,
    brief: Optional[Brief] = None,
    out_dir: str = "./.ux/generated",
) -> GeneratedBundle:
    """Write the generated tokens + manifest to ``out_dir``.

    Returns a ``GeneratedBundle`` describing what landed. Idempotent — safe
    to re-run; will overwrite files in ``out_dir``.
    """
    brief = brief or Brief()
    target = Path(out_dir)
    target.mkdir(parents=True, exist_ok=True)

    tokens_path = target / "tokens.css"
    tokens_path.write_text(
        _emit_guardrail_header(recommendation) + _emit_tokens_css(recommendation),
        encoding="utf-8",
    )

    manifest_path = target / "manifest.json"
    manifest_path.write_text(_emit_manifest(recommendation, brief), encoding="utf-8")

    return GeneratedBundle(
        out_dir=str(target),
        files_written=[str(tokens_path), str(manifest_path)],
        summary={
            "style": (recommendation.style or {}).get("id"),
            "palette": (recommendation.palette or {}).get("id"),
            "type_pair": (recommendation.type_pair or {}).get("id"),
            "guardrails": len(recommendation.guardrails),
        },
    )


# ---------------------------------------------------------------------------
# DESIGN.md emitter — the Google Stitch / awesome-design-md standard.
# A single markdown file an AI agent reads to generate on-brand UI. ux-skill
# generates a NOVEL one from a Recommendation (vs copying a frozen brand file),
# mapping the synthesized palette + type pair onto the 8-key schema. Pure
# string building: deterministic, offline, no third-party YAML dependency.
# ---------------------------------------------------------------------------

_DESIGN_MD_COLOR_RENAME = {"danger": "error"}

_DESIGN_MD_COMPONENTS = '''  button-primary:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.on-primary}"
    typography: "{typography.button}"
    rounded: "{rounded.md}"
    padding: 12px 20px
    height: 40px
  button-primary-active:
    backgroundColor: "{colors.primary-active}"
    textColor: "{colors.on-primary}"
    rounded: "{rounded.md}"
  button-secondary:
    backgroundColor: "{colors.surface}"
    textColor: "{colors.ink}"
    typography: "{typography.button}"
    rounded: "{rounded.md}"
    padding: 12px 20px
    height: 40px
  button-text-link:
    backgroundColor: transparent
    textColor: "{colors.primary}"
    typography: "{typography.button}"
  input:
    backgroundColor: "{colors.canvas}"
    textColor: "{colors.ink}"
    border: "1px solid {colors.hairline}"
    rounded: "{rounded.md}"
    padding: 10px 14px
    height: 40px
  card:
    backgroundColor: "{colors.surface}"
    textColor: "{colors.body}"
    border: "1px solid {colors.hairline}"
    rounded: "{rounded.lg}"
    padding: 24px'''


def _strip_dashes(text: str) -> str:
    """No em-dashes or en-dashes (house rule + keeps the YAML scalar clean)."""
    return (text or "").replace("—", "-").replace("–", "-").replace("‒", "-")


def _emit_design_md(rec: Recommendation, brief: Optional[Brief] = None) -> str:
    palette = rec.palette or {}
    colors = palette.get("colors", {})
    tp = rec.type_pair or {}
    style = rec.style or {}

    display = (tp.get("display") or {}).get("family") or "Georgia"
    body = (tp.get("body") or {}).get("family") or "system-ui"
    mono = (tp.get("mono") or {}).get("family") or "ui-monospace"
    pal_name = palette.get("name") or palette.get("id") or "custom palette"
    style_name = style.get("name") or style.get("id") or "custom"

    project = "ux-skill"
    if brief is not None:
        brand = getattr(brief, "brand", None)
        if isinstance(brand, dict) and brand.get("name"):
            project = str(brand["name"])
        elif getattr(brief, "project_type", None):
            project = str(brief.project_type)
    project = (_strip_dashes(project).strip() or "ux-skill").replace(" ", "-").lower()

    description = _strip_dashes(
        f"{style_name} system on the {pal_name} palette. "
        f"Display type {display}, body {body}, mono {mono}. "
        f"Generated by ux-skill: deterministic, offline, never calls an LLM."
    ).replace('"', "'")

    out: List[str] = ["---", "version: alpha", f"name: {project}-design-analysis",
                      f'description: "{description}"', "colors:"]
    for key, value in colors.items():
        if isinstance(value, str):
            dk = _DESIGN_MD_COLOR_RENAME.get(key, key).replace("_", "-")
            out.append(f'  {dk}: "{value}"')
    out.append('  on-primary: "#ffffff"')

    out.append("typography:")
    for tok, fam, sz, wt, lh, ls in [
        ("display-xl", display, 64, 600, "1.05", "-1.5"),
        ("display-lg", display, 48, 600, "1.1", "-1"),
        ("display-md", display, 36, 600, "1.15", "-0.5"),
        ("display-sm", display, 28, 600, "1.2", "-0.3"),
        ("body", body, 16, 400, "1.6", "0"),
        ("body-sm", body, 14, 400, "1.55", "0"),
        ("button", body, 15, 500, "1", "0.1"),
        ("caption", body, 12, 400, "1.4", "0.2"),
        ("code", mono, 13, 400, "1.5", "0"),
    ]:
        out.append(f"  {tok}:")
        out += [f'    fontFamily: "{fam}"', f"    fontSize: {sz}px",
                f"    fontWeight: {wt}", f"    lineHeight: {lh}", f"    letterSpacing: {ls}px"]

    out.append("spacing:")
    out += [f"  {k}: {v}px" for k, v in
            [("xxs", 4), ("xs", 8), ("sm", 12), ("md", 16), ("lg", 24), ("xl", 32), ("xxl", 48), ("xxxl", 64)]]

    out.append("rounded:")
    out += [f"  {k}: {v}" for k, v in
            [("xs", "4px"), ("sm", "6px"), ("md", "8px"), ("lg", "12px"), ("xl", "16px"), ("pill", "9999px"), ("full", "9999px")]]

    out.append("components:")
    out.append(_DESIGN_MD_COMPONENTS)
    out.append("---")
    return "\n".join(out) + "\n"


def design_md(rec: Recommendation, brief: Optional[Brief] = None,
              out_path: str = "./DESIGN.md") -> Dict[str, Any]:
    """Write a DESIGN.md from a Recommendation. Returns a small summary dict."""
    target = Path(out_path)
    if str(target.parent) not in ("", "."):
        target.parent.mkdir(parents=True, exist_ok=True)
    content = _emit_design_md(rec, brief)
    target.write_text(content, encoding="utf-8")
    palette = rec.palette or {}
    return {
        "file_written": str(target),
        "format": "DESIGN.md (Google Stitch / awesome-design-md standard)",
        "bytes": len(content),
        "picks": {
            "palette": palette.get("id"),
            "type_pair": (rec.type_pair or {}).get("id"),
            "colors": len([v for v in palette.get("colors", {}).values() if isinstance(v, str)]) + 1,
        },
    }
