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

const TagSelect = lazy(() => import(/* webpackChunkName: "tag-select" */ './components/tag-select/tag-select'));

/**
 * カンマ区切りの文字列（data-default-value / hidden input の値表現）を TagOption[] に変換する。
 */
export function createTagDefaultValue(defaultValueString: string): TagOption[] {
  const defaultValues = defaultValueString.split(',').map((value) => value.trim());
  return defaultValues
    .map((value) => ({
      label: value,
      value,
    }))
    .filter((option) => option.value && option.label);
}

export interface TagSelectFieldOptions {
  /** 初期表示の値（カンマ区切り）。省略時は空扱い。 */
  defaultValue?: string;
  id?: string;
  inputId?: string;
  isDisabled?: boolean;
  form?: string;
  name?: string;
  menuPortalTarget?: HTMLElement | null;
  /** host（`<acms-tag-select>`）の class 属性から転送される。TagSelect のルート要素へ合成する。 */
  className?: string;
}

export interface TagSelectFieldController {
  /** 新しい options（defaultValue 含む）で再マウントし、表示へ反映する */
  update: (options: TagSelectFieldOptions) => void;
  unmount: () => void;
}

/**
 * mountContainer に TagSelect をマウントし、選択内容を input（フォーム送信の実体となる
 * hidden input）へ同期する。input に書き込むだけで、input 自体の生成・保持は行わない。
 * `<acms-tag-select>`（elements/tag-select-element.ts）から呼ばれる共有ロジック。
 */
export function mountTagSelectField(
  mountContainer: HTMLElement,
  input: HTMLInputElement,
  initialOptions: TagSelectFieldOptions = {}
): TagSelectFieldController {
  let key = 0;

  const buildElement = (options: TagSelectFieldOptions) => {
    const { defaultValue: defaultValueString = '', ...rest } = options;
    return (
      <Suspense key={key} fallback={null}>
        <TagSelect
          defaultValue={createTagDefaultValue(defaultValueString)}
          onChange={(newValue) => {
            input.value = newValue.map((option) => option.value).join(',');
            void emit('tag.select.change', { target: input });
            triggerEvent(input, 'acms.tag.change', { bubbles: true });
          }}
          {...rest}
        />
      </Suspense>
    );
  };

  const root = render(buildElement(initialOptions), mountContainer);

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

export interface TagSelectAttributesResult {
  controller: TagSelectFieldController;
  /** フォーム送信の実体となる input（target が解決した外部 input、または host 内の hidden input） */
  input: HTMLInputElement;
}

/**
 * 独自属性（テンプレート `admin/include/tag/select.html` が出力するもの）から TagSelectField を
 * マウントする。同期先の input は次のどちらかで解決する。
 * - `target`（CSS セレクタ）が指定されていれば、それが指す外部の input（`context` 配下）を使う
 *   （後方互換のための経路）。
 * - `target` が無ければ、host 自身の子孫に置かれた最初の `input[type="hidden"]` を使う
 *   （`<acms-block-editor>` と同じ「自己完結」の使い方）。
 *
 * React は host 自身ではなく、host 内に動的に追加する専用のマウントコンテナへレンダリングする。
 * host 自身を React のマウント先にすると、host の子孫に置いた hidden input（値の同期先）が
 * React のレンダリング結果で上書きされて消えてしまうため（block-editor の
 * mountBlockEditorField と同じ理由。target 経由で外部の input を使う場合も含め、常にこの
 * マウントコンテナ方式に統一する）。
 *
 * @throws {Error} target 属性・host 内の hidden input のいずれも無い場合、または target の
 *   参照先の input が見つからない場合
 */
export function mountTagSelectFromAttributes(
  host: HTMLElement,
  dataset: DOMStringMap,
  context: Document | Element = document
): TagSelectAttributesResult {
  const {
    target: selector,
    defaultValue = '',
    id,
    inputId,
    isDisabled,
    form,
    name,
    menuPortalTarget: menuPortalTargetSelector,
    className,
  } = dataset;

  const input = selector
    ? context.querySelector<HTMLInputElement>(selector)
    : host.querySelector<HTMLInputElement>('input[type="hidden"]');

  if (!input) {
    throw new Error(selector ? 'Not found input element!' : 'Not found target attribute or hidden input element!');
  }

  const mountContainer = document.createElement('div');
  host.append(mountContainer);

  const controller = mountTagSelectField(mountContainer, input, {
    defaultValue,
    id,
    inputId,
    isDisabled: isDisabled === 'true',
    form,
    name,
    menuPortalTarget: menuPortalTargetSelector
      ? document.querySelector<HTMLElement>(menuPortalTargetSelector)
      : undefined,
    className,
  });

  return { controller, input };
}
