import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  Input
} from "@angular/core";
import {
  AdvancedProperty,
  Event,
  EventUtils,
  InputControl,
  LocalStorageService,
  NotNull,
  PageContextHolder,
  PageObjectUtils,
  TaskParamsProvider,
  Visible,
  WebbpmStorage
} from "@webbpm/base-package";
import {TreeValuesCacheStrategy} from "../enum/TreeValuesCacheStrategy";
import {TreeItem, TreeviewItem} from "ngx-treeview";
import {
  DropdownTreeviewSelectI18n
} from "../external/ngx-treeview/dropdown-treeview-select/dropdown-treeview-select-i18n";
import {
  TreeItemDto
} from "../../../generated/ru/micord/ervu/account_applications/component/model/TreeItemDto";
import {
  TreeItemRpcService
} from "../../../generated/ru/micord/ervu/account_applications/component/rpc/TreeItemRpcService";
import {AuthorizationService} from "../../../modules/app/service/authorization.service";


@Component({
             moduleId: module.id,
             selector: 'dropdown-tree-view',
             templateUrl: './../../../../../src/resources/template/account_applications/component/field/DropdownTreeView.html',
             providers: [{
               provide: DropdownTreeviewSelectI18n, useClass: DropdownTreeviewSelectI18n
             }],
             changeDetection: ChangeDetectionStrategy.OnPush
           })
export class DropdownTreeViewComponent extends InputControl {
  @Input()
  @AdvancedProperty()
  public treeValuesCacheStrategy: TreeValuesCacheStrategy;
  @Visible("treeValuesCacheStrategy == TreeValuesCacheStrategy.BY_CUSTOM_NAME")
  @NotNull("treeValuesCacheStrategy == TreeValuesCacheStrategy.BY_CUSTOM_NAME")
  @AdvancedProperty()
  public treeValuesCustomName: string;
  @Visible("false")
  public cachedValue: any;
  public collapseLevel: number;
  public maxHeight: number;
  @Visible("false")
  public items: TreeviewItem[];
  @Visible("false")
  public value: TreeItemDto;
  @Visible("false")
  public valueChangeEvent: Event<TreeItemDto> = new Event<TreeItemDto>();
  @AdvancedProperty()
  public skipInitialSelection: boolean;
  @AdvancedProperty()
  public allowClear: boolean = true;
  @NotNull()
  public preferBusinessId: boolean = false;
  private rpcService: TreeItemRpcService;
  private localStorageService: LocalStorageService;
  private taskParamsProvider: TaskParamsProvider;
  private pageContextHolder: PageContextHolder;
  private webbpmStorage: WebbpmStorage;
  private storageKey: string;
  private rootValues: TreeItemDto[];

  constructor(el: ElementRef, cd: ChangeDetectorRef,
              private i18n: DropdownTreeviewSelectI18n) {
    super(el, cd);
  }

  public initialize() {
    super.initialize();
    this.rpcService = this.getScript(TreeItemRpcService);
    this.taskParamsProvider = this.injector.get(TaskParamsProvider);
    this.localStorageService = this.injector.get(LocalStorageService);
    this.pageContextHolder = this.injector.get(PageContextHolder);
    let authService = this.injector.get(AuthorizationService);
    if (authService.hasRole('security_administrator')) {
      this.skipInitialSelection = true;
    }
    this.webbpmStorage =
        this.getTreeValuesStorage(this.treeValuesCacheStrategy, this.treeValuesCustomName);
    this.cachedValue = this.getCachedValue();
    this.loadTreeItems();
  }

  private getTreeValuesStorage(treeValuesCacheStrategy: TreeValuesCacheStrategy,
                               customKeyName: string) {
    if (!treeValuesCacheStrategy) {
      return null;
    }
    switch (treeValuesCacheStrategy) {
      case TreeValuesCacheStrategy.BY_PAGE_OBJECT_ID:
        this.storageKey = PageObjectUtils.getPageObjectKey(this);
        return this.readWebbpmStorage(this.storageKey);
      case TreeValuesCacheStrategy.BY_OBJECT_NAME:
        this.storageKey = this.getObjectName();
        return this.readWebbpmStorage(this.storageKey);
      case TreeValuesCacheStrategy.BY_CUSTOM_NAME:
        this.storageKey = customKeyName;
        return this.readWebbpmStorage(this.storageKey);
      default:
        throw new Error("Unknown tree values storage type = " + treeValuesCacheStrategy)
    }
  }

  readWebbpmStorage(treeStorageKey: string) {
    if (this.pageContextHolder.isPageInBpmnContext()) {
      treeStorageKey = treeStorageKey + "$" + this.taskParamsProvider.processInstanceId
    }
    return this.localStorageService.readTemporalWebbpmStorage(treeStorageKey);
  }

  @Visible()
  public loadTreeItems(): void {
    this.rpcService.loadTreeData().then((res: TreeItemDto[]) => {
      this.populateTree(res);
    });
  }

  private populateTree(res: TreeItemDto[]) {
    this.items = res.map(value => new TreeviewItem(this.createTreeItem(value)));
    this.rootValues = res;
    const rootItem = this.items[0];
    if (this.cachedValue) {
      const matchedItem = this.findTreeItemByValue(this.items, this.cachedValue);
      if (matchedItem) {
        this.i18n.selectedItem = matchedItem;
        this.value = matchedItem.value;
      }
    }
    else if (!this.skipInitialSelection) {
      this.i18n.selectedItem = rootItem;
      this.value = rootItem.value;
    }
    this.doCollapseLevel();
    this.onValueChange(this.value);
  }

  private findTreeItemByValue(rootItems: TreeviewItem[], valueToFind: any): TreeviewItem | null {
    for (const item of rootItems) {
      if (JSON.stringify(item.value) === JSON.stringify(valueToFind)) {
        return item;
      }
      if (item.children) {
        const found = this.findTreeItemByValue(item.children, valueToFind);
        if (found) {
          return found;
        }
      }
    }
    return null;
  }

  private createTreeItem(treeItemDto: TreeItemDto): TreeItem {
    let treeItem: TreeItem;
    if (treeItemDto) {
      treeItem = {
        text: treeItemDto.label,
        value: treeItemDto,
        children: this.createTreeItemArray(treeItemDto.children)
      };
    }
    return treeItem;
  }

  private createTreeItemArray(treeItemDtoArray: TreeItemDto[]): TreeItem[] {
    if (treeItemDtoArray && treeItemDtoArray.length > 0) {
      return treeItemDtoArray.map(value => this.createTreeItem(value));
    }
    return undefined;
  }

  public onValueChange($event: any) {
    this.setCachedValue(this.value);
    this.valueChangeEvent.trigger($event);
    this.applyListener(this.changeListeners);
    this.cd.markForCheck();
  }

  @Visible()
  public getBusinessId(): any {
    return this.value ? this.value.businessId : this.value;
  }

  @Visible()
  public setCollapseLevel(level: number): void {
    this.collapseLevel = level;
  }

  private doCollapseLevel(): void {
    if (this.items != null && this.collapseLevel != null) {
      this.items.forEach((value) => this.checkCollapseLevelRecursive(value, 0));
    }
  }

  private checkCollapseLevelRecursive(viewItem: TreeviewItem, level: number): void {
    if (level != null && this.collapseLevel != null && level >= this.collapseLevel) {
      viewItem.setCollapsedRecursive(true);
    }
    else if (viewItem.children != null) {
      viewItem.children.forEach((value) => this.checkCollapseLevelRecursive(value, level + 1))
    }
  }

  protected setCachedValue(newValue: TreeItemDto): void {
    if (this.webbpmStorage) {
      this.webbpmStorage.put(this.storageKey, newValue);
    }
  }

  protected getCachedValue(): TreeItemDto {
    if (this.webbpmStorage) {
      return this.webbpmStorage.get(this.storageKey);
    }
    return null;
  }

  isValueEmpty(): boolean {
    return this.value == null;
  }

  hasValidModel(): boolean {
    return !this.isRequired() || !this.isValueEmpty();
  }

  getPresentationValue(): string | number | boolean {
    return this.value ? this.value.label : '';
  }

  @Visible()
  getValue(): any {
    return this.value ? this.value.id : this.value;
  }

  getValueAsModel(): TreeItemDto {
    return this.value ? this.value : null;
  }

  getValueForForm(): any {
    return this.preferBusinessId ? this.getBusinessId() : this.getValue();
  }

  @Visible()
  setValue(value: any): void {
    this.items = [...this.items];
    this.value = this.findValueInRootsById(value);
    this.onValueChange(this.value);
  }

  private findValueInRootsById(id: any): TreeItemDto {
    let searchArray: TreeItemDto[] = this.rootValues.slice();
    while (searchArray.length > 0) {
      const current = searchArray.shift();
      if (current.id == id) {
        return current;
      }
      if (current.children && current.children.length > 0) {
        searchArray.push(...current.children);
      }
    }
    return null;
  }

  onKeyDown(event: KeyboardEvent): void {
    const isInput = (event.target as HTMLElement).tagName === 'INPUT';
    if (EventUtils.isKeyBackspace(event) && !isInput && this.value && this.allowClear) {
      this.clearValue();
      event.stopPropagation();
      event.preventDefault();
    }
  }

  onChange() {
    super.onChange();
    this.valueChangeEvent.trigger(this.value);
  }

  subscribeToModelChange() {
    //empty because there is no ngModel here
  }

  unsubscribeToModelChange() {
    //empty because there is no ngModel here
  }
}
