"""PowerPoint (.pptx) file operations via python-pptx."""

import os

from fliiq.runtime.security import check_path_allowed

_LAYOUT_MAP = {
    "TITLE": 0,
    "TITLE_AND_CONTENT": 1,
    "BLANK": 6,
}


async def handler(params: dict) -> dict:
    """Handle PowerPoint operations."""
    action = params["action"]

    if action == "create":
        return _create(params)
    elif action == "read":
        return _read(params)
    elif action == "add_slide":
        return _add_slide(params)
    elif action == "modify_slide":
        return _modify_slide(params)
    elif action == "delete_slide":
        return _delete_slide(params)
    elif action == "delete":
        return _delete(params)
    else:
        raise ValueError(f"Unknown action: {action}")


def _require_path(params: dict) -> str:
    path = params.get("path")
    if not path:
        raise ValueError("'path' is required")
    check_path_allowed(path)
    return path


def _create(params: dict) -> dict:
    """Create a new presentation with optional title slide."""
    from pptx import Presentation

    path = _require_path(params)
    title = params.get("title")
    subtitle = params.get("subtitle")

    prs = Presentation()

    if title:
        slide_layout = prs.slide_layouts[_LAYOUT_MAP["TITLE"]]
        slide = prs.slides.add_slide(slide_layout)
        slide.shapes.title.text = title
        if subtitle and len(slide.placeholders) > 1:
            slide.placeholders[1].text = subtitle

    parent = os.path.dirname(path)
    if parent:
        os.makedirs(parent, exist_ok=True)
    prs.save(path)

    slide_count = len(prs.slides)
    return {
        "success": True,
        "message": f"Created {path} with {slide_count} slide(s)",
        "data": {"path": path, "slides": slide_count},
    }


def _extract_slide_text(slide) -> list[str]:
    """Extract all text from a slide's shapes."""
    texts = []
    for shape in slide.shapes:
        if shape.has_text_frame:
            for paragraph in shape.text_frame.paragraphs:
                text = paragraph.text.strip()
                if text:
                    texts.append(text)
    return texts


def _read(params: dict) -> dict:
    """Read text and structure from all slides."""
    from pptx import Presentation

    path = _require_path(params)
    if not os.path.isfile(path):
        raise FileNotFoundError(f"File not found: {path}")

    prs = Presentation(path)
    slides = []
    for i, slide in enumerate(prs.slides):
        texts = _extract_slide_text(slide)
        slides.append({
            "index": i,
            "layout": slide.slide_layout.name,
            "text": texts,
        })

    return {
        "success": True,
        "message": f"Read {len(slides)} slide(s) from {path}",
        "data": {"path": path, "slides": slides},
    }


def _add_slide(params: dict) -> dict:
    """Add a slide with title and body."""
    from pptx import Presentation

    path = _require_path(params)
    if not os.path.isfile(path):
        raise FileNotFoundError(f"File not found: {path}")

    title = params.get("title", "")
    body = params.get("body", "")
    layout_name = params.get("layout", "TITLE_AND_CONTENT")

    layout_idx = _LAYOUT_MAP.get(layout_name)
    if layout_idx is None:
        raise ValueError(f"Unknown layout: {layout_name}. Use: {', '.join(_LAYOUT_MAP)}")

    prs = Presentation(path)
    slide_layout = prs.slide_layouts[layout_idx]
    slide = prs.slides.add_slide(slide_layout)

    if title and slide.shapes.title:
        slide.shapes.title.text = title

    if body and layout_name == "TITLE_AND_CONTENT" and len(slide.placeholders) > 1:
        tf = slide.placeholders[1].text_frame
        tf.clear()
        for i, line in enumerate(body.split("\n")):
            if i == 0:
                tf.paragraphs[0].text = line
            else:
                tf.add_paragraph().text = line

    prs.save(path)

    slide_index = len(prs.slides) - 1
    return {
        "success": True,
        "message": f"Added slide {slide_index} with layout '{layout_name}'",
        "data": {"path": path, "slide_index": slide_index, "total_slides": len(prs.slides)},
    }


def _modify_slide(params: dict) -> dict:
    """Find and replace text on a specific slide."""
    from pptx import Presentation

    path = _require_path(params)
    if not os.path.isfile(path):
        raise FileNotFoundError(f"File not found: {path}")

    slide_index = params.get("slide_index")
    if slide_index is None:
        raise ValueError("'slide_index' is required for modify_slide")

    old_text = params.get("old_text")
    new_text = params.get("new_text")
    if not old_text or new_text is None:
        raise ValueError("'old_text' and 'new_text' are required for modify_slide")

    prs = Presentation(path)
    slides = list(prs.slides)

    if slide_index < 0 or slide_index >= len(slides):
        raise ValueError(f"slide_index {slide_index} out of range (0-{len(slides) - 1})")

    slide = slides[slide_index]
    replacements = 0

    for shape in slide.shapes:
        if shape.has_text_frame:
            for paragraph in shape.text_frame.paragraphs:
                for run in paragraph.runs:
                    if old_text in run.text:
                        run.text = run.text.replace(old_text, new_text)
                        replacements += 1

    if replacements == 0:
        raise ValueError(f"Text '{old_text}' not found on slide {slide_index}")

    prs.save(path)

    return {
        "success": True,
        "message": f"Replaced {replacements} occurrence(s) on slide {slide_index}",
        "data": {"path": path, "slide_index": slide_index, "replacements": replacements},
    }


def _delete_slide(params: dict) -> dict:
    """Remove a slide by index."""
    from pptx import Presentation

    path = _require_path(params)
    if not os.path.isfile(path):
        raise FileNotFoundError(f"File not found: {path}")

    slide_index = params.get("slide_index")
    if slide_index is None:
        raise ValueError("'slide_index' is required for delete_slide")

    prs = Presentation(path)
    slides = list(prs.slides)

    if slide_index < 0 or slide_index >= len(slides):
        raise ValueError(f"slide_index {slide_index} out of range (0-{len(slides) - 1})")

    # python-pptx has no native delete — use XML manipulation
    rId = prs.slides._sldIdLst[slide_index].get(
        "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id"
    )
    prs.part.drop_rel(rId)
    slide_elem = prs.slides._sldIdLst[slide_index]
    prs.slides._sldIdLst.remove(slide_elem)

    prs.save(path)

    remaining = len(list(prs.slides))
    return {
        "success": True,
        "message": f"Deleted slide {slide_index}. {remaining} slide(s) remaining.",
        "data": {"path": path, "deleted_index": slide_index, "remaining_slides": remaining},
    }


def _delete(params: dict) -> dict:
    """Delete a .pptx file."""
    path = _require_path(params)
    if not os.path.isfile(path):
        raise FileNotFoundError(f"File not found: {path}")
    os.remove(path)
    return {
        "success": True,
        "message": f"Deleted {path}",
        "data": {"deleted": path},
    }
