/**
 * ProseMirror implementation of the HTML disclosure widget.
 */
export default class DisclosureWidget {
  /**
   * @param {Node} node            The node this view represents.
   * @param {EditorView} view      The parent EditorView.
   * @param {() => number} getPos  A function that returns the node's current position.
   */
  constructor(node, view, getPos) {
    this.#node = node;
    this.#view = view;
    this.dom = this.contentDOM = view.dom.ownerDocument.createElement("details");
    this.getPos = getPos;
    if ( node.attrs.open ) this.dom.open = true;
    this.dom.addEventListener("click", this.#onClick.bind(this));
  }

  /* -------------------------------------------- */
  /*  Properties                                  */
  /* -------------------------------------------- */

  /**
   * The DOM node to render the document node's children into.
   * @type {HTMLDetailsElement}
   */
  contentDOM;

  /* -------------------------------------------- */

  /**
   * The outer DOM node that represents the document node.
   * @type {HTMLDetailsElement}
   */
  dom;

  /* -------------------------------------------- */

  /**
   * A function that returns the node's current position.
   * @type {() => number}
   */
  getPos;

  /* -------------------------------------------- */

  /**
   * The ProseMirror document node represented by this view.
   * @type {Node}
   */
  #node;

  /* -------------------------------------------- */

  /**
   * The EditorView this NodeView belongs to.
   * @type {EditorView}
   */
  #view;

  /* -------------------------------------------- */
  /*  Methods                                     */
  /* -------------------------------------------- */

  /**
   * When the EditorView updates itself, determine if this view can update to the given node.
   * @param {Node} node  The node.
   * @returns {boolean}
   */
  update(node) {
    return node.type === this.#view.state.schema.nodes.details;
  }

  /* -------------------------------------------- */
  /*  Event Listeners & Handlers                  */
  /* -------------------------------------------- */

  /**
   * Handle interaction with the disclosure widget while inside an editor view.
   * @param {PointerEvent} event  The triggering event.
   */
  #onClick(event) {
    if ( !event.target.closest("summary") && (event.target.tagName !== "DETAILS") ) return;
    const attrs = { ...this.#node.attrs };
    if ( attrs.open ) delete attrs.open;
    else attrs.open = true;
    const tr = this.#view.state.tr.setNodeMarkup(this.getPos(), null, attrs);
    this.#view.dispatch(tr);
  }

  /* -------------------------------------------- */
  /*  Factory Methods                             */
  /* -------------------------------------------- */

  /**
   * Static instantiator function for the NodeView that can be passed to a new EditorView.
   * @param {Node} node            The node this view represents.
   * @param {EditorView} view      The parent EditorView.
   * @param {() => number} getPos  A function that returns the node's current position.
   * @returns {DisclosureWidget}
   */
  static view(node, view, getPos) {
    return new DisclosureWidget(node, view, getPos);
  }

  /* -------------------------------------------- */
  /*  Schema                                      */
  /* -------------------------------------------- */

  /**
   * Return the specs for the disclosure widget nodes.
   * @type {Record<string, NodeSpec>}
   */
  static get nodes() {
    return {
      details: {
        group: "block",
        content: "summary? block*",
        managed: {
          attributes: ["open"]
        },
        attrs: {
          open: { default: false }
        },
        parseDOM: [{
          tag: "details",
          getAttrs: el => ({ open: el.hasAttribute("open") })
        }],
        toDOM: node => node.attrs.open ? ["details", { open: "" }, 0] : ["details", 0]
      },
      summary: {
        content: "inline*",
        parseDOM: [{ tag: "summary" }],
        toDOM: () => ["summary", 0]
      }
    };
  }
}
