import React from 'react';
import hoistStatics from 'hoist-non-react-statics';
import { keyNames,keyCodes } from '../../accessibility-utils/keyCodes';
import uniqueNumber from '../../utils/uniqueId';
import componentCategories from '../../docs-utils/componentCategories';
import componentGroups from '../../docs-utils/componentGroups';

const AccessNavigationManager = (WrappedComponent,data={})=>{
    let { activecss,wrap,focus,keyName={},grid } = data;
    class AccessNavigationManager extends React.Component{
        constructor(props){
            super(props)
            this.state = { activedescendantId : undefined }
            this.rootEle = null;
            this.uitype = null;
            this.onKeyDownCallBack = null;
            this.rootEleId = uniqueNumber();
            this.getRootRef = this.getRootRef.bind(this);
            this.currentNodeEle = null;
            this.currentNodeIndex = 0;
            this.focusGridView = this.focusGridView.bind(this);
            this.focusFirst = this.focusFirst.bind(this);
            this.getFocusableTree = this.getFocusableTree.bind(this);
            this.defaultKeyPress = this.defaultKeyPress.bind(this);
            this.getNextRowCurrentIndexEle = this.getNextRowCurrentIndexEle.bind(this);
            this.getNextRowEle = this.getNextRowEle.bind(this);
            this.focusManager = this.focusManager.bind(this);
            this.wrapNextRowChangeIndex = this.wrapNextRowChangeIndex.bind(this);
            this.setCurrentNodeIndex = this.setCurrentNodeIndex.bind(this);
            this.getItemProps = this.getItemProps.bind(this);
            this.keyMoving = this.keyMoving.bind(this);
            this.navigationProps = this.navigationProps.bind(this);
        }

        getRootRef(){
          if(this.rootEle === null){
            this.rootEle = document.querySelector(`[data-aria-rootid="${this.rootEleId}"]`)
          }
          return this.rootEle;
        }

        componentWillUnmount(){
          this.rootEle = null;
        }

        wrapNextRowChangeIndex(mode,node){
          if(mode === 'next'){
            this.currentNodeIndex = 0;
          }else{
            /** not get parent children in Selection element */
            if(node.getAttribute("data-aria-row") === 'true' && !node.contains(this.currentNodeEle)){
              this.currentNodeIndex = (node && node.querySelectorAll('[data-aria-col="true"]').length) + 1;
            }
          }
        }

        getFocusableTree(root,mode){
            let walker = document.createTreeWalker(
              root,
              NodeFilter.SHOW_ELEMENT,
              (
                (mode === 'next' || mode === 'previous') ?
                { 
                  acceptNode: (node)=>{ 
                    if(node.getAttribute('data-aria-col') === 'true' || node.getAttribute('data-aria-item') === 'true'){
                      return NodeFilter.FILTER_ACCEPT; 
                    }
                    else{
                      this.wrapNextRowChangeIndex(mode,node);
                      return NodeFilter.FILTER_SKIP; 
                    }
                  } 
                } 
                :
                (mode === 'nextRow') ?
                { 
                  acceptNode: function(node) { 
                    if(node.getAttribute('data-aria-row') === 'true'){
                      return NodeFilter.FILTER_ACCEPT; 
                    }
                    else{
                      return NodeFilter.FILTER_SKIP; 
                    }
                  } 
                }
                :
                {
                  acceptNode: (node)=>{ 
                    if(node.getAttribute('data-aria-row') === 'true' && (this.currentNodeIndex <= (node && node.querySelectorAll('[data-aria-col="true"]').length))){
                      return NodeFilter.FILTER_ACCEPT; 
                    }
                    else{
                      return NodeFilter.FILTER_SKIP; 
                    }
                  }
                }
              )
            )
            return walker;
        }

        focusManager(mode){
            let nextNode;
            let rootEle = this.getRootRef();
            let walker = this.getFocusableTree(rootEle,mode);
            let node = (this.currentNodeEle ? this.currentNodeEle : walker.currentNode);
            if (rootEle.contains(node)) {
              walker.currentNode = node;
              nextNode = mode === 'next' ? walker.nextNode() : walker.previousNode();
              if(nextNode !== null) { 
                this.currentNodeEle = nextNode;
                if(mode === 'next'){
                  this.currentNodeIndex++
                }
                else{
                  this.currentNodeIndex--
                }
              }
              else{
                if(wrap){
                  walker.currentNode = rootEle;  
                  nextNode = mode === 'next' ? walker.nextNode() :walker.lastChild();
                  if(mode === 'next'){
                    this.currentNodeIndex++
                  }
                  else{
                    this.currentNodeIndex--
                  }
                }
                else{
                  return
                }
              }
              walker.currentNode = node;
              if(walker.currentNode && this.currentNodeEle){
                walker.currentNode.classList.remove(activecss);
                walker.currentNode.setAttribute('aria-selected','false')
                //this.setState({ariaSelected:undefined})
              }
            }
            if(wrap){
              this.currentNodeEle = nextNode;
            }
            if(this.currentNodeEle && activecss) {
              this.currentNodeEle.classList.add(activecss);
              this.currentNodeEle.setAttribute('aria-selected','true')
              this.setState({activedescendantId:this.currentNodeEle.id})
              !focus && this.currentNodeEle.scrollIntoView({ block: 'nearest' })
              focus && this.currentNodeEle.focus();
            }
            return this.currentNodeEle;
        }

        getNextRowCurrentIndexEle(nextSibling){
          let walker = this.getFocusableTree(nextSibling,'next')
          for(let i=0; i < this.currentNodeIndex; i++){
            this.currentNodeEle = walker.nextNode();
          }
        }

        getNextRowEle(mode,currentRoot,nextChildRow){
          let rootEle = this.getRootRef();
          let gridWalker = this.getFocusableTree(rootEle,(nextChildRow || 'nextRow'));
          let node = (this.currentNodeEle ? this.currentNodeEle : gridWalker.currentNode);
          let previousNode;
          if(rootEle.contains(node)){
            gridWalker.currentNode = currentRoot || node;
            previousNode = gridWalker.currentNode;
          }
          gridWalker.parentNode();
          let nextSibling = mode === 'last'? gridWalker.lastChild() : mode === 'next' ? gridWalker.nextNode() : gridWalker.previousNode();
          let nextSiblingChildLength = nextSibling && nextSibling.querySelectorAll('[data-aria-col="true"]').length || 0;
          return { nextSibling,previousNode,nextSiblingChildLength };
        }

        focusGridView(mode){
          let rootEle = this.getRootRef();
          let {nextSibling,previousNode,nextSiblingChildLength} = this.getNextRowEle(mode)
          if(nextSibling !==null){
            if(this.currentNodeIndex <= nextSiblingChildLength){
              this.getNextRowCurrentIndexEle(nextSibling)
            }
            else{
              let {nextSibling} = this.getNextRowEle(mode,'','nextChildRow')
              nextSibling && this.getNextRowCurrentIndexEle(nextSibling)
            }
          }
          else{
            if(wrap){
              let {nextSibling,nextSiblingChildLength} = this.getNextRowEle(mode === 'next' ? 'next' : 'last',rootEle)
              if(this.currentNodeIndex <= nextSiblingChildLength){
                this.getNextRowCurrentIndexEle(nextSibling)
              }
            }
            else{
              return
            }
          }
          if(previousNode && this.currentNodeEle){
            previousNode.classList.remove(activecss);
          }
          if(this.currentNodeEle && activecss){
            this.currentNodeEle.classList.add(activecss);
            focus && this.currentNodeEle.focus();
          }
        }

        setCurrentNodeIndex(){
          let rootEle = this.getRootRef();
          let walkerRow = this.getFocusableTree(rootEle,'nextRow'); 
          walkerRow.currentNode = this.currentNodeEle;
          let getChildNodes = walkerRow.parentNode();
          if(getChildNodes && getChildNodes.children){
            Object.keys(getChildNodes.children).forEach((element,index)=>{
              if(getChildNodes.children[element] === this.currentNodeEle){
                this.currentNodeIndex = index+1;
              }
            })
          }
          else{
            return
          }
        }

        focusFirst(itemId){
            let rootEle = this.getRootRef();
            let from = rootEle.querySelector(`[data-aria-itemid="${itemId}"]`);
            let walker = this.getFocusableTree(rootEle,'next');
            let node = (this.currentNodeEle ? this.currentNodeEle : walker.currentNode);
            if(rootEle.contains(node)){
              node && node.classList.remove(activecss);
            }
            if(!from){ 
              this.currentNodeEle = walker.nextNode() 
            }
            else{
              this.currentNodeEle = from;
              this.setCurrentNodeIndex()
            }
            if(this.currentNodeEle && activecss){
              this.currentNodeEle.classList.add(activecss);
              focus && this.currentNodeEle.focus();
            }
            return this.currentNodeEle;
        }

        keyMoving(e,keys){
          if((keys.toLowerCase()) === 'left' || (keys.toLowerCase()) === 'up'){
            if((keys.toLowerCase()) === 'up'){ 
              grid ? this.focusGridView('previous') : this.focusManager('previous');
              e && e.preventDefault(); 
            }
            else{
              this.focusManager('previous');
            }
          }else if((keys.toLowerCase()) === 'right' || (keys.toLowerCase()) === 'down'){
            if((keys.toLowerCase()) === 'down'){ 
              grid ? this.focusGridView('next') : this.focusManager('next');
              e && e.preventDefault();
            }
            else{
              this.focusManager('next');
            }
          }
          
          else if((keys.toLowerCase()) === 'tabforward' || (keys.toLowerCase()) === 'tabbackward' ){
            if((keys.toLowerCase()) === 'tabforward'){
              e && e.preventDefault();
              this.focusManager('next');
            }
            else{
              this.focusManager('previous');
            }
            
          }
        }

        defaultKeyPress(onKeyDown,e){
            onKeyDown && onKeyDown(e);
            let { keyCode } = e;
            let { arrowleft,arrowright,arrowtop,arrowbottom } = keyNames;
            let { leftArrow,rightArrow,topArrow,bottomArrow } = keyCodes;
            if(Object.keys(keyName).length){
              Object.keys(keyName).forEach((keys)=>{
                keyName[keys].forEach((value)=>{
                  if(value.indexOf('shift') !== -1 && event.shiftKey){
                    let splitedKey = value.split('+')[1];
                    if((splitedKey.toLowerCase()) === (e.key.toLowerCase())){
                      this.keyMoving(e,keys)
                    }
                  }
                  else{
                    if((value.toLowerCase()) === (e.key.toLowerCase())){
                      this.keyMoving(e,keys)
                    }
                  }
                })
              })
            }
            else{
                if(keyCode === 37){
                  grid && this.focusManager('previous');
                }
                if( keyCode === 39){
                  grid && this.focusManager('next');
                } 
                if(keyCode === 40){
                  e && e.preventDefault();
                  grid && this.focusGridView('next');
                  !grid && this.focusManager('next');
                }
                if(keyCode === 38){
                  e && e.preventDefault();
                  grid && this.focusGridView('previous')
                  !grid && this.focusManager('previous');
                }
            }
            if(keyCode === 13){
                this.currentNodeEle && this.currentNodeEle.click();
            }   
        }

        getItemProps(props){
          let { role,id,dataAriaItem={} } = props;
          let genratedRadomId = id || uniqueNumber();
          let dataAriaItems = { 
            'data-aria-item' : 'true',
            'data-aria-itemid' : genratedRadomId,
            id : genratedRadomId, 
            role,
            //'aria-selected' : this.state.ariaSelected,
            ...dataAriaItem
            
          }
          let dataAriaCol = { 
            'data-aria-col' : 'true',
            'data-aria-itemid' : genratedRadomId,
            role 
          }
          if(this.uitype === 'listbox' || this.uitype === 'grid'){
            dataAriaItems['onKeyDown'] = this.defaultKeyPress.bind(this,this.onKeyDownCallBack);
            dataAriaCol['onKeyDown'] = this.defaultKeyPress.bind(this,this.onKeyDownCallBack)
          }
        
          return {
            dataAriaCol,
            dataAriaColDisabled : { 'data-aria-col' : 'false','aria-disabled' : 'true' },
            dataAriaRow : { 'data-aria-row' : 'true' },
            dataAriaItem:dataAriaItems,
            dataAriaItemDisabled : { 'data-aria-item' : 'false','aria-disabled' : 'true' },
            itemId : genratedRadomId 
          }
        }

        navigationProps(props){
          let { onKeyDown,type,popupOpen,listElementProps={},targetElementProps={},inputProps={} } = props || {};
          this.uitype = type;
          this.onKeyDownCallBack = onKeyDown;
          let rootElementProps = {};
          let listElementProp = {
            role: 'listbox', 
            id: this.rootEleId,
            ...listElementProps
          }
          if(type === 'combobox'){
            //rootElementProps['role'] = "combobox"; 
            //rootElementProps['aria-owns'] = this.rootEleId;
            rootElementProps['data-aria-rootid'] = this.rootEleId
          }
          else if(type === 'grid'){
            rootElementProps['role'] = "grid"; 
            rootElementProps['data-aria-rootid'] = this.rootEleId
          }
          else{
            listElementProp['aria-activedescendant'] = this.state.activedescendantId
            rootElementProps['data-aria-rootid'] = this.rootEleId
          }
          return{
            rootElementProps,
            focusFirst : this.focusFirst,
            focusManager : this.focusManager,
            inputProps : { 
              onKeyDown : this.defaultKeyPress.bind(this,onKeyDown),
              'aria-activedescendant' : this.state.activedescendantId,
              'aria-owns' : this.rootEleId,
              'role':'combobox', 
              ...inputProps
            },
            getItemProps : this.getItemProps,
            listElementProps : listElementProp,
            targetElementProps:{
              'aria-controls' : this.rootEleId,
              'aria-haspopup': listElementProps.role ||'listbox',
              'aria-expanded': popupOpen ? 'true' : 'false',
              ...targetElementProps
            }  
          }             
        }

        render(){
            return <WrappedComponent navigationProps={this.navigationProps}/>
        }
    }
    return hoistStatics(AccessNavigationManager,WrappedComponent);
}

export default AccessNavigationManager;

if (__DOCS__) {
  AccessNavigationManager.docs = {
    componentGroup: componentGroups.Components,
    folderName : componentCategories.Accessibility
  };
}