import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
import { PdfManager } from '../dees-pdf-shared/PdfManager.js';
import { CanvasPool, type PooledCanvas } from '../dees-pdf-shared/CanvasPool.js';
import { PerformanceMonitor, throttle } from '../dees-pdf-shared/utils.js';
import { previewStyles } from './styles.js';
import { demo as demoFunc } from './demo.js';
import '../../dees-icon/dees-icon.js';

declare global {
  interface HTMLElementTagNameMap {
    'dees-pdf-preview': DeesPdfPreview;
  }
}

@customElement('dees-pdf-preview')
export class DeesPdfPreview extends DeesElement {
  public static demo = demoFunc;
  public static demoGroup = 'PDF';
  public static styles = previewStyles;

  @property({ type: String })
  accessor pdfUrl: string = '';

  @property({ type: Number })
  accessor currentPreviewPage: number = 1;

  @property({ type: Boolean })
  accessor clickable: boolean = true;

  @property({ type: Number })
  accessor pageCount: number = 0;

  @property({ type: Boolean })
  accessor loading: boolean = false;

  @property({ type: Boolean })
  accessor rendered: boolean = false;

  @property({ type: Boolean })
  accessor error: boolean = false;

  @property({ type: Boolean })
  accessor isHovering: boolean = false;

  @property({ type: Boolean })
  accessor isA4Format: boolean = true;

  private renderPagesTask: Promise<void> | null = null;
  private renderPagesQueued: boolean = false;

  private observer: IntersectionObserver;
  private pdfDocument: any;
  private canvases: PooledCanvas[] = [];
  private resizeObserver?: ResizeObserver;
  private previewContainer: HTMLElement | null = null;
  private stackElement: HTMLElement | null = null;
  private loadedPdfUrl: string | null = null;

  constructor() {
    super();
  }

  public render(): TemplateResult {
    return html`
      <div
        class="preview-container ${this.loading ? 'loading' : ''} ${this.error ? 'error' : ''} ${this.clickable ? 'clickable' : ''}"
        @click=${this.handleClick}
        @mouseenter=${this.handleMouseEnter}
        @mouseleave=${this.handleMouseLeave}
        @mousemove=${this.handleMouseMove}
      >
        ${this.loading ? html`
          <div class="preview-loading">
            <div class="preview-spinner"></div>
            <div class="preview-text">Loading preview...</div>
          </div>
        ` : ''}

        ${this.error ? html`
          <div class="preview-error">
            <dees-icon icon="lucide:FileX"></dees-icon>
            <div class="preview-text">Failed to load PDF</div>
          </div>
        ` : ''}

        ${!this.loading && !this.error ? html`
          <div class="preview-stack ${!this.isA4Format ? 'non-a4' : ''}">
            <canvas
              class="preview-canvas"
              data-page="${this.currentPreviewPage}"
            ></canvas>
          </div>

          ${this.pageCount > 1 && this.isHovering ? html`
            <div class="preview-page-indicator">
              Page ${this.currentPreviewPage} of ${this.pageCount}
            </div>
          ` : ''}

          ${this.pageCount > 0 && !this.isHovering ? html`
            <div class="preview-info">
              <dees-icon icon="lucide:FileText"></dees-icon>
              <span class="preview-pages">${this.pageCount} page${this.pageCount > 1 ? 's' : ''}</span>
            </div>
          ` : ''}

          ${this.clickable ? html`
            <div class="preview-overlay">
              <dees-icon icon="lucide:Eye"></dees-icon>
              <span>View PDF</span>
            </div>
          ` : ''}
        ` : ''}
      </div>
    `;
  }

  private handleMouseEnter() {
    this.isHovering = true;
  }

  private handleMouseLeave() {
    this.isHovering = false;
    // Reset to first page when not hovering
    if (this.currentPreviewPage !== 1) {
      this.currentPreviewPage = 1;
      void this.scheduleRenderPages();
    }
  }

  private handleMouseMove(e: MouseEvent) {
    if (!this.isHovering || this.pageCount <= 1) return;

    const rect = this.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const width = rect.width;

    // Calculate which page to show based on horizontal position
    const percentage = Math.max(0, Math.min(1, x / width));
    const newPage = Math.ceil(percentage * this.pageCount) || 1;

    if (newPage !== this.currentPreviewPage) {
      this.currentPreviewPage = newPage;
      void this.scheduleRenderPages();
    }
  }

  public async connectedCallback() {
    await super.connectedCallback();
    this.setupIntersectionObserver();
    await this.updateComplete;
    this.cacheElements();
    this.setupResizeObserver();
  }

  public async disconnectedCallback() {
    await super.disconnectedCallback();
    this.cleanup();
    if (this.observer) {
      this.observer.disconnect();
    }
    this.resizeObserver?.disconnect();
    this.resizeObserver = undefined;
  }

  private setupIntersectionObserver() {
    const options = {
      root: null,
      rootMargin: '200px',
      threshold: 0.01,
    };

    this.observer = new IntersectionObserver(
      throttle((entries) => {
        for (const entry of entries) {
          if (entry.isIntersecting && !this.rendered && this.pdfUrl) {
            this.loadAndRenderPreview();
          } else if (!entry.isIntersecting && this.rendered) {
            // Optional: Clear canvases when out of view for memory optimization
            // this.clearCanvases();
          }
        }
      }, 100),
      options
    );

    this.observer.observe(this);
  }

  private async loadAndRenderPreview() {
    if (this.rendered || this.loading) return;

    this.loading = true;
    this.error = false;
    PerformanceMonitor.mark(`preview-load-${this.pdfUrl}`);

    try {
      this.pdfDocument = await PdfManager.loadDocument(this.pdfUrl);
      this.pageCount = this.pdfDocument.numPages;
      this.currentPreviewPage = 1;
      this.loadedPdfUrl = this.pdfUrl;

      // Force an update to ensure the canvas element is in the DOM
      this.loading = false;
      await this.updateComplete;
      this.cacheElements();

      // Now render the first page
      await this.scheduleRenderPages();

      this.rendered = true;

      const duration = PerformanceMonitor.measure(`preview-render-${this.pdfUrl}`, `preview-load-${this.pdfUrl}`);
      console.log(`PDF preview rendered in ${duration}ms`);
    } catch (error) {
      console.error('Failed to load PDF preview:', error);
      this.error = true;
      this.loading = false;
    }
  }

  private scheduleRenderPages(): Promise<void> {
    if (!this.pdfDocument) {
      return Promise.resolve();
    }

    if (this.renderPagesTask) {
      this.renderPagesQueued = true;
      return this.renderPagesTask;
    }

    this.renderPagesTask = (async () => {
      try {
        await this.performRenderPages();
      } catch (error) {
        console.error('Failed to render PDF preview pages:', error);
      }
    })().finally(() => {
      this.renderPagesTask = null;
      if (this.renderPagesQueued) {
        this.renderPagesQueued = false;
        void this.scheduleRenderPages();
      }
    });

    return this.renderPagesTask;
  }

  private async performRenderPages() {
    if (!this.pdfDocument) return;

    // Wait a frame to ensure DOM is ready
    await new Promise(resolve => requestAnimationFrame(resolve));

    const canvas = this.shadowRoot?.querySelector('.preview-canvas') as HTMLCanvasElement;
    if (!canvas) {
      console.warn('Preview canvas not found in DOM');
      return;
    }

    // Release old canvases
    this.clearCanvases();

    this.cacheElements();

    // Get available size for the preview
    const { availableWidth, availableHeight } = this.getAvailableSize();

    try {
      // Get the page to render
      const pageNum = this.currentPreviewPage;
      const page = await this.pdfDocument.getPage(pageNum);

      // Calculate scale to fit within available area while keeping aspect ratio
      // Use higher scale for sharper rendering
      const initialViewport = page.getViewport({ scale: 1 });

      // Check if this is standard paper format (A4 or US Letter)
      const aspectRatio = initialViewport.height / initialViewport.width;

      // Common paper format ratios
      const a4PortraitRatio = 1.414; // 297mm / 210mm
      const a4LandscapeRatio = 0.707; // 210mm / 297mm
      const letterPortraitRatio = 1.294; // 11" / 8.5"
      const letterLandscapeRatio = 0.773; // 8.5" / 11"

      // Check for standard formats with 5% tolerance
      const tolerance = 0.05;
      const isA4Portrait = Math.abs(aspectRatio - a4PortraitRatio) < (a4PortraitRatio * tolerance);
      const isA4Landscape = Math.abs(aspectRatio - a4LandscapeRatio) < (a4LandscapeRatio * tolerance);
      const isLetterPortrait = Math.abs(aspectRatio - letterPortraitRatio) < (letterPortraitRatio * tolerance);
      const isLetterLandscape = Math.abs(aspectRatio - letterLandscapeRatio) < (letterLandscapeRatio * tolerance);

      // Consider it standard format if it matches A4 or US Letter
      this.isA4Format = isA4Portrait || isA4Landscape || isLetterPortrait || isLetterLandscape;

      // Debug logging
      console.log(`PDF aspect ratio: ${aspectRatio.toFixed(3)}, standard format: ${this.isA4Format}`)

      // Adjust available size for non-A4 documents (account for padding)
      const adjustedWidth = this.isA4Format ? availableWidth : availableWidth - 24;
      const adjustedHeight = this.isA4Format ? availableHeight : availableHeight - 24;

      const scaleX = adjustedWidth > 0 ? adjustedWidth / initialViewport.width : 0;
      const scaleY = adjustedHeight > 0 ? adjustedHeight / initialViewport.height : 0;
      // Increase scale by 2x for sharper rendering, but limit to 3.0 max
      const baseScale = Math.min(scaleX || 0.5, scaleY || scaleX || 0.5);
      const renderScale = Math.min(baseScale * 2, 3.0);

      if (!Number.isFinite(renderScale) || renderScale <= 0) {
        page.cleanup?.();
        return;
      }

      const viewport = page.getViewport({ scale: renderScale });

      // Acquire canvas from pool
      const pooledCanvas = CanvasPool.acquire(viewport.width, viewport.height);
      this.canvases.push(pooledCanvas);

      // Render to pooled canvas first
      const renderContext = {
        canvasContext: pooledCanvas.ctx,
        viewport: viewport,
      };

      await page.render(renderContext).promise;

      // Transfer to display canvas
      // Set actual canvas resolution for sharpness
      canvas.width = viewport.width;
      canvas.height = viewport.height;

      // Scale down display size to fit the container while keeping high resolution
      // For A4, fill the container; for non-A4, respect padding
      const displayWidth = adjustedWidth;
      const displayHeight = (viewport.height / viewport.width) * adjustedWidth;

      // If it fits height-wise better, scale by height instead
      if (displayHeight > adjustedHeight) {
        const altDisplayHeight = adjustedHeight;
        const altDisplayWidth = (viewport.width / viewport.height) * adjustedHeight;
        canvas.style.width = `${altDisplayWidth}px`;
        canvas.style.height = `${altDisplayHeight}px`;
      } else {
        canvas.style.width = `${displayWidth}px`;
        canvas.style.height = `${displayHeight}px`;
      }

      const ctx = canvas.getContext('2d');
      if (ctx) {
        // Enable image smoothing for better quality
        ctx.imageSmoothingEnabled = true;
        ctx.imageSmoothingQuality = 'high';
        ctx.drawImage(pooledCanvas.canvas, 0, 0);
      }

      // Release page to free memory
      page.cleanup();
    } catch (error) {
      console.error(`Failed to render page ${this.currentPreviewPage}:`, error);
    }
  }

  private clearCanvases() {
    // Release pooled canvases
    for (const pooledCanvas of this.canvases) {
      CanvasPool.release(pooledCanvas);
    }
    this.canvases = [];
  }

  private cleanup() {
    this.clearCanvases();

    if (this.pdfDocument) {
      PdfManager.releaseDocument(this.loadedPdfUrl ?? this.pdfUrl);
      this.pdfDocument = null;
    }

    this.renderPagesQueued = false;

    this.pageCount = 0;
    this.currentPreviewPage = 1;
    this.isHovering = false;
    this.isA4Format = true;
    this.previewContainer = null;
    this.stackElement = null;
    this.loadedPdfUrl = null;
    this.rendered = false;
    this.loading = false;
    this.error = false;
  }

  private handleClick() {
    if (!this.clickable) return;

    // Dispatch custom event for parent to handle
    this.dispatchEvent(new CustomEvent('pdf-preview-click', {
      detail: {
        pdfUrl: this.pdfUrl,
        pageCount: this.pageCount,
      },
      bubbles: true,
      composed: true,
    }));
  }

  public async updated(changedProperties: Map<PropertyKey, unknown>) {
    super.updated(changedProperties);

    if (changedProperties.has('pdfUrl') && this.pdfUrl) {
      const previousUrl = changedProperties.get('pdfUrl') as string | undefined;
      if (previousUrl) {
        PdfManager.releaseDocument(previousUrl);
      }
      this.cleanup();
      this.rendered = false;
      this.currentPreviewPage = 1;

      // Check if in viewport and render if so
      if (this.observer) {
        const rect = this.getBoundingClientRect();
        if (rect.top < window.innerHeight && rect.bottom > 0) {
          this.loadAndRenderPreview();
        }
      }
    }

    if (changedProperties.has('currentPreviewPage') && this.rendered) {
      await this.scheduleRenderPages();
    }
  }

  /**
   * Provide context menu items for right-click functionality
   */
  public getContextMenuItems() {
    const items: any[] = [];

    // If clickable, add option to view the PDF
    if (this.clickable) {
      items.push({
        name: 'View PDF',
        iconName: 'lucide:Eye',
        action: async () => {
          this.handleClick();
        }
      });
      items.push({ divider: true });
    }

    items.push(
      {
        name: 'Open PDF in New Tab',
        iconName: 'lucide:ExternalLink',
        action: async () => {
          window.open(this.pdfUrl, '_blank');
        }
      },
      { divider: true },
      {
        name: 'Copy PDF URL',
        iconName: 'lucide:Copy',
        action: async () => {
          await navigator.clipboard.writeText(this.pdfUrl);
        }
      },
      {
        name: 'Download PDF',
        iconName: 'lucide:Download',
        action: async () => {
          const link = document.createElement('a');
          link.href = this.pdfUrl;
          link.download = this.pdfUrl.split('/').pop() || 'document.pdf';
          link.click();
        }
      }
    );

    // Add page count info as a disabled item
    if (this.pageCount > 0) {
      items.push(
        { divider: true },
        {
          name: `${this.pageCount} page${this.pageCount > 1 ? 's' : ''}`,
          iconName: 'lucide:FileText',
          disabled: true,
          action: async () => {}
        }
      );
    }

    return items;
  }

  private cacheElements() {
    if (!this.previewContainer) {
      this.previewContainer = this.shadowRoot?.querySelector('.preview-container') as HTMLElement;
    }
    if (!this.stackElement) {
      this.stackElement = this.shadowRoot?.querySelector('.preview-stack') as HTMLElement;
    }
  }

  private setupResizeObserver() {
    if (!this.previewContainer || this.resizeObserver) return;

    this.resizeObserver = new ResizeObserver(() => {
      if (this.rendered && this.pdfDocument && !this.loading) {
        void this.scheduleRenderPages();
      }
    });

    this.resizeObserver.observe(this);
  }

  private getAvailableSize() {
    if (!this.stackElement) {
      // Try to get the stack element if it's not cached
      this.stackElement = this.shadowRoot?.querySelector('.preview-stack') as HTMLElement;
    }

    if (!this.stackElement) {
      // Fallback to default size if element not found
      return {
        availableWidth: 200,  // Full container width
        availableHeight: 260, // Full container height
      };
    }

    const rect = this.stackElement.getBoundingClientRect();
    const availableWidth = Math.max(rect.width, 0) || 200;
    const availableHeight = Math.max(rect.height, 0) || 260;

    return { availableWidth, availableHeight };
  }
}
