import { Component, OnInit } from '@angular/core';
import { DarsApiService } from '../services/api.service';
import { DegreePrograms, DegreeProgram } from '../models/degree-program';
import { Observable, combineLatest } from 'rxjs';
import {
  FormBuilder,
  FormGroup,
  Validators,
  FormArray,
  FormControl,
} from '@angular/forms';
import { HonorsOption } from '../models/honors-option';
import { map, share, flatMap, shareReplay } from 'rxjs/operators';
import { CourseBase } from '@app/core/models/course';
import { Store } from '@ngrx/store';
import { GlobalState } from '@app/core/state';
import { degreePlans } from '../store/selectors';
import { DegreePlan } from '@app/core/models/degree-plan';
import { MatDialogRef } from '@angular/material';

const inclusiveRange = (from: number, to: number) => {
  const range: number[] = [];
  for (let i = from; i <= to; i++) {
    range.push(i);
  }
  return range;
};

export interface NewWhatIfAuditFields {
  darsInstitutionCode: string;
  darsDegreeProgramCode: string;
  roadmapId: number;
  darsHonorsOptionCode: string;
  includeCoursesFrom: string;
  fixedCredits: {
    termCode: string;
    subjectCode: string;
    courseId: string;
    credits: number;
  }[];
}

@Component({
  selector: 'cse-new-what-if-audit-dialog',
  templateUrl: './new-what-if-audit-dialog.component.html',
  styleUrls: ['./new-what-if-audit-dialog.component.scss'],
})
export class NewWhatIfAuditDialogComponent implements OnInit {
  // Form-builder objects
  public chosenProgram: FormGroup;
  public chosenAuditSettings: FormGroup;
  public chosenCreditSettings: FormArray;

  // API observables
  public institutions$: Observable<DegreePrograms>;
  public programOrPlanOptions$: Observable<DegreeProgram[]>;
  public degreePlans$: Observable<DegreePlan[]>;
  public honorsOptions$: Observable<HonorsOption[]>;
  public variableCreditCourses$: Observable<
    (CourseBase & { range: number[] })[]
  >;

  constructor(
    private fb: FormBuilder,
    private api: DarsApiService,
    private store: Store<GlobalState>,
    private dialogRef: MatDialogRef<
      NewWhatIfAuditDialogComponent,
      NewWhatIfAuditFields
    >,
  ) {
    this.chosenProgram = fb.group({
      institution: fb.control('', Validators.required),
      planOrProgram: fb.control(
        { value: '', disabled: true },
        Validators.required,
      ),
    });

    this.chosenAuditSettings = fb.group({
      honorsOptions: fb.control('', Validators.required),
      includeCoursesFrom: fb.control('future', Validators.required),
      degreePlan: fb.control('', Validators.required),
    });

    this.chosenCreditSettings = fb.array([]);
  }

  public ngOnInit() {
    this.institutions$ = this.api.getStaticData().pipe(share());

    this.programOrPlanOptions$ = combineLatest([
      this.institutions$,
      (this.chosenProgram.get('institution') as FormControl).valueChanges,
    ]).pipe(
      map(([institutions, darsInstitutionCode]) => {
        this.chosenProgram.controls.planOrProgram.enable();
        if (institutions.hasOwnProperty(darsInstitutionCode)) {
          return institutions[darsInstitutionCode].programs;
        } else {
          return [];
        }
      }),
    );

    this.degreePlans$ = this.store.select(degreePlans).pipe(shareReplay());

    this.degreePlans$.subscribe(plans => {
      const primaryPlan = plans.find(p => p.primary);
      this.chosenAuditSettings.controls['degreePlan'].setValue(primaryPlan);
    });

    this.honorsOptions$ = combineLatest([
      this.institutions$,
      (this.chosenProgram.get('institution') as FormControl).valueChanges,
    ]).pipe(
      map(([institutions, darsInstitutionCode]) => {
        return institutions.hasOwnProperty(darsInstitutionCode)
          ? institutions[darsInstitutionCode].honorsOptions
          : [];
      }),
    );

    // prettier-ignore
    this.variableCreditCourses$ = (this.chosenAuditSettings.get('degreePlan') as FormControl).valueChanges.pipe(
      flatMap(plan => this.api.getAllCourses(plan.roadmapId)),
      map(courses => {
        return courses.filter(course => {
          return (
            !!course.creditMin &&
            !!course.creditMax &&
            course.creditMax > course.creditMin
          );
        });
      }),
      map(courses =>
        courses.map(course => ({
          ...course,
          range: inclusiveRange(
            course.creditMin as number,
            course.creditMax as number,
          ),
        })),
      ),
      share(),
    );

    this.variableCreditCourses$.subscribe(courses => {
      while (this.chosenCreditSettings.length !== 0) {
        this.chosenCreditSettings.removeAt(0);
      }

      courses.forEach(course => {
        this.chosenCreditSettings.push(
          this.fb.group({
            course,
            credits: this.fb.control(course.credits || '', Validators.required),
          }),
        );
      });
    });
  }

  public comparePlans(a: DegreePlan, b: DegreePlan): boolean {
    return a.roadmapId === b.roadmapId;
  }

  public darsInstitutionCode<T>(fallback: T): string | T {
    const control = this.chosenProgram.get('institution');
    if (control !== null) {
      return control.value.toString();
    } else {
      return fallback;
    }
  }

  public darsDegreeProgramCode<T>(fallback: T): string | T {
    const control = this.chosenProgram.get('planOrProgram');
    if (control !== null) {
      return control.value.toString();
    } else {
      return fallback;
    }
  }

  public roadmapId<T>(fallback: T): number | T {
    const control = this.chosenAuditSettings.get('degreePlan');
    if (control !== null) {
      return control.value.roadmapId;
    } else {
      return fallback;
    }
  }

  public darsHonorsOptionCode<T>(fallback: T): string | T {
    const control = this.chosenAuditSettings.get('honorsOptions');
    if (control !== null) {
      return control.value.toString();
    } else {
      return fallback;
    }
  }

  public includeCoursesFrom<T>(fallback: T): string | T {
    const control = this.chosenAuditSettings.get('includeCoursesFrom');
    if (control !== null) {
      return control.value.toString();
    } else {
      return fallback;
    }
  }

  public fixedCredits(): NewWhatIfAuditFields['fixedCredits'] {
    return (this.chosenCreditSettings.value as any[]).map(
      (group: { course: CourseBase; credits: number }) => {
        return {
          termCode: group.course.termCode || '',
          subjectCode: group.course.subjectCode || '',
          courseId: group.course.courseId || '',
          credits: group.credits,
        };
      },
    );
  }

  public submitAudit() {
    this.dialogRef.close({
      darsInstitutionCode: this.darsInstitutionCode(''),
      darsDegreeProgramCode: this.darsDegreeProgramCode(''),
      roadmapId: this.roadmapId(-1),
      darsHonorsOptionCode: this.darsHonorsOptionCode(''),
      includeCoursesFrom: this.includeCoursesFrom(''),
      fixedCredits: this.fixedCredits(),
    });
  }
}