import {
  AfterViewInit, Component, OnDestroy, OnInit, ViewChild
} from '@angular/core';
import { ElementComponent } from 'common/directives/element-component.directive';
import { ButtonElement, ButtonEvent, UnitNavParam } from 'common/models/elements/button/button';
import { MarkingPanelElement } from 'common/models/elements/text/marking-panel';
import { TriggerActionEvent, TriggerElement } from 'common/models/elements/trigger/trigger';
import { ImageElement } from 'common/models/elements/media-elements/image';
import { UIElement } from 'common/models/elements/element';
import { VeronaPostService } from 'player/modules/verona/services/verona-post.service';
import { AnchorService } from 'player/src/app/services/anchor.service';
import { StateVariableStateService } from 'player/src/app/services/state-variable-state.service';
import { MathTableCell, MathTableElement, MathTableRow } from 'common/models/elements/input-elements/math-table';
import { KeypadService } from 'player/src/app/services/keypad.service';
import { KeyboardService } from 'player/src/app/services/keyboard.service';
import { DeviceService } from 'player/src/app/services/device.service';
import { Subject, Subscription } from 'rxjs';
import { MathTableComponent } from 'common/components/input-elements/math-table.component';
import { MarkingPanelService } from 'player/src/app/services/marking-panel.service';
import { takeUntil } from 'rxjs/operators';
import { ValueChangeElement } from 'common/interfaces';
import { UnitStateService } from '../../../services/unit-state.service';
import { ElementGroupDirective } from '../../../directives/element-group.directive';
import { ElementModelElementCodeMappingService } from '../../../services/element-model-element-code-mapping.service';
import { NavigationService } from '../../../services/navigation.service';

@Component({
  selector: 'aspect-interactive-group-element',
  templateUrl: './interactive-group-element.component.html',
  styleUrls: ['./interactive-group-element.component.scss']
})
export class InteractiveGroupElementComponent
  extends ElementGroupDirective implements OnInit, AfterViewInit, OnDestroy {
  @ViewChild('elementComponent') elementComponent!: ElementComponent;
  MarkingPanelElement!: MarkingPanelElement;
  ButtonElement!: ButtonElement;
  ImageElement!: ImageElement;
  MathTableElement!: MathTableElement;
  TriggerElement!: TriggerElement;

  tableModel: MathTableRow[] = [];

  isKeypadOpen: boolean = false;
  keypadEnterKeySubscription!: Subscription;
  keypadDeleteCharactersSubscription!: Subscription;
  keypadSelectSubscription!: Subscription;

  keyboardEnterKeySubscription!: Subscription;
  keyboardDeleteCharactersSubscription!: Subscription;

  selectedColor: string | undefined;
  hasDeleteButton: boolean = false;
  private ngUnsubscribe: Subject<void> = new Subject();

  constructor(
    public unitStateService: UnitStateService,
    public veronaPostService: VeronaPostService,
    public navigationService: NavigationService,
    private elementModelElementCodeMappingService: ElementModelElementCodeMappingService,
    private anchorService: AnchorService,
    private stateVariableStateService: StateVariableStateService,
    public keypadService: KeypadService,
    private keyboardService: KeyboardService,
    private deviceService: DeviceService,
    public markingPanelService: MarkingPanelService
  ) {
    super();
    this.subscribeToMarkingColorChanged();
  }

  ngOnInit(): void {
    if (this.elementModel.type === 'math-table') {
      this.tableModel = this.elementModelElementCodeMappingService.mapToElementModelValue(
        this.unitStateService.getElementCodeById(this.elementModel.id)?.value, this.elementModel
      ) as MathTableRow[];
    }
    if (this.elementModel.type === 'marking-panel') {
      this.navigationService.currentPageIndexChanged
        .pipe(takeUntil(this.ngUnsubscribe))
        .subscribe(() => {
          this.markingPanelService.broadcastMarkingData({
            id: this.elementModel.id,
            markingData: {
              active: false,
              mode: 'mark',
              color: '',
              colorName: ''
            }
          });
        });
    }
  }

  ngAfterViewInit(): void {
    let initialValue;
    switch (this.elementModel.type) {
      case 'image':
        initialValue = ElementModelElementCodeMappingService.mapToElementCodeValue(
          (this.elementModel as ImageElement).magnifierUsed, this.elementModel.type);
        break;
      case 'math-table':
        initialValue = ElementModelElementCodeMappingService.mapToElementCodeValue(
          [], this.elementModel.type);
        break;
      default:
        initialValue = null;
    }
    this.registerAtUnitStateService(
      this.elementModel.id,
      this.elementModel.alias,
      initialValue,
      this.elementComponent,
      this.pageIndex);
  }

  private subscribeToMarkingColorChanged() {
    this.markingPanelService.markingColorChanged
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(markingColor => {
        if (markingColor.markingPanels.includes(this.elementModel.id)) {
          this.selectedColor = markingColor.color;
          this.hasDeleteButton = (markingColor.markingMode !== 'word');
        }
      });
  }

  applyButtonAction(buttonEvent: ButtonEvent): void {
    switch (buttonEvent.action) {
      case 'unitNav':
        this.veronaPostService.sendVopUnitNavigationRequestedNotification(
          (buttonEvent.param as UnitNavParam)
        );
        break;
      case 'pageNav':
        this.navigationService.setPage(buttonEvent.param as number);
        break;
      case 'highlightText':
        this.anchorService.toggleAnchor(buttonEvent.param as string);
        break;
      default:
        this.applyTriggerAction(buttonEvent as TriggerActionEvent);
    }
  }

  applyTriggerAction(triggerActionEvent: TriggerActionEvent): void {
    switch (triggerActionEvent.action) {
      case 'highlightText':
        this.anchorService.showAnchor(triggerActionEvent.param as string);
        break;
      case 'removeHighlights':
        this.anchorService.hideAllAnchors();
        break;
      case 'stateVariableChange':
        this.stateVariableStateService.changeElementCodeValue(
          triggerActionEvent.param as { id: string, value: string }
        );
        break;
      default:
    }
  }

  changeElementCodeValue(value: ValueChangeElement): void {
    this.unitStateService.changeElementCodeValue({
      id: value.id,
      value: ElementModelElementCodeMappingService
        .mapToElementCodeValue(value.value, this.elementModel.type)
    });
  }

  async toggleKeyInput(
    focusedTextInput: {
      inputElement: HTMLElement;
      row: MathTableRow;
      cell: MathTableCell;
      focused: boolean
    }
  ): Promise<void> {
    const promises: Promise<boolean>[] = [];
    if (this.elementModel.showSoftwareKeyboard && !this.elementModel.readOnly) {
      promises.push(this.keyboardService
        .toggleAsync(focusedTextInput,
          this.elementComponent as MathTableComponent,
          this.deviceService.isMobileWithoutHardwareKeyboard));
    }
    if (this.shallOpenKeypad(this.elementModel)) {
      promises.push(this.keypadService
        .toggleAsync(focusedTextInput,
          this.elementComponent as MathTableComponent));
    }
    if (promises.length) {
      await Promise.all(promises).then(() => {
        if (this.keyboardService.isOpen) {
          this.subscribeForKeyboardEvents(focusedTextInput.row, focusedTextInput.cell);
        } else {
          this.unsubscribeFromKeyboardEvents();
        }
        if (this.keypadService.isOpen) {
          this.subscribeForKeypadEvents(focusedTextInput.row, focusedTextInput.cell);
        } else {
          this.unsubscribeFromKeypadEvents();
        }
        this.isKeypadOpen = this.keypadService.isOpen;
      });
    }
  }

  private shallOpenKeypad(elementModel: UIElement): boolean {
    return !!elementModel.inputAssistancePreset &&
      !(elementModel.showSoftwareKeyboard &&
        elementModel.addInputAssistanceToKeyboard &&
        this.deviceService.isMobileWithoutHardwareKeyboard);
  }

  private subscribeForKeypadEvents(
    row: MathTableRow,
    cell: MathTableCell
  ): void {
    this.keypadEnterKeySubscription = this.keypadService.enterKey
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(key => this.enterKey(key, row, cell));
    this.keypadDeleteCharactersSubscription = this.keypadService.deleteCharacters
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(() => this.enterKey('Delete', row, cell));
    this.keypadSelectSubscription = this.keypadService.select
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(key => this.enterKey(key, row, cell));
  }

  private unsubscribeFromKeypadEvents(): void {
    if (this.keypadEnterKeySubscription) this.keypadEnterKeySubscription.unsubscribe();
    if (this.keypadDeleteCharactersSubscription) this.keypadDeleteCharactersSubscription.unsubscribe();
    if (this.keypadSelectSubscription) this.keypadSelectSubscription.unsubscribe();
  }

  private subscribeForKeyboardEvents(
    row: MathTableRow,
    cell: MathTableCell
  ): void {
    this.keyboardEnterKeySubscription = this.keyboardService.enterKey
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(key => this.enterKey(key, row, cell));
    this.keyboardDeleteCharactersSubscription = this.keyboardService.deleteCharacters
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(() => this.enterKey('Delete', row, cell));
  }

  private unsubscribeFromKeyboardEvents(): void {
    if (this.keyboardEnterKeySubscription) this.keyboardEnterKeySubscription.unsubscribe();
    if (this.keyboardDeleteCharactersSubscription) this.keyboardDeleteCharactersSubscription.unsubscribe();
  }

  private enterKey(
    key: string,
    row: MathTableRow,
    cell: MathTableCell
  ): void {
    const char = key === 'ยท' ? '*' : key;
    (this.elementComponent as MathTableComponent).setCellValue(char, cell, row);
  }

  detectHardwareKeyboard(): void {
    if (this.elementModel.showSoftwareKeyboard) {
      this.deviceService.hasHardwareKeyboard = true;
      this.keyboardService.close();
    }
  }

  ngOnDestroy(): void {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
    this.unsubscribeFromKeypadEvents();
    this.unsubscribeFromKeyboardEvents();
  }
}