/* Copyright 2026 Marimo. All rights reserved. */

import { init } from "@paralleldrive/cuid2";
import { sidebarAtom } from "@/components/editor/renderers/vertical-layout/sidebar/state";
import { defineCustomElement } from "@/core/dom/defineCustomElement";
import { SlotNames, slotsController } from "@/core/slots/slots";
import { store } from "@/core/state/jotai";
import { Logger } from "@/utils/Logger";
import { renderHTML } from "./RenderHTML";

const createId = init({ length: 6 });

/**
 * This is a custom DOM element that will be used to portal
 * it's children to a React component sidebar.
 */
export function initializeSidebarElement(): void {
  const SidebarComponent = class extends HTMLElement {
    private observer?: MutationObserver;
    private uniqueId = Symbol(createId());

    connectedCallback() {
      // Render the component for the first time
      this.mountReactComponent();

      // This observer is used to detect changes to the children and re-render the component
      this.observer = new MutationObserver(() => {
        this.updateReactComponent();
      });

      this.style.display = "none";

      // Listen for DOM changes
      this.observer.observe(this, {
        attributes: true,
        childList: true,
        subtree: true,
        characterData: true,
      });
    }

    disconnectedCallback() {
      if (this.observer) {
        this.observer.disconnect();
        this.unmountReactComponent();
      }
    }

    private mountReactComponent() {
      this.syncWidth();
      slotsController.mount({
        name: SlotNames.SIDEBAR,
        ref: this.uniqueId,
        children: this.getContents(),
      });
    }

    private unmountReactComponent() {
      slotsController.unmount({
        name: SlotNames.SIDEBAR,
        ref: this.uniqueId,
      });
    }

    private updateReactComponent() {
      this.syncWidth();
      slotsController.update({
        name: SlotNames.SIDEBAR,
        ref: this.uniqueId,
        children: this.getContents(),
      });
    }

    // Grab the data-width attribute from the element and set the width in the store
    // This is used to set the width of the sidebar when it is opened
    private syncWidth(): void {
      try {
        const width = this.dataset.width;
        if (width) {
          store.set(sidebarAtom, {
            type: "setWidth",
            width: JSON.parse(width) as string,
          });
        } else {
          store.set(sidebarAtom, {
            type: "setWidth",
            width: undefined,
          });
        }
      } catch (error) {
        Logger.error(error);
      }
    }

    /**
     * Get the children of the element as React nodes.
     */
    private getContents(): React.ReactNode {
      return renderHTML({ html: this.innerHTML });
    }
  };

  defineCustomElement("marimo-sidebar", SidebarComponent);
}
