/*
 * Copyright (C) 2023 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {SCISO8601Date, SCUuid} from '@openstapps/core';
import moment, {Moment} from 'moment';
import {materialFade, materialManualFade, materialSharedAxisX} from '../../../../animation/material-motion';
import {ScheduleProvider} from '../../../calendar/schedule.provider';
import {ScheduleEvent, ScheduleResponsiveBreakpoint} from '../schema/schema';
import {SwiperComponent} from 'swiper/angular';
import {InfiniteSwiperComponent} from '../grid/infinite-swiper.component';
import {IonContent, IonDatetime} from '@ionic/angular/standalone';
import {CalendarService} from '../../../calendar/calendar.service';
import {getScheduleCursorOffset} from '../grid/schedule-cursor-offset';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';

/**
 * Component that displays the schedule
 */
@Component({
  selector: 'stapps-calendar-component',
  templateUrl: 'calendar-component.html',
  styleUrls: ['calendar-component.scss'],
  animations: [materialFade, materialSharedAxisX, materialManualFade],
})
export class CalendarComponent implements OnInit {
  /**
   * The day that the schedule started out on
   */
  @Input() baselineDate: Moment;

  /**
   * Range of date of the slides shown on screen.
   */
  dateRange: {
    startDate: string;
    endDate: string;
  } = {
    startDate: '',
    endDate: '',
  };

  prevHeaderIndex = 0;

  /**
   * Hours for grid
   */
  @Input() hours: number[];

  /**
   * Range of hours to display
   */
  @Input() readonly hoursRange = {
    from: 5,
    to: 22,
  };

  todaySlideIndex: number;

  initialSlideIndex?: Promise<number>;

  /**
   * Layout of the schedule
   */
  @Input() layout: ScheduleResponsiveBreakpoint;

  /**
   * Route fragment
   */
  routeFragment = 'schedule/calendar';

  /**
   * Vertical scale of the schedule (distance between hour lines)
   */
  @Input() scale = 70;

  /**
   * unix -> (uid -> event)
   */
  @Input() testSchedule: Record<SCISO8601Date, Record<SCUuid, ScheduleEvent>> = {};

  /**
   * UUIDs
   */
  @Input() uuids: SCUuid[];

  @Input() useInfiniteSwiper = true;

  @Input() weekDates: Array<Moment>;

  destroy$ = inject(DestroyRef);

  constructor(
    protected readonly activatedRoute: ActivatedRoute,
    protected readonly calendarService: CalendarService,
    protected readonly scheduleProvider: ScheduleProvider,
  ) {}

  ngOnInit() {
    let dayString: string | number | null = this.activatedRoute.snapshot.paramMap.get('date');
    if (dayString == undefined || dayString === 'now') {
      const fragments = window.location.href.split('/');
      const urlFragment: string = fragments.at(-1) ?? '';

      dayString = /^\d{4}-\d{2}-\d{2}$/.test(urlFragment) ? urlFragment : moment.now();
    }

    this.baselineDate = moment(dayString).startOf('day');

    this.initialSlideIndex = new Promise(resolve => {
      this.scheduleProvider.uuids$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async result => {
        this.uuids = result;
        resolve(await this.loadEvents());
      });
    });

    this.dateRange.startDate = this.calculateDateFromIndex(0, 0, 'DD.MM.YY');
    this.dateRange.endDate = this.calculateDateFromIndex(0, this.layout.days - 1, 'DD.MM.YY');
  }

  async scrollCursorIntoView(content: IonContent) {
    const scrollElement = await content.getScrollElement();
    scrollElement.scrollTo({
      top: getScheduleCursorOffset(this.hoursRange.from, this.scale) - scrollElement.clientHeight / 3,
    });
  }

  /**
   * Get date from baseline date and index of current slide.
   * @param index number
   * @param delta number - is added to index
   * @param dateFormat string
   */
  calculateDateFromIndex(index: number, delta = 0, dateFormat = 'YYYY-MM-DD') {
    return moment(this.baselineDate)
      .add(index + delta, 'days')
      .format(dateFormat);
  }

  /**
   * Change page
   */
  onPageChange(index: number) {
    this.setDateRange(index);

    window.history.replaceState({}, '', `${this.routeFragment}/${this.calculateDateFromIndex(index)}`);
  }

  setDateRange(index: number) {
    this.dateRange.startDate = this.calculateDateFromIndex(index, 0, 'DD.MM.YY');
    this.dateRange.endDate = this.calculateDateFromIndex(index, this.layout.days - 1, 'DD.MM.YY');
  }

  onHeaderSwipe(index: number, infiniteController: InfiniteSwiperComponent) {
    if (index < this.prevHeaderIndex) {
      infiniteController?.pageBackwards();
    }
    if (index > this.prevHeaderIndex) {
      infiniteController?.pageForward();
    }
    this.prevHeaderIndex = index;
  }

  syncSwiper(self: SwiperComponent, other: SwiperComponent) {
    other.swiperRef.slideTo(self.swiperRef.activeIndex);
  }

  presentDatePopover(
    mainSwiper: InfiniteSwiperComponent,
    headerSwiper: InfiniteSwiperComponent,
    index: number,
    popoverDateTime: IonDatetime,
  ) {
    const nextIndex =
      moment(popoverDateTime.value).diff(this.baselineDate, 'days') - headerSwiper.virtualIndex - index;

    mainSwiper.goToIndex(nextIndex).then(() => {
      this.setDateRange(nextIndex);
    });

    void popoverDateTime.cancel(true);
  }

  /**
   * Load events
   */
  async loadEvents(): Promise<number> {
    const dateSeries = await this.scheduleProvider.getDateSeries(this.uuids);

    this.testSchedule = {};

    for (const series of dateSeries.dates) {
      for (const date of series.dates) {
        const index = moment(date).startOf('day').diff(this.baselineDate, 'days');

        // fall back to default
        (this.testSchedule[index] ?? (this.testSchedule[index] = {}))[series.uid] = {
          dateSeries: series,
          time: {
            start: moment(date).hours(),
            duration: series.duration,
          },
        };
      }
    }
    return this.todaySlideIndex;
  }
}
