"""
AI 技能注册表
为 AI 助手提供可复用的任务模式、工作流和工具边界。
"""

from dataclasses import dataclass
from typing import Iterable, Optional


@dataclass(frozen=True)
class AiSkill:
    id: str
    name: str
    description: str
    mode: str
    keywords: tuple[str, ...]
    allowed_tools: tuple[str, ...]
    workflow: tuple[str, ...]
    guardrails: tuple[str, ...]
    success_criteria: tuple[str, ...]
    planning_style: str
    image_hint: str = ""
    origin: str = ""
    origin_url: str = ""
    upstream_skill: str = ""
    triage_questions: tuple[str, ...] = ()
    deliverables: tuple[str, ...] = ()
    execution_notes: tuple[str, ...] = ()

    def to_public_dict(self) -> dict:
        return {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "mode": self.mode,
            "planning_style": self.planning_style,
            "image_hint": self.image_hint,
            "origin": self.origin,
            "origin_url": self.origin_url,
            "upstream_skill": self.upstream_skill,
        }


AUTO_SKILL = {
    "id": "auto",
    "name": "智能自动选择",
    "description": "按当前问题、图片和 Photoshop 上下文，自动切换最合适的技能。",
    "mode": "auto",
    "planning_style": "先判断任务类型，再切到最匹配的专家技能。",
    "image_hint": "适合不知道该选哪个技能时使用。",
    "origin": "内置技能路由",
    "origin_url": "",
    "upstream_skill": "",
}

OPENCLAW_REPO_URL = "https://github.com/openclaw/skills"
OPENCLAW_PS_AUTOMATOR_URL = (
    "https://github.com/openclaw/skills/tree/main/skills/abdul-karim-mia/photoshop-automator"
)
OPENCLAW_UI_UX_PRO_URL = (
    "https://github.com/openclaw/skills/tree/main/skills/15349185792/ui-ux-pro-max-0-1-0"
)
OPENCLAW_UI_DESIGNER_URL = (
    "https://github.com/openclaw/skills/tree/main/skills/1999azzar/ui-designer-skill"
)


SKILLS: tuple[AiSkill, ...] = (
    AiSkill(
        id="ps-generalist",
        name="PS 全能助手",
        description="处理一般 Photoshop 操作、图层整理、文档检查、稳定执行和轻量自动化任务。",
        mode="tool_agent",
        keywords=(
            "ps",
            "photoshop",
            "图层",
            "文档",
            "画布",
            "对齐",
            "移动",
            "旋转",
            "缩放",
            "文字",
            "保存",
            "导出",
            "出图",
            "效果图",
            "不会",
            "怎么做",
            "怎么操作",
            "如何操作",
            "教我",
            "报错",
            "错误",
            "失败",
            "没反应",
            "不能",
            "无法",
            "为什么",
        ),
        allowed_tools=(
            "get_document_info",
            "get_layer_structure",
            "get_active_layer_info",
            "list_all_layer_names",
            "list_documents",
            "switch_document",
            "create_layer",
            "rename_layer",
            "delete_layer",
            "duplicate_layer",
            "create_layer_group",
            "move_layer_to_group",
            "group_layers",
            "move_layer",
            "resize_layer",
            "rotate_layer",
            "flip_layer",
            "align_layers",
            "align_layers_tool",
            "distribute_layers",
            "set_layer_visible",
            "set_layer_opacity",
            "set_layer_blend_mode",
            "bring_to_front",
            "send_to_back",
            "resize_canvas",
            "create_text_layer",
            "set_text_content",
            "set_text_color",
            "set_text_size",
            "generate_design_images",
            "save_document",
            "save_document_as",
            "undo",
            "close_document",
        ),
        workflow=(
            "先确认当前活动文档、目标图层和任务边界，再决定操作路径。",
            "优先用最少的步骤完成任务，不做多余改动，能查就先查。",
            "执行后总结结果，并明确告诉用户下一步还能继续做什么。",
        ),
        guardrails=(
            "涉及删除、关闭、覆盖保存时先征求确认。",
            "不清楚目标图层时先查询，不要盲改。",
            "能在新图层或新图层组完成的改动，不直接破坏原图层。",
            "Photoshop 若有模态弹窗或保存窗口，先提醒用户关闭，再继续工具执行。",
            "按图层名操作时要求精确匹配，不要自己猜测最像的图层。",
        ),
        success_criteria=(
            "操作路径清晰",
            "图层关系不混乱",
            "文档仍然可继续编辑",
        ),
        planning_style="先查活动文档和目标图层，再决定是查询、轻改还是执行一串可回退的 PS 操作。",
        image_hint="适合边聊边操作 Photoshop，而不是深度看图分析。",
        origin="改造自 OpenClaw 的 photoshop-automator",
        origin_url=OPENCLAW_PS_AUTOMATOR_URL,
        upstream_skill="openclaw/photoshop-automator",
        execution_notes=(
            "默认围绕当前活动文档工作；没有文档时先提示用户打开文件。",
            "执行链路尽量短、原子化，避免一次堆很多风险操作。",
            "遇到文本替换、图层修改这类任务时，先确认名称和对象，再执行。",
            "如果用户是在问不会怎么做、为什么失败或下一步怎么操作，先读取当前上下文再答疑，必要时同轮直接执行。",
        ),
    ),
    AiSkill(
        id="garment-pattern-mapper",
        name="服装套图师",
        description="处理服装成衣、裁片、花型、面料和平铺套图任务。",
        mode="tool_agent",
        keywords=(
            "套图",
            "裁片",
            "成衣",
            "服装",
            "花型",
            "花样",
            "印花",
            "面料",
            "前片",
            "后片",
            "袖子",
            "pattern",
            "garment",
        ),
        allowed_tools=(
            "get_document_info",
            "get_layer_structure",
            "list_all_layer_names",
            "identify_pieces",
            "generate_garment_preview",
            "extract_and_apply_all_pieces",
            "verify_pattern_result",
        ),
        workflow=(
            "先判断用户是要识别、预览还是正式套图。",
            "需要套图时，先识别裁片和花型模式，再生成预览或直接走 all_over 流程。",
            "正式套图后用验证结果总结问题和改进建议。",
        ),
        guardrails=(
            "生成预览后，除非用户明确确认，否则不要直接进入正式套图。",
            "识别结果不确定时要先说明，再继续下一步。",
            "没有参考图或没有有效裁片上下文时，不要假装已经完成套图。",
        ),
        success_criteria=(
            "裁片识别合理",
            "花型方向和比例尽量接近原图",
            "流程状态对用户透明",
        ),
        planning_style="把任务拆成 识别 -> 预览 -> 确认 -> 正式套图 -> 验证 五段式流程。",
        image_hint="适合用户上传成衣图、要求识别裁片、生成预览或正式套图时使用。",
    ),
    AiSkill(
        id="ps-layout-designer",
        name="PS 排版设计师",
        description="处理海报、详情页、主视觉、标题层级、品牌感和版式整理任务。",
        mode="tool_agent",
        keywords=(
            "海报",
            "排版",
            "版式",
            "标题",
            "字体",
            "封面",
            "主视觉",
            "详情页",
            "banner",
            "构图",
            "留白",
            "层级",
            "品牌感",
            "视觉方向",
            "卖点",
            "背景图",
            "效果图",
            "方案图",
        ),
        allowed_tools=(
            "get_document_info",
            "get_layer_structure",
            "list_all_layer_names",
            "get_active_layer_info",
            "create_text_layer",
            "set_text_content",
            "set_text_color",
            "set_text_size",
            "create_layer_group",
            "group_layers",
            "move_layer",
            "resize_layer",
            "align_layers_tool",
            "distribute_layers",
            "bring_to_front",
            "send_to_back",
            "set_layer_opacity",
            "set_layer_blend_mode",
            "add_stroke",
            "add_drop_shadow",
            "generate_design_images",
        ),
        workflow=(
            "先分析画布比例、已有图层、信息密度和视觉重心。",
            "先给出版式思路，再搭主标题、辅文、卖点和装饰层。",
            "最后统一对齐、层级、间距、可读性和品牌感。",
        ),
        guardrails=(
            "涉及大改版式时，优先新建组或新图层，而不是覆盖原设计。",
            "没有足够上下文时，先做查询或提出一版轻量方案。",
            "不要一次性做太多不可逆操作。",
        ),
        success_criteria=(
            "视觉层级清楚",
            "对齐和间距统一",
            "画面有主次和留白",
        ),
        planning_style="先做版式诊断和信息架构，再分步搭建图层，不要一上来就乱动。",
        image_hint="适合海报排版、标题优化、版式调整和页面重构。",
        origin="改造自 OpenClaw 的 ui-ux-pro-max / ui-designer-skill",
        origin_url=OPENCLAW_UI_UX_PRO_URL,
        upstream_skill="openclaw/ui-ux-pro-max + ui-designer-skill",
        triage_questions=(
            "当前作品更偏海报、封面、电商主图还是详情页？",
            "这次要优先提升点击率、品牌感、层级清晰度还是信息转化？",
            "有没有必须保留的品牌色、主标题、卖点或参考风格？",
        ),
        deliverables=(
            "一句话视觉方向",
            "标题/副标题/卖点的层级方案",
            "对齐、留白、构图和配色建议",
            "对应到 Photoshop 的图层调整动作",
        ),
        execution_notes=(
            "先拆信息优先级，再决定字体大小、位置和装饰强度。",
            "尽量在新组内搭结构，保留原稿便于回退和对比。",
            "如果上下文不足，先给一版轻量方向，再等用户确认后深化。",
        ),
    ),
    AiSkill(
        id="creative-direction-strategist",
        name="创意方向设计师",
        description="处理参考图拆解、品牌调性、视觉方向、配色与构图方案，把灵感转成可执行设计路线。",
        mode="hybrid_chat",
        keywords=(
            "创意",
            "方向",
            "灵感",
            "调性",
            "参考图",
            "品牌感",
            "构图",
            "配色",
            "氛围",
            "风格方案",
            "视觉方向",
            "art direction",
            "moodboard",
            "方向图",
            "概念图",
            "效果图",
            "出图",
            "方案图",
            "插画",
            "画面",
            "方向稿",
            "素材图",
        ),
        allowed_tools=(
            "get_document_info",
            "get_layer_structure",
            "list_all_layer_names",
            "create_layer_group",
            "create_text_layer",
            "align_layers_tool",
            "add_guide",
            "generate_design_images",
        ),
        workflow=(
            "先理解任务目标和参考图里最有价值的视觉线索。",
            "再给出 2 到 3 条创意方向，说明各自的调性、构图和适用场景。",
            "最后把选中的方向转成 Photoshop 可执行的排版、图层和颜色建议。",
        ),
        guardrails=(
            "不要只给抽象评价，要把风格结论落到可执行动作上。",
            "没有足够上下文时，不要假设品牌调性和目标人群。",
            "除非用户要求，不直接大改现有图层结构。",
        ),
        success_criteria=(
            "风格判断可信",
            "方向方案有区分度",
            "建议能直接落回 Photoshop",
        ),
        planning_style="先做视觉诊断，再给 2 到 3 条方向，再落成可执行的排版和图层建议。",
        image_hint="适合上传参考图后，让 AI 像设计师一样拆方向、配色、构图和执行路径。",
        origin="改造自 OpenClaw 的 ui-ux-pro-max / ui-designer-skill",
        origin_url=OPENCLAW_REPO_URL,
        upstream_skill="openclaw/ui-ux-pro-max + ui-designer-skill",
        triage_questions=(
            "这次更偏品牌升级、活动海报、电商转化还是氛围图？",
            "想保留参考图里的哪些东西：配色、构图、质感还是情绪？",
            "输出要更稳重、高级、年轻还是更强转化？",
        ),
        deliverables=(
            "2 到 3 条创意方向",
            "主视觉、标题、辅助元素的布局思路",
            "配色、字体和材质建议",
            "进入 Photoshop 后的第一轮动作清单",
        ),
        execution_notes=(
            "有参考图时先拆视觉语言，再给风格方向，不要直接开始操作。",
            "没有图片时也要先问清目标和场景，再给方案。",
            "当用户确认方向后，再切到排版或修图类技能深入执行。",
        ),
    ),
    AiSkill(
        id="product-retoucher",
        name="修图精修师",
        description="处理抠图、调色、去背、产品主图优化、亮度对比和图层修整任务。",
        mode="tool_agent",
        keywords=(
            "修图",
            "精修",
            "抠图",
            "去背",
            "调色",
            "亮度",
            "对比度",
            "磨皮",
            "主图",
            "高光",
            "阴影",
            "retouch",
        ),
        allowed_tools=(
            "get_document_info",
            "get_layer_structure",
            "get_active_layer_info",
            "duplicate_layer",
            "create_layer",
            "rasterize_layer",
            "adjust_brightness_contrast",
            "adjust_levels",
            "adjust_hsl",
            "auto_levels",
            "auto_contrast",
            "desaturate",
            "gaussian_blur",
            "create_clipping_mask",
            "release_clipping_mask",
            "add_layer_mask",
            "delete_layer_mask",
            "fill_selection",
            "inverse_selection",
            "feather_selection",
            "copy_merged_to_new_layer",
            "set_layer_blend_mode",
            "set_layer_opacity",
            "save_document",
            "save_document_as",
        ),
        workflow=(
            "先判断任务属于结构修图、颜色修正还是产品精修。",
            "优先做可回退的副本或蒙版，再进行调整。",
            "最终说明改动方向和还可以继续优化的点。",
        ),
        guardrails=(
            "不要直接破坏唯一原图层。",
            "没有明确选区或图层时先查询上下文。",
            "涉及批量覆盖时先提醒用户风险。",
        ),
        success_criteria=(
            "改动可回退",
            "主体更清晰",
            "颜色和层次更稳定",
        ),
        planning_style="优先非破坏性修图：复制、蒙版、调整，再视情况合并。",
        image_hint="适合产品主图、商品精修、调色和去背类任务。",
    ),
    AiSkill(
        id="visual-analysis-advisor",
        name="看图分析师",
        description="处理上传图片后的版型、风格、配色、元素拆解、设计建议和参考分析。",
        mode="vision_chat",
        keywords=(
            "看图",
            "分析",
            "你看见",
            "这张图",
            "参考图",
            "风格",
            "版型",
            "配色",
            "元素",
            "灵感",
            "建议",
            "评价",
        ),
        allowed_tools=(),
        workflow=(
            "先解释看到了什么，再拆解风格、结构和重点元素。",
            "如果用户有执行目标，再把分析转成可落地的 Photoshop 操作建议。",
        ),
        guardrails=(
            "不要虚构已经执行过 Photoshop 操作。",
            "分析不确定时用概率表达，不要装作绝对正确。",
        ),
        success_criteria=(
            "描述准确",
            "建议可执行",
            "重点突出",
        ),
        planning_style="先看图，再给结论，最后给下一步操作建议。",
        image_hint="适合用户上传图片后问“你看见了什么”“这个版型/风格怎么做”。",
        origin="改造自 OpenClaw 的 ui-designer-skill",
        origin_url=OPENCLAW_UI_DESIGNER_URL,
        upstream_skill="openclaw/ui-designer-skill",
        deliverables=(
            "图片内容描述",
            "风格、构图、配色和元素拆解",
            "可以继续在 Photoshop 落地的建议",
        ),
    ),
)


SKILL_BY_ID = {skill.id: skill for skill in SKILLS}


def list_ai_skills_public() -> list[dict]:
    return [AUTO_SKILL, *(skill.to_public_dict() for skill in SKILLS)]


def get_ai_skill(skill_id: Optional[str]) -> Optional[AiSkill]:
    if not skill_id or skill_id == "auto":
        return None
    return SKILL_BY_ID.get(skill_id)


def _history_text(history_messages: Iterable[dict]) -> str:
    parts: list[str] = []
    for item in history_messages:
        content = str(item.get("content", "")).strip()
        if content:
            parts.append(content)
    return "\n".join(parts[-6:])


def _score_skill(skill: AiSkill, text: str, has_image: bool) -> int:
    lowered = text.lower()
    score = 0

    for keyword in skill.keywords:
        if keyword.lower() in lowered:
            score += 3

    if has_image and skill.mode == "vision_chat":
        score += 2
    if has_image and skill.id == "garment-pattern-mapper":
        score += 1

    if skill.id == "garment-pattern-mapper" and any(
        token in lowered for token in ("套图", "裁片", "成衣", "花型", "面料", "pattern")
    ):
        score += 6
    if skill.id == "ps-layout-designer" and any(
        token in lowered
        for token in (
            "排版",
            "海报",
            "标题",
            "版式",
            "封面",
            "banner",
            "品牌感",
            "卖点",
            "背景图",
            "方案图",
        )
    ):
        score += 5
    if skill.id == "creative-direction-strategist" and any(
        token in lowered
        for token in (
            "创意",
            "方向",
            "灵感",
            "调性",
            "参考图",
            "品牌感",
            "构图",
            "配色",
            "氛围",
            "风格方案",
            "方向图",
            "效果图",
            "概念图",
            "方案图",
            "背景图",
            "出图",
            "生成一张",
            "生成四张",
            "生成一组",
            "插画",
            "画面",
            "方向稿",
            "连贯",
        )
    ):
        score += 6
    if has_image and skill.id == "creative-direction-strategist":
        score += 2
    if skill.id == "product-retoucher" and any(
        token in lowered for token in ("修图", "去背", "抠图", "调色", "精修", "主图")
    ):
        score += 5
    if skill.id == "visual-analysis-advisor" and has_image and any(
        token in lowered for token in ("分析", "看", "风格", "版型", "你看见", "建议", "元素")
    ):
        score += 6
    if skill.id == "ps-generalist" and any(
        token in lowered
        for token in (
            "不会",
            "怎么做",
            "怎么操作",
            "如何操作",
            "教我",
            "帮我操作",
            "帮我弄",
            "为什么",
            "报错",
            "错误",
            "失败",
            "没反应",
            "不能",
            "无法",
            "下一步",
            "该怎么",
        )
    ):
        score += 7
    if skill.mode == "tool_agent" and any(
        token in lowered for token in ("帮我做", "直接做", "你来做", "顺手做", "帮我操作")
    ):
        score += 4

    return score


def resolve_ai_skill(
    message: str,
    history_messages: Optional[Iterable[dict]] = None,
    requested_skill_id: Optional[str] = None,
    has_image: bool = False,
) -> AiSkill:
    explicit_skill = get_ai_skill(requested_skill_id)
    if explicit_skill:
        return explicit_skill

    combined_text = f"{_history_text(history_messages or [])}\n{message or ''}".strip()
    combined_text = combined_text or ""

    best_skill = SKILL_BY_ID["ps-generalist"]
    best_score = -1

    for skill in SKILLS:
        score = _score_skill(skill, combined_text, has_image)
        if score > best_score:
            best_skill = skill
            best_score = score

    if has_image and best_score <= 1:
        return SKILL_BY_ID["visual-analysis-advisor"]

    return best_skill
