/*
 * Copyright (C) 2023 Robert Bosch GmbH Copyright (C) 2023 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import {
  Component,
  ElementRef,
  EventEmitter,
  HostListener,
  Injector,
  Input,
  OnInit,
  Output,
  ViewChild
} from '@angular/core';
import {BciNumberPickerComponent} from "./bci-number-picker/bci-number-picker.component";
import {
  Duration,
  DurationPickerTypeStructure,
  DurationType,
  MaxNumberOfDurations
} from "./bci-duration-picker.model";
import {NgIf} from "@angular/common";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {MatExpansionModule} from "@angular/material/expansion";
import { BciSharedModule } from "../../shared";
import {
  autoFillDuration,
  fitDurationToMaxValues,
  matchDurationWithDurationType,
  parseDurationObjectToString,
  parseDurationStringToObject
} from "./bci-duration-picker.helper";
import {BciDropdownPositionSetterDirective} from "../bci-dropdown-place-setter/bci-dropdown-position-setter.directive";
import {BciDirtyFlagDirective} from "../bci-dirty-flag/bci-dirty-flag.directive";
import {BciTouchedFlagDirective} from "../bci-touched-flag/bci-touched-flag.directive";
import { BCI_DURATION_PICKER_INTL_PROVIDER, BciDurationPickerIntl } from "./bci-duration-picker.intl";

const DEFAULT_TYPE = 'ddd.HH:MM:SS';

const DEFAULT_DURATION: Duration = {
  days: 0,
  hours: 0,
  minutes: 0,
  seconds: 0
}

const PATTERN = /^[0-9.:\s]+$/;

/**
 * Duration Picker it's a WebCore standalone component made for setting up a specific number of
 * days, hours, minutes and seconds in different duration types based on a strict list.
 * This component has many ways to be used: keyboard navigation and setting, mouse
 * hover&scroll, direct input into the input field or by clicking the arrow up and down from flyout.
 */
@Component({
  standalone: true,
  selector: 'bci-duration-picker',
  templateUrl: './bci-duration-picker.component.html',
  styleUrls: ['./bci-duration-picker.component.scss'],
  imports: [
    BciNumberPickerComponent,
    NgIf,
    MatIconModule,
    MatButtonModule,
    MatFormFieldModule,
    MatInputModule,
    MatExpansionModule,
    BciSharedModule,
    BciDropdownPositionSetterDirective,
    BciDirtyFlagDirective,
    BciTouchedFlagDirective,
    MatFormFieldModule
  ],
  providers: [BCI_DURATION_PICKER_INTL_PROVIDER]
})

export class BciDurationPickerComponent implements OnInit{
  /**
   * Defines the duration. Factory set to be 0.
   */
  @Input() duration: Duration | string = DEFAULT_DURATION;

  /**
   * Defines max number of each picker.
   */
  @Input() maxNumberOfDurations: MaxNumberOfDurations = {
    maxDays: undefined,
    maxHours: 23,
    maxMinutes: 59,
    maxSeconds: 59
  };

  /**
   * Defines the type of duration picker based on DurationType enum. Factory set to be 'ddd.HHH:MM:SS'.
   */
  @Input() type: string = DEFAULT_TYPE;

  /**
   * Defines if the duration can be changed or not.
   */
  @Input() disabled = false;

  /**
   * Defines if the duration is a mandatory field or not.
   */
  @Input() required = false;

  /**
   * Defines if the duration is in readonly mode.
   */
  @Input() readonly = false;

  /**
   * Propagates the selected duration.
   */
  @Output() selectedDuration: EventEmitter<Duration | string> = new EventEmitter<Duration | string>();

  @ViewChild('durationPickerContainer') durationPickerContainer: ElementRef;
  @HostListener('document:click', ['$event']) onDocumentClick(event: MouseEvent): void {
    if (!this.durationPickerContainer.nativeElement.contains(event.target)) {
      if(this.isPanelOpen) {
        this.closePanel();
      }
    }
  }

  get isPanelOpen(): boolean {
    return this.panelOpen;
  }

  set isPanelOpen(status: boolean) {
    if(this.panelOpen !== status) {
      this.panelOpen = status;
      if(this.panelOpen === false) {
        this.setBindDuration();
      }
    }
  }

  panelOpen = false;
  inputDirty = true; // because we always have duration value except on reset event
  inputTouched = false;
  inputNotMatching = false;
  onlyOnePicker = false;
  typeStructure: DurationPickerTypeStructure = {
    days: true,
    hours: true,
    minutes: true,
    seconds: true
  };

  translations: BciDurationPickerIntl;
  encapsulatedDuration: Duration;
  durationType: DurationType;
  bindDuration: string;
  typeLabel: string;

  constructor(private injector: Injector){
    this.translations = this.injector.get(BciDurationPickerIntl);
  }

  ngOnInit(): void {
    if(Object.values(DurationType).includes(this.type)) {
      this.durationType = DurationType[this.type];
      this.typeLabel = this.type;
    } else {
      this.durationType = DurationType[DEFAULT_TYPE];
      this.typeLabel = DEFAULT_TYPE;
    }

    if(this.duration === null || !matchDurationWithDurationType(this.duration, this.durationType)) {
      this.durationType = DurationType[DEFAULT_TYPE];
      this.duration = DEFAULT_DURATION;
      this.typeLabel = DEFAULT_TYPE;
    }

    this.handleTypeStructure();

    if(typeof this.duration === 'string') {
      this.bindDuration = fitDurationToMaxValues(this.duration, this.maxNumberOfDurations, this.durationType);
      this.encapsulatedDuration = parseDurationStringToObject(this.bindDuration, this.durationType);
    } else if(typeof this.duration === 'object') {
      const filteredDuration = this.filterObjectDurationNullProperties(this.duration);
      const durationString = parseDurationObjectToString(filteredDuration);
      this.bindDuration = fitDurationToMaxValues(durationString, this.maxNumberOfDurations, this.durationType);
      this.encapsulatedDuration = filteredDuration;
    }
  }

  openPanel(event?: Event): void {
    if(this.encapsulatedDuration !== null && this.encapsulatedDuration !== undefined) {
      this.inputDirty = true;
    }

    if(!this.disabled && !this.readonly) {
      if(event){
        if(event.type === 'select') {
          // if user selects/highlight the value from the input
          this.inputTouched = true;
        }
        // prevent the double calling the setter method for isPanelOpen
        event.stopPropagation();
      }
      this.inputNotMatching= false;
      this.isPanelOpen = true;
    }
  }

  closePanel(): void {
    if(this.inputDirty) {
      this.handleDuration();
    }
    this.isPanelOpen = false;
    this.inputTouched = false;
    this.emitDuration();
  }

  resetDuration(event: Event): void {
    this.encapsulatedDuration = {
      days: this.resetNumberIf(this.encapsulatedDuration.days),
      hours: this.resetNumberIf(this.encapsulatedDuration.hours),
      minutes: this.resetNumberIf(this.encapsulatedDuration.minutes),
      seconds: this.resetNumberIf(this.encapsulatedDuration.seconds),
    }
    // prevent the double calling the setter method for isPanelOpen
    event.stopPropagation();
    this.bindDuration = null;
    this.inputDirty = false;
  }

  onTouched(event: boolean): void {
    this.inputTouched = event;
  }

  onDirtyInput(event: boolean): void {
    this.inputDirty = event;
  }

  takePickerValue(event: number, type: 'days'| 'hours' | 'minutes' | 'seconds'): void {
    // condition to avoid entering letters, for example 'e' (Euler's number)
    if (PATTERN.test(event.toString())) {
      switch (type) {
        case 'days': {
          if(event !== this.encapsulatedDuration.days) {
            this.encapsulatedDuration = {...this.encapsulatedDuration, days: event};

          }
          break;
        }
        case 'hours': {
          if(event !== this.encapsulatedDuration.hours) {
            this.encapsulatedDuration = {...this.encapsulatedDuration, hours: event};

          }
          break;
        }
        case 'minutes': {
          if(event !== this.encapsulatedDuration.minutes) {
            this.encapsulatedDuration = {...this.encapsulatedDuration, minutes: event};

          }
          break;
        }
        case 'seconds': {
          if(event !== this.encapsulatedDuration.seconds) {
            this.encapsulatedDuration = {...this.encapsulatedDuration, seconds: event};

          }
          break;
        }
      }
    }
    this.setBindDuration();
  }

  // NICE TO HAVE: this method can be replaced with ngx-mask library that's using a mask applied to the input
  private handleDuration(): void {
    if(PATTERN.test(this.bindDuration)){
      if(matchDurationWithDurationType(this.bindDuration, DurationType.ddd)) {
        this.encapsulatedDuration = autoFillDuration(Number(this.bindDuration), this.durationType);

        return;
      }

      if(matchDurationWithDurationType(this.bindDuration, this.durationType)){
        const fitToMaxNbOfDuration: string = fitDurationToMaxValues(this.bindDuration, this.maxNumberOfDurations, this.durationType);
        this.encapsulatedDuration = parseDurationStringToObject(fitToMaxNbOfDuration as string, this.durationType);

      } else {
        this.showNotificationError();
      }
    } else {
      this.showNotificationError();
    }
  }

  private showNotificationError(): void {
    // undo DurationPicker to the previous value
    this.setBindDuration();
    this.inputNotMatching= true;
  }

  private setBindDuration(): void {
    this.bindDuration = parseDurationObjectToString(this.encapsulatedDuration);
  }

  private resetNumberIf(condition: number | undefined): number {
    if(condition || condition === 0) return 0;
  }

  private emitDuration(): void {
    if(typeof this.duration === 'string') {
      this.selectedDuration.emit(parseDurationObjectToString(this.encapsulatedDuration));
    } else {
      this.selectedDuration.emit(this.encapsulatedDuration);
    }
  }

  private setTypeStructure(days: boolean, hours: boolean, minutes:  boolean, seconds: boolean): void {
    this.typeStructure = { days, hours, minutes, seconds };
  }

  private setMaxNbOfDurations(maxDays: number | undefined, maxHours: number | undefined, maxMinutes: number | undefined, maxSeconds: number | undefined, ): void {
    this.maxNumberOfDurations = { maxDays, maxHours, maxMinutes, maxSeconds };
  }

  private handleTypeStructure(): void {
    switch (this.durationType) {
      case DurationType["ddd.HH:MM:SS"]: {
        this.setTypeStructure(true, true, true, true);
        this.setMaxNbOfDurations(undefined, this.maxNumberOfDurations.maxHours, this.maxNumberOfDurations.maxMinutes, this.maxNumberOfDurations.maxSeconds);
        break;
      }
      case DurationType["ddd.HH:MM"]: {
        this.setTypeStructure(true, true, true, false);
        this.setMaxNbOfDurations(undefined, 23, 59, 59);
        break;
      }
      case DurationType["HH:MM:SS"]: {
        this.setTypeStructure(false, true, true, true);
        this.setMaxNbOfDurations(undefined, undefined, 59, 59);
        break;
      }
      case DurationType["ddd.HH"]: {
        this.setTypeStructure(true, true, false, false);
        this.setMaxNbOfDurations(undefined, 23, undefined, undefined);
        break;
      }
      case DurationType["HH:MM"]: {
        this.setTypeStructure(false, true, true, false);
        this.setMaxNbOfDurations(undefined, undefined, 59, undefined);
        break;
      }
      case DurationType["MM:SS"]: {
        this.setTypeStructure(false, false, true, true);
        this.setMaxNbOfDurations(undefined, undefined, undefined, 59);
        break;
      }
      case DurationType["ddd"]: {
        this.setTypeStructure(true, false, false, false);
        this.setMaxNbOfDurations(undefined, 23, 59, 59);
        this.onlyOnePicker = true;
        break;
      }
      case DurationType["HH"]: {
        this.setTypeStructure(false, true, false, false);
        this.setMaxNbOfDurations(undefined, undefined, 59, 59);
        this.onlyOnePicker = true;
        break;
      }
      case DurationType["MM"]: {
        this.setTypeStructure(false, false, true, false);
        this.setMaxNbOfDurations(undefined, undefined, undefined, 59);
        this.onlyOnePicker = true;
        break;
      }
      case DurationType["SS"]: {
        this.setTypeStructure(false, false, false, true);
        this.setMaxNbOfDurations(undefined, undefined, undefined, undefined);
        this.onlyOnePicker = true;
        break;
      }
      default: {
        this.setTypeStructure(true, true, true, true);
        this.setMaxNbOfDurations(undefined, 23, 59, 59);
      }
    }
  }

  private filterObjectDurationNullProperties(duration: Duration): Duration {
    const newDuration: Duration = {};

    Object.keys(duration).forEach((prop: string): void => {
      newDuration[prop] = duration[prop] === null ? 0 : duration[prop];
    });

    return newDuration;
  }
}
