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.tableColorSchema = [];
    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' && this.popoverRef) {
        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' && this.popoverRef) {
        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();
  }
  /**
   * Renders a vega-popover whose target and content are provided dynamically by callers.
   *
   * @param {VegaRTECreateElementFunction} h - The create element function.
   * @param {() => HTMLElement} getTableRef - Current table DOM reference
   * @param {RTETableBlock} renderData - Current table block being rendered.
   * @returns {VegaRTERenderResult} The rendered popover host.
   */
  render(h, getTableRef, renderData) {
    this.getTableRef = getTableRef;
    this.currentTableBlock = renderData;
    return h('vega-popover', {
      trigger: 'click',
      isScreenPosition: true,
      class: 'rte-table-dynamic-popover',
      // eslint-disable-next-line jsdoc/require-jsdoc
      ref: (popover) => {
        this.popoverRef = popover;
        DomNodeSubjectObserverFactory.addUniqueObserverToNode(popover, VegaPopoverHide, () => {
          const extensionContext = this.getTableExtensionContext();
          if (this.popoverCloseType === 'change' && extensionContext) {
            extensionContext.flushValue(extensionContext.host.value);
          }
        });
      },
    }, [
      h('div', {
        slot: 'popover',
        class: 'rte-table-dynamic-popover-content',
      }, [
        h('vega-rich-text-table-properties', {
          // eslint-disable-next-line jsdoc/require-jsdoc
          ref: (el) => (this.tablePropertiesFormRef = el),
          colors: this.tableColorSchema,
          type: TableSettingType.TABLE,
          onVegaCancel: this.handleTablePropertiesUpdate,
          onVegaChange: this.handleTablePropertiesUpdate,
          onVegaSave: this.handleTablePropertiesUpdate,
        }),
        h('vega-rich-text-table-properties', {
          // eslint-disable-next-line jsdoc/require-jsdoc
          ref: (el) => (this.cellPropertiesFormRef = el),
          colors: this.tableColorSchema,
          type: TableSettingType.CELL,
          onVegaCancel: this.handleCellPropertiesUpdate,
          onVegaChange: this.handleCellPropertiesUpdate,
          onVegaSave: this.handleCellPropertiesUpdate,
        }),
      ]),
    ]);
  }
  /**
   * 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 (this.tablePropertiesFormRef && type === 'TABLE') {
      this.tablePropertiesFormRef.classList.remove('vega-hidden');
      this.cellPropertiesFormRef.classList.add('vega-hidden');
      this.tablePropertiesFormRef.defaultValue =
        TablePropertiesEventHandler.generateTablePropertiesForm(this.currentTableBlock);
    }
    else if (this.cellPropertiesFormRef && 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;
    }
  }
}
