import { IEventBus } from '../types/CalendarTypes';
import { IGridConfig } from '../core/IGridConfig';
import { pixelsToMinutes, minutesToPixels, snapToGrid } from '../utils/PositionUtils';
import { DateService } from '../core/DateService';
import { CoreEvents } from '../constants/CoreEvents';
import { IResizeStartPayload, IResizeEndPayload } from '../types/ResizeTypes';
import { SwpEvent } from '../types/SwpEvent';

/**
 * ResizeManager - Handles resize of calendar events
 *
 * Step 1: Handle creation on mouseover (CSS handles visibility)
 * Step 2: Pointer events + resize start
 * Step 3: RAF animation for smooth height update
 * Step 4: Grid snapping + timestamp update
 */

interface ResizeState {
  eventId: string;
  element: HTMLElement;
  handleElement: HTMLElement;
  startY: number;
  startHeight: number;
  startDurationMinutes: number;
  pointerId: number;
  prevZIndex: string;
  // Animation state
  currentHeight: number;
  targetHeight: number;
  animationId: number | null;
}

export class ResizeManager {
  private container: HTMLElement | null = null;
  private resizeState: ResizeState | null = null;

  private readonly Z_INDEX_RESIZING = '1000';
  private readonly ANIMATION_SPEED = 0.35;
  private readonly MIN_HEIGHT_MINUTES = 15;

  constructor(
    private eventBus: IEventBus,
    private gridConfig: IGridConfig,
    private dateService: DateService
  ) {}

  /**
   * Initialize resize functionality on container
   */
  init(container: HTMLElement): void {
    this.container = container;

    // Mouseover listener for handle creation (capture phase like V1)
    container.addEventListener('mouseover', this.handleMouseOver, true);

    // Pointer listeners for resize (capture phase like V1)
    document.addEventListener('pointerdown', this.handlePointerDown, true);
    document.addEventListener('pointermove', this.handlePointerMove, true);
    document.addEventListener('pointerup', this.handlePointerUp, true);
  }

  /**
   * Create resize handle element
   */
  private createResizeHandle(): HTMLElement {
    const handle = document.createElement('swp-resize-handle');
    handle.setAttribute('aria-label', 'Resize event');
    handle.setAttribute('role', 'separator');
    return handle;
  }

  /**
   * Handle mouseover - create resize handle if not exists
   */
  private handleMouseOver = (e: Event): void => {
    const target = e.target as HTMLElement;
    const eventElement = target.closest('swp-event') as HTMLElement;

    if (!eventElement || this.resizeState) return;

    // Check if handle already exists
    if (!eventElement.querySelector(':scope > swp-resize-handle')) {
      const handle = this.createResizeHandle();
      eventElement.appendChild(handle);
    }
  };

  /**
   * Handle pointerdown - start resize if on handle
   */
  private handlePointerDown = (e: PointerEvent): void => {
    const handle = (e.target as HTMLElement).closest('swp-resize-handle') as HTMLElement;
    if (!handle) return;

    const element = handle.parentElement as HTMLElement;
    if (!element) return;

    const eventId = element.dataset.eventId || '';
    const startHeight = element.offsetHeight;
    const startDurationMinutes = pixelsToMinutes(startHeight, this.gridConfig);

    // Store previous z-index
    const container = element.closest('swp-event-group') as HTMLElement ?? element;
    const prevZIndex = container.style.zIndex;

    // Set resize state
    this.resizeState = {
      eventId,
      element,
      handleElement: handle,
      startY: e.clientY,
      startHeight,
      startDurationMinutes,
      pointerId: e.pointerId,
      prevZIndex,
      // Animation state
      currentHeight: startHeight,
      targetHeight: startHeight,
      animationId: null
    };

    // Elevate z-index
    container.style.zIndex = this.Z_INDEX_RESIZING;

    // Capture pointer for smooth tracking
    try {
      handle.setPointerCapture(e.pointerId);
    } catch (err) {
      console.warn('Pointer capture failed:', err);
    }

    // Add global resizing class
    document.documentElement.classList.add('swp--resizing');

    // Emit resize start event
    this.eventBus.emit(CoreEvents.EVENT_RESIZE_START, {
      eventId,
      element,
      startHeight
    } as IResizeStartPayload);

    e.preventDefault();
  };

  /**
   * Handle pointermove - update target height during resize
   */
  private handlePointerMove = (e: PointerEvent): void => {
    if (!this.resizeState) return;

    const deltaY = e.clientY - this.resizeState.startY;
    const minHeight = (this.MIN_HEIGHT_MINUTES / 60) * this.gridConfig.hourHeight;
    const newHeight = Math.max(minHeight, this.resizeState.startHeight + deltaY);

    // Set target height for animation
    this.resizeState.targetHeight = newHeight;

    // Start animation if not running
    if (this.resizeState.animationId === null) {
      this.animateHeight();
    }
  };

  /**
   * RAF animation loop for smooth height interpolation
   */
  private animateHeight = (): void => {
    if (!this.resizeState) return;

    const diff = this.resizeState.targetHeight - this.resizeState.currentHeight;

    // Stop animation when close enough
    if (Math.abs(diff) < 0.5) {
      this.resizeState.animationId = null;
      return;
    }

    // Interpolate towards target (35% per frame like V1)
    this.resizeState.currentHeight += diff * this.ANIMATION_SPEED;
    this.resizeState.element.style.height = `${this.resizeState.currentHeight}px`;

    // Update timestamp display (snapped)
    this.updateTimestampDisplay();

    // Continue animation
    this.resizeState.animationId = requestAnimationFrame(this.animateHeight);
  };

  /**
   * Update timestamp display with snapped end time
   */
  private updateTimestampDisplay(): void {
    if (!this.resizeState) return;

    const timeEl = this.resizeState.element.querySelector('swp-event-time');
    if (!timeEl) return;

    // Get start time from element position
    const top = parseFloat(this.resizeState.element.style.top) || 0;
    const startMinutesFromGrid = pixelsToMinutes(top, this.gridConfig);
    const startMinutes = (this.gridConfig.dayStartHour * 60) + startMinutesFromGrid;

    // Calculate snapped end time from current height
    const snappedHeight = snapToGrid(this.resizeState.currentHeight, this.gridConfig);
    const durationMinutes = pixelsToMinutes(snappedHeight, this.gridConfig);
    const endMinutes = startMinutes + durationMinutes;

    // Format and update
    const start = this.minutesToDate(startMinutes);
    const end = this.minutesToDate(endMinutes);
    timeEl.textContent = this.dateService.formatTimeRange(start, end);
  }

  /**
   * Convert minutes since midnight to Date
   */
  private minutesToDate(minutes: number): Date {
    const date = new Date();
    date.setHours(Math.floor(minutes / 60) % 24, minutes % 60, 0, 0);
    return date;
  };

  /**
   * Handle pointerup - finish resize
   */
  private handlePointerUp = (e: PointerEvent): void => {
    if (!this.resizeState) return;

    // Cancel any pending animation
    if (this.resizeState.animationId !== null) {
      cancelAnimationFrame(this.resizeState.animationId);
    }

    // Release pointer capture
    try {
      this.resizeState.handleElement.releasePointerCapture(e.pointerId);
    } catch (err) {
      console.warn('Pointer release failed:', err);
    }

    // Snap final height to grid
    this.snapToGridFinal();

    // Update timestamp one final time
    this.updateTimestampDisplay();

    // Restore z-index
    const container = this.resizeState.element.closest('swp-event-group') as HTMLElement ?? this.resizeState.element;
    container.style.zIndex = this.resizeState.prevZIndex;

    // Remove global resizing class
    document.documentElement.classList.remove('swp--resizing');

    // Get columnKey and date from parent column
    const column = this.resizeState.element.closest('swp-day-column') as HTMLElement;
    const columnKey = column?.dataset.columnKey || '';
    const date = column?.dataset.date || '';

    // Create SwpEvent from element (reads top/height/eventId from element)
    const swpEvent = SwpEvent.fromElement(
      this.resizeState.element,
      columnKey,
      date,
      this.gridConfig
    );

    // Emit resize end event
    this.eventBus.emit(CoreEvents.EVENT_RESIZE_END, {
      swpEvent
    } as IResizeEndPayload);

    // Reset state
    this.resizeState = null;
  };

  /**
   * Snap final height to grid interval
   */
  private snapToGridFinal(): void {
    if (!this.resizeState) return;

    const currentHeight = this.resizeState.element.offsetHeight;
    const snappedHeight = snapToGrid(currentHeight, this.gridConfig);
    const minHeight = minutesToPixels(this.MIN_HEIGHT_MINUTES, this.gridConfig);
    const finalHeight = Math.max(minHeight, snappedHeight);

    this.resizeState.element.style.height = `${finalHeight}px`;
    this.resizeState.currentHeight = finalHeight;
  }
}
