import { App } from '../../gogocarto';
import { Category } from '../../classes/classes'
declare let $: any;

export enum CategoryOptionTreeNodeType {
  Option,
  Category,
}

export enum NodeState {
  Checked,
  PartiallyChecked,
  Disabled
}

/**
 * Class representating a Node in the Directory Menu Tree
 *
 * A CategoryOptionTreeNode can be a Category or an Option
 */
export class CategoryOptionTreeNode {
  id: any;
  customId: any;
  name: string;
  nameShort: string;

  displayInMenu: boolean;
  displayInInfoBar: boolean;

  enableDescription: boolean;
  descriptionLabel: string;
  showExpanded: boolean;
  unexpandable: boolean;

  children: CategoryOptionTreeNode[] = [];
  state: NodeState = NodeState.Checked

  isPristine: boolean = false
  isHidden: boolean = false // we can load the map hiding some categories with ?fullTaxonomy=0

  ownerId: number = null;
  // main option Id, or "all"
  mainOwnerId: any = null;

  isMainOption = false;

  constructor(
    private TYPE: CategoryOptionTreeNodeType,
    private DOM_ID: string,
    private DOM_CHECKBOX_ID: string,
    private DOM_CHILDREN_CLASS: string
  ) {}

  getDom() {
    return $(this.DOM_ID + this.id);
  }

  getDomCheckbox() {
    return $(this.DOM_CHECKBOX_ID + this.id);
  }

  getDomChildren() {
    return this.getDom().next(this.DOM_CHILDREN_CLASS);
  }

  getOwner(): CategoryOptionTreeNode {
    if (this.isOption()) return App.taxonomyModule.getCategoryById(this.ownerId);
    else return App.taxonomyModule.getOptionById(this.ownerId);
  }

  getMainOwner() {
    return ['RootFakeOption', 'all'].indexOf(this.mainOwnerId) > -1
      ? this
      : App.taxonomyModule.getOptionById(this.mainOwnerId);
  }
  getMainCategoryOwner(): CategoryOptionTreeNode {
    return this.getMainOwner().getOwner();
  }

  protected disabledChildren(): CategoryOptionTreeNode[] {
    return this.children.filter((child) => child.isDisabled);
  }
  protected nonDisabledChildren(): CategoryOptionTreeNode[] {
    return this.children.filter((child) => child.state != NodeState.Disabled);
  }
  protected activeChildren(): CategoryOptionTreeNode[] {
    return this.children.filter((child) => child.isActive);
  }
  protected checkedChildren(): CategoryOptionTreeNode[] {
    return this.children.filter((child) => child.isChecked);
  }

  isOption() {
    return this.TYPE == CategoryOptionTreeNodeType.Option;
  }

  setState(state: NodeState) {
    if (this.isHidden && state != NodeState.Disabled) return

    this.state = state
    let displayAsChecked = this.isOption() ? this.isChecked && !this.isPristine : this.isChecked;
    this.getDom().toggleClass('checked',  displayAsChecked);
    this.getDomCheckbox().prop('checked', displayAsChecked);
    this.getDom().toggleClass('disabled', this.isDisabled);
  }

  get isChecked() : boolean {
    return this.state == NodeState.Checked
  }
  get isDisabled() : boolean {
    return this.state == NodeState.Disabled
  }
  get isActive() : boolean {
    return this.isChecked
           || !this.isDisabled && this.isMainOption && App.config.menu.showOnePanePerMainOption
           || !this.isDisabled && this.children.length == 0
           || !this.isDisabled && this.children.length > 0 && this.children.every(child => child.isActive)
  }

  toggle(value: boolean = null, humanAction = true): void {
    let wasInPristine = this.isPristine
    this.isPristine = false

    let check = !this.isChecked;
    if (value !== null) {
      check = value;
    }
    // console.log("toggle " + this.name, check)
    let humanClickedOnPristineOption = this.isOption() && wasInPristine && humanAction
    
    if (humanClickedOnPristineOption) {
      // console.log("click on pristine", this.recursivelyGetPristine(this).map(o => o.name))
      this.recursivelyGetPristine(this).forEach((node) => {
        node.toggle(false, false);
      });
      this.recursivelySetPristine()
      // Force this option to be checked cause recursivelySetPristine affect also current option
      this.isPristine = false
      this.setState(NodeState.Checked)
      check = true
    } else {
      this.setState(check ? NodeState.Checked : NodeState.Disabled)
    }      

    // Toggle children
    if (humanAction && !this.isOption() && check) {
      this.recursivelySetPristine()
    }
    else if (!humanClickedOnPristineOption && !(this.isMainOption && App.config.menu.showOnePanePerMainOption)) {
      for (const child of this.children) {
        child.toggle(check, false);
      }
    } 

    if (humanAction) {
      if (this.getOwner()) {       
        this.getOwner().updateState();
      }      

      // delay the update so it's not freezing the UI
      setTimeout(() => {
        App.elementsModule.updateElementsToDisplay(true, true);
        App.historyModule.updateCurrState();
      }, 200);
    }
  }

  toggleVisibility(value: boolean, recursive = false) {
    // console.log("toggle visibility " + this.name, `mainOption ${this.isMainOption}`, value);
    if (value) {
      this.getDom().show();
      this.isHidden = false
      this.setState(NodeState.Checked)      
    } else {
      this.getDom().hide();
      this.isHidden = true
      this.setState(NodeState.Disabled)      
    }

    if (this.isMainOption) {
      $('#main-option-gogo-icon-' + this.id).toggle(value);
    }

    // if (value && this.getOwner()) this.getOwner().toggleVisibility(true);

    if (recursive === true) {
      for (const child of this.children) child.toggleVisibility(value, true);
    }
  }

  recursivelySetPristine() {
    this.isPristine = true
    this.setState(NodeState.Checked)

    for (const child of this.children) {
      child.recursivelySetPristine()
    }
  }

  updateState(propage = true, humanAction = true) {
    if (this.isOption()) {
      if (this.children.length > 0 && !(this.isMainOption && App.config.menu.showOnePanePerMainOption)) {
        if (this.children.every(child => child.isChecked)) {
          this.setState(NodeState.Checked)
        } else if (this.children.every(child => child.isDisabled)) {
          this.setState(NodeState.Disabled)
        } else {
          this.setState(NodeState.PartiallyChecked)
        }
      } else {
        // Init first state so DOM is updated
        this.setState(this.isChecked ? NodeState.Checked : NodeState.Disabled)
      }

      // If one pane per mainOption, we do not propage to the RootCategory
      if (this.getOwner() && (<Category>this.getOwner()).isRootCategory 
          && App.config.menu.showOnePanePerMainOption && App.currMainId != 'all') 
        propage = false
    } else {
      if (this.children.every(child => child.isChecked || child.isHidden)) {
        // If all options are checked, we restore the pristine state
        this.recursivelySetPristine()
      } else if (this.nonDisabledChildren().length == 0) {
        // If all options are disabled, we restore the pristine state
        // humanAction : when loading filters from URL we don't want to restore all category with only disbaled options
        humanAction ? this.recursivelySetPristine() : this.setState(NodeState.Disabled)
      } else {
        this.setState(NodeState.PartiallyChecked)
      }
    }
    
    if (this.getOwner() && propage) this.getOwner().updateState(propage, humanAction);
  }

  recursivelyUpdateStates(humanAction = true) {
    for (const child of this.children) {
      child.recursivelyUpdateStates(humanAction);
    }
    this.updateState(false, humanAction);
  }

  isExpanded(): boolean {
    return this.getDom().hasClass('expanded');
  }
  isUnexpandable(): boolean {
    return this.getDom().hasClass('unexpandable');
  }

  toggleChildrenDetail(forceExpand: null | boolean = null): void {
    if (this.isUnexpandable()) {
      return;
    }

    if (false === forceExpand || (null === forceExpand && this.isExpanded())) {
      this.getDomChildren()
        .stop(true, false)
        .slideUp({
          duration: 350,
          easing: 'easeOutQuart',
          queue: false,
          complete: function () {
            $(this).css('height', '');
          },
        });
      this.getDom().removeClass('expanded');
      return;
    }

    this.getDomChildren()
      .stop(true, false)
      .slideDown({
        duration: 350,
        easing: 'easeOutQuart',
        queue: false,
        complete: function () {
          $(this).css('height', '');
        },
      });
    this.getDom().addClass('expanded');
  }

  getSiblingsPristine(): CategoryOptionTreeNode[] {
    return this.getOwner().children.filter((node) => node.isPristine && node.id != this.id);
  }

  private recursivelyGetPristine(currOption: CategoryOptionTreeNode) {
    let resultNodes = [];
    resultNodes = resultNodes.concat(currOption.getSiblingsPristine());
    const parentOption = currOption.getOwner().getOwner();
    if (this.isMainOption || (App.config.menu.showOnePanePerMainOption && parentOption && parentOption.isMainOption))
      return resultNodes;
    else if (parentOption) resultNodes = resultNodes.concat(this.recursivelyGetPristine(parentOption));

    return resultNodes;
  }
}
