// Invoice Generator - Native HTML5 Canvas Implementation

(function() {
  'use strict';

  class InvoiceGenerator {
    constructor(containerId) {
      this.canvas = null;
      this.ctx = null;
      this.elements = new Map();
      this.selectedElement = null;
      this.elementCounter = 0;
      this.isDragging = false;
      this.isResizing = false;
      this.dragStart = { x: 0, y: 0 };
      this.elementStart = { x: 0, y: 0, w: 0, h: 0 };
      this.activeHandle = null;
      this.container = null;
      this.containerId = containerId || 'invoice-canvas-container';
      
      // Page dimensions (A4 at 96 DPI)
      this.pageX = 50;
      this.pageY = 30;
      this.pageWidth = 794;
      this.pageHeight = 1123;
      this.scale = 1;
      
      // Handle configuration
      this.handleSize = 10;
      this.handlePadding = 3;
      
      // Grid configuration (Snap to Grid)
      this.gridSize = 5;
      this.showGrid = true;
    }

    // Snap value to grid
    snapToGrid(value) {
      return Math.round(value / this.gridSize) * this.gridSize;
    }

    init() {
      this.container = document.getElementById(this.containerId);
      if (!this.container) {
        console.error('Canvas container not found: ' + this.containerId);
        return;
      }

      // Create canvas
      this.canvas = document.createElement('canvas');
      this.canvas.style.display = 'block';
      this.updateCanvasSize();
      
      this.ctx = this.canvas.getContext('2d');
      this.container.innerHTML = '';
      this.container.appendChild(this.canvas);
      
      // Bind events
      this.canvas.addEventListener('mousedown', this.onMouseDown.bind(this));
      this.canvas.addEventListener('mousemove', this.onMouseMove.bind(this));
      this.canvas.addEventListener('mouseup', this.onMouseUp.bind(this));
      this.canvas.addEventListener('mouseleave', this.onMouseUp.bind(this));
      
      // Keyboard navigation for selected element
      this.canvas.setAttribute('tabindex', '0');
      this.canvas.addEventListener('keydown', this.onKeyDown.bind(this));
      
      window.addEventListener('resize', () => {
        this.updateCanvasSize();
        this.draw();
      });
      
      this.draw();
      console.log('Invoice generator initialized');
    }

    updateCanvasSize() {
      const rect = this.container.getBoundingClientRect();
      this.canvas.width = rect.width;
      this.canvas.height = rect.height;
      
      // Calculate scale to maximize page size while maintaining aspect ratio
      // Use full available space with minimal padding
      const padding = 10;
      const availableWidth = this.canvas.width - padding * 2;
      const availableHeight = this.canvas.height - padding * 2;
      
      // Scale to fit the available space (can be larger than 1 if container is bigger than A4)
      this.scale = Math.min(availableWidth / this.pageWidth, availableHeight / this.pageHeight);
      
      // Center the scaled page
      const scaledPageWidth = this.pageWidth * this.scale;
      const scaledPageHeight = this.pageHeight * this.scale;
      this.pageX = (this.canvas.width - scaledPageWidth) / 2;
      this.pageY = (this.canvas.height - scaledPageHeight) / 2;
    }

    getMousePos(e) {
      const rect = this.canvas.getBoundingClientRect();
      // Convert screen coordinates to canvas coordinates
      const canvasX = e.clientX - rect.left;
      const canvasY = e.clientY - rect.top;
      // Convert to page coordinates (accounting for scale and offset)
      return {
        x: (canvasX - this.pageX) / this.scale,
        y: (canvasY - this.pageY) / this.scale
      };
    }

    // Check if point is inside element
    hitTest(x, y, el) {
      const w = el.width || 150;
      const h = el.height || 30;
      return x >= el.x && x <= el.x + w && y >= el.y && y <= el.y + h;
    }

    // Check if point is on a resize handle
    getHandleAt(x, y, el) {
      if (!el) return null;
      
      const w = el.width || 150;
      const h = el.height || 30;
      const hs = this.handleSize + this.handlePadding;
      
      // Define handle positions (center points)
      const handles = {
        'nw': { x: el.x, y: el.y },
        'n':  { x: el.x + w / 2, y: el.y },
        'ne': { x: el.x + w, y: el.y },
        'w':  { x: el.x, y: el.y + h / 2 },
        'e':  { x: el.x + w, y: el.y + h / 2 },
        'sw': { x: el.x, y: el.y + h },
        's':  { x: el.x + w / 2, y: el.y + h },
        'se': { x: el.x + w, y: el.y + h }
      };
      
      for (const [name, pos] of Object.entries(handles)) {
        const dx = x - pos.x;
        const dy = y - pos.y;
        if (Math.abs(dx) <= hs && Math.abs(dy) <= hs) {
          return name;
        }
      }
      return null;
    }

    onMouseDown(e) {
      const pos = this.getMousePos(e);
      
      // First check if clicking on a handle of selected element
      if (this.selectedElement) {
        const handle = this.getHandleAt(pos.x, pos.y, this.selectedElement);
        if (handle) {
          console.log('Resize handle clicked:', handle);
          this.isResizing = true;
          this.activeHandle = handle;
          this.dragStart = { x: pos.x, y: pos.y };
          this.elementStart = {
            x: this.selectedElement.x,
            y: this.selectedElement.y,
            w: this.selectedElement.width || 100,
            h: this.selectedElement.height || 30
          };
          return;
        }
      }
      
      // Check if clicking on an element
      let clickedElement = null;
      for (const el of Array.from(this.elements.values()).reverse()) {
        if (this.hitTest(pos.x, pos.y, el)) {
          clickedElement = el;
          break;
        }
      }
      
      if (clickedElement) {
        this.selectElement(clickedElement.id);
        this.isDragging = true;
        this.dragStart = { x: pos.x, y: pos.y };
        this.elementStart = { x: clickedElement.x, y: clickedElement.y };
        this.canvas.style.cursor = 'move';
      } else {
        this.deselectAll();
      }
      
      this.draw();
    }

    onMouseMove(e) {
      const pos = this.getMousePos(e);
      
      if (this.isResizing && this.selectedElement) {
        this.doResize(pos.x, pos.y);
        return;
      }
      
      if (this.isDragging && this.selectedElement) {
        const dx = pos.x - this.dragStart.x;
        const dy = pos.y - this.dragStart.y;
        
        let newX = this.elementStart.x + dx;
        let newY = this.elementStart.y + dy;
        
        // Constrain to page (in page coordinates, page starts at 0,0)
        newX = Math.max(0, Math.min(newX, 
          this.pageWidth - (this.selectedElement.width || 100)));
        newY = Math.max(0, Math.min(newY,
          this.pageHeight - (this.selectedElement.height || 30)));
        
        // Snap to grid
        this.selectedElement.x = this.snapToGrid(newX);
        this.selectedElement.y = this.snapToGrid(newY);
        
        this.draw();
        this.notifyChange();
        return;
      }
      
      // Update cursor
      if (this.selectedElement) {
        const handle = this.getHandleAt(pos.x, pos.y, this.selectedElement);
        if (handle) {
          const cursors = {
            'nw': 'nw-resize', 'ne': 'ne-resize', 'sw': 'sw-resize', 'se': 'se-resize',
            'n': 'ns-resize', 's': 'ns-resize', 'e': 'ew-resize', 'w': 'ew-resize'
          };
          this.canvas.style.cursor = cursors[handle] || 'default';
          return;
        }
      }
      
      // Check hover over elements
      let hovering = false;
      for (const el of Array.from(this.elements.values()).reverse()) {
        if (this.hitTest(pos.x, pos.y, el)) {
          hovering = true;
          break;
        }
      }
      this.canvas.style.cursor = hovering ? 'move' : 'default';
    }

    onMouseUp(e) {
      this.isDragging = false;
      this.isResizing = false;
      this.activeHandle = null;
      this.canvas.style.cursor = 'default';
    }

    onKeyDown(e) {
      if (!this.selectedElement) return;
      
      const step = this.gridSize; // Move by grid size (5px)
      let moved = false;
      
      switch(e.key) {
        case 'ArrowUp':
          this.selectedElement.y = Math.max(0, this.selectedElement.y - step);
          moved = true;
          e.preventDefault();
          break;
        case 'ArrowDown':
          this.selectedElement.y = Math.min(
            this.pageHeight - (this.selectedElement.height || 30),
            this.selectedElement.y + step
          );
          moved = true;
          e.preventDefault();
          break;
        case 'ArrowLeft':
          this.selectedElement.x = Math.max(0, this.selectedElement.x - step);
          moved = true;
          e.preventDefault();
          break;
        case 'ArrowRight':
          this.selectedElement.x = Math.min(
            this.pageWidth - (this.selectedElement.width || 100),
            this.selectedElement.x + step
          );
          moved = true;
          e.preventDefault();
          break;
      }
      
      if (moved) {
        this.draw();
        this.notifyChange();
      }
    }

    doResize(mouseX, mouseY) {
      if (!this.selectedElement || !this.activeHandle) return;
      
      const el = this.selectedElement;
      el.manuallyResized = true; // Mark as manually resized
      
      const start = this.elementStart;
      const minSize = 20;
      
      // Calculate new dimensions based on handle
      switch (this.activeHandle) {
        case 'se':
          el.width = Math.max(minSize, mouseX - start.x);
          el.height = Math.max(minSize, mouseY - start.y);
          break;
          
        case 'sw':
          const newWsw = start.w + (start.x - mouseX);
          if (newWsw >= minSize) {
            el.x = mouseX;
            el.width = newWsw;
          }
          el.height = Math.max(minSize, mouseY - start.y);
          break;
          
        case 'ne':
          el.width = Math.max(minSize, mouseX - start.x);
          const newHne = start.h + (start.y - mouseY);
          if (newHne >= minSize) {
            el.y = mouseY;
            el.height = newHne;
          }
          break;
          
        case 'nw':
          const newWnw = start.w + (start.x - mouseX);
          const newHnw = start.h + (start.y - mouseY);
          if (newWnw >= minSize) {
            el.x = mouseX;
            el.width = newWnw;
          }
          if (newHnw >= minSize) {
            el.y = mouseY;
            el.height = newHnw;
          }
          break;
          
        case 'e':
          el.width = Math.max(minSize, mouseX - start.x);
          break;
          
        case 'w':
          const newWw = start.w + (start.x - mouseX);
          if (newWw >= minSize) {
            el.x = mouseX;
            el.width = newWw;
          }
          break;
          
        case 's':
          el.height = Math.max(minSize, mouseY - start.y);
          break;
          
        case 'n':
          const newHn = start.h + (start.y - mouseY);
          if (newHn >= minSize) {
            el.y = mouseY;
            el.height = newHn;
          }
          break;
      }
      
      this.draw();
      this.notifyChange();
    }

    notifyChange() {
      if (this.selectedElement && window.invoiceGeneratorView?.$server) {
        const el = this.selectedElement;
        window.invoiceGeneratorView.$server.updatePropertiesPanel(
          el.id, el.type, el.text || '', el.x, el.y, el.fontSize || 14, el.color || '#000000'
        );
      }
    }

    draw() {
      if (!this.ctx) return;
      
      const ctx = this.ctx;
      
      // Clear
      ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      
      // Background
      ctx.fillStyle = '#e8e8e8';
      ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
      
      ctx.save();
      
      // Apply scale and offset
      ctx.translate(this.pageX, this.pageY);
      ctx.scale(this.scale, this.scale);
      
      // Page shadow (drawn before scaling offset)
      ctx.save();
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.fillStyle = 'rgba(0,0,0,0.2)';
      ctx.fillRect(this.pageX + 4 * this.scale, this.pageY + this.pageHeight * this.scale, this.pageWidth * this.scale, 4);
      ctx.fillRect(this.pageX + this.pageWidth * this.scale, this.pageY + 4 * this.scale, 4, this.pageHeight * this.scale);
      ctx.restore();
      
      // Page
      ctx.fillStyle = '#ffffff';
      ctx.fillRect(0, 0, this.pageWidth, this.pageHeight);
      ctx.strokeStyle = '#cccccc';
      ctx.lineWidth = 1 / this.scale;
      ctx.strokeRect(0, 0, this.pageWidth, this.pageHeight);
      
      // Draw grid if enabled
      if (this.showGrid) {
        this.drawGrid(ctx);
      }
      
      // Draw elements
      this.elements.forEach(el => this.drawElement(el));
      
      // Draw selection
      if (this.selectedElement) {
        this.drawSelection(this.selectedElement);
      }
      
      ctx.restore();
    }

    drawImagePlaceholder(ctx, el) {
      ctx.fillStyle = '#f0f0f0';
      ctx.fillRect(el.x, el.y, el.width || 100, el.height || 100);
      ctx.strokeStyle = '#999999';
      ctx.lineWidth = 1;
      ctx.strokeRect(el.x, el.y, el.width || 100, el.height || 100);
      ctx.fillStyle = '#666666';
      ctx.font = '12px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText('Bild', el.x + (el.width || 100) / 2, el.y + (el.height || 100) / 2);
    }

    drawElement(el) {
      const ctx = this.ctx;
      ctx.save();
      
      switch (el.type) {
        case 'line':
          ctx.strokeStyle = el.color || '#333333';
          ctx.lineWidth = el.strokeWidth || 1;
          ctx.beginPath();
          ctx.moveTo(el.x, el.y);
          ctx.lineTo(el.x + (el.width || 200), el.y);
          ctx.stroke();
          break;

        case 'vline':
          ctx.strokeStyle = el.color || '#333333';
          ctx.lineWidth = el.strokeWidth || 1;
          ctx.beginPath();
          ctx.moveTo(el.x, el.y);
          ctx.lineTo(el.x, el.y + (el.height || 200));
          ctx.stroke();
          break;
          
        case 'image':
          if (el.imageData) {
            // Draw actual image
            const img = el.imageObj;
            if (img && img.complete) {
              // Draw image scaled to fit element while maintaining aspect ratio
              const imgAspect = img.width / img.height;
              const elAspect = (el.width || 100) / (el.height || 100);
              
              let drawWidth, drawHeight, drawX, drawY;
              
              if (imgAspect > elAspect) {
                // Image is wider than element
                drawWidth = el.width || 100;
                drawHeight = drawWidth / imgAspect;
                drawX = el.x;
                drawY = el.y + ((el.height || 100) - drawHeight) / 2;
              } else {
                // Image is taller than element
                drawHeight = el.height || 100;
                drawWidth = drawHeight * imgAspect;
                drawX = el.x + ((el.width || 100) - drawWidth) / 2;
                drawY = el.y;
              }
              
              ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
              
              // Draw border
              ctx.strokeStyle = '#cccccc';
              ctx.lineWidth = 1;
              ctx.strokeRect(el.x, el.y, el.width || 100, el.height || 100);
            } else {
              // Image still loading or failed
              this.drawImagePlaceholder(ctx, el);
            }
          } else {
            // No image uploaded yet - draw placeholder
            this.drawImagePlaceholder(ctx, el);
          }
          break;
          
        default: // text elements
          this.drawTextElement(el, ctx);
      }
      
      ctx.restore();
    }

    // Wrap text to fit within maxWidth
    wrapText(ctx, text, maxWidth) {
      const words = text.split(' ');
      const lines = [];
      let currentLine = words[0];

      for (let i = 1; i < words.length; i++) {
        const word = words[i];
        const width = ctx.measureText(currentLine + ' ' + word).width;
        if (width < maxWidth) {
          currentLine += ' ' + word;
        } else {
          lines.push(currentLine);
          currentLine = word;
        }
      }
      lines.push(currentLine);
      return lines;
    }

    drawTextElement(el, ctx) {
      ctx.font = `${el.fontStyle || ''} ${el.fontSize || 14}px ${el.fontFamily || 'Arial'}`;
      ctx.fillStyle = el.color || '#333333';
      ctx.textBaseline = 'top';
      
      const maxWidth = el.width || 150;
      const lineHeight = (el.fontSize || 14) * 1.2;
      const maxHeight = el.height || 1000;
      
      // Calculate max lines that fit
      const maxLines = Math.floor(maxHeight / lineHeight);
      
      // Split by explicit newlines first, then wrap each line
      const explicitLines = (el.text || '').split('\n');
      const allLines = [];
      
      for (const line of explicitLines) {
        const metrics = ctx.measureText(line);
        if (metrics.width <= maxWidth) {
          allLines.push(line);
        } else {
          const wrapped = this.wrapText(ctx, line, maxWidth);
          allLines.push(...wrapped);
        }
      }
      
      // Determine if we need ellipsis
      const needsEllipsis = allLines.length > maxLines;
      const linesToDraw = needsEllipsis ? maxLines : allLines.length;
      
      // Draw lines
      let y = el.y;
      for (let i = 0; i < linesToDraw; i++) {
        const line = allLines[i];
        
        // If this is the last line and we need ellipsis
        if (needsEllipsis && i === linesToDraw - 1) {
          const ellipsis = '...';
          // Check if the line with ellipsis would fit
          if (ctx.measureText(line + ellipsis).width <= maxWidth) {
            ctx.fillText(line + ellipsis, el.x, y);
          } else {
            // Need to trim the line to fit ellipsis
            let trimmed = line;
            while (trimmed.length > 0 && ctx.measureText(trimmed + ellipsis).width > maxWidth) {
              trimmed = trimmed.slice(0, -1);
            }
            ctx.fillText(trimmed + ellipsis, el.x, y);
          }
        } else {
          ctx.fillText(line, el.x, y);
        }
        y += lineHeight;
      }
      
      // Update height based on actual content if not manually resized
      if (!el.manuallyResized) {
        el.height = Math.max(lineHeight, allLines.length * lineHeight);
      }
    }

    drawSelection(el) {
      const ctx = this.ctx;
      const x = el.x;
      const y = el.y;
      const w = el.width || 150;
      const h = el.height || 30;
      const hs = this.handleSize / this.scale;
      
      // Selection border
      ctx.strokeStyle = '#1976d2';
      ctx.lineWidth = 2;
      ctx.setLineDash([5, 3]);
      ctx.strokeRect(x, y, w, h);
      ctx.setLineDash([]);
      
      // Handles
      ctx.fillStyle = '#ffffff';
      ctx.strokeStyle = '#1976d2';
      ctx.lineWidth = 2;
      
      const positions = [
        [x - hs/2, y - hs/2],           // nw
        [x + w/2 - hs/2, y - hs/2],     // n
        [x + w - hs/2, y - hs/2],       // ne
        [x - hs/2, y + h/2 - hs/2],     // w
        [x + w - hs/2, y + h/2 - hs/2], // e
        [x - hs/2, y + h - hs/2],       // sw
        [x + w/2 - hs/2, y + h - hs/2], // s
        [x + w - hs/2, y + h - hs/2]    // se
      ];
      
      positions.forEach(([hx, hy]) => {
        ctx.fillRect(hx, hy, hs, hs);
        ctx.strokeRect(hx, hy, hs, hs);
      });
    }

    drawGrid(ctx) {
      ctx.save();
      ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)';
      ctx.lineWidth = 0.5 / this.scale;
      
      // Vertical lines (draw in page coordinates)
      for (let x = 0; x <= this.pageWidth; x += this.gridSize) {
        ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, this.pageHeight);
        ctx.stroke();
      }
      
      // Horizontal lines
      for (let y = 0; y <= this.pageHeight; y += this.gridSize) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(this.pageWidth, y);
        ctx.stroke();
      }
      
      ctx.restore();
    }

    addElement(type, label, dropX, dropY) {
      this.elementCounter++;
      const id = `element-${this.elementCounter}`;
      
      // dropX and dropY are in container coordinates, convert to page coordinates
      const pageCoordX = (dropX - this.pageX) / this.scale;
      const pageCoordY = (dropY - this.pageY) / this.scale;
      
      // Snap initial position to grid (relative to page origin)
      const rawX = Math.max(10, pageCoordX - 50);
      const rawY = Math.max(10, pageCoordY - 15);
      const x = this.snapToGrid(rawX);
      const y = this.snapToGrid(rawY);
      
      let el = {
        id, type, x, y,
        width: 150,
        height: 30,
        fontSize: 14,
        fontFamily: 'Arial',
        color: '#333333'
      };
      
      switch (type) {
        case 'text':
          el.text = 'Text eingeben...';
          el.height = 20;
          break;
        case 'header':
          el.text = 'Überschrift';
          el.fontSize = 24;
          el.fontStyle = 'bold';
          el.color = '#000000';
          el.width = 200;
          el.height = 30;
          break;
        case 'date':
          el.text = `Datum: ${new Date().toLocaleDateString('de-DE')}`;
          el.fontSize = 12;
          el.color = '#666666';
          el.height = 16;
          break;
        case 'customer':
        case 'company':
          el.text = type === 'customer' ? 'Kundenname\nStraße Nr.\nPLZ Ort' : 'Ihr Unternehmen\nIhre Straße\nIhre PLZ Ort';
          el.height = 50;
          el.fontSize = 12;
          break;
        case 'amount':
          el.text = 'Gesamtbetrag: 0,00 €';
          el.fontStyle = 'bold';
          el.width = 180;
          el.height = 20;
          break;
        case 'line':
          el.text = '';
          el.width = 200;
          el.height = 2;
          break;
        case 'vline':
          el.text = '';
          el.width = 2;
          el.height = 200;
          break;
        case 'image':
          el.text = 'Bild';
          el.width = 100;
          el.height = 100;
          break;
        default:
          el.text = label || 'Neues Element';
          el.height = 20;
      }
      
      this.elements.set(id, el);
      this.selectElement(id);
      this.draw();
    }

    selectElement(id) {
      this.selectedElement = this.elements.get(id) || null;
      this.draw();
      if (this.selectedElement) {
        this.notifyChange();
        // Focus canvas for keyboard navigation
        if (this.canvas) {
          this.canvas.focus();
        }
      }
    }

    deselectAll() {
      this.selectedElement = null;
      this.draw();
      if (window.invoiceGeneratorView?.$server) {
        window.invoiceGeneratorView.$server.resetPropertiesPanel();
      }
    }

    updateElementText(id, text) {
      const el = this.elements.get(id);
      if (el) {
        el.text = text;
        this.draw();
      }
    }

    updateElementPosition(id, x, y) {
      const el = this.elements.get(id);
      if (el) {
        if (x !== null) el.x = this.snapToGrid(x);
        if (y !== null) el.y = this.snapToGrid(y);
        this.draw();
      }
    }

    updateElementFontSize(id, size) {
      const el = this.elements.get(id);
      if (el) {
        el.fontSize = size;
        this.draw();
      }
    }

    updateElementColor(id, color) {
      const el = this.elements.get(id);
      if (el) {
        el.color = color;
        this.draw();
      }
    }

    updateElementImage(id, imageDataUrl) {
      const el = this.elements.get(id);
      if (el && el.type === 'image') {
        el.imageData = imageDataUrl;
        
        // Create image object
        const img = new Image();
        img.onload = () => {
          el.imageObj = img;
          this.draw();
        };
        img.onerror = () => {
          console.error('Failed to load image');
          el.imageData = null;
          el.imageObj = null;
          this.draw();
        };
        img.src = imageDataUrl;
      }
    }

    deleteElement(id) {
      this.elements.delete(id);
      if (this.selectedElement?.id === id) {
        this.selectedElement = null;
      }
      this.draw();
    }

    clearCanvas() {
      this.elements.clear();
      this.selectedElement = null;
      this.draw();
    }

    getCanvasData() {
      return {
        elements: Array.from(this.elements.values())
      };
    }

    exportTemplate() {
      const data = JSON.stringify(this.getCanvasData(), null, 2);
      const blob = new Blob([data], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'template.json';
      a.click();
      URL.revokeObjectURL(url);
    }

    exportTemplateJson() {
      // Return template data as JSON string for Java processing
      const data = this.getCanvasData();
      return JSON.stringify(data);
    }

    generatePreview() {
      const temp = document.createElement('canvas');
      temp.width = this.pageWidth;
      temp.height = this.pageHeight;
      const tctx = temp.getContext('2d');
      
      tctx.fillStyle = '#ffffff';
      tctx.fillRect(0, 0, temp.width, temp.height);
      
      // Draw elements offset by page position
      const originalPageX = this.pageX;
      const originalPageY = this.pageY;
      this.pageX = 0;
      this.pageY = 0;
      
      this.elements.forEach(el => {
        const ox = el.x, oy = el.y;
        el.x = ox - originalPageX;
        el.y = oy - originalPageY;
        this.drawElement.call({ ctx: tctx, elements: this.elements }, el);
        el.x = ox;
        el.y = oy;
      });
      
      this.pageX = originalPageX;
      this.pageY = originalPageY;
      
      const win = window.open();
      if (win) {
        win.document.write(`<img src="${temp.toDataURL()}" style="max-width:100%">`);
      }
    }
  }

  window.invoiceGenerator = new InvoiceGenerator();
  window.InvoiceGenerator = InvoiceGenerator;
})();
