import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { MatDialog, ErrorStateMatcher } from '@angular/material';
import { Observable, Subscription, pipe } from 'rxjs';
import { filter, map, distinctUntilChanged } from 'rxjs/operators';
import { Store, select } from '@ngrx/store';
import { DegreePlannerState } from '@app/degree-planner/store/state';
import {
  ToggleCourseSearch,
  OpenCourseSearch,
} from '@app/degree-planner/store/actions/ui.actions';

// Models
import * as actions from '@app/degree-planner/store/actions/course.actions';
import * as selectors from '@app/degree-planner/store/selectors';
import { PlannedTerm, PlannedTermNote } from '@app/core/models/planned-term';
import { Note } from '@app/core/models/note';
import { Course } from '@app/core/models/course';
import {
  NotesDialogComponent,
  NotesDialogData,
} from '@app/degree-planner/dialogs/notes-dialog/notes-dialog.component';
import * as utils from '@app/degree-planner/shared/utils';
import { findSafariExecutable } from 'selenium-webdriver/safari';
import { TermCode } from '@app/core/models/termcode';

// Dialogs
import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component';
import { maybeQueueResolutionOfComponentResources } from '@angular/core/src/metadata/resource_loading';
import { WeekDay } from '@angular/common';
import { MediaMatcher } from '@angular/cdk/layout';
const isntUndefined = <T>(thing: T | undefined): thing is T => {
  return thing !== undefined;
};

@Component({
  selector: 'cse-term-container',
  templateUrl: './term-container.component.html',
  styleUrls: ['./term-container.component.scss'],
})
export class TermContainerComponent implements OnInit, OnDestroy {
  @Input() termCode: TermCode;

  public term$: Observable<PlannedTerm>;
  public note$: Observable<PlannedTermNote | undefined>;
  public dropZoneIds$: Observable<string[]>;

  public termSubscription: Subscription;
  public activeTermHasNotOffered: boolean;
  // List of courses pulled for the Observable
  public plannedCourses: ReadonlyArray<Course>;
  public enrolledCourses: ReadonlyArray<Course>;
  public era: 'past' | 'active' | 'future';
  public hasItemDraggedOver: boolean;
  public plannedCredits: string;
  public enrolledCredits: number;
  public visibleCredits: 'enrolled' | 'planned';
  public courseNotOfferedInTerm: ReadonlyArray<Course>;
  public mobileView: MediaQueryList;

  constructor(
    public dialog: MatDialog,
    private store: Store<{ degreePlanner: DegreePlannerState }>,
    mediaMatcher: MediaMatcher,
  ) {
    this.mobileView = mediaMatcher.matchMedia('(max-width: 900px)');
  }

  public ngOnInit() {
    this.hasItemDraggedOver = false;

    this.term$ = this.store.pipe(
      select(selectors.selectVisibleTerm, { termCode: this.termCode }),
      filter(isntUndefined),
      distinctUntilChanged(),
    );

    this.termSubscription = this.term$.subscribe(term => {
      // const {plannedCourses, enrolledCourses} = term;
      this.plannedCourses = term.plannedCourses;
      this.plannedCredits = this.sumPlannedCredits(term.plannedCourses);

      this.enrolledCourses = term.enrolledCourses;
      this.enrolledCredits = this.sumEnrolledCredits(term.enrolledCourses);

      this.era = term.era;
      const activeTermEnrollmentStatus = this.plannedCourses.forEach(
        course => course.studentEnrollmentStatus,
      );
      this.visibleCredits = 'planned';
      if (term.era === 'past' && this.plannedCourses.length === 0) {
        this.visibleCredits = 'enrolled';
      }
    });

    this.note$ = this.term$.pipe(
      map(term => term.note),
      distinctUntilChanged(),
    );

    this.dropZoneIds$ = this.store.pipe(
      select(selectors.selectAllVisibleYears),
      utils.yearsToDropZoneIds(),
      distinctUntilChanged(utils.compareStringArrays),
    );
  }

  ngOnDestroy() {
    this.termSubscription.unsubscribe();
  }

  openNotesDialog(note?: PlannedTermNote) {
    if (note === undefined || note.isLoaded) {
      const termCode = this.termCode;
      const data: NotesDialogData = note
        ? {
            termCode,
            hasExistingNote: true,
            initialText: note.text,
            noteId: note.id,
          }
        : { termCode, hasExistingNote: false };
      this.dialog.open(NotesDialogComponent, { data });
    }
  }

  openCourseSearch() {
    this.store.dispatch(new OpenCourseSearch(this.termCode));
  }

  changeVisibleCredits(event) {
    switch (event.index) {
      case 0:
        this.visibleCredits = 'enrolled';
        break;
      default:
        this.visibleCredits = 'planned';
    }
  }

  drop(event: CdkDragDrop<TermCode>) {
    const newContainer = event.container.id;
    const previousContainer = event.previousContainer.id;
    const { courseId } = event.item.data as Course;
    const isCourseInPlannedCourses = this.plannedCourses.some(
      course => course.courseId === courseId,
    );

    if (newContainer !== previousContainer && isCourseInPlannedCourses) {
      this.dialog
        .open(ConfirmDialogComponent, {
          data: {
            title: "Can't add course to term",
            confirmText: 'OK',
            dialogClass: 'alertDialog',
            text: `This course already exists in selected term!`,
          },
        })
        .afterClosed();
      return;
    }

    if (newContainer === previousContainer) {
      const newIndex = event.currentIndex;
      const { id: recordId, termCode } = event.item.data as Course;

      if (recordId !== null) {
        const action = new actions.MoveCourseInsideTerm({
          termCode: new TermCode(termCode),
          recordId,
          newIndex,
        });
        this.store.dispatch(action);
      }
    } else if (previousContainer.indexOf('term-') === 0) {
      // If moving from term to term

      // Get the pervious and new term code, and the record ID
      const from = event.previousContainer.data;
      const to = event.container.data;
      // FIXME: if `event.item.data` is a Course, the `id` property could be null
      const { id, courseId, subjectCode } = event.item.data;
      const newIndex = event.currentIndex;
      const { classNumber } = event.item.data as Course;

      if (classNumber !== null) {
        // If moving course with packages to future term
        this.dialog
          .open(ConfirmDialogComponent, {
            data: {
              title: 'Are you sure?',
              confirmText: 'Move course',
              text: `Moving this course to a future term will remove your selected section`,
            },
          })
          .afterClosed()
          .subscribe((result: { confirmed: true } | undefined) => {
            if (result !== undefined && result.confirmed === true) {
              this.store.dispatch(
                new actions.MoveCourseBetweenTerms({
                  to,
                  from,
                  id,
                  newIndex,
                  courseId,
                  subjectCode,
                }),
              );
            }
          });
      } else {
        // Dispatch a new change request
        this.store.dispatch(
          new actions.MoveCourseBetweenTerms({
            to,
            from,
            id,
            newIndex,
            courseId,
            subjectCode,
          }),
        );
      }
    } else if (previousContainer === 'saved-courses') {
      // If moving from saved courses to term

      // Get the term code from the new term dropzone's ID
      const termCode = event.container.data;
      const newIndex = event.currentIndex;
      // Pull the course data from the moved item
      const { subjectCode, courseId, title, catalogNumber } = event.item.data;

      this.store.dispatch(
        new actions.AddCourse({
          courseId,
          termCode,
          subjectCode,
          title,
          catalogNumber,
          newIndex,
        }),
      );
      this.store.dispatch(
        new actions.RemoveSaveForLater({ subjectCode, courseId }),
      );
    } else if (
      previousContainer === 'queried-courses-list' &&
      newContainer.indexOf('term-') === 0
    ) {
      const termCode = event.container.data;
      const newIndex = event.currentIndex;
      this.store.dispatch(
        new actions.AddCourse({
          courseId: event.item.data.courseId,
          termCode,
          subjectCode: event.item.data.subjectCode,
          title: event.item.data.title,
          catalogNumber: event.item.data.catalogNumber,
          newIndex,
        }),
      );
    }
  }

  dragEnter(event, item) {
    this.hasItemDraggedOver = true;
  }

  dragExit(event, item) {
    this.hasItemDraggedOver = false;
  }

  sumEnrolledCredits(courses: ReadonlyArray<Course>): number {
    return courses.reduce((sum, course) => sum + course.credits, 0);
  }

  sumPlannedCredits(courses: ReadonlyArray<Course>): string {
    const credits = { min: 0, max: 0 };
    courses.forEach(course => {
      credits.min = credits.min + course.creditMin;
      credits.max = credits.max + course.creditMax;
    });

    return credits.min === credits.max
      ? credits.min.toString()
      : `${credits.min}-${credits.max}`;
  }
}