"""生成时由服务端注入的规则，对用户不可见。"""

from __future__ import annotations

import re
from typing import Any

# 用户明确不要按截图还原时才退出忠实模式
_SCREENSHOT_OPT_OUT = (
    "不要还原截图",
    "不按截图",
    "无需还原截图",
    "忽略截图布局",
    "自定义布局",
    "改成通用后台",
    "不要一模一样",
    "重新设计界面",
    "不要照搬截图",
)

_SCREENSHOT_OPT_IN = (
    "还原截图",
    "对齐截图",
    "一模一样",
    "截图还原",
    "screenshot_faithful",
)

DEFAULT_ACTION_LABELS: dict[str, str] = {
    "create": "新增",
    "refresh": "刷新",
    "import": "导入 Excel",
    "export": "导出 Excel",
    "edit": "编辑",
    "delete": "删除",
    "save": "保存",
    "cancel": "取消",
    "back": "返回",
    "search": "查询",
}

# 较长别名优先匹配
_ACTION_ALIASES: list[tuple[str, str]] = [
    ("导入 excel", "import"),
    ("导出 excel", "export"),
    ("导入xlsx", "import"),
    ("导出xlsx", "export"),
    ("导入csv", "import"),
    ("导出csv", "export"),
    ("导入", "import"),
    ("导出", "export"),
    ("新建", "create"),
    ("新增", "create"),
    ("添加", "create"),
    ("创建", "create"),
    ("刷新", "refresh"),
    ("重载", "refresh"),
    ("编辑", "edit"),
    ("修改", "edit"),
    ("删除", "delete"),
    ("移除", "delete"),
    ("保存", "save"),
    ("取消", "cancel"),
    ("返回", "back"),
    ("查询", "search"),
    ("搜索", "search"),
    ("筛选", "search"),
]

SYSTEM_RULES = """
[系统规则 · 用户不可见]
你是业务前端生成器。输出 App Blueprint（entities / apis / pages / widgets）。
适用于任意行业，禁止把所有应用生成成同一种通用绿主题 CRUD 壳。

还原原则（全局）：
- 有截图/HTML：默认一模一样（meta.ui_preset=screenshot_faithful，dashboard layout.preset=screenshot_faithful）。
  用户文字点名要改的才改；未提及的顶栏/导航/筛选/图表/表格/按键/配色保持与截图一致。
- 生成闭环（有截图时由服务端自动执行）：视觉模型摘录 → 代码模型出蓝图 → **发布并截取 Web 真页面**
  → 视觉模型对照原图与真页面打分 → 按差异再改蓝图，直到还原度≥95%
  （环境变量：FIDELITY_TARGET / FIDELITY_MAX_ROUNDS / FIDELITY_REAL_SCREEN）。
- 无截图：严格按用户文字与数据字段还原；勿套默认「记录列表/概览」。
- 业务名称、平台抬头、页签、按钮文案必须来自用户/截图/HTML，禁止擅自换成套话。
- 尽量写全 meta.platform_title / project_context / ui.nav_items / ui.filter_radios /
  ui.select_field / ui.filter_hint / ui.*_side_label / ui.table_title（无材料则留空，禁止编造行业词）。

其它：
- 功能按键写入 pages[].layout.actions + action_labels，原文显示。
- 业务表 apis.resources[].operations **默认**含 list/get/create/update/delete/**import**/**export**；
  list 页 layout.actions **默认**含 create/edit/delete/export/**import**/refresh。
  需求未提「导入」也要加；仅用户明确禁止导入/导出时才去掉。
- 数据：Excel/CSV 表头→字段；JSON list→表；并行数组先展平再关联。snake_case；高基数不做 enum。
- REST：GET/POST/PUT/DELETE；看板 widgets（kpi/line_chart/status_strip/table/bar/pie）按截图或需求取舍。
- 全量导入：校验失败只记错误，不提前中断。
""".strip()

SCREENSHOT_RULES = """
[截图忠实 · 用户不可见 · 适用于任意业务]
已上传界面截图。目标：展示页与截图一模一样（screenshot_faithful）。
必须从截图提取并写入蓝图：平台抬头、系统名称、页签文案、筛选控件（单选/下拉）、工具栏按键原文、
主区分区（含左侧竖标签文案）、图表类型与系列名、表格列名。
主图为「着色测点/短棒 + 设计曲线」时：meta.ui.chart_style=section_marks，并写
value_field / color_field / design_field / legend_items / y_axis_label / invert_y。
监督条为上灰下绿双段时：status_strip.variant=stacked_days，value_field+secondary_field，cycle_days。
禁止只有侧标「主图」却不配 line_chart widget 或让主图区空白。
多张图若为同一页展开/收起/滚动态：合并为一套布局，以信息最完整的一帧为准，不要生成两套互斥页面。
禁止套用其它行业模板（含铁路沉降专用文案），除非截图本身就是该业务。
""".strip()


def screenshot_opted_out(prompt: str) -> bool:
    p = prompt or ""
    return any(k in p for k in _SCREENSHOT_OPT_OUT)


def wants_screenshot_layout(prompt: str, image_count: int, html_count: int = 0) -> bool:
    """有截图或页面 HTML 即默认忠实还原；用户显式退出除外。"""
    p = prompt or ""
    if screenshot_opted_out(p):
        return False
    if image_count > 0 or html_count > 0:
        return True
    if any(k in p for k in _SCREENSHOT_OPT_IN):
        return True
    return False


def _map_button_label(raw: str) -> tuple[str, str] | None:
    """返回 (action_key, display_label)。"""
    label = re.sub(r"\s+", " ", (raw or "").strip()).strip("。.;；")
    if not label or len(label) > 32:
        return None
    low = label.lower().replace(" ", "")
    for alias, key in _ACTION_ALIASES:
        a = alias.replace(" ", "")
        if low == a or low.startswith(a) or a in low:
            return key, label
    slug = re.sub(r"[^a-z0-9_]+", "_", low)[:24].strip("_") or "action"
    return f"custom_{slug}", label


def parse_action_bar(prompt: str) -> dict[str, Any]:
    """从用户说明/截图理解中解析功能按键，得到 actions 顺序与 action_labels。"""
    p = prompt or ""
    labels: dict[str, str] = {}
    order: list[str] = []

    def add(raw: str) -> None:
        mapped = _map_button_label(raw)
        if not mapped:
            return
        key, lab = mapped
        if key not in labels:
            order.append(key)
        labels[key] = lab

    for m in re.finditer(
        r"(?:操作|功能按键|按键|按钮|工具栏|工具条)[：:]\s*(.+)",
        p,
    ):
        chunk = m.group(1).splitlines()[0]
        chunk = re.split(r"[。；;（(]", chunk, 1)[0]
        for part in re.split(r"[、，,/|｜]\s*", chunk):
            add(part)

    for m in re.finditer(
        r"(?:包含|含有|提供|支持)[：:]?\s*((?:新增|新建|刷新|导入|导出|编辑|删除|查询|保存)[^。\n]{0,40})",
        p,
    ):
        for part in re.split(r"[、，,/|｜]\s*", m.group(1)):
            add(part)

    for m in re.finditer(r"(?:工具栏|顶栏)?按钮[：:\s]+(.+)", p):
        chunk = m.group(1).splitlines()[0][:80]
        for part in re.split(r"[、，,/|｜\s]{1,}", chunk):
            if part.strip():
                add(part)

    if not order:
        return {}

    for k in ("edit", "delete"):
        if k not in labels:
            labels[k] = DEFAULT_ACTION_LABELS[k]

    toolbar = [k for k in order if k not in ("edit", "delete")]
    actions = toolbar + [k for k in ("edit", "delete") if k in labels]
    seen: set[str] = set()
    uniq: list[str] = []
    for k in actions:
        if k not in seen:
            seen.add(k)
            uniq.append(k)

    return {"actions": uniq, "action_labels": labels, "toolbar_from_user": True}


def compose_effective_prompt(
    user_prompt: str,
    image_count: int,
    vision_text: str = "",
    html_layout_text: str = "",
    html_count: int = 0,
) -> str:
    """把系统规则拼进有效提示词；控制台只传用户原文。"""
    parts = [SYSTEM_RULES]
    if wants_screenshot_layout(user_prompt, image_count, html_count):
        parts.append(SCREENSHOT_RULES)
        if html_count:
            parts.append(
                "[HTML 布局优先 · 用户不可见]\n"
                "已上传浏览器另存为的 HTML/MHTML。文案与控件名以 HTML 为准；分区与配色以截图为准。"
            )
    user = (user_prompt or "").strip()
    if user:
        parts.append("[用户需求]\n" + user)
    else:
        parts.append("[用户需求]\n（未填写文字需求：仅依据数据文件、截图与 HTML 生成。）")
    if html_layout_text:
        parts.append(html_layout_text)
    if vision_text:
        parts.append("[截图理解]\n" + vision_text)
    return "\n\n".join(parts)
