import { type Editor } from '@ablogcms/unit-editor/core';
import { mountUnitEditorFromAttributes, type UnitEditorFieldController } from '@features/unit-editor/mount-unit-editor';
import { resetMountMarker, readForwardedHostAttributes } from './element-helpers';

/**
 * host が持つ、実際にレンダリングされる UnitEditor のルート要素へ転送する独自属性。
 * host は display: contents（register.ts 参照）で自身のボックスを持たないため、host に標準の
 * class を当てても見た目には反映されない。テンプレート側は最初から `root-class` を独自属性として
 * 書く（詳細は README.md「共通の設計」参照）。
 */
const FORWARDED_HOST_ATTRIBUTES = ['root-class'] as const;

/**
 * `<acms-unit-editor>` カスタム要素。
 *
 * エントリー本文（ユニットツリー）を編集する UnitEditor をマウントする。Light DOM に直接
 * React（UnitEditor、内部で自身の `.acms-admin-unit-editor` ラッパーを描画する）をマウントし、
 * 自身は追加の DOM を持たない。マウント処理そのものは
 * features/unit-editor/mount-unit-editor.tsx の mountUnitEditorFromAttributes を通じて行う。
 *
 * 外部 API:
 * - `editor: Editor | null`（get のみ）— unit-editor コアの Editor インスタンス。マウント完了前は
 *   null。`window.ACMS.unitEditor`（`packages/frontend/unit-editor` の useUnitEditor が自動同期する
 *   既存のグローバル公開）と同一インスタンスを指す。
 * - `change` イベント（CustomEvent, bubbles）: ユニットツリーの transaction（内容変更）を検知して発火する
 *
 * 属性は `admin/_layouts/entry/edit.html` が出力する独自属性（data-* ではない。カスタム要素自身の
 * 属性はその要素の公開 API であり HTML 標準要素向けの data-* 制約は適用されないため）
 * default-value（ユニットツリーの JSON）/ settings（UnitEditorSettings の JSON、
 * Admin_Unit_EditorSettings モジュールが出力）を connectedCallback 時に一度だけ読む。詳細は
 * ablogcms/js/src/elements/README.md を参照。
 */
export class UnitEditorElement extends HTMLElement {
  private controller: UnitEditorFieldController | null = null;

  private editorInstance: Editor | null = null;

  connectedCallback(): void {
    if (this.controller) {
      return;
    }

    try {
      this.controller = mountUnitEditorFromAttributes(this, {
        onEditorReady: (editor) => {
          this.editorInstance = editor;
        },
        onChange: () => {
          this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
        },
        ...readForwardedHostAttributes(this, FORWARDED_HOST_ATTRIBUTES),
      });
    } catch (error) {
      // 単一要素の初期化失敗が管理画面全体を止めないよう、ここで握りつぶして報告する。

      console.error(error);
    }
  }

  disconnectedCallback(): void {
    const controller = this.controller;
    // Why not: この要素がカスタムユニットの中に埋め込まれる等で物理的に DOM 移動されると、
    // disconnectedCallback → connectedCallback が同期的に再発火する。this.controller を
    // 同期的に null にすると、再発火した connectedCallback のガードが効かず、まだ unmount
    // されていない旧 root が残ったまま同じ host（React はここへ直接レンダリングしている）に
    // 新しい root を二重に作ってしまう。後片付け全体をマイクロタスクへ遅らせ、その時点で
    // this.isConnected を見て「本当に取り外されたのか、移動で戻ってきたのか」を判定する
    // （詳細は elements/block-editor-element.ts の同名コメント参照）。
    queueMicrotask(() => {
      if (this.isConnected) {
        return;
      }
      controller?.unmount();
      this.controller = null;
      this.editorInstance = null;
      resetMountMarker(this);
    });
  }

  get editor(): Editor | null {
    return this.editorInstance;
  }
}
