import remoteInvocationRegistry from '../../../../../helpers/remote-invocation/remote-invocation-registry';
import domNodeSubjectFactory from '../../../../../helpers/change-manager/subject/dom-node-subject/dom-node-subject-factory';
import { VegaInternalPopoverUpdateTarget, VegaInternalRTEExtensionTableCellsSelectionAnchor, } from '../../../../../helpers/event-manager/event-id/vega-internal-event-id';
import { tryGetDocument } from '../../../../../utils/try-get-document';
import { isNonNullable } from '../../../../../types/type-guard';
import { TableSettingType } from '../setting-event-handlers/setting-event-handler.abstract';
import stateEntityRenderingRegistry from '../../../slimmers/controllers/state-entity-rendering-registry';
import { DomNodeSubjectObserverFactory } from '../../../../../helpers/change-manager/subject/dom-node-subject/dom-node-subject-observer-factory';
import { TablePropertiesEventHandler } from '../setting-event-handlers/table-properties';
import { TablePropertiesCancelEventHandler } from '../setting-event-handlers/table-properties-cancel-event-handler';
import { TablePropertiesSaveEventHandler } from '../setting-event-handlers/table-properties-save-event-handler';
import { TablePropertiesChangeEventHandler } from '../setting-event-handlers/table-properties-change-event-handler';
import { CellPropertiesSaveEventHandler } from '../setting-event-handlers/cell-properties-save-event-handler';
import { CellPropertiesEventHandler } from '../setting-event-handlers/cell-properties';
import { CellPropertiesChangeEventHandler } from '../setting-event-handlers/cell-properties-change-event-handler';
import { CellPropertiesCancelEventHandler } from '../setting-event-handlers/cell-properties-cancel-event-handler';
import { VegaPopoverHide } from '../../../../../helpers/event-manager/event-id/vega-event-id';
import { TablePopoverBaseRenderer } from './table-popover-base-renderer';
/**
 * Renderer for a generic, dynamically positioned popover used within table-related extensions.
 *
 * - The popover target is based on the current cursor/selection position.
 * - The popover content is rendered dynamic content.
 */
export class TableDynamicPopoverRenderer extends TablePopoverBaseRenderer {
  constructor(colors, renderContext) {
    super(renderContext);
    this.popoverCloseType = 'none'; // 'save' | 'cancel' | 'change' | 'none'
    this.tablePropertiesHandlerMap = {
      save: new TablePropertiesSaveEventHandler(),
      cancel: new TablePropertiesCancelEventHandler(),
      change: new TablePropertiesChangeEventHandler(),
    };
    this.cellPropertiesHandlerMap = {
      save: new CellPropertiesSaveEventHandler(),
      cancel: new CellPropertiesCancelEventHandler(),
      change: new CellPropertiesChangeEventHandler(),
    };
    /**
     * Updates the table properties based on the emitted event.
     *
     * @param {CustomEvent} e - The custom event containing the update details.
     */
    this.handleTablePropertiesUpdate = (e) => {
      const eventKey = e.type.replace('vega', '').toLowerCase(); // 'save' | 'cancel' | 'change'
      this.popoverCloseType = eventKey;
      if (eventKey !== 'change') {
        void this.hide();
      }
      const extensionContext = this.getTableExtensionContext();
      if (extensionContext) {
        this.tablePropertiesHandlerMap[eventKey].handle(extensionContext, this.currentTableBlock, e.detail, this.currentFocusNode);
      }
      // Update popover position to follow the target after styles change
      if (eventKey === 'change') {
        void this.popoverRef.show(true);
      }
    };
    /**
     * Updates the cell properties based on the emitted event.
     *
     * @param {CustomEvent} e - The custom event containing the update details.
     */
    this.handleCellPropertiesUpdate = (e) => {
      const eventKey = e.type.replace('vega', '').toLowerCase(); // 'save' | 'cancel' | 'change'
      this.popoverCloseType = eventKey;
      if (eventKey !== 'change') {
        void this.hide();
      }
      const extensionContext = this.getTableExtensionContext();
      if (extensionContext) {
        this.cellPropertiesHandlerMap[eventKey].handle(extensionContext, e.detail, this.currentFocusNode);
      }
      // Update popover position to follow the target after styles change
      if (eventKey === 'change') {
        void this.popoverRef.show(true);
      }
    };
    this.tableColorSchema = colors;
  }
  /**
   * removes the multi-cell selection anchor element from the DOM, if present,
   * and clears the internal reference.
   */
  destroy() {
    if (this.selectionAnchorRef && this.selectionAnchorRef.parentNode) {
      DomNodeSubjectObserverFactory.removeUniqueObserverFromNode(this.selectionAnchorRef, VegaInternalRTEExtensionTableCellsSelectionAnchor);
      this.selectionAnchorRef.parentNode.removeChild(this.selectionAnchorRef);
      this.selectionAnchorRef = null;
    }
  }
  /**
   * Shows the popover. Before showing, it will refresh the target so
   * that the popover is positioned relative to the latest cursor position.
   *
   * @param {string} type - The table setting type determining which
   *  content variant to display (for example, table properties or cell properties).
   */
  async show(type) {
    let target = this.getTableRef();
    const extensionContext = this.getTableExtensionContext();
    let cells = [];
    if (extensionContext) {
      cells = extensionContext.tableSelection.getSelectedCells();
    }
    if (type === TableSettingType.CELL && cells.length > 1) {
      const anchor = this.getOrCreateSelectionAnchor(cells, target);
      if (anchor) {
        target = anchor;
      }
    }
    else if (type === TableSettingType.CELL && cells.length === 1) {
      target = stateEntityRenderingRegistry.getDOMByEntity(cells[0]);
    }
    this.updateContent(type);
    this.storeCurrentFocusNode();
    remoteInvocationRegistry.call(domNodeSubjectFactory.getSubject(this.popoverRef, VegaInternalPopoverUpdateTarget), target);
    await this.popoverRef.show();
  }
  /**
   * hide the popover.
   */
  async hide() {
    await this.popoverRef.hide();
  }
  /**
   * Re-targets the shared popovers to the given table.
   *
   * @param {RTETableBlock} tableBlock - The clicked table's block data.
   * @param {() => HTMLElement} getTableRef - Getter for the clicked table's DOM element.
   */
  reTarget(tableBlock, getTableRef) {
    this.getTableRef = getTableRef;
    this.currentTableBlock = tableBlock;
  }
  /**
   * Builds the popover DOM and appends it to the given container.
   *
   * @returns {Nullable<HTMLElement>} The constructed popover element.
   */
  render() {
    const safeDocument = tryGetDocument();
    if (safeDocument) {
      const popover = safeDocument.createElement('vega-popover');
      popover.trigger = 'click';
      popover.isScreenPosition = true;
      popover.className = 'rte-table-dynamic-popover';
      DomNodeSubjectObserverFactory.addUniqueObserverToNode(popover, VegaPopoverHide, () => {
        const extensionContext = this.getTableExtensionContext();
        if (this.popoverCloseType === 'change' && extensionContext) {
          extensionContext.flushValue(extensionContext.host.value);
        }
      });
      const slotDiv = safeDocument.createElement('div');
      slotDiv.setAttribute('slot', 'popover');
      slotDiv.className = 'rte-table-dynamic-popover-content';
      const tablePropsForm = safeDocument.createElement('vega-rich-text-table-properties');
      tablePropsForm.colors = this.tableColorSchema;
      tablePropsForm.type = TableSettingType.TABLE;
      tablePropsForm.setAttribute('type', TableSettingType.TABLE);
      tablePropsForm.addEventListener('vegaCancel', this.handleTablePropertiesUpdate);
      tablePropsForm.addEventListener('vegaChange', this.handleTablePropertiesUpdate);
      tablePropsForm.addEventListener('vegaSave', this.handleTablePropertiesUpdate);
      const cellPropsForm = safeDocument.createElement('vega-rich-text-table-properties');
      cellPropsForm.colors = this.tableColorSchema;
      cellPropsForm.type = TableSettingType.CELL;
      cellPropsForm.setAttribute('type', TableSettingType.CELL);
      cellPropsForm.addEventListener('vegaCancel', this.handleCellPropertiesUpdate);
      cellPropsForm.addEventListener('vegaChange', this.handleCellPropertiesUpdate);
      cellPropsForm.addEventListener('vegaSave', this.handleCellPropertiesUpdate);
      slotDiv.appendChild(tablePropsForm);
      slotDiv.appendChild(cellPropsForm);
      popover.appendChild(slotDiv);
      this.popoverRef = popover;
      this.tablePropertiesFormRef = tablePropsForm;
      this.cellPropertiesFormRef = cellPropsForm;
      return popover;
    }
  }
  /**
   * Updates the visibility of the pre-rendered content blocks based on type.
   *
   * @param {string} type - The table setting type determining which content variant should be visible.
   */
  updateContent(type) {
    this.popoverCloseType = 'none';
    const extensionContext = this.getTableExtensionContext();
    if (type === 'TABLE') {
      this.tablePropertiesFormRef.classList.remove('vega-hidden');
      this.cellPropertiesFormRef.classList.add('vega-hidden');
      this.tablePropertiesFormRef.defaultValue =
        TablePropertiesEventHandler.generateTablePropertiesForm(this.currentTableBlock);
    }
    else if (type === 'CELL') {
      this.cellPropertiesFormRef.classList.remove('vega-hidden');
      this.tablePropertiesFormRef.classList.add('vega-hidden');
      if (extensionContext) {
        this.cellPropertiesFormRef.styleMap = CellPropertiesEventHandler.getStyleCollectionFromCell(extensionContext.tableSelection.getSelectedCells());
      }
      this.cellPropertiesFormRef.defaultValue = CellPropertiesEventHandler.convertStyleMapToFormField(this.cellPropertiesFormRef.styleMap);
    }
    // when open the properties dialog
    // the properties form will set default value
    // this will trigger selectionchange event
    // the `rte` selection should be cleared.
    // Otherwise, it will cause `getSelection` to retrieve incorrect data in the `selectionchange` callback.
    // For example, in table selection.
    extensionContext && extensionContext.getSelection().removeAllRanges();
  }
  /**
   * Stores the current focus node before properties update action.
   */
  storeCurrentFocusNode() {
    const extensionContext = this.getTableExtensionContext();
    extensionContext && (this.currentFocusNode = extensionContext.getSelectedNodes()[0]);
  }
  /**
   * Create and position an invisible anchor element whose position and size are identical to the geometry of the current multi-cell selection.
   * This anchor element will then be used as the target of the popup, so that the popup appears near the entire selection area.
   *
   * @param {RTETableCellBlock[]} cells - The currently selected table cells.
   * @param {Nullable<HTMLElement>} tableRef - The container table element.
   * @returns {Nullable<HTMLElement>} The positioned anchor element.
   */
  getOrCreateSelectionAnchor(cells, tableRef) {
    const safeDocument = tryGetDocument();
    if (safeDocument && tableRef) {
      const container = tableRef.closest('.rich-text-content');
      const parentRect = container.getBoundingClientRect();
      let leftViewport = Number.POSITIVE_INFINITY;
      let rightViewport = Number.NEGATIVE_INFINITY;
      let topViewport = Number.POSITIVE_INFINITY;
      let bottomViewport = Number.NEGATIVE_INFINITY;
      cells
        .map((cell) => stateEntityRenderingRegistry.getDOMByEntity(cell))
        .filter(isNonNullable)
        .forEach((el) => {
        const elRect = el.getBoundingClientRect();
        // get the minimum and maximum cell edges to get the bounding rectangle of the whole selection relative to the container.
        leftViewport = Math.min(leftViewport, elRect.left - parentRect.left);
        rightViewport = Math.max(rightViewport, elRect.right - parentRect.left);
        topViewport = Math.min(topViewport, elRect.top - parentRect.top);
        bottomViewport = Math.max(bottomViewport, elRect.bottom - parentRect.top);
      });
      const widthViewport = rightViewport - leftViewport;
      const heightViewport = bottomViewport - topViewport;
      this.selectionAnchorRef = container.querySelector('.rte-table-selection-popover-anchor');
      if (!this.selectionAnchorRef) {
        this.selectionAnchorRef = safeDocument.createElement('div');
        this.selectionAnchorRef.className = 'rte-table-selection-popover-anchor';
        this.selectionAnchorRef.style.position = 'absolute';
        this.selectionAnchorRef.style.pointerEvents = 'none';
        container.appendChild(this.selectionAnchorRef);
        DomNodeSubjectObserverFactory.addUniqueObserverToNode(this.selectionAnchorRef, VegaInternalRTEExtensionTableCellsSelectionAnchor, () => {
          this.destroy();
        });
      }
      this.selectionAnchorRef.style.left = `${leftViewport}px`;
      this.selectionAnchorRef.style.top = `${topViewport}px`;
      this.selectionAnchorRef.style.width = `${widthViewport}px`;
      this.selectionAnchorRef.style.height = `${heightViewport}px`;
      return this.selectionAnchorRef;
    }
  }
}
