import { puterChatComplete } from "../puter-inference.js";
import { publishLiveEvent } from "../live-events.js";
import { resolveBrowserPath } from "./diagram-renderer.js";
import type { RenderResult, PresentationBundle } from "./types.js";

// ─── Local helpers ──────────────────────────────────────────────────────────────

function stripMarkdownFences(raw: string): string {
  return raw
    .trim()
    .replace(/^```(?:json)?\s*/i, "")
    .replace(/\s*```$/, "")
    .trim();
}

// ─── Slide types (mirrors compositions) ────────────────────────────────────────

interface Slide {
  title: string;
  body: string;
  accent: string;
}

interface DemoSlide {
  title: string;
  content: string;
  narration: string;
}

// ─── LLM slide generation ───────────────────────────────────────────────────────

async function generatePitchDeckSlides(prompt: string): Promise<Slide[]> {
  const systemPrompt = [
    "You are a professional pitch deck designer.",
    "Generate slide content as a JSON array of slide objects.",
    "Respond with ONLY a JSON array — no markdown fences, no explanation.",
    "Required fields per slide:",
    '  "title": slide heading (string)',
    '  "body": slide body text (2-4 sentences, string)',
    '  "accent": a distinct accent hex color for this slide (e.g. "#3B82F6", string)',
    "Generate 6-10 slides covering: title slide, problem, solution, market opportunity,",
    "business model, traction/milestones, team, ask/closing.",
    "Each slide should have a visually distinct accent color that fits a dark presentation theme.",
    "The first slide is the title/hero slide.",
  ].join("\n");

  const raw = await puterChatComplete([
    { role: "system", content: systemPrompt },
    {
      role: "user",
      content: `Create a pitch deck for: ${prompt}`,
    },
  ]);

  const cleaned = stripMarkdownFences(raw);
  let parsed: unknown;
  try {
    parsed = JSON.parse(cleaned);
  } catch (err) {
    throw new Error(
      `generatePitchDeckSlides: failed to parse LLM response as JSON.\n` +
        `Raw response: ${raw}\nError: ${String(err)}`,
    );
  }

  if (!Array.isArray(parsed)) {
    throw new Error(
      `generatePitchDeckSlides: expected JSON array, got ${typeof parsed}. Raw: ${raw}`,
    );
  }

  return (parsed as Partial<Slide>[]).map((s, i) => ({
    title: s.title ?? `Slide ${i + 1}`,
    body: s.body ?? "",
    accent: s.accent ?? "#3B82F6",
  }));
}

async function generateDemoVideoSlides(prompt: string): Promise<DemoSlide[]> {
  const systemPrompt = [
    "You are a professional product demo video scriptwriter.",
    "Generate slide content as a JSON array of demo slide objects.",
    "Respond with ONLY a JSON array — no markdown fences, no explanation.",
    "Required fields per slide:",
    '  "title": slide heading (string)',
    '  "content": main content / key points for this slide (2-3 sentences, string)',
    '  "narration": voiceover narration script for this slide (1-3 sentences, string)',
    "Generate 4-8 slides covering: intro/hook, key features (one per slide),",
    "demo walkthrough, call-to-action/conclusion.",
    "Each narration should flow naturally as a spoken script.",
  ].join("\n");

  const raw = await puterChatComplete([
    { role: "system", content: systemPrompt },
    {
      role: "user",
      content: `Create a demo video for: ${prompt}`,
    },
  ]);

  const cleaned = stripMarkdownFences(raw);
  let parsed: unknown;
  try {
    parsed = JSON.parse(cleaned);
  } catch (err) {
    throw new Error(
      `generateDemoVideoSlides: failed to parse LLM response as JSON.\n` +
        `Raw response: ${raw}\nError: ${String(err)}`,
    );
  }

  if (!Array.isArray(parsed)) {
    throw new Error(
      `generateDemoVideoSlides: expected JSON array, got ${typeof parsed}. Raw: ${raw}`,
    );
  }

  return (parsed as Partial<DemoSlide>[]).map((s, i) => ({
    title: s.title ?? `Slide ${i + 1}`,
    content: s.content ?? "",
    narration: s.narration ?? "",
  }));
}

// ─── Main exported renderer ─────────────────────────────────────────────────────

/**
 * Render a presentation (pitch deck or demo video) to MP4 via Remotion.
 *
 * Flow:
 * 1. Extract prompt, videoType, title from input
 * 2. Generate slide JSON via LLM (puterChatComplete)
 * 3. Build inputProps for Remotion
 * 4. Dynamic-import @paperclipai/content-renderer
 * 5. Resolve Playwright Chromium browser path
 * 6. Render via renderPresentationComposition with onProgress SSE events
 * 7. Wrap result in PresentationBundle and return as RenderResult
 */
export async function renderPresentation(
  input: Record<string, unknown>,
  companyId: string = "",
  jobId: string = "",
): Promise<RenderResult> {
  const prompt =
    typeof input.prompt === "string" ? input.prompt : "Create a presentation";
  const videoType =
    input.videoType === "demo-video" || input.videoType === "demo"
      ? "demo-video"
      : "pitch-deck";
  const title =
    typeof input.title === "string" && input.title.length > 0
      ? input.title
      : "Presentation";

  // Step 1: Generate slides via LLM
  let slides: Slide[] | DemoSlide[];
  if (videoType === "demo-video") {
    slides = await generateDemoVideoSlides(prompt);
  } else {
    slides = await generatePitchDeckSlides(prompt);
  }

  // Step 2: Build inputProps for Remotion
  // Note: content-renderer uses videoType === "demo" to select DemoVideo composition
  const inputProps: Record<string, unknown> =
    videoType === "demo-video"
      ? { slides, title, videoType: "demo" }
      : { slides, companyName: title, videoType: "pitch-deck" };

  // Step 3: Dynamic import of content-renderer (keeps webpack/rspack out of server tsc context)
  const { getBundlePath, renderPresentationComposition } = await import(
    "@paperclipai/content-renderer"
  );

  // Step 4: Resolve Playwright Chromium binary — fallback to undefined for Remotion auto-download
  let browserExecutable: string | undefined;
  try {
    browserExecutable = resolveBrowserPath();
  } catch {
    browserExecutable = undefined;
  }

  // Step 5: Render via Remotion — onProgress publishes SSE content_job.progress events
  const serveUrl = await getBundlePath();

  const result = await renderPresentationComposition({
    serveUrl,
    input: inputProps,
    browserExecutable,
    onProgress: (progress: number) => {
      if (companyId) {
        publishLiveEvent({
          companyId,
          type: "content_job.progress",
          payload: { jobId, progress },
        });
      }
    },
  });

  // Step 6: Assemble PresentationBundle
  const bundle: PresentationBundle = {
    type: "presentation-bundle",
    presentationType: videoType === "demo-video" ? "demo-video" : "pitch-deck",
    title,
    slideCount: slides.length,
    durationInFrames: result.durationInFrames,
    fps: result.fps,
    mp4Base64: result.buffer.toString("base64"),
    inputProps: result.inputProps,
  };

  // Step 7: Return as JSON RenderResult — bundle contains both MP4 (base64) and metadata
  const safeFilename = title.replace(/[^a-z0-9_\-]/gi, "-").toLowerCase();
  return {
    filename: `${safeFilename}.json`,
    contentType: "application/json",
    buffer: Buffer.from(JSON.stringify(bundle)),
  };
}
