import { CalendarOrchestrator } from './CalendarOrchestrator';
import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer';
import { NavigationAnimator } from './NavigationAnimator';
import { DateService } from './DateService';
import { ScrollManager } from './ScrollManager';
import { HeaderDrawerManager } from './HeaderDrawerManager';
import { ViewConfig } from './ViewConfig';
import { DragDropManager } from '../managers/DragDropManager';
import { EdgeScrollManager } from '../managers/EdgeScrollManager';
import { ResizeManager } from '../managers/ResizeManager';
import { EventPersistenceManager } from '../managers/EventPersistenceManager';
import { HeaderDrawerRenderer } from '../features/headerdrawer/HeaderDrawerRenderer';
import { SettingsService } from '../storage/settings/SettingsService';
import { ViewConfigService } from '../storage/viewconfigs/ViewConfigService';
import { IWorkweekPreset } from '../types/SettingsTypes';
import { IEventBus } from '../types/CalendarTypes';
import {
  CalendarEvents,
  RenderPayload,
  WorkweekChangePayload,
  ViewUpdatePayload
} from './CalendarEvents';

export class CalendarApp {
  private animator!: NavigationAnimator;
  private container!: HTMLElement;
  private dayOffset = 0;
  private currentViewId = 'simple';
  private workweekPreset: IWorkweekPreset | null = null;
  private groupingOverrides: Map<string, string[]> = new Map();

  constructor(
    private orchestrator: CalendarOrchestrator,
    private timeAxisRenderer: TimeAxisRenderer,
    private dateService: DateService,
    private scrollManager: ScrollManager,
    private headerDrawerManager: HeaderDrawerManager,
    private dragDropManager: DragDropManager,
    private edgeScrollManager: EdgeScrollManager,
    private resizeManager: ResizeManager,
    private headerDrawerRenderer: HeaderDrawerRenderer,
    private eventPersistenceManager: EventPersistenceManager,
    private settingsService: SettingsService,
    private viewConfigService: ViewConfigService,
    private eventBus: IEventBus
  ) {}

  async init(container: HTMLElement): Promise<void> {
    this.container = container;

    // Load settings
    const gridSettings = await this.settingsService.getGridSettings();
    if (!gridSettings) {
      throw new Error('GridSettings not found');
    }

    this.workweekPreset = await this.settingsService.getDefaultWorkweekPreset();

    // Create NavigationAnimator with DOM elements
    this.animator = new NavigationAnimator(
      container.querySelector('swp-header-track') as HTMLElement,
      container.querySelector('swp-content-track') as HTMLElement,
      container.querySelector('swp-header-drawer')
    );

    // Render time axis from settings
    this.timeAxisRenderer.render(
      container.querySelector('#time-axis') as HTMLElement,
      gridSettings.dayStartHour,
      gridSettings.dayEndHour
    );

    // Init managers
    this.scrollManager.init(container);
    this.headerDrawerManager.init(container);
    this.dragDropManager.init(container);
    this.resizeManager.init(container);

    const scrollableContent = container.querySelector('swp-scrollable-content') as HTMLElement;
    this.edgeScrollManager.init(scrollableContent);

    // Setup command event listeners
    this.setupEventListeners();

    // Emit ready status
    this.emitStatus('ready');
  }

  private setupEventListeners(): void {
    // Navigation commands via EventBus
    this.eventBus.on(CalendarEvents.CMD_NAVIGATE_PREV, () => {
      this.handleNavigatePrev();
    });

    this.eventBus.on(CalendarEvents.CMD_NAVIGATE_NEXT, () => {
      this.handleNavigateNext();
    });

    // Drawer toggle via EventBus
    this.eventBus.on(CalendarEvents.CMD_DRAWER_TOGGLE, () => {
      this.headerDrawerManager.toggle();
    });

    // Render command via EventBus
    this.eventBus.on(CalendarEvents.CMD_RENDER, (e: Event) => {
      const { viewId } = (e as CustomEvent<RenderPayload>).detail;
      this.handleRenderCommand(viewId);
    });

    // Workweek change via EventBus
    this.eventBus.on(CalendarEvents.CMD_WORKWEEK_CHANGE, (e: Event) => {
      const { presetId } = (e as CustomEvent<WorkweekChangePayload>).detail;
      this.handleWorkweekChange(presetId);
    });

    // View update via EventBus
    this.eventBus.on(CalendarEvents.CMD_VIEW_UPDATE, (e: Event) => {
      const { type, values } = (e as CustomEvent<ViewUpdatePayload>).detail;
      this.handleViewUpdate(type, values);
    });
  }

  private async handleRenderCommand(viewId: string): Promise<void> {
    this.currentViewId = viewId;
    await this.render();
    this.emitStatus('rendered', { viewId });
  }

  private async handleNavigatePrev(): Promise<void> {
    const step = this.workweekPreset?.periodDays ?? 7;
    this.dayOffset -= step;
    await this.animator.slide('right', () => this.render());
    this.emitStatus('rendered', { viewId: this.currentViewId });
  }

  private async handleNavigateNext(): Promise<void> {
    const step = this.workweekPreset?.periodDays ?? 7;
    this.dayOffset += step;
    await this.animator.slide('left', () => this.render());
    this.emitStatus('rendered', { viewId: this.currentViewId });
  }

  private async handleWorkweekChange(presetId: string): Promise<void> {
    const preset = await this.settingsService.getWorkweekPreset(presetId);
    if (preset) {
      this.workweekPreset = preset;
      await this.render();
      this.emitStatus('rendered', { viewId: this.currentViewId });
    }
  }

  private async handleViewUpdate(type: string, values: string[]): Promise<void> {
    this.groupingOverrides.set(type, values);
    await this.render();
    this.emitStatus('rendered', { viewId: this.currentViewId });
  }

  private async render(): Promise<void> {
    const storedConfig = await this.viewConfigService.getById(this.currentViewId);
    if (!storedConfig) {
      this.emitStatus('error', { message: `ViewConfig not found: ${this.currentViewId}` });
      return;
    }

    // Populate date values based on workweek preset and day offset
    const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5];
    const periodDays = this.workweekPreset?.periodDays ?? 7;

    // For single-day navigation (periodDays=1), show consecutive days from offset
    // For week navigation (periodDays=7), show workDays from the week containing offset
    const dates = periodDays === 1
      ? this.dateService.getDatesFromOffset(this.dayOffset, workDays.length)
      : this.dateService.getWorkDaysFromOffset(this.dayOffset, workDays);

    // Clone config and apply overrides
    const viewConfig: ViewConfig = {
      ...storedConfig,
      groupings: storedConfig.groupings.map(g => {
        // Apply date values
        if (g.type === 'date') {
          return { ...g, values: dates };
        }
        // Apply grouping overrides
        const override = this.groupingOverrides.get(g.type);
        if (override) {
          return { ...g, values: override };
        }
        return g;
      })
    };

    await this.orchestrator.render(viewConfig, this.container);
  }

  private emitStatus(status: string, detail?: object): void {
    this.container.dispatchEvent(new CustomEvent(`calendar:status:${status}`, {
      detail,
      bubbles: true
    }));
  }
}
