text: import Eventify from 'eventify';
  
  
  /**
   * @memberof smx
   * @desc
   * The Playhead class is a {@link smx.Document Document} navigation controller.
   *
   * Provides a plain interface to navigate along a Document tree,
   * keeps a navigation registry and emits useful events for listening
   * to any movement.
   */
  class Playhead{
  
  	/**
  	 * @param {smx.Document} document - The document to navigate through
  	 */
  	constructor(doc){
      
  		//document is required
  		if(!doc) return;
      
  		//extend with events on, off, trigger
  		Eventify.enable(this);
      
  		/**
  		 * The document to navigate through
  		 * @type {smx.Document}
  		 * @private
  		 */
  		this._document = doc;
  		
  		/**
  		 * Contains all currently selected nodes ordered from outter to inner.
  		 * @type {smx.Node[]}
  		 * @private
  		 */
  		this._selection = [];
  
  	}
  
  
  	/**
  	 * Gets the associated document
  	 * @type {smx.Document}
  	 * @readonly
  	 */
  	get document(){
  		return this._document;
  	}
  
  
  	/**
  	 * Gets all currently selected nodes ordered from outter to inner.
  	 * @type {smx.Node[]}
  	 * @readonly
  	 */
  	get selection() {
  		return this._selection;
  	}
  
  	/**
  	 * Gets the head node, which is the last node in selection.
  	 * @type {smx.Node}
  	 * @readonly
  	 */
  	get head(){
  		return this._selection[this._selection.length - 1];
  	}
  
  	/**
  	 * Gets the root node, which is the first node selection.
  	 * @type {smx.Node}
  	 * @readonly
  	 */
  	get root(){
  		return this._selection[0];
  	}
  
  	/**
  	 * Navigates to document's root node.
  	 */
    reset() {
      return this.navigate(this.document.root);
    }
  
  	/**
  	 * Performs play action
  	 * @param {(String|smx.Node)=} ref target reference
  	 */
  	play(ref){
      
  		//no reference? just do a forward
  		if(!ref) return this.forward();
  		
  		//resolve target node
  		var tnode = (ref.id)? ref : this.document.getNodeById(ref);
      
  		//not found? ignore...
  		if(tnode) return this.navigate(tnode);
  		
  		//else ignore
  		return;
      
    }
    
    /**
     * Navigates to an inner node, moves the head to current head's first child.
     */
    enter(){
      
      //get current node
      var cnode = this.head; if(!cnode) return;
      
      //get children nodes
      let children = cnode.children;
      
      //no children?
      if (!children.length) return;
      
      //get first child
      var tnode = children[0];
      
      //navigates to target node
  		return this.navigate(tnode);
      
    }
  
    /**
     * Navigates to an outter node, moves the head to current head's parent.
     */
    exit(){
      
      //get current node
      var cnode = this.head; if(!cnode) return;
      
      //has parent node?
      if(!cnode.parent) return;
      
      //get parent node
      var tnode = cnode.parent;
      
      //navigates to target node
  		return this.navigate(tnode);
      
    }
  
  	/**
  	 * Navigates to current head's next sibling node.
  	 */
    next(){
  		
  		//get current node
  		var cnode = this.head; if(!cnode) return;
  
  		//get next node
  		var tnode = cnode.next; if (!tnode) return;
  		
      //navigates to target node
  		return this.navigate(tnode);
  	}
  
  	/**
  	 * Navigates to current head's previous sibling node.
  	 */
  	previous(){
  		
  		//get current node
  		var cnode = this.head; if(!cnode) return;
      
  		//get previous node
  		var tnode = cnode.previous; if (!tnode) return;
      
      //navigates to target node
  		return this.navigate(tnode);
  		
  	}
  	
  	/**
  	 * Navigates to current head's next node in flat tree mode.
  	 */
  	forward(){
  		
  		let tnode, cnode, children;
  		
  		//get current node
  		cnode = this.head;
  		
  		//no current node? ignore
  		if(!cnode) return;
      
  		tnode = cnode.first || cnode.next;
      
  		if(!tnode){
        
  			var parent = cnode.parent;
  			while(parent && !tnode){
  				tnode = parent.next;
  				parent = parent.parent;
  			}
        
      }
      
      return (tnode)? this.navigate(tnode) : null;
      
  	}
    
  	/**
     * Navigates to current head's previous node in flat tree mode.
  	 */
    backward(){
      
  		if(!this.head) return;
      var tnode = this.head.previous || this.head.parent;
      return (tnode)? this.navigate(tnode) : null;
      
  	}
  
  	/**
     * @param {String} keyword
     * @desc
  	 * Executes a playhead command based on the given action keyword. keywords
  	 * are basically some playhead's method names.
  	 *
  	 * List of valid commands:
  	 * `reset`, `play`, `next`, `previous`,
  	 * `enter`, `exit`, `forward`, `backward`.
  	 *
  	 */
  	exec(keyword){
      	  
  		//define valid keywords mapping existing methods
  		var keywords = [
  		  'reset', 'play', 'next', 'previous',
  		  'enter', 'exit', 'forward', 'backward'
  		];
      
      //resolve for a valid keyword
      var isValidKeyword = (keywords.indexOf(keyword)>=0);
      
      //not valid keyword? error!
  		if(!isValidKeyword)
  		  throw new Error( 'UNKNOWN KEYWORD "!"'+ keyword +'"' );
      
      //try-catched execution
  		try{ return this[keyword]()	}
  		catch(e){	throw new Error( 'Playhead Error: Keyword exec "!'+ keyword +'"', e) }
      
  	}
  
  	/**
     * @param {String|smx.Node} target
     * @desc
     *
  	 * Navigates to a given target node or executes a playhead command.
  	 * If `target` is a node will navigate to it, if `target` is a string will
  	 * try to find a node identified as `target` and will navigate to it. If
  	 * `target` is a `!` preffixed string will execute it as a playhead command.
  	 *
  	 * See {@link smx.Playhead#exec .exec()} for a list of valid commands.
  	 *
  	 * @example
  	 * //instance a new Playhead
  	 * var playhead = new smx.Playhead(doc);
  	 *
  	 * //navigate by node identifier
  	 * playhead.navigate('a42');
  	 *
  	 * //to to given node
  	 * playhead.navigate(node);
  	 *
  	 * //using commands
  	 * playhead.navigate('!next')
  	 * //same as
  	 * playhead.exec('next')
  	 * // or
  	 * playhead.next();
  	 *
  	 */
    navigate(target){
  
  		//check for a keyword, must be '!' preffixed string
      var isKeyword = (typeof target === 'string' && target.indexOf('!') === 0);
  		
  		//keyword? resolve by exec unpreffixed reference
  		if(isKeyword)
        return this.exec(target.substr(1));
      
  		//resolve target node by reference
  		//assuming having and id property means smx.Node...
      var tnode = (target.id) ? target : this.document.getNodeById(target);
      
      //no target found? error!
  		if(!tnode)
        throw new Error('Playhead Error: Invalid target ' + target);
  		
  		//get current node
  		var cnode = this.head;
  		
  		//no need to move...
  		if(tnode === cnode) return cnode;
  		
      //--> LEGACY ASYNC ATTR CONDITIONAL NAVIGATION WAS HERE...
      //see leagacy playhead implementations for more info
      
      //resets private navigation registry
  		var selected = [], deselected = [];
      
      
      if(!cnode){
        cnode = this.document.root;
        selected.push(cnode);
      }
  		
  		/* trying a better approach */
  		
  		var isDescendant = cnode.isAncestorOf(tnode);
  		var isAncestor = tnode.isAncestorOf(cnode);
      
      //aux filter fn for later use
  		var isNodeOrAncestorOf = (n) => (n==tnode || n.isAncestorOf(tnode));
  		
  		var r = cnode;
  		if(cnode === tnode){
  		  //..
  		}
  		else if(isDescendant){
  		  while(r!=tnode){
  		    r = r.children.filter(isNodeOrAncestorOf)[0];
  		    selected.push(r);
  		  }
  		}
  		else if(isAncestor){
  		  while(r!=tnode){
  		    deselected.push(r);
  		    r = r.parent;
  		  }
  		}
  		else{
  		  while(!r.isAncestorOf(cnode) || !r.isAncestorOf(tnode)){
  		    deselected.push(r);
  		    r = r.parent;
  		  }
  		  while(r!=tnode){
  		    r = r.children.filter(isNodeOrAncestorOf)[0]
  		    selected.push(r);
  		  }
  		}
  		
  		
  		//update path
  		for(var i=0; i<deselected.length; i++){
  		  this._selection.pop();
  		}
  		for(var i=0; i<selected.length; i++){
  		  this._selection.push(selected[i]);
  		}
  
  
      this.trigger('change',{
        selected: selected,
        deselected: deselected,
        selection: this._selection,
        origin: cnode,
        target: tnode
      });
      
  		/*
  		//FIRE EVENTS
      
  		//FIRE 'LEAVE' EVENT
  		if(cnode){
        
  			//fire generic 'leave' event in resulting current node
  			this.trigger('leave', cnode);
  			
  			//fire specific node 'leave' event
  			this.trigger('leave:'+cnode.id, cnode);
  			
  		}
      
  		//--> LEGACY CONDITIONAL NOSTOP ATTRIBUTE WAS HERE...
      //see leagacy playhead implementations for more info
      
  		//fire generic 'stay' event in resulting current node
  		this.trigger('stay',tnode);
  		
  		//fire specific node 'stay' event
  		this.trigger('stay:'+tnode.id,tnode);
      
  		//notify node navigation completed
  		this.trigger('ready',tnode);
      
      //return head node
  		return this.head;
  		
  		*/
  		
  	}
  	
  
  
  }
  
  //Doclets for Eventify extended methods
  
  /**
   * Binds an event to a `callback` function. Passing `"all"` will bind
   * the callback to all events fired.
   * @memberof smx.Playhead
   * @instance
   * @method
   * @name on
   * @param {String} name
   * @param {Function} callback
   * @param {Object} context
   */
  
  /**
   * Binds an event to only be triggered a single time. After the first time
   * the callback is invoked, it will be removed.
   * @memberof smx.Playhead
   * @instance
   * @method
   * @name once
   * @param {String} name
   * @param {Function} callback
   * @param {Object} context
   */
  
  /**
   * Remove one or many callbacks. If `context` is null, removes all
   * callbacks with that function. If `callback` is null, removes all
   * callbacks for the event. If `name` is null, removes all bound
   * callbacks for all events.
   * @memberof smx.Playhead
   * @instance
   * @method
   * @name off
   * @param {String} name
   * @param {Function} callback
   * @param {Object} context
   */
  
  
  //Doclet for PlayheadEvent definition
  /**
   * Playhead Event Object
   * @typedef {Object} smx.Playhead.PlayheadEvent
   * @property {smx.Node[]} selected
   * @property {smx.Node[]} deselected
   * @property {smx.Node[]} selection
   * @property {smx.Node} origin
   * @property {smx.Node} target
   */
  
  
  //Doclets for events
  
  /*
   * Fired every time the head changes.
   * @event change
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired when entering to any node
   * @event enter
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired just after `enter` but for a specific node
   * @event enter:id
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired when exiting from any node
   * @event exit
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired just after `exit` but for a specific node
   * @event exit:id
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired every time a head change occurs and stays on any node
   * @event stay
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired just after `stay` but for a specific node
   * @event stay:id
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired every time a node stops being the head
   * @event leave
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired just after `leave` but for a specific node
   * @event leave:id
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired every time the playhead finishes all operations and goes idle
   * @event ready
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  /*
   * Fired when playhed goes to sync mode
   * @event sync
   * @memberof smx.Playhead
   * @return {smx.Playhead.PlayheadEvent}
   */
  
  export default Playhead;