import { Suspense, lazy, StrictMode } from 'react';
import { emit } from '@ablogcms/events';
import { render } from '@ablogcms/react-utils';
import { triggerEvent } from '@ablogcms/utils';
import { RelatedEntryType } from './types';

const RelatedEntries = lazy(
  () => import(/* webpackChunkName: "related-entries" */ './components/related-entries/related-entries')
);

export interface RelatedEntryFieldOptions {
  type: string;
  title: string;
  moduleId: string;
  ctx: string;
  thumbnail?: string;
  maxItem: number;
  className?: string;
}

export interface RelatedEntryFieldController {
  /** 現在の entries / options で再マウントする */
  update: (entries: RelatedEntryType[], options?: RelatedEntryFieldOptions) => void;
  unmount: () => void;
}

/**
 * container に RelatedEntries をマウントし、変更を acms.related-entry.change として container へ通知する。
 * `<acms-related-entry>`（elements/related-entry-element.ts）から呼ばれる共有ロジック。
 *
 * RelatedEntries 自身が related[] / related_type[] などの hidden input をレンダリングするため、
 * このモジュールは外部向けの hidden input を別途持たない。
 */
export function mountRelatedEntryField(
  container: HTMLElement,
  entries: RelatedEntryType[],
  initialOptions: RelatedEntryFieldOptions
): RelatedEntryFieldController {
  let key = 0;

  const buildElement = (entries: RelatedEntryType[], options: RelatedEntryFieldOptions) => {
    const { type, title, moduleId, ctx, thumbnail, maxItem, className } = options;
    return (
      <Suspense key={key} fallback={null}>
        <RelatedEntries
          entries={entries}
          type={type}
          title={title}
          moduleId={moduleId}
          ctx={ctx}
          thumbnail={thumbnail}
          maxItem={maxItem}
          className={className}
          onChange={(newEntries) => {
            void emit('related-entry.select.change', {
              target: container,
              entries: newEntries,
              type,
              title,
              moduleId,
              ctx,
              maxItem,
            });
            triggerEvent(container, 'acms.related-entry.change', {
              bubbles: true,
              detail: { entries: newEntries, type, title, moduleId, ctx, maxItem },
            });
          }}
        />
      </Suspense>
    );
  };

  const root = render(buildElement(entries, initialOptions), container);

  return {
    update(newEntries, options = initialOptions) {
      // entries は RelatedEntries のマウント時にしか初期化されない（内部 state）ため、
      // 外部から値を書き換えて反映させるには Suspense の key を変えて丸ごと再マウントする。
      key += 1;
      root.render(<StrictMode>{buildElement(newEntries, options)}</StrictMode>);
    },
    unmount() {
      root.unmount();
    },
  };
}

export interface RelatedEntryAttributesResult {
  controller: RelatedEntryFieldController;
  entries: RelatedEntryType[];
  options: RelatedEntryFieldOptions;
}

/**
 * `<acms-related-entry>` の属性（テンプレート `admin/entry/related-table.html` が出力するもの。
 * `title` のみ HTML 標準のグローバル属性と衝突するため data-* のまま）から RelatedEntryField を
 * マウントする。第2引数は `HTMLElement.dataset` 互換（camelCase キー）であれば良く、呼び出し側
 * （`elements/related-entry-element.ts`）が data-* から読むか独自属性から読むかは問わない。
 * `jsonId` が指す `<script>` の中身を初期 entries として使う。
 *
 * @throws {Error} json-id が無い場合
 */
export function mountRelatedEntryFromAttributes(
  host: HTMLElement,
  dataset: DOMStringMap,
  context: Document | Element = document
): RelatedEntryAttributesResult {
  const type = dataset.type ?? '';
  const title = dataset.title ?? '';
  const moduleId = dataset.moduleId ?? '';
  const ctx = dataset.ctx ?? '';
  const thumbnail = dataset.thumbnail ?? '';
  const maxItem = parseInt(dataset.maxItem ?? '0', 10);
  const jsonId = dataset.jsonId ?? '';
  const className = dataset.className;

  if (!jsonId) {
    throw new Error('Not Found json-id attribute.');
  }

  const ownerDocument = context instanceof Document ? context : (context.ownerDocument ?? document);
  const json = ownerDocument.getElementById(jsonId)?.innerHTML || '[]';
  const entries: RelatedEntryType[] = JSON.parse(json);
  const options: RelatedEntryFieldOptions = { type, title, moduleId, ctx, thumbnail, maxItem, className };

  const controller = mountRelatedEntryField(host, entries, options);

  return { controller, entries, options };
}
