/**
 * SVG Template Renderer
 *
 * Replaces {{placeholders}} inside SVG XML with real data values,
 * then renders the result to PNG / JPG / PDF using sharp.
 *
 * Usage:
 *   const result = await renderSvgTemplate({
 *     templateSvg: '<svg...>{{name}}</svg>',
 *     data: { name: 'John Doe' },
 *     outputFormat: 'png',
 *     width: 512,
 *     height: 512
 *   });
 */

import sharp from 'sharp';
import PDFDocument from 'pdfkit';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
import path from 'path';

// ─── Types ───────────────────────────────────────────────────────────────────

export interface SvgRenderOptions {
  /** Raw SVG XML string (may contain {{placeholders}}) */
  templateSvg: string;
  /** Key/value map to replace {{keys}} in the template */
  data: Record<string, string>;
  /** Desired output format */
  outputFormat: 'png' | 'jpg' | 'pdf';
  /** Output width in px (defaults to viewBox or 512) */
  width?: number;
  /** Output height in px (defaults to viewBox or 512) */
  height?: number;
  /** Background colour for raster output (default: transparent) */
  background?: string;
  /** Optional companyId for organised file storage */
  companyId?: string;
}

export interface SvgRenderResult {
  /** Relative URL to the generated file (e.g. /uploads/svg-rendered/abc.png) */
  url: string;
  /** Absolute filesystem path */
  filePath: string;
  /** MIME type */
  mimeType: string;
  /** File size in bytes */
  fileSize: number;
}

// ─── Constants ───────────────────────────────────────────────────────────────

const RENDERED_DIR = path.resolve(process.cwd(), 'uploads', 'svg-rendered');

// Ensure output directory exists
if (!fs.existsSync(RENDERED_DIR)) {
  fs.mkdirSync(RENDERED_DIR, { recursive: true });
}

// ─── Placeholder Replacement ─────────────────────────────────────────────────

/**
 * Replace all {{key}} placeholders in the SVG string with values from data.
 * Falls back to empty string if a key is missing.
 */
function replacePlaceholders(svg: string, data: Record<string, string>): string {
  return svg.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
    const value = data[key];
    if (value === undefined || value === null) {
      console.warn(`[SvgRenderer] Missing placeholder: {{${key}}}`);
      return '';
    }
    // XML-escape the value so special chars don't break the SVG
    return xmlEscape(value);
  });
}

/** Escape special XML characters to keep SVG valid */
function xmlEscape(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

// ─── Dimension Extraction ──────────────────────────────────────────────────

/**
 * Extract width/height from SVG viewBox or width/height attributes.
 * Returns defaults if none are found.
 */
function extractDimensions(svg: string): { width: number; height: number } {
  const viewBoxMatch = svg.match(/viewBox="\d+\s+\d+\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)"/);
  if (viewBoxMatch) {
    return { width: parseFloat(viewBoxMatch[1]), height: parseFloat(viewBoxMatch[2]) };
  }

  const widthMatch = svg.match(/width="(\d+(?:\.\d+)?)(?:px)?"/);
  const heightMatch = svg.match(/height="(\d+(?:\.\d+)?)(?:px)?"/);
  if (widthMatch && heightMatch) {
    return { width: parseFloat(widthMatch[1]), height: parseFloat(heightMatch[1]) };
  }

  return { width: 512, height: 512 };
}

// ─── Main Render Function ──────────────────────────────────────────────────

/**
 * Render an SVG template to a raster image or PDF.
 *
 * @returns File metadata for the generated output
 */
export async function renderSvgTemplate(options: SvgRenderOptions): Promise<SvgRenderResult> {
  const {
    templateSvg,
    data,
    outputFormat,
    width: userWidth,
    height: userHeight,
    background,
    companyId,
  } = options;

  // 1. Replace placeholders
  const finalSvg = replacePlaceholders(templateSvg, data);

  // 2. Determine dimensions
  const { width: svgWidth, height: svgHeight } = extractDimensions(finalSvg);
  const targetWidth = userWidth || svgWidth;
  const targetHeight = userHeight || svgHeight;

  // 3. Build output path
  const id = uuidv4();
  const subDir = companyId ? path.join(RENDERED_DIR, companyId) : RENDERED_DIR;
  if (!fs.existsSync(subDir)) {
    fs.mkdirSync(subDir, { recursive: true });
  }

  const ext = outputFormat === 'jpg' ? 'jpg' : outputFormat;
  const fileName = `${id}.${ext}`;
  const filePath = path.join(subDir, fileName);

  // 4. Render
  if (outputFormat === 'pdf') {
    // Create a PDF with the SVG embedded as a vector image
    const doc = new PDFDocument();
    const writeStream = fs.createWriteStream(filePath);
    doc.pipe(writeStream);

    // Embed SVG as an image (pdfkit supports SVG via addImage if sharp converts first)
    // For simplicity, render SVG to PNG first then embed in PDF
    const pngBuffer = await sharp(Buffer.from(finalSvg), { density: 300 })
      .png()
      .resize(targetWidth, targetHeight, { fit: 'contain', background: background ? { r: 255, g: 255, b: 255, alpha: 1 } : undefined })
      .toBuffer();

    // Write PNG to a temp file, then embed in PDF
    const tempPngPath = path.join(subDir, `${id}-temp.png`);
    fs.writeFileSync(tempPngPath, pngBuffer);
    doc.image(tempPngPath, 0, 0, { width: targetWidth, height: targetHeight });
    doc.end();

    // Wait for PDF to finish writing
    await new Promise<void>((resolve, reject) => {
      writeStream.on('finish', () => {
        // Clean up temp PNG
        if (fs.existsSync(tempPngPath)) fs.unlinkSync(tempPngPath);
        resolve();
      });
      writeStream.on('error', reject);
    });
  } else {
    // Raster output (PNG or JPG)
    const sharpInstance = sharp(Buffer.from(finalSvg), {
      density: 300, // High DPI for crisp text
    }).resize(targetWidth, targetHeight, {
      fit: 'contain',
      background: background
        ? { r: 255, g: 255, b: 255, alpha: 1 }
        : { r: 0, g: 0, b: 0, alpha: 0 },
    });

    if (outputFormat === 'jpg') {
      await sharpInstance.jpeg({ quality: 95 }).toFile(filePath);
    } else {
      await sharpInstance.png().toFile(filePath);
    }
  }

  // 5. Build result
  const stats = fs.statSync(filePath);
  const relativeDir = companyId ? `/uploads/svg-rendered/${companyId}` : '/uploads/svg-rendered';
  const mimeType = outputFormat === 'jpg' ? 'image/jpeg' : outputFormat === 'pdf' ? 'application/pdf' : 'image/png';

  return {
    url: `${relativeDir}/${fileName}`,
    filePath,
    mimeType,
    fileSize: stats.size,
  };
}

// ─── Batch Render ────────────────────────────────────────────────────────────

/**
 * Render multiple SVG templates in parallel.
 */
export async function batchRenderSvgTemplates(
  jobs: SvgRenderOptions[]
): Promise<SvgRenderResult[]> {
  return Promise.all(jobs.map((job) => renderSvgTemplate(job)));
}
