function getContainer(appId, nodeId) {
  try {
    return window.Vaadin.Flow.clients[appId].getByNodeId(nodeId);
  } catch (error) {
    console.error('Could not get node %s from app %s', nodeId, appId);
    console.error(error);
  }
}

/**
 * Initializes the connector for a context menu element.
 *
 * @param {HTMLElement} contextMenu
 * @param {string} appId
 */
function initLazy(contextMenu, appId) {
  if (contextMenu.$connector) {
    return;
  }

  contextMenu.$connector = {
    /**
     * Generates and assigns the items to the context menu.
     *
     * @param {number} nodeId
     */
    generateItems(nodeId) {
      const items = generateItemsTree(appId, nodeId);

      contextMenu.items = items;
    }
  };
}

/**
 * Generates an items tree compatible with the context-menu web component
 * by traversing the given Flow DOM tree of context menu item nodes
 * whose root node is identified by the `nodeId` argument.
 *
 * The app id is required to access the store of Flow DOM nodes.
 *
 * @param {string} appId
 * @param {number} nodeId
 */
function generateItemsTree(appId, nodeId) {
  const container = getContainer(appId, nodeId);
  if (!container) {
    return;
  }

  return Array.from(container.children).map((child) => {
    const item = {
      component: child,
      checked: child._checked,
      keepOpen: child._keepOpen,
      className: child.className,
      theme: child.__theme
    };
    // Do not hardcode tag name to allow `vaadin-menu-bar-item`
    if (child._hasVaadinItemMixin && child._containerNodeId) {
      item.children = generateItemsTree(appId, child._containerNodeId);
    }
    child._item = item;
    return item;
  });
}

/**
 * Sets the checked state for a context menu item.
 *
 * This method is supposed to be called when the context menu item is closed,
 * so there is no need for triggering a re-render eagarly.
 *
 * @param {HTMLElement} component
 * @param {boolean} checked
 */
function setChecked(component, checked) {
  if (component._item) {
    component._item.checked = checked;

    // Set the attribute in the connector to show the checkmark
    // without having to re-render the whole menu while opened.
    if (component._item.keepOpen) {
      component.toggleAttribute('menu-item-checked', checked);
    }
  }
}

/**
 * Sets the keep open state for a context menu item.
 *
 * @param {HTMLElement} component
 * @param {boolean} keepOpen
 */
function setKeepOpen(component, keepOpen) {
  if (component._item) {
    component._item.keepOpen = keepOpen;
  }
}

/**
 * Sets the theme for a context menu item.
 *
 * This method is supposed to be called when the context menu item is closed,
 * so there is no need for triggering a re-render eagarly.
 *
 * @param {HTMLElement} component
 * @param {string | undefined | null} theme
 */
function setTheme(component, theme) {
  if (component._item) {
    component._item.theme = theme;
  }
}

window.Vaadin.Flow.contextMenuConnector = {
  initLazy,
  generateItemsTree,
  setChecked,
  setKeepOpen,
  setTheme
};
