import sharp from "sharp";
import { optimize } from "svgo";
import { puterChatComplete } from "../puter-inference.js";
import type { RenderResult, IconSetBundle } from "./types.js";

// ─── SVG validation and cleanup ────────────────────────────────────────────────

/**
 * Validate and clean an SVG string.
 * - Runs SVGO optimization (preset-default)
 * - Ensures viewBox="0 0 24 24" is present
 * - Ensures xmlns is present
 * - Validates at least one drawable element (path/circle/rect) exists
 */
export function validateAndCleanSvg(raw: string): {
  svg: string;
  valid: boolean;
  error?: string;
} {
  let svgStr = raw.trim();

  // Run SVGO optimization
  try {
    const result = optimize(svgStr, {
      plugins: ["preset-default"],
    });
    svgStr = result.data;
  } catch {
    // If SVGO fails, continue with the raw string
  }

  // Ensure xmlns is present
  if (!svgStr.includes("xmlns")) {
    svgStr = svgStr.replace("<svg", '<svg xmlns="http://www.w3.org/2000/svg"');
  }

  // Ensure viewBox is present (normalize to 0 0 24 24)
  if (!svgStr.includes("viewBox")) {
    svgStr = svgStr.replace("<svg", '<svg viewBox="0 0 24 24"');
  }

  // Validate at least one drawable element exists
  const hasDrawable =
    svgStr.includes("<path") ||
    svgStr.includes("<circle") ||
    svgStr.includes("<rect");

  if (!hasDrawable) {
    return {
      svg: svgStr,
      valid: false,
      error:
        "SVG must contain at least one path, circle, or rect element",
    };
  }

  return { svg: svgStr, valid: true };
}

// ─── LLM prompt building ────────────────────────────────────────────────────────

const STYLE_CONSTRAINTS: Record<string, string> = {
  outline:
    "Use stroke only (no fills). Set stroke='currentColor', fill='none', stroke-width='1.5'.",
  filled:
    "Use fill only (no strokes). Set fill='currentColor', stroke='none'.",
  rounded:
    "Use stroke with rounded caps. Set stroke='currentColor', fill='none', stroke-width='1.5', stroke-linecap='round', stroke-linejoin='round'.",
};

export function buildIconPrompt(
  description: string,
  style: string,
  count: number,
): string {
  const styleConstraint =
    STYLE_CONSTRAINTS[style] ??
    STYLE_CONSTRAINTS["outline"]!;

  return [
    `Generate exactly ${count} SVG icon(s) for: ${description}`,
    "",
    "Rules:",
    "- Each SVG must use viewBox=\"0 0 24 24\"",
    "- No text elements, no embedded images, no scripts",
    "- Use only geometric path/circle/rect elements",
    `- Style: ${styleConstraint}`,
    "- Use currentColor for color values",
    "",
    `Respond with ONLY a JSON array of exactly ${count} objects in this format:`,
    '[{"name": "icon-name", "svg": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\">...</svg>"}, ...]',
    "",
    "No explanation, no markdown fences. Just the JSON array.",
  ].join("\n");
}

// ─── PNG rasterization ────────────────────────────────────────────────────────

async function rasterizeSvgToPng(svgString: string, size: number): Promise<Buffer> {
  return sharp(Buffer.from(svgString), { density: 96 })
    .resize(size)
    .png()
    .toBuffer();
}

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

/**
 * Generate a set of SVG icons from a description using the LLM,
 * then clean via SVGO and rasterize to PNG at 16/32/64px.
 */
export async function renderIconSet(
  input: Record<string, unknown>,
): Promise<RenderResult> {
  const description =
    typeof input.description === "string" ? input.description : "icons";
  const style =
    typeof input.style === "string" &&
    ["outline", "filled", "rounded"].includes(input.style)
      ? (input.style as "outline" | "filled" | "rounded")
      : "outline";
  const count =
    typeof input.count === "number" &&
    [1, 4, 8, 16].includes(input.count)
      ? input.count
      : 4;

  const prompt = buildIconPrompt(description, style, count);

  // ── Call LLM ────────────────────────────────────────────────────────────────
  let rawResponse = await puterChatComplete([
    { role: "user", content: prompt },
  ]);

  // ── Parse JSON (retry once on failure) ────────────────────────────────────
  let iconList: Array<{ name: string; svg: string }>;
  try {
    iconList = parseIconJson(rawResponse);
  } catch {
    // Retry with explicit JSON-only instruction
    rawResponse = await puterChatComplete([
      { role: "user", content: prompt },
      {
        role: "assistant",
        content: rawResponse,
      },
      {
        role: "user",
        content:
          "Your response was not valid JSON. Respond with ONLY the JSON array, no other text.",
      },
    ]);
    iconList = parseIconJson(rawResponse);
  }

  // ── Validate, clean, and rasterize each icon ────────────────────────────────
  const PNG_SIZES = [16, 32, 64] as const;
  const validIcons: IconSetBundle["icons"] = [];

  for (const item of iconList) {
    const validation = validateAndCleanSvg(item.svg);
    if (!validation.valid) {
      // Skip invalid icons (partial failure handling)
      continue;
    }

    const pngs: Record<string, string> = {};
    for (const size of PNG_SIZES) {
      try {
        const pngBuffer = await rasterizeSvgToPng(validation.svg, size);
        pngs[String(size)] = pngBuffer.toString("base64");
      } catch {
        // If rasterization fails for a size, skip it
      }
    }

    validIcons.push({
      name: item.name ?? `icon-${validIcons.length + 1}`,
      svgSource: validation.svg,
      pngs,
    });
  }

  // ── Build bundle ───────────────────────────────────────────────────────────
  const bundle: IconSetBundle = {
    type: "icon-set-bundle",
    style,
    icons: validIcons,
  };

  return {
    filename: "icon-set-bundle.json",
    contentType: "application/json",
    buffer: Buffer.from(JSON.stringify(bundle)),
  };
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function parseIconJson(raw: string): Array<{ name: string; svg: string }> {
  const trimmed = raw.trim();
  // Strip markdown fences if present
  const stripped = trimmed
    .replace(/^```(?:json)?\s*/i, "")
    .replace(/\s*```$/, "")
    .trim();

  const parsed = JSON.parse(stripped) as unknown;
  if (!Array.isArray(parsed)) {
    throw new Error("LLM response is not a JSON array");
  }
  return parsed as Array<{ name: string; svg: string }>;
}
