#!/usr/bin/env python3
"""
Standalone Slide Generator - Uses Google Gemini directly

Generates professional presentation slides from documents using Google's
Gemini image generation API. No external dependencies on PPT2Vid.

Output: Single PDF file (like PPT2Vid)

Usage:
    python generate_slides.py <file_path> --vibe executive --slides 10

Requirements:
    - GEMINI_API_KEY environment variable set
    - pip install google-generativeai pymupdf python-dotenv pillow
"""

import sys
import os
import asyncio
import argparse
import base64
import uuid
import time
from pathlib import Path
from dataclasses import dataclass, field
from typing import Optional, Dict, List, Any


# ============================================================================
# CONFIGURATION - Copied from PPT2Vid concepts (standalone, no imports)
# ============================================================================

VIBES = [
    "corporate", "sketchnotes", "tech", "minimal", "bold", "keynote",
    "pitch-deck", "playful", "vintage", "hand-drawn", "editorial", "training",
    "swiss", "japanese-minimal", "bauhaus", "midcentury", "art-deco",
    "executive", "consulting", "financial", "legal", "military",
    "dark-mode", "cyberpunk", "saas", "terminal", "blockchain", "ai-neural", "glassmorphism",
    "watercolor", "earth-tones", "ocean", "forest",
    "vintage-scientific", "newspaper", "graffiti", "sports", "festival",
    "space", "industrial", "luxury",
    "photorealistic", "scrapbook", "dashboard", "minecraft", "collage",
    "high-end-editorial", "anime", "flowchart", "workflow",
]

DENSITIES = ["concise", "standard", "dense"]

INTENTS = [
    "inform", "building-process", "brainstorming", "comparison", "emotion",
    "persuade", "inspire", "report", "propose", "request", "warn", "celebrate",
    "introduce", "train", "pitch", "justify", "align", "onboard", "summarize",
    "recommend", "challenge", "establish-credibility", "create-urgency",
    "simplify", "document", "provoke", "reassure", "secure-commitment",
]

COLOR_PALETTES = [
    "midnight-executive", "nordic-frost", "cyber-neon", "desert-sand",
    "ocean-depth", "forest-canopy", "sunset-gradient", "monochrome-gray",
    "corporate-trust", "berry-luxe", "mint-fresh", "warm-terracotta",
    "electric-violet", "newspaper-classic", "synthwave-sunset", "sage-stone",
    "tech-slate", "coral-reef", "arctic-blue", "gold-standard", "pastel-dream",
    "industrial-steel", "nature-botanical", "cybersecurity-dark", "warm-minimal",
]

# Style definitions - key concepts from PPT2Vid
VIBE_STYLES = {
    "corporate": {
        "name": "Corporate",
        "style": "clean vector lines, professional corporate aesthetic, minimalist geometric shapes, subtle gradient overlays, flat design with sophisticated depth cues, matte finish surfaces, soft drop shadows, rounded corners, modern enterprise software aesthetic, generous whitespace, Behance-quality business presentation",
        "typography": "modern geometric sans-serif (Inter/Helvetica style), bold 700 weight headers, regular 400 weight body, professional hierarchy",
        "colors": {"primary": "#1E3A5F", "secondary": "#4A6FA5", "accent": "#14B8A6", "background": "#FFFFFF", "text": "#1F2937"},
    },
    "executive": {
        "name": "Executive Boardroom",
        "style": "Fortune 500 executive boardroom aesthetic, radical minimalism, 40%+ dark space, single powerful stat or statement per slide, no bullet points, Tim Cook presentation restraint, dramatic pauses in visual flow",
        "typography": "Inter or SF Pro Display style geometric sans-serif, ultra-clean with wide letter-spacing on titles, generous line height",
        "colors": {"primary": "#1E1E1E", "secondary": "#424242", "accent": "#C9A962", "background": "#0A0A0A", "text": "#FFFFFF"},
    },
    "consulting": {
        "name": "Consulting Firm",
        "style": "McKinsey BCG Bain consulting deck aesthetic, data-driven clarity, every slide answers 'so what?' with action title, monochromatic icon sets, generous white space, Pyramid Principle logic flow",
        "typography": "Arial Bold or Helvetica Bold for headlines, 14-16pt bold action titles as complete sentences stating insight",
        "colors": {"primary": "#005EB8", "secondary": "#222222", "accent": "#F3C13A", "background": "#F5F5F5", "text": "#222222"},
    },
    "tech": {
        "name": "Tech",
        "style": "isometric 3D graphics, neon accent glows, futuristic glass and metal materials, holographic UI elements, dark mode interface aesthetic, subtle grid patterns, SaaS product aesthetic",
        "typography": "geometric sans-serif (SF Pro/Roboto style), monospace accents for data and code, futuristic but readable",
        "colors": {"primary": "#00D4FF", "secondary": "#8B5CF6", "accent": "#FF006E", "background": "#0F172A", "text": "#F8FAFC"},
    },
    "minimal": {
        "name": "Minimal",
        "style": "maximum negative space (70%+ white), single accent color used sparingly, ultra-clean Swiss International Style, Dieter Rams inspired restraint, Apple-level refinement",
        "typography": "Helvetica-style neo-grotesque, strict baseline grid, mathematical proportions",
        "colors": {"primary": "#000000", "secondary": "#9CA3AF", "accent": "#EF4444", "background": "#FFFFFF", "text": "#000000"},
    },
    "cyberpunk": {
        "name": "Cyberpunk",
        "style": "neon-noir aesthetic, electric cyan and hot pink neons against deep void black, scanlines and noise textures, glitch effects, holographic iridescent gradients, Blade Runner aesthetic",
        "typography": "bold condensed sans-serif, ALL CAPS for impact, glitchy distorted display fonts for emphasis",
        "colors": {"primary": "#00F0FF", "secondary": "#FF2A6D", "accent": "#FCEE0A", "background": "#0A0A0F", "text": "#E8E8E8"},
    },
    "financial": {
        "name": "Financial Services",
        "style": "Bloomberg terminal aesthetic, amber and orange accents on pure black, data-forward design, dense but organized data tables, professional financial dashboard quality",
        "typography": "monospace fonts for numbers (Roboto Mono style), clean sans-serif for labels, tabular figures",
        "colors": {"primary": "#000000", "secondary": "#F39F41", "accent": "#0068FF", "background": "#000000", "text": "#FB8B1E"},
    },
    "keynote": {
        "name": "Keynote",
        "style": "Apple WWDC keynote aesthetic, clean smooth gradients, premium luxury feel, subtle depth, frosted glass UI elements, Tim Cook presentation quality",
        "typography": "San Francisco Pro Display style, generous whitespace, Apple Human Interface Guidelines",
        "colors": {"primary": "#007AFF", "secondary": "#8E8E93", "accent": "#34C759", "background": "#FFFFFF", "text": "#1C1C1E"},
    },
    "dark-mode": {
        "name": "Dark Mode UI",
        "style": "modern dark mode app interface like Discord Notion Slack, layered grays creating hierarchy, subtle elevation, desaturated accent colors, soft glows instead of hard shadows",
        "typography": "Inter SF Pro or system sans-serif, 400 weight body 500-600 headings, off-white text",
        "colors": {"primary": "#1E1F22", "secondary": "#313338", "accent": "#5865F2", "background": "#2B2D31", "text": "#DCDDDE"},
    },
    "saas": {
        "name": "SaaS Product",
        "style": "modern SaaS product aesthetic like Linear Vercel Stripe, extreme minimalism with monochrome base, dark mode primary, glassmorphism accents, code snippet aesthetic",
        "typography": "Inter or Geist font style, bold 700 weight large headings, regular 400 body",
        "colors": {"primary": "#0A0A0A", "secondary": "#888888", "accent": "#0070F3", "background": "#171717", "text": "#EDEDED"},
    },
    "pitch-deck": {
        "name": "Pitch Deck",
        "style": "Y Combinator pitch deck aesthetic, clean data visualizations, modern SaaS iconography, investor-ready quality, Sequoia presentation standards",
        "typography": "modern geometric sans-serif (Circular/Product Sans style), bold metric numbers, startup confidence",
        "colors": {"primary": "#0D9488", "secondary": "#64748B", "accent": "#22C55E", "background": "#FFFFFF", "text": "#0F172A"},
    },
    "training": {
        "name": "Training",
        "style": "instructional design excellence, clear educational diagrams, step-by-step visual sequences, learning path visualization, Khan Academy clarity",
        "typography": "highly readable sans-serif (Open Sans style), clear numbered lists, accessibility-first sizing",
        "colors": {"primary": "#059669", "secondary": "#3B82F6", "accent": "#FCD34D", "background": "#F3F4F6", "text": "#1F2937"},
    },
}

# Add remaining vibes with generic professional fallback
for vibe in VIBES:
    if vibe not in VIBE_STYLES:
        VIBE_STYLES[vibe] = {
            "name": vibe.replace("-", " ").title(),
            "style": f"{vibe.replace('-', ' ')} visual style, professional quality, clean layout, clear hierarchy",
            "typography": "clean sans-serif typography, clear hierarchy, readable",
            "colors": {"primary": "#1E3A5F", "secondary": "#4A6FA5", "accent": "#14B8A6", "background": "#FFFFFF", "text": "#1F2937"},
        }

# Quality prompts
QUALITY_PREFIX = """QUALITY REQUIREMENTS:
- Masterpiece quality, best quality, highly detailed, professional grade
- 4K resolution rendering, ultra-sharp edges, pixel-perfect precision
- Publication-ready, Behance/Dribbble portfolio quality
- Expert graphic design, award-winning presentation aesthetic
"""

NEGATIVE_PROMPT = """
AVOID:
- Blurry or soft edges, pixelated text
- Watermarks, stock photo logos
- Extra or hallucinated text, spelling errors
- Clip art aesthetic, cheap design
- Busy or cluttered backgrounds
- 3D text effects, word art
"""


# ============================================================================
# DATA CLASSES
# ============================================================================

@dataclass
class SlideContent:
    slide_number: int
    title: str
    bullet_points: List[str] = field(default_factory=list)
    visual_suggestion: str = ""


@dataclass
class GeneratedSlide:
    slide_number: int
    title: str
    image_path: str


# ============================================================================
# FILE READING
# ============================================================================

def read_file(file_path: str) -> str:
    """Read content from a file (txt, md, pdf, docx, pptx)."""
    path = Path(file_path)
    ext = path.suffix.lower()

    if ext in [".txt", ".md"]:
        return path.read_text(encoding="utf-8")

    elif ext == ".pdf":
        try:
            import pymupdf
            doc = pymupdf.open(str(path))
            text = ""
            for page in doc:
                text += page.get_text()
            doc.close()
            return text
        except ImportError:
            print("ERROR: pymupdf required for PDF. Install with: pip install pymupdf")
            sys.exit(1)

    elif ext == ".docx":
        try:
            from docx import Document
            doc = Document(str(path))
            return "\n".join([p.text for p in doc.paragraphs])
        except ImportError:
            print("ERROR: python-docx required for DOCX. Install with: pip install python-docx")
            sys.exit(1)

    elif ext == ".pptx":
        try:
            from pptx import Presentation
            prs = Presentation(str(path))
            text_parts = []
            for slide_num, slide in enumerate(prs.slides, 1):
                slide_text = []
                for shape in slide.shapes:
                    if hasattr(shape, "text") and shape.text.strip():
                        slide_text.append(shape.text.strip())
                if slide_text:
                    text_parts.append(f"Slide {slide_num}:\n" + "\n".join(slide_text))
            return "\n\n".join(text_parts)
        except ImportError:
            print("ERROR: python-pptx required for PPTX. Install with: pip install python-pptx")
            sys.exit(1)

    else:
        print(f"ERROR: Unsupported file type: {ext}")
        sys.exit(1)


# ============================================================================
# SLIDE GENERATOR
# ============================================================================

class SlideGenerator:
    """Generate slides using Google Gemini directly."""

    def __init__(self, api_key: str):
        try:
            import google.generativeai as genai
        except ImportError:
            print("ERROR: google-generativeai required. Install with: pip install google-generativeai")
            sys.exit(1)

        genai.configure(api_key=api_key)
        self.genai = genai

        # Image generation model - Gemini 3 Pro Image (Nano Banana Pro)
        # This is the same model PPT2Vid uses for slide generation
        self.image_model = genai.GenerativeModel("gemini-3-pro-image-preview")
        # Text model for content planning
        self.text_model = genai.GenerativeModel("gemini-2.0-flash")

    async def plan_slides(
        self,
        content: str,
        num_slides: int,
        vibe: str,
        density: str,
        intent: str,
        audience: str,
    ) -> List[SlideContent]:
        """Use Gemini to plan slide content from raw text."""

        density_guidance = {
            "concise": "Use minimal text, just key headlines and 1-2 bullet points max per slide",
            "standard": "Balanced content with headlines and 3-4 bullet points per slide",
            "dense": "Comprehensive content with detailed bullet points (5-7 per slide)",
        }

        prompt = f"""Analyze this content and create a {num_slides}-slide presentation outline.

CONTENT:
{content[:8000]}

REQUIREMENTS:
- Create exactly {num_slides} slides
- Target audience: {audience or 'General professional audience'}
- Presentation intent: {intent}
- Density: {density_guidance.get(density, density_guidance['standard'])}
- Visual style: {vibe}

For each slide, provide:
1. Slide number
2. Title (clear, action-oriented)
3. 3-5 bullet points with key content
4. Visual suggestion for imagery

Format your response as a JSON array:
[
  {{"slide_number": 1, "title": "...", "bullet_points": ["...", "..."], "visual_suggestion": "..."}},
  ...
]

Return ONLY the JSON array, no other text."""

        try:
            response = await self.text_model.generate_content_async(prompt)
            text = response.text.strip()

            # Extract JSON from response
            if "```json" in text:
                text = text.split("```json")[1].split("```")[0].strip()
            elif "```" in text:
                text = text.split("```")[1].split("```")[0].strip()

            import json
            slides_data = json.loads(text)

            slides = []
            for s in slides_data:
                slides.append(SlideContent(
                    slide_number=s.get("slide_number", len(slides) + 1),
                    title=s.get("title", f"Slide {len(slides) + 1}"),
                    bullet_points=s.get("bullet_points", []),
                    visual_suggestion=s.get("visual_suggestion", ""),
                ))

            return slides[:num_slides]

        except Exception as e:
            print(f"Error planning slides: {e}")
            # Fallback: create basic slides
            return [
                SlideContent(slide_number=i + 1, title=f"Slide {i + 1}", bullet_points=["Content"])
                for i in range(num_slides)
            ]

    def construct_prompt(self, slide: SlideContent, vibe: str, colors: Dict) -> str:
        """Build the image generation prompt."""

        style = VIBE_STYLES.get(vibe, VIBE_STYLES["corporate"])

        # Build bullet text
        bullets_text = "\n".join([f"• {bp}" for bp in slide.bullet_points[:5]])

        prompt = f"""{QUALITY_PREFIX}

Create a professional presentation slide image (16:9 aspect ratio, 1920x1080 pixels).

SLIDE CONTENT:
Title: {slide.title}
{bullets_text}

VISUAL STYLE: {style['name']}
{style['style']}

TYPOGRAPHY: {style['typography']}

COLOR SCHEME:
- Primary: {colors.get('primary', style['colors']['primary'])}
- Secondary: {colors.get('secondary', style['colors']['secondary'])}
- Accent: {colors.get('accent', style['colors']['accent'])}
- Background: {colors.get('background', style['colors']['background'])}
- Text: {colors.get('text', style['colors']['text'])}

LAYOUT REQUIREMENTS:
- Title prominently displayed at top
- Content clearly readable and well-organized
- Professional presentation slide layout
- Clean margins and spacing
- 16:9 landscape aspect ratio

{NEGATIVE_PROMPT}

Generate a high-quality presentation slide image."""

        return prompt

    async def generate_slide_image(
        self,
        slide: SlideContent,
        vibe: str,
        colors: Dict,
        output_dir: Path,
    ) -> Optional[GeneratedSlide]:
        """Generate a single slide image."""

        prompt = self.construct_prompt(slide, vibe, colors)

        try:
            response = self.image_model.generate_content(prompt)

            # Extract image data
            image_data = None
            if response.candidates and response.candidates[0].content:
                for part in response.candidates[0].content.parts:
                    if hasattr(part, "inline_data") and part.inline_data:
                        image_data = part.inline_data.data
                        break

            if not image_data:
                print(f"  Warning: No image generated for slide {slide.slide_number}")
                return None

            # Save image
            image_path = output_dir / f"slide_{slide.slide_number:03d}.png"

            if isinstance(image_data, str):
                image_bytes = base64.b64decode(image_data)
            else:
                image_bytes = image_data

            with open(image_path, "wb") as f:
                f.write(image_bytes)

            return GeneratedSlide(
                slide_number=slide.slide_number,
                title=slide.title,
                image_path=str(image_path),
            )

        except Exception as e:
            print(f"  Error generating slide {slide.slide_number}: {e}")
            return None

    async def generate_deck(
        self,
        content: str,
        vibe: str,
        num_slides: int,
        density: str,
        intent: str,
        audience: str,
        output_dir: Path,
        colors: Optional[Dict] = None,
        include_title: bool = False,
        include_thank_you: bool = False,
    ) -> List[GeneratedSlide]:
        """Generate a complete slide deck."""

        output_dir.mkdir(parents=True, exist_ok=True)

        print(f"\nPlanning {num_slides} slides...")
        slides = await self.plan_slides(content, num_slides, vibe, density, intent, audience)

        style_colors = VIBE_STYLES.get(vibe, VIBE_STYLES["corporate"])["colors"]
        if colors:
            style_colors.update(colors)

        generated = []
        total = len(slides)

        for i, slide in enumerate(slides, 1):
            print(f"  Generating slide {i}/{total}: {slide.title[:40]}...")
            result = await self.generate_slide_image(slide, vibe, style_colors, output_dir)
            if result:
                generated.append(result)
            # Delay to avoid rate limiting (10 requests/min = 6 seconds between)
            if i < total:
                print(f"    Waiting 8s for rate limit...")
                await asyncio.sleep(8)

        return generated


# ============================================================================
# PDF GENERATION
# ============================================================================

def combine_slides_to_pdf(image_paths: List[Path], output_path: Path) -> bool:
    """Combine PNG slides into a single PDF file."""
    try:
        from PIL import Image
    except ImportError:
        print("ERROR: Pillow required for PDF generation. Install with: pip install pillow")
        return False

    if not image_paths:
        print("ERROR: No images to combine into PDF")
        return False

    # Sort by filename to ensure correct order
    image_paths = sorted(image_paths, key=lambda p: p.name)

    images = []
    first_image = None

    for img_path in image_paths:
        try:
            img = Image.open(img_path)
            # Convert to RGB if necessary (PNG might be RGBA)
            if img.mode in ('RGBA', 'LA', 'P'):
                # Create white background
                background = Image.new('RGB', img.size, (255, 255, 255))
                if img.mode == 'P':
                    img = img.convert('RGBA')
                background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
                img = background
            elif img.mode != 'RGB':
                img = img.convert('RGB')

            if first_image is None:
                first_image = img
            else:
                images.append(img)
        except Exception as e:
            print(f"  Warning: Could not process {img_path.name}: {e}")

    if first_image is None:
        print("ERROR: No valid images found")
        return False

    # Save as PDF
    try:
        first_image.save(
            output_path,
            "PDF",
            save_all=True,
            append_images=images,
            resolution=150.0,
        )
        return True
    except Exception as e:
        print(f"ERROR: Failed to save PDF: {e}")
        return False


# ============================================================================
# MAIN
# ============================================================================

async def main_async(args):
    """Async main function."""

    # Get API key
    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        # Try loading from PPT2Vid .env
        env_path = Path.home() / "Documents/Code/PPT2Vid/.env"
        if env_path.exists():
            try:
                from dotenv import load_dotenv
                load_dotenv(env_path)
                api_key = os.environ.get("GEMINI_API_KEY")
            except ImportError:
                pass

    if not api_key:
        print("ERROR: GEMINI_API_KEY not set")
        print("Set it in environment or in ~/Documents/Code/PPT2Vid/.env")
        return 1

    # Read input file
    print(f"Reading: {args.file}")
    content = read_file(args.file)
    print(f"Content: {len(content)} characters")

    # Determine output path
    timestamp = time.strftime("%Y%m%d_%H%M%S")
    if args.output:
        output_path = Path(args.output)
        # If output is a directory, put PDF there with default name
        if output_path.is_dir() or not output_path.suffix:
            output_path.mkdir(parents=True, exist_ok=True)
            pdf_path = output_path / f"slides_{timestamp}.pdf"
        else:
            pdf_path = output_path
    else:
        # Default to Desktop with timestamp
        pdf_path = Path.home() / "Desktop" / f"slides_{timestamp}.pdf"

    # Temp directory for PNG generation
    import tempfile
    temp_dir = Path(tempfile.mkdtemp(prefix="slides_"))

    print(f"\nGenerating {args.slides} slides")
    print(f"  Vibe: {args.vibe}")
    print(f"  Density: {args.density}")
    print(f"  Intent: {args.intent}")
    print(f"  Output: {pdf_path}")

    # Generate slides
    generator = SlideGenerator(api_key)
    start_time = time.time()

    slides = await generator.generate_deck(
        content=content,
        vibe=args.vibe,
        num_slides=args.slides,
        density=args.density,
        intent=args.intent,
        audience=args.audience,
        output_dir=temp_dir,
        include_title=args.title_slide,
        include_thank_you=args.thank_you_slide,
    )

    # Combine into PDF
    pdf_success = False
    if slides:
        print(f"\nCombining {len(slides)} slides into PDF...")
        image_paths = [Path(s.image_path) for s in slides]
        pdf_success = combine_slides_to_pdf(image_paths, pdf_path)

        if pdf_success:
            # Clean up temp PNGs
            import shutil
            shutil.rmtree(temp_dir, ignore_errors=True)
        else:
            print(f"  Warning: PDF generation failed. PNGs available at: {temp_dir}")

    elapsed = time.time() - start_time

    # Report results
    print(f"\n{'='*60}")
    print(f"Generated {len(slides)} slides in {elapsed:.1f}s")
    print(f"{'='*60}")
    if slides and pdf_success:
        print(f"\nPDF: {pdf_path}")
    else:
        print(f"\nOutput: {temp_dir}")
    for slide in slides:
        print(f"  - Slide {slide.slide_number}: {slide.title}")

    return 0


def main():
    parser = argparse.ArgumentParser(
        description="Generate presentation slides from documents using Gemini AI",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    parser.add_argument("file", help="Input file (pdf, docx, pptx, txt, md)")
    parser.add_argument("--vibe", "-v", default="corporate", choices=VIBES,
                        help="Visual style (default: corporate)")
    parser.add_argument("--slides", "-n", type=int, default=10,
                        help="Number of slides 1-15 (default: 10)")
    parser.add_argument("--density", "-d", default="standard", choices=DENSITIES,
                        help="Information density (default: standard)")
    parser.add_argument("--intent", "-i", default="inform", choices=INTENTS,
                        help="Presentation intent (default: inform)")
    parser.add_argument("--palette", "-p", choices=COLOR_PALETTES,
                        help="Color palette override")
    parser.add_argument("--audience", "-a", help="Target audience")
    parser.add_argument("--output", "-o", help="Output directory (default: ~/Desktop/slides_<timestamp>)")

    # Special slides
    parser.add_argument("--title-slide", action="store_true", help="Include title slide")
    parser.add_argument("--thank-you-slide", action="store_true", help="Include thank you slide")

    # List options
    parser.add_argument("--list-vibes", action="store_true", help="List all vibes")
    parser.add_argument("--list-intents", action="store_true", help="List all intents")
    parser.add_argument("--list-palettes", action="store_true", help="List all color palettes")

    args = parser.parse_args()

    if args.list_vibes:
        print("Available vibes:")
        for v in VIBES:
            print(f"  {v}")
        return 0

    if args.list_intents:
        print("Available intents:")
        for i in INTENTS:
            print(f"  {i}")
        return 0

    if args.list_palettes:
        print("Available color palettes:")
        for p in COLOR_PALETTES:
            print(f"  {p}")
        return 0

    if args.slides < 1 or args.slides > 15:
        print("ERROR: slides must be between 1 and 15")
        return 1

    if not Path(args.file).exists():
        print(f"ERROR: File not found: {args.file}")
        return 1

    return asyncio.run(main_async(args))


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