/**
 * Image Generator for LaTeX/Markdown content
 * 
 * Uses html-to-image to convert rendered math content to PNG/SVG images.
 * Leverages KaTeX for LaTeX rendering.
 */

import { toPng, toSvg, toBlob } from 'html-to-image';
import katex from 'katex';
import 'katex/dist/katex.min.css';

/**
 * Options for image generation
 */
export interface ImageGeneratorOptions {
  /** Background color (default: white) */
  backgroundColor?: string;
  /** Padding in pixels (default: 20) */
  padding?: number;
  /** Font size in pixels (default: 20) */
  fontSize?: number;
  /** Scale factor for higher resolution (default: 2) */
  scale?: number;
  /** Output format */
  format?: 'png' | 'svg' | 'blob';
}

const defaultOptions: Required<ImageGeneratorOptions> = {
  backgroundColor: '#ffffff',
  padding: 20,
  fontSize: 20,
  scale: 2,
  format: 'png',
};

/**
 * Renders LaTeX content to an HTML element using KaTeX
 */
function renderLatexToHtml(latex: string, fontSize: number): string {
  try {
    // Check if it's display mode (block math)
    const isDisplayMode = latex.includes('\\begin{') || 
                          latex.includes('\\frac') || 
                          latex.includes('\\sum') ||
                          latex.includes('\\int') ||
                          latex.length > 50;
    
    const html = katex.renderToString(latex, {
      throwOnError: false,
      displayMode: isDisplayMode,
      output: 'html',
      strict: false,
    });
    
    return `
      <div style="font-size: ${fontSize}px; line-height: 1.5;">
        ${html}
      </div>
    `;
  } catch (error) {
    console.error('KaTeX render error:', error);
    return `<div style="color: red;">Render error: ${error}</div>`;
  }
}

/**
 * Waits for all KaTeX fonts to be loaded
 */
async function waitForKatexFonts(): Promise<void> {
  // KaTeX font families that need to be loaded
  const katexFontFamilies = [
    'KaTeX_Main',
    'KaTeX_Math',
    'KaTeX_Size1',
    'KaTeX_Size2',
    'KaTeX_Size3',
    'KaTeX_Size4',
  ];

  // Wait for document fonts to be ready
  await document.fonts.ready;

  // Check if any KaTeX fonts are loaded, with timeout
  const checkFonts = () => {
    return katexFontFamilies.some(family => {
      try {
        return document.fonts.check(`16px ${family}`);
      } catch {
        return false;
      }
    });
  };

  // Wait up to 2 seconds for fonts
  const maxAttempts = 20;
  for (let i = 0; i < maxAttempts; i++) {
    if (checkFonts()) {
      return;
    }
    await new Promise(resolve => setTimeout(resolve, 100));
  }
}

/**
 * Creates a temporary container element for rendering
 * Uses z-index and opacity to hide from user but keep in viewport
 */
function createRenderContainer(
  htmlContent: string,
  options: Required<ImageGeneratorOptions>
): HTMLDivElement {
  const container = document.createElement('div');
  container.innerHTML = htmlContent;
  
  // Place in viewport but hidden
  // 1. z-index: -9999 to put behind everything
  // 2. opacity: 0 to make invisible
  // 3. pointer-events: none to avoid interaction
  container.style.cssText = `
    position: fixed;
    left: 0;
    top: 0;
    z-index: -9999;
    opacity: 0;
    padding: ${options.padding}px;
    background-color: ${options.backgroundColor};
    display: inline-block;
    max-width: 800px;
    pointer-events: none;
  `;
  container.setAttribute('data-image-generator', 'true');
  
  document.body.appendChild(container);
  return container;
}

/**
 * Removes the container from the DOM
 */
function removeRenderContainer(container: HTMLDivElement): void {
  if (container.parentNode) {
    container.parentNode.removeChild(container);
  }
}

/**
 * Common image generation logic
 */
async function generateImage(
  container: HTMLDivElement,
  opts: Required<ImageGeneratorOptions>
): Promise<string | Blob> {
  // Wait for KaTeX fonts to load
  await waitForKatexFonts();
  
  // Wait for layout
  await new Promise(resolve => setTimeout(resolve, 200));
  
  // Force layout recalculation and get actual dimensions
  const rect = container.getBoundingClientRect();
  
  // Ensure container has minimum dimensions
  if (rect.width === 0 || rect.height === 0) {
    container.style.minWidth = '100px';
    container.style.minHeight = '50px';
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  
  // Options for html-to-image
  // IMPORTANT: We override opacity to 1 here so the generated image is visible
  // even though the DOM element is invisible (opacity: 0)
  const imageOptions = {
    backgroundColor: opts.backgroundColor,
    pixelRatio: opts.scale,
    cacheBust: true,
    width: rect.width > 0 ? rect.width : undefined,
    height: rect.height > 0 ? rect.height : undefined,
    style: {
      margin: '0',
      padding: `${opts.padding}px`,
      opacity: '1', // Force visible in screenshot
      visibility: 'visible',
      display: 'inline-block',
    },
    skipFonts: false,
  };
  
  let result: string | Blob;
  
  try {
    switch (opts.format) {
      case 'svg':
        result = await toSvg(container, imageOptions);
        break;
      case 'blob':
        result = await toBlob(container, imageOptions) as Blob;
        break;
      case 'png':
      default:
        result = await toPng(container, imageOptions);
        break;
    }
    return result;
  } catch (error) {
    console.error('Image generation failed:', error);
    throw error;
  }
}

/**
 * Generates an image from LaTeX content
 * 
 * @param latex Pure LaTeX string (without $ delimiters)
 * @param options Image generation options
 * @returns Promise resolving to data URL or Blob
 */
export async function generateImageFromLatex(
  latex: string,
  options: ImageGeneratorOptions = {}
): Promise<string | Blob> {
  const opts = { ...defaultOptions, ...options };
  
  // Clean up LaTeX: remove $$ or $ delimiters if present
  let cleanLatex = latex.trim();
  if (cleanLatex.startsWith('$$') && cleanLatex.endsWith('$$')) {
    cleanLatex = cleanLatex.slice(2, -2).trim();
  } else if (cleanLatex.startsWith('$') && cleanLatex.endsWith('$')) {
    cleanLatex = cleanLatex.slice(1, -1).trim();
  }
  
  const htmlContent = renderLatexToHtml(cleanLatex, opts.fontSize);
  const container = createRenderContainer(htmlContent, opts);
  
  try {
    return await generateImage(container, opts);
  } finally {
    removeRenderContainer(container);
  }
}

/**
 * Generates an image directly from an existing DOM element
 * This captures the element exactly as rendered, supporting all CSS styles
 * 
 * @param elementId The ID of the DOM element to capture
 * @param options Image generation options
 */
export async function generateImageFromElement(
  elementId: string,
  options: ImageGeneratorOptions = {}
): Promise<string | Blob> {
  const element = document.getElementById(elementId);
  if (!element) {
    throw new Error(`Element with ID "${elementId}" not found`);
  }

  const opts = { ...defaultOptions, ...options };

  // Wait for KaTeX fonts if needed
  await waitForKatexFonts();
  
  // Wait a bit to ensure everything is rendered
  await new Promise(resolve => setTimeout(resolve, 100));

  // Options for html-to-image
  const imageOptions = {
    backgroundColor: opts.backgroundColor,
    pixelRatio: opts.scale,
    cacheBust: true,
    // Use scroll dimensions to capture full content
    width: element.scrollWidth + (opts.padding * 2),
    height: element.scrollHeight + (opts.padding * 2),
    style: {
      padding: `${opts.padding}px`,
      // Reset transform/overflow to ensure full content is visible
      transform: 'none',
      overflow: 'visible',
      // Force visible
      visibility: 'visible',
      display: 'block',
      // Reset height constraints
      height: 'auto',
      maxHeight: 'none',
    },
    skipFonts: false,
  };

  let result: string | Blob;

  try {
    switch (opts.format) {
      case 'svg':
        result = await toSvg(element, imageOptions);
        break;
      case 'blob':
        result = await toBlob(element, imageOptions) as Blob;
        break;
      case 'png':
      default:
        result = await toPng(element, imageOptions);
        break;
    }
    return result;
  } catch (error) {
    console.error('Element image generation failed:', error);
    throw error;
  }
}

/**
 * Copies a generated image to clipboard
 */
export async function copyImageToClipboard(dataUrlOrBlob: string | Blob): Promise<void> {
  let blob: Blob;
  
  if (typeof dataUrlOrBlob === 'string') {
    // Convert data URL to Blob
    const response = await fetch(dataUrlOrBlob);
    blob = await response.blob();
  } else {
    blob = dataUrlOrBlob;
  }
  
  await navigator.clipboard.write([
    new ClipboardItem({
      [blob.type]: blob,
    }),
  ]);
}

/**
 * Downloads a generated image
 */
export function downloadImage(dataUrl: string, filename: string = 'formula.png'): void {
  const link = document.createElement('a');
  link.href = dataUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

