import { ChangeDetectorRef, Directive, Input, OnDestroy, OnInit } from '@angular/core';

import { Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { uniq } from 'lodash';

import { SectionsService } from './sections.service';
import { hasValue, isNotEmpty, isNotNull } from '../../shared/empty.util';
import { SubmissionSectionError, SubmissionSectionObject } from '../objects/submission-objects.reducer';
import parseSectionErrorPaths, { SectionErrorPath } from '../utils/parseSectionErrorPaths';
import { SubmissionService } from '../submission.service';

/**
 * Directive for handling generic section functionality
 */
@Directive({
  selector: '[dsSection]',
  exportAs: 'sectionRef'
})
export class SectionsDirective implements OnDestroy, OnInit {

  /**
   * A boolean representing if section is mandatory
   * @type {boolean}
   */
  @Input() mandatory = true;

  /**
   * The section id
   * @type {string}
   */
  @Input() sectionId: string;

  /**
   * The submission id
   * @type {string}
   */
  @Input() submissionId: string;

  /**
   * The list of generic errors related to the section
   * @type {Array}
   */
  public genericSectionErrors: string[] = [];

  /**
   * The list of all errors related to the element belonging to this section
   * @type {Array}
   */
  public allSectionErrors: string[] = [];

  /**
   * A boolean representing if section is active
   * @type {boolean}
   */
  private active = true;

  /**
   * A boolean representing if section is enabled
   * @type {boolean}
   */
  private enabled: Observable<boolean>;

  /**
   * A boolean representing the panel collapsible state: opened (true) or closed (false)
   * @type {boolean}
   */
  private sectionState = this.mandatory;

  /**
   * Array to track all subscriptions and unsubscribe them onDestroy
   * @type {Array}
   */
  private subs: Subscription[] = [];

  /**
   * A boolean representing if section is valid
   * @type {boolean}
   */
  private valid: Observable<boolean>;

  /**
   * Initialize instance variables
   *
   * @param {ChangeDetectorRef} changeDetectorRef
   * @param {SubmissionService} submissionService
   * @param {SectionsService} sectionService
   */
  constructor(private changeDetectorRef: ChangeDetectorRef,
              private submissionService: SubmissionService,
              private sectionService: SectionsService) {
  }

  /**
   * Initialize instance variables
   */
  ngOnInit() {
    this.valid = this.sectionService.isSectionValid(this.submissionId, this.sectionId).pipe(
      map((valid: boolean) => {
        if (valid) {
          this.resetErrors();
        }
        return valid;
      }));

    this.subs.push(
      this.sectionService.getSectionState(this.submissionId, this.sectionId).pipe(
        map((state: SubmissionSectionObject) => state.errors))
        .subscribe((errors: SubmissionSectionError[]) => {
          if (isNotEmpty(errors)) {
            errors.forEach((errorItem: SubmissionSectionError) => {
              const parsedErrors: SectionErrorPath[] = parseSectionErrorPaths(errorItem.path);

              parsedErrors.forEach((error: SectionErrorPath) => {
                if (!error.fieldId) {
                  this.genericSectionErrors = uniq(this.genericSectionErrors.concat(errorItem.message));
                } else {
                  this.allSectionErrors.push(errorItem.message);
                }
              });
            });
          } else {
            this.resetErrors();
          }
        }),
      this.submissionService.getActiveSectionId(this.submissionId)
        .subscribe((activeSectionId) => {
          const previousActive = this.active;
          this.active = (activeSectionId === this.sectionId);
          if (previousActive !== this.active) {
            this.changeDetectorRef.detectChanges();
            // If section is no longer active dispatch save action
            if (!this.active && isNotNull(activeSectionId)) {
              this.submissionService.dispatchSave(this.submissionId);
            }
          }
        })
    );

    this.enabled = this.sectionService.isSectionEnabled(this.submissionId, this.sectionId);
  }

  /**
   * Unsubscribe from all subscriptions
   */
  ngOnDestroy() {
    this.subs
      .filter((subscription) => hasValue(subscription))
      .forEach((subscription) => subscription.unsubscribe());
  }

  /**
   * Change section state
   *
   * @param event
   *    the event emitted
   */
  public sectionChange(event) {
    this.sectionState = event.nextState;
  }

  /**
   * Check if section panel is open
   *
   * @returns {boolean}
   *    Returns true when section panel is open
   */
  public isOpen(): boolean {
    return this.sectionState;
  }

  /**
   * Check if section is mandatory
   *
   * @returns {boolean}
   *    Returns true when section is mandatory
   */
  public isMandatory(): boolean {
    return this.mandatory;
  }

  /**
   * Check if section panel is active
   *
   * @returns {boolean}
   *    Returns true when section panel is active
   */
  public isSectionActive(): boolean {
    return this.active;
  }

  /**
   * Check if section is enabled
   *
   * @returns {Observable<boolean>}
   *    Emits true whenever section is enabled
   */
  public isEnabled(): Observable<boolean> {
    return this.enabled;
  }

  /**
   * Check if section is valid
   *
   * @returns {Observable<boolean>}
   *    Emits true whenever section is valid
   */
  public isValid(): Observable<boolean> {
    return this.valid;
  }

  /**
   * Remove section panel from submission form
   *
   * @param submissionId
   *    the submission id
   * @param sectionId
   *    the section id
   * @returns {Observable<boolean>}
   *    Emits true whenever section is valid
   */
  public removeSection(submissionId: string, sectionId: string) {
    this.sectionService.removeSection(submissionId, sectionId)
  }

  /**
   * Check if section has only generic errors
   *
   * @returns {boolean}
   *    Returns true when section has only generic errors
   */
  public hasGenericErrors(): boolean {
    return this.genericSectionErrors && this.genericSectionErrors.length > 0
  }

  /**
   * Check if section has errors
   *
   * @returns {boolean}
   *    Returns true when section has errors
   */
  public hasErrors(): boolean {
    return (this.genericSectionErrors && this.genericSectionErrors.length > 0) ||
      (this.allSectionErrors && this.allSectionErrors.length > 0)
  }

  /**
   * Return section errors
   *
   * @returns {Array}
   *    Returns section errors list
   */
  public getErrors(): string[] {
    return this.genericSectionErrors;
  }

  /**
   * Set form focus to this section panel
   *
   * @param event
   *    The event emitted
   */
  public setFocus(event): void {
    if (!this.active) {
      this.submissionService.setActiveSection(this.submissionId, this.sectionId);
    }
  }

  /**
   * Remove error from list
   *
   * @param index
   *    The error array key
   */
  public removeError(index): void {
    this.genericSectionErrors.splice(index);
  }

  /**
   * Remove all errors from list
   */
  public resetErrors() {
    if (isNotEmpty(this.genericSectionErrors)) {
      this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionId);
    }
    this.genericSectionErrors = [];
    this.allSectionErrors = [];

  }
}
