text: import {
  	qs,
  	qsa,
  	querySelectorByType,
  	filterChildren,
  	getParentByTagName,
  } from "./utils/core";
  
  /**
   * Navigation Parser
   * @param {document} xml navigation html / xhtml / ncx
   */
  class Navigation {
  	constructor(xml) {
  		this.toc = [];
  		this.tocByHref = {};
  		this.tocById = {};
  
  		this.landmarks = [];
  		this.landmarksByType = {};
  
  		this.length = 0;
  		if (xml) {
  			this.parse(xml);
  		}
  	}
  
  	/**
  	 * Parse out the navigation items
  	 * @param {document} xml navigation html / xhtml / ncx
  	 */
  	parse(xml) {
  		let isXml = xml.nodeType;
  		let html;
  		let ncx;
  
  		if (isXml) {
  			html = qs(xml, "html");
  			ncx = qs(xml, "ncx");
  		}
  
  		if (!isXml) {
  			this.toc = this.load(xml);
  		} else if (html) {
  			this.toc = this.parseNav(xml);
  			this.landmarks = this.parseLandmarks(xml);
  		} else if (ncx) {
  			this.toc = this.parseNcx(xml);
  		}
  
  		this.length = 0;
  
  		this.unpack(this.toc);
  	}
  
  	/**
  	 * Unpack navigation items
  	 * @private
  	 * @param  {array} toc
  	 */
  	unpack(toc) {
  		var item;
  
  		for (var i = 0; i < toc.length; i++) {
  			item = toc[i];
  
  			if (item.href) {
  				this.tocByHref[item.href] = i;
  			}
  
  			if (item.id) {
  				this.tocById[item.id] = i;
  			}
  
  			this.length++;
  
  			if (item.subitems.length) {
  				this.unpack(item.subitems);
  			}
  		}
  	}
  
  	/**
  	 * Get an item from the navigation
  	 * @param  {string} target
  	 * @return {object} navItem
  	 */
  	get(target) {
  		var index;
  
  		if (!target) {
  			return this.toc;
  		}
  
  		if (target.indexOf("#") === 0) {
  			index = this.tocById[target.substring(1)];
  		} else if (target in this.tocByHref) {
  			index = this.tocByHref[target];
  		}
  
  		return this.getByIndex(target, index, this.toc);
  	}
  
  	/**
  	 * Get an item from navigation subitems recursively by index
  	 * @param  {string} target
  	 * @param  {number} index
  	 * @param  {array} navItems
  	 * @return {object} navItem
  	 */
  	getByIndex(target, index, navItems) {
  		if (navItems.length === 0) {
  			return;
  		}
  
  		const item = navItems[index];
  		if (item && (target === item.id || target === item.href)) {
  			return item;
  		} else {
  			let result;
  			for (let i = 0; i < navItems.length; ++i) {
  				result = this.getByIndex(target, index, navItems[i].subitems);
  				if (result) {
  					break;
  				}
  			}
  			return result;
  		}
  	}
  
  	/**
  	 * Get a landmark by type
  	 * List of types: https://idpf.github.io/epub-vocabs/structure/
  	 * @param  {string} type
  	 * @return {object} landmarkItem
  	 */
  	landmark(type) {
  		var index;
  
  		if (!type) {
  			return this.landmarks;
  		}
  
  		index = this.landmarksByType[type];
  
  		return this.landmarks[index];
  	}
  
  	/**
  	 * Parse toc from a Epub > 3.0 Nav
  	 * @private
  	 * @param  {document} navHtml
  	 * @return {array} navigation list
  	 */
  	parseNav(navHtml) {
  		var navElement = querySelectorByType(navHtml, "nav", "toc");
  		var navItems = navElement ? qsa(navElement, "li") : [];
  		var length = navItems.length;
  		var i;
  		var toc = {};
  		var list = [];
  		var item, parent;
  
  		if (!navItems || length === 0) return list;
  
  		for (i = 0; i < length; ++i) {
  			item = this.navItem(navItems[i]);
  			if (item) {
  				toc[item.id] = item;
  				if (!item.parent) {
  					list.push(item);
  				} else {
  					parent = toc[item.parent];
  					parent.subitems.push(item);
  				}
  			}
  		}
  
  		return list;
  	}
  
  	/**
  	 * Create a navItem
  	 * @private
  	 * @param  {element} item
  	 * @return {object} navItem
  	 */
  	navItem(item) {
  		let id = item.getAttribute("id") || undefined;
  		let content = filterChildren(item, "a", true);
  
  		if (!content) {
  			return;
  		}
  
  		let src = content.getAttribute("href") || "";
  
  		if (!id) {
  			id = src;
  		}
  		let text = content.textContent || "";
  		let subitems = [];
  		let parentItem = getParentByTagName(item, "li");
  		let parent;
  
  		if (parentItem) {
  			parent = parentItem.getAttribute("id");
  			if (!parent) {
  				const parentContent = filterChildren(parentItem, "a", true);
  				parent = parentContent && parentContent.getAttribute("href");
  			}
  		}
  
  		while (!parent && parentItem) {
  			parentItem = getParentByTagName(parentItem, "li");
  			if (parentItem) {
  				parent = parentItem.getAttribute("id");
  				if (!parent) {
  					const parentContent = filterChildren(parentItem, "a", true);
  					parent = parentContent && parentContent.getAttribute("href");
  				}
  			}
  		}
  
  		return {
  			id: id,
  			href: src,
  			label: text,
  			subitems: subitems,
  			parent: parent,
  		};
  	}
  
  	/**
  	 * Parse landmarks from a Epub > 3.0 Nav
  	 * @private
  	 * @param  {document} navHtml
  	 * @return {array} landmarks list
  	 */
  	parseLandmarks(navHtml) {
  		var navElement = querySelectorByType(navHtml, "nav", "landmarks");
  		var navItems = navElement ? qsa(navElement, "li") : [];
  		var length = navItems.length;
  		var i;
  		var list = [];
  		var item;
  
  		if (!navItems || length === 0) return list;
  
  		for (i = 0; i < length; ++i) {
  			item = this.landmarkItem(navItems[i]);
  			if (item) {
  				list.push(item);
  				this.landmarksByType[item.type] = i;
  			}
  		}
  
  		return list;
  	}
  
  	/**
  	 * Create a landmarkItem
  	 * @private
  	 * @param  {element} item
  	 * @return {object} landmarkItem
  	 */
  	landmarkItem(item) {
  		let content = filterChildren(item, "a", true);
  
  		if (!content) {
  			return;
  		}
  
  		let type =
  			content.getAttributeNS("http://www.idpf.org/2007/ops", "type") ||
  			undefined;
  		let href = content.getAttribute("href") || "";
  		let text = content.textContent || "";
  
  		return {
  			href: href,
  			label: text,
  			type: type,
  		};
  	}
  
  	/**
  	 * Parse from a Epub > 3.0 NC
  	 * @private
  	 * @param  {document} navHtml
  	 * @return {array} navigation list
  	 */
  	parseNcx(tocXml) {
  		var navPoints = qsa(tocXml, "navPoint");
  		var length = navPoints.length;
  		var i;
  		var toc = {};
  		var list = [];
  		var item, parent;
  
  		if (!navPoints || length === 0) return list;
  
  		for (i = 0; i < length; ++i) {
  			item = this.ncxItem(navPoints[i]);
  			toc[item.id] = item;
  			if (!item.parent) {
  				list.push(item);
  			} else {
  				parent = toc[item.parent];
  				parent.subitems.push(item);
  			}
  		}
  
  		return list;
  	}
  
  	/**
  	 * Create a ncxItem
  	 * @private
  	 * @param  {element} item
  	 * @return {object} ncxItem
  	 */
  	ncxItem(item) {
  		var id = item.getAttribute("id") || false,
  			content = qs(item, "content"),
  			src = content.getAttribute("src"),
  			navLabel = qs(item, "navLabel"),
  			text = navLabel.textContent ? navLabel.textContent : "",
  			subitems = [],
  			parentNode = item.parentNode,
  			parent;
  
  		if (
  			parentNode &&
  			(parentNode.nodeName === "navPoint" ||
  				parentNode.nodeName.split(":").slice(-1)[0] === "navPoint")
  		) {
  			parent = parentNode.getAttribute("id");
  		}
  
  		return {
  			id: id,
  			href: src,
  			label: text,
  			subitems: subitems,
  			parent: parent,
  		};
  	}
  
  	/**
  	 * Load Spine Items
  	 * @param  {object} json the items to be loaded
  	 * @return {Array} navItems
  	 */
  	load(json) {
  		return json.map((item) => {
  			item.label = item.title;
  			item.subitems = item.children ? this.load(item.children) : [];
  			return item;
  		});
  	}
  
  	/**
  	 * forEach pass through
  	 * @param  {Function} fn function to run on each item
  	 * @return {method} forEach loop
  	 */
  	forEach(fn) {
  		return this.toc.forEach(fn);
  	}
  }
  
  export default Navigation;
  