"""
skills/ppt_skill.py - PPT PowerPoint 演示文稿技能
===============================================
基于 python-pptx 生成 PowerPoint 演示文稿，融入 MiniMax PPT Skill 设计理念。
支持多种幻灯片类型、配色方案、图表等。
"""
from __future__ import annotations

import json
import os
import time
from pathlib import Path
from typing import Any, Dict, List, Optional

from aiskills.base import Skill, SkillParameter, SkillResult

# 配色方案 (源自 MiniMax PPT design-system)
THEMES = {
    "professional": {"primary": "1A365D", "secondary": "2B6CB0", "accent": "E53E3E",
                     "light": "EBF8FF", "bg": "FFFFFF", "text": "1A202C", "muted": "718096"},
    "modern":       {"primary": "553C9A", "secondary": "805AD5", "accent": "ED8936",
                     "light": "FAF5FF", "bg": "FFFFFF", "text": "1A202C", "muted": "718096"},
    "nature":       {"primary": "22543D", "secondary": "38A169", "accent": "D69E2E",
                     "light": "F0FFF4", "bg": "FFFFFF", "text": "1A202C", "muted": "718096"},
    "warm":         {"primary": "744210", "secondary": "C05621", "accent": "DD6B20",
                     "light": "FFFFF0", "bg": "FFFFFF", "text": "1A202C", "muted": "718096"},
    "dark":         {"primary": "E2E8F0", "secondary": "A0AEC0", "accent": "63B3ED",
                     "light": "2D3748", "bg": "1A202C", "text": "E2E8F0", "muted": "A0AEC0"},
    "minimal":      {"primary": "2D3748", "secondary": "4A5568", "accent": "3182CE",
                     "light": "F7FAFC", "bg": "FFFFFF", "text": "1A202C", "muted": "718096"},
}


def _hex_to_rgb(hex_str: str):
    from pptx.dml.color import RGBColor
    h = hex_str.lstrip("#")
    return RGBColor(*[int(h[i:i+2], 16) for i in (0, 2, 4)])


class PPTCreateSkill(Skill):
    """生成 PowerPoint 演示文稿

    slides 为 JSON 数组，每个元素代表一张幻灯片:
    - {"type": "cover", "title": "...", "subtitle": "...", "author": "..."}
    - {"type": "toc", "title": "...", "items": ["章节1", "章节2"]}
    - {"type": "section", "title": "章节标题"}
    - {"type": "content", "title": "...", "body": ["要点1", "要点2"]}
    - {"type": "content", "title": "...", "table": {"headers": [], "rows": []}}
    - {"type": "content", "title": "...", "image": {"path": "...", "caption": "..."}}
    - {"type": "summary", "title": "...", "points": ["要点1", "要点2"]}
    """
    name = "ppt_create"
    description = (
        "生成 PowerPoint (PPTX) 演示文稿。支持封面、目录、章节、内容、表格、"
        "图片、总结等幻灯片类型。"
        "slides 为 JSON 数组，每项: {type, title, body/items/points...}。"
        "theme 可选: professional/modern/nature/warm/dark/minimal。"
    )
    category = "doc"
    dangerous = True
    parameters = [
        SkillParameter("slides", "string",
            "幻灯片 JSON 数组。type: cover/toc/section/content/summary", required=True),
        SkillParameter("output_path", "string", "输出 PPTX 文件路径", required=True),
        SkillParameter("theme", "string", "配色主题", required=False,
                       default="professional",
                       enum=["professional","modern","nature","warm","dark","minimal"]),
        SkillParameter("font", "string", "中文字体", required=False, default="SimHei"),
    ]

    async def execute(self, slides: str = "", output_path: str = "",
                      theme: str = "professional", font: str = "SimHei",
                      **kwargs) -> SkillResult:
        try:
            from pptx import Presentation
            from pptx.util import Inches, Pt, Emu
            from pptx.dml.color import RGBColor
            from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
            from pptx.enum.shapes import MSO_SHAPE
        except ImportError:
            return SkillResult(success=False, error="python-pptx 未安装: pip install python-pptx")

        try:
            slide_data = json.loads(slides) if isinstance(slides, str) else slides
            if not isinstance(slide_data, list):
                slide_data = [{"type": "content", "title": str(slide_data)}]
        except json.JSONDecodeError as e:
            return SkillResult(success=False, error=f"slides JSON 解析失败: {e}")

        try:
            if not output_path.strip():
                default_dir = Path.home() / ".myagent" / "data" / "workspace" / "userfiles"
                default_dir.mkdir(parents=True, exist_ok=True)
                out = default_dir / f"ppt_{int(time.time())}.pptx"
            else:
                out = Path(output_path).expanduser().resolve()
            out.parent.mkdir(parents=True, exist_ok=True)

            t = THEMES.get(theme, THEMES["professional"])
            pr = _hex_to_rgb(t["primary"])
            sc = _hex_to_rgb(t["secondary"])
            ac = _hex_to_rgb(t["accent"])
            tc = _hex_to_rgb(t["text"])
            mc = _hex_to_rgb(t["muted"])
            lc = _hex_to_rgb(t["light"])

            prs = Presentation()
            prs.slide_width = Inches(13.333)
            prs.slide_height = Inches(7.5)
            BLANK = 6

            def add_text_box(slide, left, top, width, height, text, font_size=18,
                             bold=False, color=tc, align=PP_ALIGN.LEFT, font_name=font):
                txBox = slide.shapes.add_textbox(Inches(left), Inches(top),
                                                  Inches(width), Inches(height))
                tf = txBox.text_frame
                tf.word_wrap = True
                p = tf.paragraphs[0]
                p.text = text
                p.font.size = Pt(font_size)
                p.font.bold = bold
                p.font.color.rgb = color
                p.font.name = font_name
                p.alignment = align
                return tf

            def add_shape_bg(slide, color_hex):
                shape = slide.shapes.add_shape(
                    MSO_SHAPE.RECTANGLE, Inches(0), Inches(0),
                    prs.slide_width, prs.slide_height)
                shape.fill.solid()
                shape.fill.fore_color.rgb = _hex_to_rgb(color_hex)
                shape.line.fill.background()

            def add_accent_line(slide, left, top, width, color_hex):
                shape = slide.shapes.add_shape(
                    MSO_SHAPE.RECTANGLE, Inches(left), Inches(top),
                    Inches(width), Inches(0.06))
                shape.fill.solid()
                shape.fill.fore_color.rgb = _hex_to_rgb(color_hex)
                shape.line.fill.background()

            def add_page_number(slide, num, total):
                add_text_box(slide, 12.2, 7.0, 1, 0.4, f"{num}/{total}",
                             font_size=10, color=mc, align=PP_ALIGN.RIGHT)

            total_slides = len(slide_data)

            for idx, s in enumerate(slide_data):
                if not isinstance(s, dict):
                    continue
                st = s.get("type", "content")
                slide = prs.slides.add_slide(prs.slide_layouts[BLANK])

                if st != "cover":
                    add_page_number(slide, idx + 1, total_slides)

                if st == "cover":
                    add_shape_bg(slide, t["primary"])
                    add_accent_line(slide, 2, 3.0, 2, t["accent"])
                    add_text_box(slide, 2, 3.3, 9, 1.5, s.get("title", ""),
                                 font_size=40, bold=True, color=RGBColor(255, 255, 255))
                    if s.get("subtitle"):
                        add_text_box(slide, 2, 5.0, 9, 0.8, s["subtitle"],
                                     font_size=18, color=RGBColor(200, 200, 200))
                    if s.get("author"):
                        add_text_box(slide, 2, 6.0, 9, 0.6, s["author"],
                                     font_size=14, color=mc)

                elif st == "toc":
                    add_text_box(slide, 0.8, 0.5, 8, 0.8, s.get("title", "目录"),
                                 font_size=28, bold=True, color=pr)
                    add_accent_line(slide, 0.8, 1.4, 1.5, t["accent"])
                    for i, item in enumerate(s.get("items", [])):
                        add_text_box(slide, 1.2, 2.0 + i * 0.7, 8, 0.6,
                                     f"{i+1}.  {item}", font_size=18, color=tc)

                elif st == "section":
                    add_shape_bg(slide, t["primary"])
                    add_text_box(slide, 1, 2.5, 11, 2, s.get("title", ""),
                                 font_size=36, bold=True, color=RGBColor(255, 255, 255),
                                 align=PP_ALIGN.CENTER)

                elif st == "content":
                    # 标题栏
                    title_shape = slide.shapes.add_shape(
                        MSO_SHAPE.RECTANGLE, Inches(0), Inches(0),
                        prs.slide_width, Inches(1.2))
                    title_shape.fill.solid()
                    title_shape.fill.fore_color.rgb = pr
                    title_shape.line.fill.background()
                    add_text_box(slide, 0.8, 0.2, 11, 0.8, s.get("title", ""),
                                 font_size=24, bold=True, color=RGBColor(255, 255, 255))

                    body = s.get("body", [])
                    if body and isinstance(body, list):
                        for i, item in enumerate(body):
                            y = 1.6 + i * 0.7
                            if y > 6.8:
                                break
                            add_text_box(slide, 1.0, y, 11, 0.6,
                                         f"\u25cf  {item}", font_size=16, color=tc)

                    table_data = s.get("table", None)
                    if table_data and isinstance(table_data, dict):
                        headers = table_data.get("headers", [])
                        rows = table_data.get("rows", [])
                        if headers:
                            n_rows = len(rows) + 1
                            n_cols = len(headers)
                            tbl = slide.shapes.add_table(
                                n_rows, n_cols, Inches(0.8), Inches(1.6),
                                Inches(11.7), Inches(min(n_rows * 0.5 + 0.5, 5.0))).table
                            for j, h in enumerate(headers):
                                cell = tbl.cell(0, j)
                                cell.text = str(h)
                                for p in cell.text_frame.paragraphs:
                                    p.font.size = Pt(12)
                                    p.font.bold = True
                                    p.font.color.rgb = RGBColor(255, 255, 255)
                                    p.font.name = font
                                cell.fill.solid()
                                cell.fill.fore_color.rgb = pr
                            for i, row in enumerate(rows):
                                for j, val in enumerate(row):
                                    if j < n_cols:
                                        cell = tbl.cell(i + 1, j)
                                        cell.text = str(val)
                                        for p in cell.text_frame.paragraphs:
                                            p.font.size = Pt(11)
                                            p.font.color.rgb = tc
                                            p.font.name = font
                                        if i % 2 == 0:
                                            cell.fill.solid()
                                            cell.fill.fore_color.rgb = lc

                    image_data = s.get("image", None)
                    if image_data and isinstance(image_data, dict):
                        img_path = image_data.get("path", "")
                        caption = image_data.get("caption", "")
                        if img_path and os.path.isfile(img_path):
                            try:
                                slide.shapes.add_picture(img_path, Inches(2), Inches(1.8),
                                                          width=Inches(9))
                            except Exception:
                                add_text_box(slide, 2, 3, 9, 1, "[图片加载失败]",
                                             font_size=14, color=mc, align=PP_ALIGN.CENTER)
                        if caption:
                            add_text_box(slide, 2, 6.2, 9, 0.6, caption,
                                         font_size=12, color=mc, align=PP_ALIGN.CENTER)

                elif st == "summary":
                    add_text_box(slide, 0.8, 0.5, 11, 0.8, s.get("title", "总结"),
                                 font_size=28, bold=True, color=pr)
                    add_accent_line(slide, 0.8, 1.4, 1.5, t["accent"])
                    for i, pt in enumerate(s.get("points", [])):
                        y = 2.0 + i * 0.8
                        if y > 6.5:
                            break
                        add_text_box(slide, 1.2, y, 10, 0.6,
                                     f"\u2713  {pt}", font_size=17, color=tc)

            prs.save(str(out))
            return SkillResult(
                success=True,
                message=f"PPT 已生成: {out} ({total_slides} 张幻灯片)",
                files=[str(out)],
                data={"path": str(out), "slides": total_slides, "theme": theme},
            )
        except Exception as e:
            return SkillResult(success=False, error=f"PPT 生成失败: {e}")


class PPTReadSkill(Skill):
    """读取 PowerPoint 文件内容"""
    name = "ppt_read"
    description = "读取 PowerPoint (PPTX) 文件，提取文本内容。"
    category = "doc"
    parameters = [
        SkillParameter("path", "string", "PPTX 文件路径", required=True),
        SkillParameter("max_chars", "integer", "最大字符数", required=False, default=30000),
    ]

    async def execute(self, path: str = "", max_chars: int = 30000, **kwargs) -> SkillResult:
        try:
            from pptx import Presentation
        except ImportError:
            return SkillResult(success=False, error="python-pptx 未安装: pip install python-pptx")

        fp = Path(path).expanduser().resolve()
        if not fp.exists():
            return SkillResult(success=False, error=f"文件不存在: {path}")

        try:
            prs = Presentation(str(fp))
            parts = []
            for i, slide in enumerate(prs.slides):
                slide_texts = []
                for shape in slide.shapes:
                    if hasattr(shape, "text") and shape.text.strip():
                        slide_texts.append(shape.text.strip())
                if slide_texts:
                    parts.append(f"--- 幻灯片 {i+1} ---")
                    parts.extend(slide_texts)

            full = "\n".join(parts)
            if len(full) > max_chars:
                full = full[:max_chars] + "\n..."

            return SkillResult(
                success=True,
                message=f"已读取 PPT: {fp.name} ({len(prs.slides)} 张幻灯片)",
                data={"path": str(fp), "slides": len(prs.slides)},
                output=full,
            )
        except Exception as e:
            return SkillResult(success=False, error=f"PPT 读取失败: {e}")
