/*
    ***** BEGIN LICENSE BLOCK *****
    
    Copyright © 2020 Corporation for Digital Scholarship
                     Vienna, Virginia, USA
                     https://digitalscholar.org
    
    This file is part of Zotero.
    
    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.
    
    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
    
    ***** END LICENSE BLOCK *****
*/

// TODO: Fix import/require related issues that might be
//  related with `require` not reusing the context
var React = require('react');
var ReactDOM = require('react-dom');
var NotesList = require('components/itemPane/notesList').default;

var ZoteroContextPane = new function () {
	var _tabCover;
	var _contextPane;
	var _contextPaneInner;
	var _contextPaneSplitter;
	var _contextPaneSplitterStacked;
	var _itemToggle;
	var _notesToggle;
	var _panesDeck;
	var _itemPaneDeck;
	var _notesPaneDeck;
	
	var _splitButton;
	var _itemPaneToggle;
	var _notesPaneToggle;
	var _tabToolbar;
	
	var _itemContexts = [];
	var _notesContexts = [];
	
	// Using attribute instead of property to set 'selectedIndex'
	// is more reliable
	
	this.update = _update;
	this.getActiveEditor = _getActiveEditor;
	this.focus = _focus;

	this.init = function () {
		if (!Zotero) {
			return;
		}

		_tabCover = document.getElementById('zotero-tab-cover');
		_itemToggle = document.getElementById('zotero-tb-toggle-item-pane');
		_notesToggle = document.getElementById('zotero-tb-toggle-notes-pane');
		_contextPane = document.getElementById('zotero-context-pane');
		_contextPaneInner = document.getElementById('zotero-context-pane-inner');
		_contextPaneSplitter = document.getElementById('zotero-context-splitter');
		_contextPaneSplitterStacked = document.getElementById('zotero-context-splitter-stacked');
		
		_splitButton = document.getElementById('zotero-tb-split');
		_itemPaneToggle = document.getElementById('zotero-tb-toggle-item-pane');
		_notesPaneToggle = document.getElementById('zotero-tb-toggle-notes-pane');
		_tabToolbar = document.getElementById('zotero-tab-toolbar');
		
		if (Zotero.rtl) {
			_tabToolbar.style.left = 0;
			_splitButton.style.transform = 'scaleX(-1)';
		}
		else {
			_tabToolbar.style.right = 0;
		}
		
		// vbox
		var vbox = document.createXULElement('vbox');
		vbox.setAttribute('flex', '1');

		_contextPaneInner.append(vbox);

		// Toolbar extension
		var toolbarExtension = document.createXULElement('box');
		toolbarExtension.style.height = '32px';
		toolbarExtension.id = 'zotero-context-toolbar-extension';
		
		_panesDeck = document.createXULElement('deck');
		_panesDeck.setAttribute('flex', 1);
		_panesDeck.setAttribute('selectedIndex', 0);

		vbox.append(toolbarExtension, _panesDeck);

		// Item pane deck
		_itemPaneDeck = document.createXULElement('deck');
		// Notes pane deck
		_notesPaneDeck = document.createXULElement('deck');
		_notesPaneDeck.style.backgroundColor = 'white';
		_notesPaneDeck.setAttribute('flex', 1);
		_notesPaneDeck.className = 'notes-pane-deck';

		_panesDeck.append(_itemPaneDeck, _notesPaneDeck);

		this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'tab'], 'contextPane');
		window.addEventListener('resize', _update);
		_itemToggle.addEventListener('click', _toggleItemButton);
		_notesToggle.addEventListener('click', _toggleNotesButton);
		Zotero.Reader.onChangeSidebarWidth = _updatePaneWidth;
		Zotero.Reader.onChangeSidebarOpen = _updatePaneWidth;
	};

	this.destroy = function () {
		_itemToggle.removeEventListener('click', _toggleItemButton);
		_notesToggle.removeEventListener('click', _toggleNotesButton);
		window.removeEventListener('resize', _update);
		Zotero.Notifier.unregisterObserver(this._notifierID);
		Zotero.Reader.onChangeSidebarWidth = () => {};
		Zotero.Reader.onChangeSidebarOpen = () => {};
		_contextPaneInner.innerHTML = '';
		_itemContexts = [];
		_notesContexts = [];
	};

	this.notify = function (action, type, ids, extraData) {
		if (type == 'item') {
			// Update, remove or re-create item panes
			for (let context of _itemContexts.slice()) {
				let item = Zotero.Items.get(context.itemID);
				if (!item) {
					_removeItemContext(context.tabID);
				}
				else if (item.parentID != context.parentID) {
					_removeItemContext(context.tabID);
					_addItemContext(context.tabID, context.itemID);
				}
				else {
					context.update();
				}
			}
			
			// Update notes lists for affected libraries
			let libraryIDs = [];
			for (let id of ids) {
				let item = Zotero.Items.get(id);
				if (item && (item.isNote() || item.isRegularItem())) {
					libraryIDs.push(item.libraryID);
				}
				else if (action == 'delete') {
					libraryIDs.push(extraData[id].libraryID);
				}
			}
			for (let context of _notesContexts) {
				if (libraryIDs.includes(context.libraryID)) {
					context.affectedIDs = new Set([...context.affectedIDs, ...ids]);
					context.update();
				}
			}
		}
		else if (type == 'tab') {
			if (action == 'add') {
				_addItemContext(ids[0], extraData[ids[0]].itemID);
			}
			else if (action == 'close') {
				_removeItemContext(ids[0]);
				if (Zotero_Tabs.deck.children.length == 1) {
					_notesContexts.forEach(x => x.notesListRef.current.setExpanded(false));
				}
			}
			else if (action == 'select') {
				// It seems that changing `hidden` or `collapsed` values might
				// be related with significant slow down when there are too many
				// DOM nodes (i.e. 10k notes)
				if (Zotero_Tabs.selectedType == 'library') {
					_contextPaneSplitter.setAttribute('hidden', true);
					_contextPane.setAttribute('collapsed', true);
					_tabToolbar.hidden = true;
					_tabCover.classList.add('hidden');
				}
				else if (Zotero_Tabs.selectedType == 'reader') {
					var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
					if (reader) {
						_tabCover.classList.remove('hidden');
						(async () => {
							await reader._initPromise;
							_tabCover.classList.add('hidden');
							// Focus reader pages view if context pane note editor is not selected
							if (Zotero_Tabs.selectedID == reader.tabID
								&& (!document.activeElement
									|| !document.activeElement.closest('.context-node iframe[anonid="editor-view"]'))) {
								reader.focus();
							}
							
							var attachment = await Zotero.Items.getAsync(reader.itemID);
							if (attachment) {
								_selectNotesContext(attachment.libraryID);
								var notesContext = _getNotesContext(attachment.libraryID);
								notesContext.updateFromCache();
							}
						})();
					}
				
					_contextPaneSplitter.setAttribute('hidden', false);
					// It seems that on heavy load (i.e. syncing) the line below doesn't set the correct value
					setTimeout(() => {
						_contextPane.setAttribute('collapsed', !(_contextPaneSplitter.getAttribute('state') != 'collapsed'));
					});
					_tabToolbar.hidden = false;
				}

				_selectItemContext(ids[0]);
				_update();
			}
		}
	};

	function _toggleItemButton() {
		_togglePane(0);
	}

	function _toggleNotesButton() {
		_togglePane(1);
	}
	
	function _getActiveEditor() {
		var splitter;
		if (Zotero.Prefs.get('layout') == 'stacked') {
			splitter = _contextPaneSplitterStacked;
		}
		else {
			splitter = _contextPaneSplitter;
		}

		if (splitter.getAttribute('state') != 'collapsed') {
			if (_panesDeck.selectedIndex == 1) {
				var node = _notesPaneDeck.selectedPanel;
				if (node.selectedIndex == 1) {
					return node.querySelector('note-editor');
				}
			}
		}
	}

	function _focus() {
		var splitter;
		if (Zotero.Prefs.get('layout') == 'stacked') {
			splitter = _contextPaneSplitterStacked;
		}
		else {
			splitter = _contextPaneSplitter;
		}

		if (splitter.getAttribute('state') != 'collapsed') {
			if (_panesDeck.selectedIndex == 0) {
				var node = _itemPaneDeck.selectedPanel;
				node.querySelector('tab[selected]').focus();
				return true;
			}
			else {
				var node = _notesPaneDeck.selectedPanel;
				if (node.selectedIndex == 0) {
					node.querySelector('search-textbox').focus();
					return true;
				}
				else {
					node.querySelector('note-editor').focusFirst();
					return true;
				}
			}
		}
		return false;
	}

	function _updateAddToNote() {
		var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
		if (reader) {
			var editor = _getActiveEditor();
			var libraryReadOnly = editor && editor.item && _isLibraryReadOnly(editor.item.libraryID);
			var noteReadOnly = editor && editor.item
				&& (editor.item.deleted || editor.item.parentItem && editor.item.parentItem.deleted);
			reader.enableAddToNote(!!editor && !libraryReadOnly && !noteReadOnly);
		}
	}
	
	function _updatePaneWidth() {
		var stacked = Zotero.Prefs.get('layout') == 'stacked';
		var width = Zotero.Reader.getSidebarWidth() + 'px';
		if (!Zotero.Reader.getSidebarOpen()) {
			width = 0;
		}
		if (Zotero.rtl) {
			_contextPane.style.left = 0;
			_contextPane.style.right = stacked ? width : 'unset';
		}
		else {
			_contextPane.style.left = stacked ? width : 'unset';
			_contextPane.style.right = 0;
		}
	}

	function _updateToolbarWidth() {
		var stacked = Zotero.Prefs.get('layout') == 'stacked';
		var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
		if (reader) {
			if ((stacked || _contextPaneSplitter.getAttribute('state') == 'collapsed')) {
				reader.setToolbarPlaceholderWidth(_tabToolbar.offsetWidth);
			}
			else {
				reader.setToolbarPlaceholderWidth(0);
			}
		}
	}

	function _update() {
		if (Zotero_Tabs.selectedIndex == 0) {
			return;
		}
	
		var splitter;
		var stacked = Zotero.Prefs.get('layout') == 'stacked';
		if (stacked) {
			_contextPaneSplitterStacked.setAttribute('hidden', false);
			_contextPaneSplitter.setAttribute('state', 'open');
			_contextPaneSplitter.setAttribute('hidden', true);
			_contextPane.classList.add('stacked');
			_contextPane.classList.remove('standard');
			splitter = _contextPaneSplitterStacked;
		}
		else {
			_contextPaneSplitter.setAttribute('hidden', false);
			_contextPaneSplitterStacked.setAttribute('hidden', true);
			_contextPaneSplitterStacked.setAttribute('state', 'open');
			_contextPane.classList.add('standard');
			_contextPane.classList.remove('stacked');
			splitter = _contextPaneSplitter;
		}
		
		var collapsed = splitter.getAttribute('state') == 'collapsed';
		
		var selectedIndex = _panesDeck.selectedIndex;
		if (!collapsed && selectedIndex == 0) {
			_itemPaneToggle.classList.add('toggled');
		}
		else {
			_itemPaneToggle.classList.remove('toggled');
		}

		if (!collapsed && selectedIndex == 1) {
			_notesPaneToggle.classList.add('toggled');
		}
		else {
			_notesPaneToggle.classList.remove('toggled');
		}
		
		if (Zotero_Tabs.selectedIndex > 0) {
			var height = 0;
			if (Zotero.Prefs.get('layout') == 'stacked'
				&& _contextPane.getAttribute('collapsed') != 'true') {
				height = _contextPaneInner.getBoundingClientRect().height;
			}
			Zotero.Reader.setBottomPlaceholderHeight(height);
		}
		
		_updatePaneWidth();
		_updateToolbarWidth();
		_updateAddToNote();
	}

	function _togglePane(paneIndex) {
		var splitter = Zotero.Prefs.get('layout') == 'stacked'
			? _contextPaneSplitterStacked : _contextPaneSplitter;

		var isOpen = splitter.getAttribute('state') != 'collapsed';
		var hide = false;
		var currentPane = _panesDeck.selectedIndex;
		if (isOpen && currentPane == paneIndex) {
			hide = true;
		}
		else {
			_panesDeck.setAttribute('selectedIndex', paneIndex);
		}
		
		splitter.setAttribute('state', hide ? 'collapsed' : 'open');
		_update();

		if (!hide) {
			ZoteroContextPane.focus();
		}
	}
	
	function _getCurrentAttachment() {
		var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
		if (reader) {
			return Zotero.Items.get(reader.itemID);
		}
	}
	
	function _addNotesContext(libraryID) {
		let readOnly = _isLibraryReadOnly(libraryID);
		
		var list = document.createXULElement('vbox');
		list.setAttribute('flex', 1);
		list.className = 'zotero-context-notes-list';

		var noteContainer = document.createXULElement('vbox');
		var title = document.createXULElement('vbox');
		title.className = 'zotero-context-pane-editor-parent-line';
		var editor = new (customElements.get('note-editor'));
		editor.className = 'zotero-context-pane-pinned-note';
		editor.setAttribute('flex', 1);
		noteContainer.append(title, editor);
		
		let contextNode = document.createXULElement('deck');
		contextNode.append(list, noteContainer);
		_notesPaneDeck.append(contextNode);
		
		contextNode.className = 'context-node';
		contextNode.setAttribute('selectedIndex', 0);
		
		editor.returnHandler = () => {
			contextNode.setAttribute('selectedIndex', 0);
			_updateAddToNote();
		};
		
		var head = document.createXULElement('hbox');
		head.style.display = 'flex';
		
		async function _createNoteFromAnnotations(child) {
			var attachment = _getCurrentAttachment();
			if (!attachment) {
				return;
			}
			var note = await Zotero.EditorInstance.createNoteFromAnnotations(
				attachment.getAnnotations().filter(x => x.annotationType != 'ink'), child && attachment.parentID
			);

			_updateAddToNote();

			input.value = '';
			_updateNotesList();

			_setPinnedNote(note.id);
		}

		function _createNote(child) {
			contextNode.setAttribute('selectedIndex', 1);
			var item = new Zotero.Item('note');
			item.libraryID = libraryID;
			if (child) {
				var attachment = _getCurrentAttachment();
				if (!attachment) {
					return;
				}
				item.parentID = attachment.parentID;
			}
			editor.mode = 'edit';
			editor.item = item;
			editor.parentItem = null;
			editor.focus();
			_updateAddToNote();
			
			input.value = '';
			_updateNotesList();
		}

		var vbox = document.createXULElement('vbox');
		vbox.style.flex = '1';
		var input = document.createXULElement('search-textbox');
		input.style.margin = '4px 7px';
		input.setAttribute('type', 'search');
		input.setAttribute('timeout', '250');
		input.addEventListener('command', () => {
			notesListRef.current.setExpanded(false);
			_updateNotesList();
		});
		vbox.append(input);
		
		head.append(vbox);

		var listBox = document.createXULElement('vbox');
		listBox.style.display = 'flex';
		listBox.setAttribute('flex', '1');
		var listInner = document.createElement('div');
		listInner.className = 'notes-list-container';
		// Otherwise it can be focused with tab
		listInner.tabIndex = -1;
		listBox.append(listInner);

		list.append(head, listBox);

		var notesListRef = React.createRef();

		function _isVisible() {
			let splitter = Zotero.Prefs.get('layout') == 'stacked'
				? _contextPaneSplitterStacked : _contextPaneSplitter;
			
			return Zotero_Tabs.selectedID != 'zotero-pane'
				&& _panesDeck.selectedIndex == 1
				&& context.node.selectedIndex == 0
				&& splitter.getAttribute('state') != 'collapsed';
		}

		async function _updateNotesList(useCached) {
			var query = input.value;
			var notes;
			
			// Calls itself and debounces until notes list becomes
			// visible, and then updates
			if (!useCached && !_isVisible()) {
				context.update();
				return;
			}
			
			if (useCached && context.cachedNotes.length) {
				notes = context.cachedNotes;
			}
			else {
				await Zotero.Schema.schemaUpdatePromise;
				var s = new Zotero.Search();
				s.addCondition('libraryID', 'is', libraryID);
				s.addCondition('itemType', 'is', 'note');
				if (query) {
					let parts = Zotero.SearchConditions.parseSearchString(query);
					for (let part of parts) {
						s.addCondition('note', 'contains', part.text);
					}
				}
				notes = await s.search();
				notes = Zotero.Items.get(notes);
				if (Zotero.Prefs.get('sortNotesChronologically.reader')) {
					notes.sort((a, b) => {
						a = a.dateModified;
						b = b.dateModified;
						return (a > b ? -1 : (a < b ? 1 : 0));
					});
				}
				else {
					let collation = Zotero.getLocaleCollation();
					notes.sort((a, b) => {
						let aTitle = Zotero.Items.getSortTitle(a.getNoteTitle());
						let bTitle = Zotero.Items.getSortTitle(b.getNoteTitle());
						return collation.compareString(1, aTitle, bTitle);
					});
				}
				
				let cachedNotesIndex = new Map();
				for (let cachedNote of context.cachedNotes) {
					cachedNotesIndex.set(cachedNote.id, cachedNote);
				}
				notes = notes.map(note => {
					var parentItem = note.parentItem;
					// If neither note nor parent item is affected try to return the cached note
					if (!context.affectedIDs.has(note.id)
						&& (!parentItem || !context.affectedIDs.has(parentItem.id))) {
						let cachedNote = cachedNotesIndex.get(note.id);
						if (cachedNote) {
							return cachedNote;
						}
					}
					var text = note.note;
					text = Zotero.Utilities.unescapeHTML(text);
					text = text.trim();
					text = text.slice(0, 500);
					var parts = text.split('\n').map(x => x.trim()).filter(x => x.length);
					var title = parts[0] && parts[0].slice(0, Zotero.Notes.MAX_TITLE_LENGTH);
					var date = Zotero.Date.sqlToDate(note.dateModified, true);
					date = Zotero.Date.toFriendlyDate(date);
					
					return {
						id: note.id,
						title: title || Zotero.getString('pane.item.notes.untitled'),
						body: parts[1] || '',
						date,
						parentID: note.parentID,
						parentItemType: parentItem && parentItem.itemType,
						parentTitle: parentItem && parentItem.getDisplayTitle()
					};
				});
				context.cachedNotes = notes;
				context.affectedIDs = new Set();
			}

			var attachment = _getCurrentAttachment();
			var parentID = attachment && attachment.parentID;
			notesListRef.current.setHasParent(!!parentID);
			notesListRef.current.setNotes(notes.map(note => ({
				...note,
				isCurrentChild: parentID && note.parentID == parentID
			})));
		}

		var context = {
			libraryID,
			node: contextNode,
			editor,
			notesListRef,
			cachedNotes: [],
			affectedIDs: new Set(),
			update: Zotero.Utilities.throttle(_updateNotesList, 1000, { leading: false }),
			updateFromCache: () => _updateNotesList(true)
		};
		
		function _handleListPopupClick(id, event) {
			switch (event.originalTarget.id) {
				case 'context-pane-list-show-in-library':
					ZoteroPane_Local.selectItem(id);
					Zotero_Tabs.select('zotero-pane');
					break;

				case 'context-pane-list-edit-in-window':
					ZoteroPane_Local.openNoteWindow(id);
					break;

				case 'context-pane-list-move-to-trash':
					if (!readOnly) {
						Zotero.Items.trashTx(id);
						context.cachedNotes = context.cachedNotes.filter(x => x.id != id);
						_updateNotesList(true);
					}
					break;

				default:
			}
		}
		
		function _handleAddChildNotePopupClick(event) {
			if (readOnly) {
				return;
			}
			switch (event.originalTarget.id) {
				case 'context-pane-add-child-note':
					_createNote(true);
					break;

				case 'context-pane-add-child-note-from-annotations':
					_createNoteFromAnnotations(true);
					break;

				default:
			}
		}

		function _handleAddStandaloneNotePopupClick(event) {
			if (readOnly) {
				return;
			}
			switch (event.originalTarget.id) {
				case 'context-pane-add-standalone-note':
					_createNote();
					break;

				case 'context-pane-add-standalone-note-from-annotations':
					_createNoteFromAnnotations();
					break;

				default:
			}
		}

		ReactDOM.render(
			<NotesList
				ref={notesListRef}
				onClick={(id) => {
					_setPinnedNote(id);
				}}
				onContextMenu={(id, event) => {
					document.getElementById('context-pane-list-move-to-trash').setAttribute('disabled', readOnly);
					var popup = document.getElementById('context-pane-list-popup');
					popup.onclick = (event) => _handleListPopupClick(id, event);
					popup.openPopupAtScreen(event.screenX, event.screenY);
				}}
				onAddChildButtonDown={(event) => {
					document.getElementById('context-pane-add-child-note').setAttribute('disabled', readOnly);
					document.getElementById('context-pane-add-child-note-from-annotations').setAttribute('disabled', readOnly);
					var popup = document.getElementById('context-pane-add-child-note-button-popup');
					popup.onclick = _handleAddChildNotePopupClick;
					popup.openPopup(event.target, 'after_end');
				}}
				onAddStandaloneButtonDown={(event) => {
					document.getElementById('context-pane-add-standalone-note').setAttribute('disabled', readOnly);
					document.getElementById('context-pane-add-standalone-note-from-annotations').setAttribute('disabled', readOnly);
					var popup = document.getElementById('context-pane-add-standalone-note-button-popup');
					popup.onclick = _handleAddStandaloneNotePopupClick;
					popup.openPopup(event.target, 'after_end');
				}}
			/>,
			listInner,
			() => {
				_updateNotesList();
			}
		);
		_notesContexts.push(context);
		return context;
	}
	
	function _getNotesContext(libraryID) {
		var context = _notesContexts.find(x => x.libraryID == libraryID);
		if (!context) {
			context = _addNotesContext(libraryID);
		}
		return context;
	}
	
	function _selectNotesContext(libraryID) {
		let context = _getNotesContext(libraryID);
		_notesPaneDeck.setAttribute('selectedIndex', Array.from(_notesPaneDeck.children).findIndex(x => x == context.node));
	}
	
	function _removeNotesContext(libraryID) {
		var context = _notesContexts.find(x => x.libraryID == libraryID);
		context.node.remove();
		_notesContexts = _notesContexts.filter(x => x.libraryID != libraryID);
	}
	
	function _isLibraryReadOnly(libraryID) {
		return !Zotero.Libraries.get(libraryID).editable;
	}

	function _setPinnedNote(itemID) {
		var item = Zotero.Items.get(itemID);
		if (!item) {
			return;
		}
		var readOnly = _isLibraryReadOnly(item.libraryID);
		var context = _getNotesContext(item.libraryID);
		if (context) {
			var { editor, node } = context;
			node.setAttribute('selectedIndex', 1);
			editor.mode = readOnly ? 'view' : 'edit';
			editor.item = item;
			editor.parentItem = null;
			
			node.querySelector('.zotero-context-pane-editor-parent-line').innerHTML = '';
			var parentItem = item.parentItem;
			if (parentItem) {
				var container = document.createElement('div');
				var img = document.createElement('img');
				img.src = Zotero.ItemTypes.getImageSrc(parentItem.itemType);
				img.className = 'parent-item-type';
				var title = document.createElement('div');
				title.append(parentItem.getDisplayTitle());
				title.className = 'parent-title';
				container.append(img, title);
				node.querySelector('.zotero-context-pane-editor-parent-line').append(container);
			}
			_updateAddToNote();
		}
	}

	function _removeItemContext(tabID) {
		document.getElementById(tabID + '-context').remove();
		_itemContexts = _itemContexts.filter(x => x.tabID != tabID);
	}

	function _selectItemContext(tabID) {
		let selectedIndex = Array.from(_itemPaneDeck.children).findIndex(x => x.id == tabID + '-context');
		if (selectedIndex != -1) {
			_itemPaneDeck.setAttribute('selectedIndex', selectedIndex);
		}
	}

	async function _addItemContext(tabID, itemID) {
		var container = document.createXULElement('vbox');
		container.id = tabID + '-context';
		container.className = 'zotero-item-pane-content';
		_itemPaneDeck.appendChild(container);
	
		var { libraryID } = Zotero.Items.getLibraryAndKeyFromID(itemID);
		var library = Zotero.Libraries.get(libraryID);
		await library.waitForDataLoad('item');

		var item = Zotero.Items.get(itemID);
		if (!item) {
			return;
		}
		var libraryID = item.libraryID;
		var readOnly = _isLibraryReadOnly(libraryID);
		var parentID = item.parentID;
		
		var context = {
			tabID,
			itemID,
			parentID,
			libraryID,
			update: () => {}
		};
		_itemContexts.push(context);
		
		if (!parentID) {
			var vbox = document.createXULElement('vbox');
			vbox.setAttribute('flex', '1');
			vbox.setAttribute('align', 'center');
			vbox.setAttribute('pack', 'center');
			var description = document.createXULElement('description');
			vbox.append(description);
			description.append(Zotero.getString('pane.context.noParent'));
			container.append(vbox);
			return;
		}
		var parentItem = Zotero.Items.get(item.parentID);
		
		// Dynamically create item pane tabs and panels as in itemPane.xul.
		// Keep the code below in sync with itemPane.xul

		// tabbox
		var tabbox = document.createXULElement('tabbox');
		tabbox.setAttribute('flex', '1');
		tabbox.className = 'zotero-view-tabbox';

		container.append(tabbox);

		// tabs
		var tabs = document.createXULElement('tabs');
		tabs.className = 'zotero-editpane-tabs';
		// tabpanels
		var tabpanels = document.createXULElement('tabpanels');
		tabpanels.setAttribute('flex', '1');
		tabpanels.className = 'zotero-view-item';
		tabpanels.addEventListener('select', () => {
			_updateAddToNote();
		});

		// Info tab
		var tabInfo = document.createXULElement('tab');
		tabInfo.setAttribute('label', Zotero.Intl.strings['zotero.tabs.info.label']);
		// Tags tab
		var tabTags = document.createXULElement('tab');
		tabTags.setAttribute('label', Zotero.Intl.strings['zotero.tabs.tags.label']);
		// Related tab
		var tabRelated = document.createXULElement('tab');
		tabRelated.setAttribute('label', Zotero.Intl.strings['zotero.tabs.related.label']);

		tabs.append(tabInfo, tabTags, tabRelated);
		tabbox.append(tabs, tabpanels);

		// Info panel
		var panelInfo = document.createXULElement('tabpanel');
		panelInfo.setAttribute('flex', '1');
		panelInfo.className = 'zotero-editpane-item-box';
		var itemBox = new (customElements.get('item-box'));
		itemBox.setAttribute('flex', '1');
		panelInfo.append(itemBox);
		// Tags panel
		var panelTags = document.createXULElement('tabpanel');
		var tagsBox = new (customElements.get('tags-box'));
		tagsBox.setAttribute('flex', '1');
		tagsBox.className = 'zotero-editpane-tags';
		panelTags.append(tagsBox);

		// Related panel
		var panelRelated = document.createXULElement('tabpanel');
		var relatedBox = new (customElements.get('related-box'));
		relatedBox.setAttribute('flex', '1');
		relatedBox.className = 'zotero-editpane-related';
		panelRelated.addEventListener('click', (event) => {
			if (event.originalTarget.closest('.zotero-clicky')) {
				Zotero_Tabs.select('zotero-pane');
			}
		});
		panelRelated.append(relatedBox);

		tabpanels.append(panelInfo, panelTags, panelRelated);
		tabbox.selectedIndex = 0;


		itemBox.mode = readOnly ? 'view' : 'edit';
		itemBox.item = parentItem;

		tagsBox.mode = readOnly ? 'view' : 'edit';
		tagsBox.item = parentItem;

		relatedBox.mode = readOnly ? 'view' : 'edit';
		relatedBox.item = parentItem;
	}
};
