import { IEventBus, ICalendarEvent } from '../../types/CalendarTypes';
import { IGridConfig } from '../../core/IGridConfig';
import { CoreEvents } from '../../constants/CoreEvents';
import { HeaderDrawerManager } from '../../core/HeaderDrawerManager';
import { EventService } from '../../storage/events/EventService';
import { DateService } from '../../core/DateService';
import { FilterTemplate } from '../../core/FilterTemplate';
import {
  IDragEnterHeaderPayload,
  IDragMoveHeaderPayload,
  IDragLeaveHeaderPayload,
  IDragEndPayload
} from '../../types/DragTypes';

/**
 * Layout information for a header item
 */
interface IHeaderItemLayout {
  event: ICalendarEvent;
  columnKey: string;  // Opaque column identifier
  row: number;        // 1-indexed
  colStart: number;   // 1-indexed
  colEnd: number;     // exclusive
}

/**
 * HeaderDrawerRenderer - Handles rendering of items in the header drawer
 *
 * Listens to drag events from DragDropManager and creates/manages
 * swp-header-item elements in the header drawer.
 *
 * Uses subgrid for column alignment with parent swp-calendar-header.
 * Position items via gridArea for explicit row/column placement.
 */
export class HeaderDrawerRenderer {
  private currentItem: HTMLElement | null = null;
  private container: HTMLElement | null = null;
  private sourceElement: HTMLElement | null = null;
  private wasExpandedBeforeDrag = false;
  private filterTemplate: FilterTemplate | null = null;

  constructor(
    private eventBus: IEventBus,
    private gridConfig: IGridConfig,
    private headerDrawerManager: HeaderDrawerManager,
    private eventService: EventService,
    private dateService: DateService
  ) {
    this.setupListeners();
  }

  /**
   * Render allDay events into the header drawer with row stacking
   * @param filterTemplate - Template for matching events to columns
   */
  async render(container: HTMLElement, filter: Record<string, string[]>, filterTemplate: FilterTemplate): Promise<void> {
    // Store filterTemplate for buildColumnKeyFromEvent
    this.filterTemplate = filterTemplate;

    const drawer = container.querySelector('swp-header-drawer');
    if (!drawer) return;

    const visibleDates = filter['date'] || [];
    if (visibleDates.length === 0) return;

    // Get column keys from DOM for correct multi-resource positioning
    const visibleColumnKeys = this.getVisibleColumnKeysFromDOM();
    if (visibleColumnKeys.length === 0) return;

    // Fetch events for date range
    const startDate = new Date(visibleDates[0]);
    const endDate = new Date(visibleDates[visibleDates.length - 1]);
    endDate.setHours(23, 59, 59, 999);

    const events = await this.eventService.getByDateRange(startDate, endDate);

    // Filter to allDay events only (allDay !== false)
    const allDayEvents = events.filter(event => event.allDay !== false);

    // Clear existing items
    drawer.innerHTML = '';

    if (allDayEvents.length === 0) return;

    // Calculate layout with row stacking using columnKeys
    const layouts = this.calculateLayout(allDayEvents, visibleColumnKeys);
    const rowCount = Math.max(1, ...layouts.map(l => l.row));

    // Render each item with layout
    layouts.forEach(layout => {
      const item = this.createHeaderItem(layout);
      drawer.appendChild(item);
    });

    // Expand drawer to fit all rows
    this.headerDrawerManager.expandToRows(rowCount);
  }

  /**
   * Create a header item element from layout
   */
  private createHeaderItem(layout: IHeaderItemLayout): HTMLElement {
    const { event, columnKey, row, colStart, colEnd } = layout;

    const item = document.createElement('swp-header-item');
    item.dataset.eventId = event.id;
    item.dataset.itemType = 'event';
    item.dataset.start = event.start.toISOString();
    item.dataset.end = event.end.toISOString();
    item.dataset.columnKey = columnKey;
    item.textContent = event.title;

    // Color class
    const colorClass = this.getColorClass(event);
    if (colorClass) item.classList.add(colorClass);

    // Grid position from layout
    item.style.gridArea = `${row} / ${colStart} / ${row + 1} / ${colEnd}`;

    return item;
  }

  /**
   * Calculate layout for all events with row stacking
   * Uses track-based algorithm to find available rows for overlapping events
   */
  private calculateLayout(events: ICalendarEvent[], visibleColumnKeys: string[]): IHeaderItemLayout[] {
    // tracks[row][col] = occupied
    const tracks: boolean[][] = [new Array(visibleColumnKeys.length).fill(false)];
    const layouts: IHeaderItemLayout[] = [];

    for (const event of events) {
      // Build columnKey from event fields (only place we need to construct it)
      const columnKey = this.buildColumnKeyFromEvent(event);
      const startCol = visibleColumnKeys.indexOf(columnKey);
      const endColumnKey = this.buildColumnKeyFromEvent(event, event.end);
      const endCol = visibleColumnKeys.indexOf(endColumnKey);
      if (startCol === -1 && endCol === -1) continue;

      // Clamp til synlige kolonner
      const colStart = Math.max(0, startCol);
      const colEnd = (endCol !== -1 ? endCol : visibleColumnKeys.length - 1) + 1;

      // Find ledig række
      const row = this.findAvailableRow(tracks, colStart, colEnd);

      // Marker som optaget
      for (let c = colStart; c < colEnd; c++) {
        tracks[row][c] = true;
      }

      layouts.push({ event, columnKey, row: row + 1, colStart: colStart + 1, colEnd: colEnd + 1 });
    }

    return layouts;
  }

  /**
   * Build columnKey from event using FilterTemplate
   * Uses the same template that columns use for matching
   */
  private buildColumnKeyFromEvent(event: ICalendarEvent, date?: Date): string {
    if (!this.filterTemplate) {
      // Fallback if no template - shouldn't happen in normal flow
      const dateStr = this.dateService.getDateKey(date || event.start);
      return dateStr;
    }

    // For multi-day events, we need to override the date in the event
    if (date && date.getTime() !== event.start.getTime()) {
      // Create temporary event with overridden start for key generation
      const tempEvent = { ...event, start: date };
      return this.filterTemplate.buildKeyFromEvent(tempEvent);
    }

    return this.filterTemplate.buildKeyFromEvent(event);
  }

  /**
   * Find available row for event spanning columns [colStart, colEnd)
   */
  private findAvailableRow(tracks: boolean[][], colStart: number, colEnd: number): number {
    for (let row = 0; row < tracks.length; row++) {
      let available = true;
      for (let c = colStart; c < colEnd; c++) {
        if (tracks[row][c]) { available = false; break; }
      }
      if (available) return row;
    }
    // Ny række
    tracks.push(new Array(tracks[0].length).fill(false));
    return tracks.length - 1;
  }

  /**
   * Get color class based on event metadata or type
   */
  private getColorClass(event: ICalendarEvent): string {
    if (event.metadata?.color) {
      return `is-${event.metadata.color}`;
    }
    const typeColors: Record<string, string> = {
      'customer': 'is-blue',
      'vacation': 'is-green',
      'break': 'is-amber',
      'meeting': 'is-purple',
      'blocked': 'is-red'
    };
    return typeColors[event.type] || 'is-blue';
  }

  /**
   * Setup event listeners for drag events
   */
  private setupListeners(): void {
    this.eventBus.on(CoreEvents.EVENT_DRAG_ENTER_HEADER, (e) => {
      const payload = (e as CustomEvent<IDragEnterHeaderPayload>).detail;
      this.handleDragEnter(payload);
    });

    this.eventBus.on(CoreEvents.EVENT_DRAG_MOVE_HEADER, (e) => {
      const payload = (e as CustomEvent<IDragMoveHeaderPayload>).detail;
      this.handleDragMove(payload);
    });

    this.eventBus.on(CoreEvents.EVENT_DRAG_LEAVE_HEADER, (e) => {
      const payload = (e as CustomEvent<IDragLeaveHeaderPayload>).detail;
      this.handleDragLeave(payload);
    });

    this.eventBus.on(CoreEvents.EVENT_DRAG_END, (e) => {
      const payload = (e as CustomEvent<IDragEndPayload>).detail;
      this.handleDragEnd(payload);
    });

    this.eventBus.on(CoreEvents.EVENT_DRAG_CANCEL, () => {
      this.cleanup();
    });
  }

  /**
   * Handle drag entering header zone - create preview item
   */
  private handleDragEnter(payload: IDragEnterHeaderPayload): void {
    this.container = document.querySelector('swp-header-drawer');
    if (!this.container) return;

    // Remember if drawer was already expanded
    this.wasExpandedBeforeDrag = this.headerDrawerManager.isExpanded();

    // Expand to at least 1 row if collapsed, otherwise keep current height
    if (!this.wasExpandedBeforeDrag) {
      this.headerDrawerManager.expandToRows(1);
    }

    // Store reference to source element
    this.sourceElement = payload.element;

    // Create header item
    const item = document.createElement('swp-header-item');
    item.dataset.eventId = payload.eventId;
    item.dataset.itemType = payload.itemType;
    item.dataset.duration = String(payload.duration);
    item.dataset.columnKey = payload.sourceColumnKey;
    item.textContent = payload.title;

    // Apply color class if present
    if (payload.colorClass) {
      item.classList.add(payload.colorClass);
    }

    // Add dragging state
    item.classList.add('dragging');

    // Initial placement (duration determines column span)
    // gridArea format: "row / col-start / row+1 / col-end"
    const col = payload.sourceColumnIndex + 1;
    const endCol = col + payload.duration;
    item.style.gridArea = `1 / ${col} / 2 / ${endCol}`;

    this.container.appendChild(item);
    this.currentItem = item;

    // Hide original element while in header
    payload.element.style.visibility = 'hidden';
  }

  /**
   * Handle drag moving within header - update column position
   */
  private handleDragMove(payload: IDragMoveHeaderPayload): void {
    if (!this.currentItem) return;

    // Update column position
    const col = payload.columnIndex + 1;
    const duration = parseInt(this.currentItem.dataset.duration || '1', 10);
    const endCol = col + duration;

    this.currentItem.style.gridArea = `1 / ${col} / 2 / ${endCol}`;

    // Update columnKey to new position
    this.currentItem.dataset.columnKey = payload.columnKey;
  }

  /**
   * Handle drag leaving header - cleanup for grid→header drag only
   */
  private handleDragLeave(payload: IDragLeaveHeaderPayload): void {
    // Only cleanup for grid→header drag (when grid event leaves header back to grid)
    // For header→grid drag, the header item stays as ghost until drop
    if (payload.source === 'grid') {
      this.cleanup();
    }
    // For header source, do nothing - ghost stays until EVENT_DRAG_END
  }

  /**
   * Handle drag end - finalize based on drop target
   */
  private handleDragEnd(payload: IDragEndPayload): void {
    if (payload.target === 'header') {
      // Grid→Header: Finalize the header item (it stays in header)
      if (this.currentItem) {
        this.currentItem.classList.remove('dragging');
        this.recalculateDrawerLayout();
        this.currentItem = null;
        this.sourceElement = null;
      }
    } else {
      // Header→Grid: Remove ghost header item and recalculate
      const ghost = document.querySelector(`swp-header-item.drag-ghost[data-event-id="${payload.swpEvent.eventId}"]`);
      ghost?.remove();
      this.recalculateDrawerLayout();
    }
  }

  /**
   * Recalculate layout for all items currently in the drawer
   * Called after drop to reposition items and adjust height
   */
  private recalculateDrawerLayout(): void {
    const drawer = document.querySelector('swp-header-drawer');
    if (!drawer) return;

    const items = Array.from(drawer.querySelectorAll('swp-header-item')) as HTMLElement[];
    if (items.length === 0) return;

    // Get visible column keys for correct multi-resource positioning
    const visibleColumnKeys = this.getVisibleColumnKeysFromDOM();
    if (visibleColumnKeys.length === 0) return;

    // Build layout data from DOM items - use columnKey directly (opaque matching)
    const itemData = items.map(item => ({
      element: item,
      columnKey: item.dataset.columnKey || '',
      duration: parseInt(item.dataset.duration || '1', 10)
    }));

    // Calculate new layout using track algorithm
    const tracks: boolean[][] = [new Array(visibleColumnKeys.length).fill(false)];

    for (const item of itemData) {
      // Direct columnKey matching - no parsing or construction needed
      const startCol = visibleColumnKeys.indexOf(item.columnKey);
      if (startCol === -1) continue;

      const colStart = startCol;
      const colEnd = Math.min(startCol + item.duration, visibleColumnKeys.length);

      const row = this.findAvailableRow(tracks, colStart, colEnd);

      for (let c = colStart; c < colEnd; c++) {
        tracks[row][c] = true;
      }

      // Update element position
      item.element.style.gridArea = `${row + 1} / ${colStart + 1} / ${row + 2} / ${colEnd + 1}`;
    }

    // Update drawer height
    const rowCount = tracks.length;
    this.headerDrawerManager.expandToRows(rowCount);
  }

  /**
   * Get visible column keys from DOM (preserves order for multi-resource views)
   * Uses filterTemplate.buildKeyFromColumn() for consistent key format with events
   */
  private getVisibleColumnKeysFromDOM(): string[] {
    if (!this.filterTemplate) return [];
    const columns = document.querySelectorAll('swp-day-column');
    const columnKeys: string[] = [];
    columns.forEach(col => {
      const columnKey = this.filterTemplate!.buildKeyFromColumn(col as HTMLElement);
      if (columnKey) columnKeys.push(columnKey);
    });
    return columnKeys;
  }

  /**
   * Cleanup preview item and restore source visibility
   */
  private cleanup(): void {
    // Remove preview item
    this.currentItem?.remove();
    this.currentItem = null;

    // Restore source element visibility
    if (this.sourceElement) {
      this.sourceElement.style.visibility = '';
      this.sourceElement = null;
    }

    // Collapse drawer if it wasn't expanded before drag
    if (!this.wasExpandedBeforeDrag) {
      this.headerDrawerManager.collapse();
    }
  }
}
