#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
"""Export PowerPoint slides to SVG with optional slide filtering.

Converts a PPTX file to individual SVG images via an intermediate PDF
generated by LibreOffice headless mode. Each slide is rendered to SVG
using PyMuPDF's vector export.

Usage:
    python export_svg.py --input presentation.pptx --output-dir svg/
    python export_svg.py --input presentation.pptx --output-dir svg/ --slides 1,3,5
"""

from __future__ import annotations

import argparse
import logging
import platform
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

from pptx_utils import (
    EXIT_ERROR,
    EXIT_FAILURE,
    EXIT_SUCCESS,
    configure_logging,
    parse_slide_filter,
)

logger = logging.getLogger(__name__)


class LibreOfficeError(RuntimeError):
    """Raised when LibreOffice is missing or conversion fails."""


class PyMuPDFError(RuntimeError):
    """Raised when PyMuPDF is missing or SVG rendering fails."""


def create_parser() -> argparse.ArgumentParser:
    """Create and configure argument parser."""
    parser = argparse.ArgumentParser(
        description="Export PowerPoint slides to SVG images"
    )
    parser.add_argument(
        "--input", required=True, type=Path, help="Input PPTX file path"
    )
    parser.add_argument(
        "--output-dir",
        required=True,
        type=Path,
        help="Output directory for SVG files",
    )
    parser.add_argument(
        "--slides",
        help="Comma-separated slide numbers to export (1-based, default: all)",
    )
    parser.add_argument(
        "-v", "--verbose", action="store_true", help="Enable verbose output"
    )
    return parser


def find_libreoffice() -> str | None:
    """Locate the LibreOffice/soffice executable across platforms."""
    for cmd in ("libreoffice", "soffice"):
        path = shutil.which(cmd)
        if path:
            return path

    system = platform.system()
    if system == "Darwin":
        candidates = [
            "/Applications/LibreOffice.app/Contents/MacOS/soffice",
        ]
    elif system == "Windows":
        candidates = [
            r"C:\Program Files\LibreOffice\program\soffice.exe",
            r"C:\Program Files (x86)\LibreOffice\program\soffice.exe",
        ]
    else:
        candidates = [
            "/usr/bin/libreoffice",
            "/usr/bin/soffice",
            "/snap/bin/libreoffice",
        ]

    for candidate in candidates:
        if Path(candidate).is_file():
            return candidate

    return None


def convert_pptx_to_pdf(pptx_path: Path, output_dir: Path) -> Path:
    """Convert a PPTX file to PDF using LibreOffice headless mode.

    Args:
        pptx_path: Path to the input PPTX file.
        output_dir: Directory where the PDF will be written.

    Returns:
        Path to the generated PDF file.
    """
    soffice = find_libreoffice()
    if not soffice:
        raise LibreOfficeError(
            "LibreOffice is required for PPTX-to-PDF conversion. "
            "Install via: brew install --cask libreoffice (macOS), "
            "sudo apt-get install libreoffice (Linux), "
            "winget install TheDocumentFoundation.LibreOffice (Windows)"
        )

    output_dir.mkdir(parents=True, exist_ok=True)
    logger.info("Converting %s to PDF via LibreOffice", pptx_path.name)

    try:
        result = subprocess.run(
            [
                soffice,
                "--headless",
                "--convert-to",
                "pdf",
                "--outdir",
                str(output_dir),
                str(pptx_path),
            ],
            capture_output=True,
            text=True,
            check=True,
            timeout=300,
        )
        logger.debug("LibreOffice stdout: %s", result.stdout)
    except subprocess.TimeoutExpired as e:
        raise LibreOfficeError(
            f"LibreOffice conversion timed out after {e.timeout}s"
        ) from e
    except subprocess.CalledProcessError as e:
        raise LibreOfficeError(f"LibreOffice conversion failed: {e.stderr}") from e
    except FileNotFoundError as e:
        raise LibreOfficeError(f"LibreOffice executable not found: {soffice}") from e

    pdf_name = pptx_path.stem + ".pdf"
    pdf_path = output_dir / pdf_name
    if not pdf_path.exists():
        raise LibreOfficeError(f"Expected PDF not found: {pdf_path}")

    return pdf_path


def export_pdf_to_svg(
    pdf_path: Path,
    output_dir: Path,
    slides: list[int] | None = None,
) -> list[Path]:
    """Render PDF pages to individual SVG files using PyMuPDF.

    Args:
        pdf_path: Path to the intermediate PDF.
        output_dir: Directory where SVG files will be written.
        slides: Optional 1-based slide numbers to export. Exports all when None.

    Returns:
        List of paths to the generated SVG files.
    """
    try:
        import fitz  # noqa: PLC0415 — PyMuPDF
    except ImportError as e:
        raise PyMuPDFError(
            "PyMuPDF is required for SVG export. Install via: pip install pymupdf"
        ) from e

    with fitz.open(str(pdf_path)) as doc:
        total_pages = len(doc)
        output_dir.mkdir(parents=True, exist_ok=True)

        if slides:
            page_numbers = [n for n in slides if 1 <= n <= total_pages]
            skipped = [n for n in slides if n < 1 or n > total_pages]
            for num in skipped:
                logger.warning(
                    "Slide %d out of range (1-%d), skipping",
                    num,
                    total_pages,
                )
        else:
            page_numbers = list(range(1, total_pages + 1))

        exported: list[Path] = []
        for page_num in page_numbers:
            page = doc[page_num - 1]
            svg_text = page.get_svg_image()
            svg_path = output_dir / f"slide-{page_num:03d}.svg"
            svg_path.write_text(svg_text, encoding="utf-8")
            logger.info("Exported slide %d → %s", page_num, svg_path.name)
            exported.append(svg_path)

    return exported


def run(args: argparse.Namespace) -> int:
    """Execute the SVG export pipeline."""
    pptx_path = args.input.resolve()
    output_dir = args.output_dir.resolve()

    if not pptx_path.exists():
        logger.error("Input file not found: %s", pptx_path)
        return EXIT_ERROR

    if pptx_path.suffix.lower() != ".pptx":
        logger.error("Input file must be a .pptx file: %s", pptx_path)
        return EXIT_ERROR

    slides: list[int] | None = None
    if args.slides:
        slide_set = parse_slide_filter(args.slides)
        slides = sorted(slide_set) if slide_set else None
        logger.info("Filtering to slides: %s", slides)

    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        try:
            pdf_path = convert_pptx_to_pdf(pptx_path, tmp_path)
            exported = export_pdf_to_svg(pdf_path, output_dir, slides)
        except (LibreOfficeError, PyMuPDFError) as e:
            logger.error("%s", e)
            return EXIT_FAILURE

    logger.info("SVG export complete: %d slide(s) → %s", len(exported), output_dir)
    return EXIT_SUCCESS


def main() -> int:
    """Main entry point with error handling."""
    parser = create_parser()
    args = parser.parse_args()
    configure_logging(args.verbose)

    try:
        return run(args)
    except KeyboardInterrupt:
        print("\nInterrupted by user", file=sys.stderr)
        return 130
    except BrokenPipeError:
        sys.stderr.close()
        return EXIT_FAILURE
    except Exception as e:
        logger.error("Unexpected error: %s", e)
        return EXIT_FAILURE


if __name__ == "__main__":
    sys.exit(main())
