import pptxgen from "pptxgenjs";
import { NextRequest, NextResponse } from "next/server";
import {
  LayoutSchema,
  SlideElement,
  mergeElementsWithContent,
  isJsonLayoutCode,
} from "@/app/hooks/parseLayoutSchema";

// Slide dimensions: 1280×720 px at 96 DPI = 13.333×7.5 inches
const PX_TO_IN = 1 / 96;

interface IncomingSlide {
  id: string;
  layout: string;
  layout_group: string;
  index: number;
  content: Record<string, unknown>;
  speaker_note?: string;
}

interface IncomingLayout {
  layout_id: string;
  layout_name: string;
  layout_code: string;
}

interface GeneratePptxPayload {
  title?: string;
  slides: IncomingSlide[];
  layouts: IncomingLayout[];
}

export async function POST(request: NextRequest) {
  try {
    const body: GeneratePptxPayload = await request.json();
    const { title = "presentation", slides, layouts } = body;

    if (!slides || slides.length === 0) {
      return NextResponse.json({ error: "No slides provided" }, { status: 400 });
    }

    const pres = new pptxgen();
    pres.defineLayout({ name: "WIDE", width: 13.333, height: 7.5 });
    pres.layout = "WIDE";

    // Build layout lookup by layout_id
    const layoutMap = new Map<string, IncomingLayout>();
    for (const layout of layouts || []) {
      layoutMap.set(layout.layout_id, layout);
    }

    for (const slideData of slides) {
      const slide = pres.addSlide();
      const layoutData = layoutMap.get(slideData.layout);

      if (layoutData && isJsonLayoutCode(layoutData.layout_code)) {
        renderJsonLayout(pres, slide, layoutData.layout_code, slideData.content);
      } else {
        // Fallback: render content as text blocks
        renderFallback(slide, slideData.content);
      }

      if (slideData.speaker_note) {
        slide.addNotes(slideData.speaker_note);
      }
    }

    const buffer = await pres.write({ outputType: "arraybuffer" });
    const safeTitle = title.replace(/[^a-zA-Z0-9_\- ]/g, "").trim() || "presentation";

    return new NextResponse(buffer as ArrayBuffer, {
      headers: {
        "Content-Type":
          "application/vnd.openxmlformats-officedocument.presentationml.presentation",
        "Content-Disposition": `attachment; filename="${safeTitle}.pptx"`,
      },
    });
  } catch (error: any) {
    console.error("[generate-pptx] Error:", error);
    return NextResponse.json(
      { error: `Failed to generate PPTX: ${error?.message || error}` },
      { status: 500 }
    );
  }
}

function renderJsonLayout(
  pres: pptxgen,
  slide: pptxgen.Slide,
  layoutCode: string,
  content: Record<string, unknown>
) {
  const schema: LayoutSchema = JSON.parse(layoutCode);

  // Background color
  const bgColor = parseCssColor(schema.background);
  if (bgColor) {
    slide.background = { color: bgColor };
  }

  const elements = mergeElementsWithContent(schema, content);

  for (const elem of elements) {
    if (!elem.w || !elem.h) continue;

    const pos = {
      x: elem.x * PX_TO_IN,
      y: elem.y * PX_TO_IN,
      w: elem.w * PX_TO_IN,
      h: elem.h * PX_TO_IN,
    };

    if (elem.type === "text") {
      addTextElement(slide, elem, pos);
    } else if (elem.type === "image") {
      addImageElement(slide, elem, pos);
    } else if (elem.type === "shape") {
      addShapeElement(pres, slide, elem, pos);
    }
  }
}

function addTextElement(
  slide: pptxgen.Slide,
  elem: SlideElement,
  pos: { x: number; y: number; w: number; h: number }
) {
  const text = (elem.content || elem.defaultContent || "").trim();
  if (!text) return;

  const fontSize = parseFontSize(elem.style?.fontSize);
  const colorHex = parseCssColor(String(elem.style?.color || ""));
  const align = parseTextAlign(String(elem.style?.textAlign || "left"));
  const fontFace = parseFontFamily(String(elem.style?.fontFamily || "Calibri"));
  const bold =
    elem.style?.fontWeight === "bold" ||
    String(elem.style?.fontWeight) === "700";
  const italic = elem.style?.fontStyle === "italic";

  try {
    slide.addText(text, {
      ...pos,
      fontSize,
      color: colorHex || "000000",
      bold,
      italic,
      fontFace,
      align,
      valign: "top",
      wrap: true,
    });
  } catch {
    // Skip element if PptxGenJS rejects it
  }
}

function addImageElement(
  slide: pptxgen.Slide,
  elem: SlideElement,
  pos: { x: number; y: number; w: number; h: number }
) {
  const src = elem.imageUrl;
  if (!src) return;

  try {
    slide.addImage({ path: src, ...pos });
  } catch {
    // Skip if image path/URL is invalid
  }
}

function addShapeElement(
  pres: pptxgen,
  slide: pptxgen.Slide,
  elem: SlideElement,
  pos: { x: number; y: number; w: number; h: number }
) {
  const fillColor =
    parseCssColor(String(elem.style?.backgroundColor || "")) ||
    parseCssColor(String(elem.style?.background || "")) ||
    "EEEEEE";
  try {
    slide.addShape(pres.ShapeType.rect, {
      ...pos,
      fill: { color: fillColor },
      line: { color: fillColor, width: 0 },
    });
  } catch {
    // Skip if shape fails
  }
}

function renderFallback(
  slide: pptxgen.Slide,
  content: Record<string, unknown>
) {
  // Render content keys as simple text list
  const lines: string[] = [];
  for (const [key, val] of Object.entries(content)) {
    if (key.startsWith("__") || !val) continue;
    lines.push(String(val));
  }
  if (lines.length === 0) return;

  try {
    slide.addText(lines.join("\n"), {
      x: 0.5,
      y: 0.5,
      w: 12.333,
      h: 6.5,
      fontSize: 18,
      color: "333333",
      wrap: true,
      valign: "top",
    });
  } catch {
    // skip
  }
}

// ─── helpers ────────────────────────────────────────────────────────────────

/**
 * Parse a CSS color value to a 6-char uppercase hex string.
 * Returns undefined if the color can't be parsed.
 */
function parseCssColor(color: string | undefined): string | undefined {
  if (!color) return undefined;
  const s = color.trim();

  // Already 6-char hex (with or without #)
  const hex6 = s.replace(/^#/, "");
  if (/^[0-9A-Fa-f]{6}$/.test(hex6)) return hex6.toUpperCase();

  // 3-char hex
  if (/^[0-9A-Fa-f]{3}$/.test(hex6)) {
    return `${hex6[0]}${hex6[0]}${hex6[1]}${hex6[1]}${hex6[2]}${hex6[2]}`.toUpperCase();
  }

  // rgb(r, g, b) or rgba(r, g, b, a)
  const rgb = s.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
  if (rgb) {
    const r = parseInt(rgb[1]).toString(16).padStart(2, "0");
    const g = parseInt(rgb[2]).toString(16).padStart(2, "0");
    const b = parseInt(rgb[3]).toString(16).padStart(2, "0");
    return `${r}${g}${b}`.toUpperCase();
  }

  // Named colors (minimal set for dark/light backgrounds)
  const named: Record<string, string> = {
    white: "FFFFFF",
    black: "000000",
    red: "FF0000",
    green: "008000",
    blue: "0000FF",
    gray: "808080",
    grey: "808080",
    transparent: "FFFFFF",
  };
  if (named[s.toLowerCase()]) return named[s.toLowerCase()];

  return undefined;
}

/**
 * Parse CSS fontSize (e.g. "24px", "18pt", 24) → points for PptxGenJS.
 * PptxGenJS uses pt. 1px = 0.75pt at 96 DPI.
 */
function parseFontSize(
  val: string | number | undefined,
  fallback = 16
): number {
  if (!val) return fallback;
  const s = String(val).trim();
  if (s.endsWith("pt")) {
    const n = parseFloat(s);
    return isNaN(n) ? fallback : Math.round(n);
  }
  if (s.endsWith("px")) {
    const n = parseFloat(s);
    return isNaN(n) ? fallback : Math.round(n * 0.75);
  }
  const n = parseFloat(s);
  // Heuristic: if > 100, probably px; otherwise pt
  if (!isNaN(n)) return n > 100 ? Math.round(n * 0.75) : Math.round(n);
  return fallback;
}

function parseTextAlign(
  val: string
): "left" | "center" | "right" | "justify" {
  if (val === "center" || val === "right" || val === "justify") return val;
  return "left";
}

function parseFontFamily(val: string): string {
  if (!val) return "Calibri";
  // Take first font in CSS font-family stack
  return val.split(",")[0].trim().replace(/['"]/g, "") || "Calibri";
}
