# pptx_to_images_python_pptx.py
from pathlib import Path
from typing import List, Optional, Tuple
from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE
from PIL import Image, ImageDraw, ImageFont
import textwrap
import io
import os

def _load_font(preferred_size: int, bold: bool = False):
    """
    Try to load a TTF for nicer rendering; fallback to default PIL font.
    """
    # Try common system font names; adjust if missing
    candidates = [
        "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        "/Library/Fonts/Arial.ttf",
        "C:/Windows/Fonts/arial.ttf",
    ]
    for p in candidates:
        try:
            if Path(p).exists():
                return ImageFont.truetype(str(p), preferred_size)
        except Exception:
            continue
    # fallback
    return ImageFont.load_default()

def pptx_to_images_via_python_pptx(
    pptx_path: str,
    out_dir: str,
    size: Tuple[int, int] = (1280, 720),
    title_font_size: int = 44,
    bullet_font_size: int = 26,
    max_bullets: int = 6,
    right_image_width_ratio: float = 0.37
) -> List[str]:
    """
    Convert PPTX to PNG images using python-pptx + Pillow.
    - pptx_path: input .pptx
    - out_dir: directory where slide_{:03d}.png will be placed
    - size: (width, height) of output images
    - right_image_width_ratio: fraction of width reserved for images on right (0..1)
    Returns: list of generated image paths (in order).
    """
    W, H = size
    out_dir_p = Path(out_dir)
    out_dir_p.mkdir(parents=True, exist_ok=True)

    pres = Presentation(pptx_path)
    slides = pres.slides

    title_font = _load_font(title_font_size, bold=True)
    bullet_font = _load_font(bullet_font_size, bold=False)

    generated: List[str] = []

    for idx, slide in enumerate(slides, start=1):
        # Extract text and images
        title_text = ""
        bullet_lines: List[str] = []
        slide_images: List[Path] = []

        for shape in slide.shapes:
            # pictures
            if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
                try:
                    img = shape.image
                    blob = img.blob
                    ext = img.ext
                    # Save in-memory then to file
                    img_name = out_dir_p / f"slide_{idx:03d}_img_{len(slide_images)+1}.{ext}"
                    with open(img_name, "wb") as f:
                        f.write(blob)
                    slide_images.append(img_name)
                except Exception:
                    # ignore if can't extract image
                    continue

            # text frames
            elif shape.has_text_frame:
                text = shape.text.strip()
                if not text:
                    continue
                # If we don't yet have a title, pick first text box as title
                if not title_text:
                    # take first line as heading and remaining lines as bullets for convenience
                    lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
                    if lines:
                        title_text = lines[0]
                        if len(lines) > 1:
                            for l in lines[1:]:
                                bullet_lines.extend([ln for ln in l.splitlines() if ln.strip()])
                else:
                    # append other text boxes as bullets (preserve internal line breaks)
                    for l in text.splitlines():
                        if l.strip():
                            bullet_lines.append(l.strip())

        # Fallback title if none found
        if not title_text:
            title_text = f"Slide {idx}"

        # Limit bullets
        bullet_lines = [b for b in bullet_lines if b]  # clean
        if len(bullet_lines) > max_bullets:
            bullet_lines = bullet_lines[:max_bullets]

        # Post-process bullets: if bullets are short fragments, keep them but wrap into 1-2 sentences later in pipeline
        # Now render image with Pillow
        img = Image.new("RGB", (W, H), color=(255, 255, 255))
        draw = ImageDraw.Draw(img)

        # Title area (top)
        padding = 36
        title_h_area = int(H * 0.18)
        # Draw title centered (wrap if necessary)
        title_wrap_width = 40
        title_lines = textwrap.wrap(title_text, width=title_wrap_width)
        # compute title start y
        y = padding
        for line in title_lines:
            bbox = draw.textbbox((0, 0), line, font=title_font)
            tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
            x = (W - tw) // 2
            draw.text((x, y), line, fill=(20, 20, 20), font=title_font)
            y += th + 6

        # Body area (left) and image area (right)
        right_width = int(W * right_image_width_ratio)
        left_width = W - right_width - padding * 2
        left_x = padding
        left_y = title_h_area
        bullet_wrap_chars = max(20, int(left_width / (bullet_font_size * 0.5)))  # heuristic

        # Draw bullets
        bullet_y = left_y + 8
        for b in bullet_lines:
            wrapped = textwrap.wrap(b, width=bullet_wrap_chars)
            # draw a small bullet circle
            bullet_dot_radius = 4
            # draw wrapped lines
            for i, wline in enumerate(wrapped):
                if i == 0:
                    # draw bullet circle on first line
                    draw.ellipse((left_x, bullet_y + 6, left_x + bullet_dot_radius*2, bullet_y + 6 + bullet_dot_radius*2), fill=(34, 120, 200))
                    text_x = left_x + bullet_dot_radius*2 + 8
                else:
                    text_x = left_x + bullet_dot_radius*2 + 8
                draw.text((text_x, bullet_y), wline, fill=(40,40,40), font=bullet_font)
                bbox = draw.textbbox((0, 0), wline, font=bullet_font)
                tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
                bullet_y += th + 6
            bullet_y += 4  # small gap after bullet

        # If slide has images, place first image on right area (scale to fit)
        if slide_images:
            img_path = slide_images[0]
            try:
                pil_img = Image.open(img_path).convert("RGB")
                # scale to fit inside right_width x available_height
                avail_w = right_width - padding
                avail_h = H - title_h_area - padding
                pil_w, pil_h = pil_img.size
                scale = min(avail_w / pil_w, avail_h / pil_h, 1.0)
                new_w = int(pil_w * scale)
                new_h = int(pil_h * scale)
                pil_img = pil_img.resize((new_w, new_h), Image.LANCZOS)
                # paste at right area centered vertically
                paste_x = W - new_w - padding
                paste_y = title_h_area + (avail_h - new_h) // 2
                img.paste(pil_img, (paste_x, paste_y))
            except Exception:
                pass

        # Save to file with zero-padded slide index
        out_path = out_dir_p / f"slide_{idx:03d}.png"
        img.save(out_path, format="PNG", optimize=True)
        generated.append(str(out_path))

    return generated

# Example usage
if __name__ == "__main__":
    # adjust paths
    pptx_file = "Video_generation/ppt/photosynthesis-basics-1758447500.pptx"
    out_images_dir = "slides_images"
    imgs = pptx_to_images_via_python_pptx(pptx_file, out_images_dir, size=(1280,720))
    print("Generated images:", imgs)
