import type { ProfileShape } from './dees-input-profilepicture.js';

export interface CropperOptions {
  container: HTMLElement;
  image: string;
  shape: ProfileShape;
  aspectRatio: number;
  minSize?: number;
  outputSize?: number;
  outputQuality?: number;
}

export class ImageCropper {
  private options: CropperOptions;
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  private img: HTMLImageElement;
  private overlayCanvas: HTMLCanvasElement;
  private overlayCtx: CanvasRenderingContext2D;
  
  // Crop area properties
  private cropX: number = 0;
  private cropY: number = 0;
  private cropSize: number = 200;
  private minCropSize: number = 50;
  
  // Interaction state
  private isDragging: boolean = false;
  private isResizing: boolean = false;
  private dragStartX: number = 0;
  private dragStartY: number = 0;
  private resizeHandle: string = '';
  
  // Image properties
  private imageScale: number = 1;
  private imageOffsetX: number = 0;
  private imageOffsetY: number = 0;

  constructor(options: CropperOptions) {
    this.options = {
      minSize: 50,
      outputSize: 800, // Higher default resolution
      outputQuality: 0.95, // Higher quality
      ...options
    };
    
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d')!;
    
    this.overlayCanvas = document.createElement('canvas');
    this.overlayCtx = this.overlayCanvas.getContext('2d')!;
    
    this.img = new Image();
  }

  async initialize(): Promise<void> {
    // Load image
    await this.loadImage();
    
    // Setup canvases
    this.setupCanvases();
    
    // Setup event listeners
    this.setupEventListeners();
    
    // Initial render
    this.render();
  }

  private async loadImage(): Promise<void> {
    return new Promise((resolve, reject) => {
      this.img.onload = () => resolve();
      this.img.onerror = reject;
      this.img.src = this.options.image;
    });
  }

  private setupCanvases(): void {
    const container = this.options.container;
    const containerSize = Math.min(container.clientWidth, container.clientHeight);
    
    // Set canvas sizes
    this.canvas.width = containerSize;
    this.canvas.height = containerSize;
    this.canvas.style.width = '100%';
    this.canvas.style.height = '100%';
    this.canvas.style.position = 'absolute';
    this.canvas.style.top = '0';
    this.canvas.style.left = '0';
    
    this.overlayCanvas.width = containerSize;
    this.overlayCanvas.height = containerSize;
    this.overlayCanvas.style.width = '100%';
    this.overlayCanvas.style.height = '100%';
    this.overlayCanvas.style.position = 'absolute';
    this.overlayCanvas.style.top = '0';
    this.overlayCanvas.style.left = '0';
    this.overlayCanvas.style.cursor = 'move';
    
    container.appendChild(this.canvas);
    container.appendChild(this.overlayCanvas);
    
    // Calculate image scale to fit within container (not fill)
    const scale = Math.min(
      containerSize / this.img.width,
      containerSize / this.img.height
    );
    
    this.imageScale = scale;
    this.imageOffsetX = (containerSize - this.img.width * scale) / 2;
    this.imageOffsetY = (containerSize - this.img.height * scale) / 2;
    
    // Initialize crop area
    // Make the crop area fit within the actual image bounds
    const scaledImageWidth = this.img.width * scale;
    const scaledImageHeight = this.img.height * scale;
    const maxCropSize = Math.min(scaledImageWidth, scaledImageHeight, containerSize * 0.8);
    
    this.cropSize = maxCropSize * 0.8; // Start at 80% of max possible size
    this.cropX = (containerSize - this.cropSize) / 2;
    this.cropY = (containerSize - this.cropSize) / 2;
  }

  private setupEventListeners(): void {
    this.overlayCanvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
    this.overlayCanvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
    this.overlayCanvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
    this.overlayCanvas.addEventListener('mouseleave', this.handleMouseUp.bind(this));
    
    // Touch events
    this.overlayCanvas.addEventListener('touchstart', this.handleTouchStart.bind(this));
    this.overlayCanvas.addEventListener('touchmove', this.handleTouchMove.bind(this));
    this.overlayCanvas.addEventListener('touchend', this.handleTouchEnd.bind(this));
  }

  private handleMouseDown(e: MouseEvent): void {
    const rect = this.overlayCanvas.getBoundingClientRect();
    const x = (e.clientX - rect.left) * (this.overlayCanvas.width / rect.width);
    const y = (e.clientY - rect.top) * (this.overlayCanvas.height / rect.height);
    
    const handle = this.getResizeHandle(x, y);
    
    if (handle) {
      this.isResizing = true;
      this.resizeHandle = handle;
    } else if (this.isInsideCropArea(x, y)) {
      this.isDragging = true;
    }
    
    this.dragStartX = x;
    this.dragStartY = y;
  }

  private handleMouseMove(e: MouseEvent): void {
    const rect = this.overlayCanvas.getBoundingClientRect();
    const x = (e.clientX - rect.left) * (this.overlayCanvas.width / rect.width);
    const y = (e.clientY - rect.top) * (this.overlayCanvas.height / rect.height);
    
    // Update cursor
    const handle = this.getResizeHandle(x, y);
    if (handle) {
      this.overlayCanvas.style.cursor = this.getResizeCursor(handle);
    } else if (this.isInsideCropArea(x, y)) {
      this.overlayCanvas.style.cursor = 'move';
    } else {
      this.overlayCanvas.style.cursor = 'default';
    }
    
    // Handle dragging
    if (this.isDragging) {
      const dx = x - this.dragStartX;
      const dy = y - this.dragStartY;
      
      // Constrain crop area to image bounds
      const minX = this.imageOffsetX;
      const maxX = this.imageOffsetX + this.img.width * this.imageScale - this.cropSize;
      const minY = this.imageOffsetY;
      const maxY = this.imageOffsetY + this.img.height * this.imageScale - this.cropSize;
      
      this.cropX = Math.max(minX, Math.min(maxX, this.cropX + dx));
      this.cropY = Math.max(minY, Math.min(maxY, this.cropY + dy));
      
      this.dragStartX = x;
      this.dragStartY = y;
      this.render();
    }
    
    // Handle resizing
    if (this.isResizing) {
      this.handleResize(x, y);
      this.dragStartX = x;
      this.dragStartY = y;
      this.render();
    }
  }

  private handleMouseUp(): void {
    this.isDragging = false;
    this.isResizing = false;
    this.resizeHandle = '';
  }

  private handleTouchStart(e: TouchEvent): void {
    e.preventDefault();
    const touch = e.touches[0];
    const mouseEvent = new MouseEvent('mousedown', {
      clientX: touch.clientX,
      clientY: touch.clientY
    });
    this.handleMouseDown(mouseEvent);
  }

  private handleTouchMove(e: TouchEvent): void {
    e.preventDefault();
    const touch = e.touches[0];
    const mouseEvent = new MouseEvent('mousemove', {
      clientX: touch.clientX,
      clientY: touch.clientY
    });
    this.handleMouseMove(mouseEvent);
  }

  private handleTouchEnd(e: TouchEvent): void {
    e.preventDefault();
    this.handleMouseUp();
  }

  private getResizeHandle(x: number, y: number): string {
    const handleSize = 20;
    const handles = {
      'nw': { x: this.cropX, y: this.cropY },
      'ne': { x: this.cropX + this.cropSize, y: this.cropY },
      'sw': { x: this.cropX, y: this.cropY + this.cropSize },
      'se': { x: this.cropX + this.cropSize, y: this.cropY + this.cropSize }
    };
    
    for (const [key, pos] of Object.entries(handles)) {
      if (Math.abs(x - pos.x) < handleSize && Math.abs(y - pos.y) < handleSize) {
        return key;
      }
    }
    
    return '';
  }

  private getResizeCursor(handle: string): string {
    const cursors: Record<string, string> = {
      'nw': 'nw-resize',
      'ne': 'ne-resize',
      'sw': 'sw-resize',
      'se': 'se-resize'
    };
    return cursors[handle] || 'default';
  }

  private isInsideCropArea(x: number, y: number): boolean {
    return x >= this.cropX && x <= this.cropX + this.cropSize &&
           y >= this.cropY && y <= this.cropY + this.cropSize;
  }

  private handleResize(x: number, y: number): void {
    const dx = x - this.dragStartX;
    const dy = y - this.dragStartY;
    
    // Get image bounds
    const imgLeft = this.imageOffsetX;
    const imgTop = this.imageOffsetY;
    const imgRight = this.imageOffsetX + this.img.width * this.imageScale;
    const imgBottom = this.imageOffsetY + this.img.height * this.imageScale;
    
    switch (this.resizeHandle) {
      case 'se':
        this.cropSize = Math.max(this.minCropSize, Math.min(
          this.cropSize + Math.max(dx, dy),
          Math.min(
            imgRight - this.cropX,
            imgBottom - this.cropY
          )
        ));
        break;
      case 'nw':
        const newSize = Math.max(this.minCropSize, this.cropSize - Math.max(dx, dy));
        const sizeDiff = this.cropSize - newSize;
        const newX = this.cropX + sizeDiff;
        const newY = this.cropY + sizeDiff;
        if (newX >= imgLeft && newY >= imgTop) {
          this.cropX = newX;
          this.cropY = newY;
          this.cropSize = newSize;
        }
        break;
      case 'ne':
        const neSizeDx = Math.max(dx, -dy);
        const neNewSize = Math.max(this.minCropSize, this.cropSize + neSizeDx);
        const neSizeDiff = neNewSize - this.cropSize;
        const neNewY = this.cropY - neSizeDiff;
        if (neNewY >= imgTop && this.cropX + neNewSize <= imgRight) {
          this.cropY = neNewY;
          this.cropSize = neNewSize;
        }
        break;
      case 'sw':
        const swSizeDx = Math.max(-dx, dy);
        const swNewSize = Math.max(this.minCropSize, this.cropSize + swSizeDx);
        const swSizeDiff = swNewSize - this.cropSize;
        const swNewX = this.cropX - swSizeDiff;
        if (swNewX >= imgLeft && this.cropY + swNewSize <= imgBottom) {
          this.cropX = swNewX;
          this.cropSize = swNewSize;
        }
        break;
    }
  }

  private render(): void {
    // Clear canvases
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.overlayCtx.clearRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height);
    
    // Fill background
    this.ctx.fillStyle = '#000000';
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    
    // Draw image
    this.ctx.drawImage(
      this.img,
      this.imageOffsetX,
      this.imageOffsetY,
      this.img.width * this.imageScale,
      this.img.height * this.imageScale
    );
    
    // Draw overlay only over the image area
    this.overlayCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
    this.overlayCtx.fillRect(
      this.imageOffsetX,
      this.imageOffsetY,
      this.img.width * this.imageScale,
      this.img.height * this.imageScale
    );
    
    // Clear crop area
    this.overlayCtx.save();
    
    if (this.options.shape === 'round') {
      this.overlayCtx.beginPath();
      this.overlayCtx.arc(
        this.cropX + this.cropSize / 2,
        this.cropY + this.cropSize / 2,
        this.cropSize / 2,
        0,
        Math.PI * 2
      );
      this.overlayCtx.clip();
    } else {
      this.overlayCtx.beginPath();
      this.overlayCtx.rect(this.cropX, this.cropY, this.cropSize, this.cropSize);
      this.overlayCtx.clip();
    }
    
    this.overlayCtx.clearRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height);
    this.overlayCtx.restore();
    
    // Draw crop border
    this.overlayCtx.strokeStyle = 'white';
    this.overlayCtx.lineWidth = 2;
    
    if (this.options.shape === 'round') {
      this.overlayCtx.beginPath();
      this.overlayCtx.arc(
        this.cropX + this.cropSize / 2,
        this.cropY + this.cropSize / 2,
        this.cropSize / 2,
        0,
        Math.PI * 2
      );
      this.overlayCtx.stroke();
    } else {
      this.overlayCtx.strokeRect(this.cropX, this.cropY, this.cropSize, this.cropSize);
    }
    
    // Draw resize handles
    this.drawResizeHandles();
  }

  private drawResizeHandles(): void {
    const handleSize = 8;
    const handles = [
      { x: this.cropX, y: this.cropY },
      { x: this.cropX + this.cropSize, y: this.cropY },
      { x: this.cropX, y: this.cropY + this.cropSize },
      { x: this.cropX + this.cropSize, y: this.cropY + this.cropSize }
    ];
    
    this.overlayCtx.fillStyle = 'white';
    
    handles.forEach(handle => {
      this.overlayCtx.beginPath();
      this.overlayCtx.arc(handle.x, handle.y, handleSize, 0, Math.PI * 2);
      this.overlayCtx.fill();
    });
  }

  async getCroppedImage(): Promise<string> {
    const cropCanvas = document.createElement('canvas');
    const cropCtx = cropCanvas.getContext('2d')!;
    
    // Calculate the actual crop size in original image pixels
    const scale = 1 / this.imageScale;
    const originalCropSize = this.cropSize * scale;
    
    // Use requested output size, but warn if upscaling
    const outputSize = this.options.outputSize!;
    
    if (outputSize > originalCropSize) {
      console.info(`Profile picture: Upscaling from ${Math.round(originalCropSize)}px to ${outputSize}px`);
    }
    
    cropCanvas.width = outputSize;
    cropCanvas.height = outputSize;
    
    // Calculate source coordinates
    const sx = (this.cropX - this.imageOffsetX) * scale;
    const sy = (this.cropY - this.imageOffsetY) * scale;
    const sSize = this.cropSize * scale;
    
    // Apply shape mask if round
    if (this.options.shape === 'round') {
      cropCtx.beginPath();
      cropCtx.arc(outputSize / 2, outputSize / 2, outputSize / 2, 0, Math.PI * 2);
      cropCtx.clip();
    }
    
    // Enable image smoothing for quality
    cropCtx.imageSmoothingEnabled = true;
    cropCtx.imageSmoothingQuality = 'high';
    
    // Draw cropped image
    cropCtx.drawImage(
      this.img,
      sx, sy, sSize, sSize,
      0, 0, outputSize, outputSize
    );
    
    // Detect format from original image
    const isPng = this.options.image.includes('image/png');
    const format = isPng ? 'image/png' : 'image/jpeg';
    
    return cropCanvas.toDataURL(format, this.options.outputQuality);
  }

  destroy(): void {
    this.canvas.remove();
    this.overlayCanvas.remove();
  }
}