import { emit } from '@ablogcms/events';
import { Suspense, lazy, StrictMode } from 'react';
import { render } from '@ablogcms/react-utils';
import { triggerEvent } from '@ablogcms/utils';
import { appendMountContainer, hasContentOtherThanHiddenInputsOrMountContainer } from '../../elements/element-helpers';

const MOUNT_CONTAINER_MARKER = 'data-category-select-mount';

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

export interface CategorySelectFieldOptions {
  /** 初期表示の値（カテゴリー ID）。省略時は未選択扱い。 */
  defaultValue?: string;
  narrowDown?: boolean;
  isCreatable?: boolean;
  isClearable?: boolean;
  noOption?: boolean;
  mtOption?: boolean;
  id?: string;
  inputId?: string;
  isDisabled?: boolean;
  form?: string;
  name?: string;
  menuPortalTarget?: HTMLElement | null;
  /** host（`<acms-category-select>`）の class 属性から転送される。CategorySelect のルート要素へ合成する。 */
  className?: string;
}

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

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

  const buildElement = (options: CategorySelectFieldOptions) => {
    const { defaultValue, ...rest } = options;
    return (
      <Suspense key={key} fallback={null}>
        <CategorySelect
          defaultValue={defaultValue || undefined}
          onChange={(newValue) => {
            inputs.forEach((input) => {
              input.value = newValue?.value || '';
              void emit('category.select.change', { target: input });
              triggerEvent(input, 'acms.category.change', { bubbles: true });
            });
          }}
          {...rest}
        />
      </Suspense>
    );
  };

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

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

export interface CategorySelectAttributesResult {
  controller: CategorySelectFieldController;
  /**
   * フォーム送信の実体となる input 群（target が複数マッチした場合は先頭が主、以降は同値を
   * 複製する input。host 内の hidden input を使う場合は最初の1つだけの単一要素配列）
   */
  inputs: HTMLInputElement[];
}

/**
 * 独自属性（テンプレート `admin/include/category/select.html` が出力するもの）から
 * CategorySelectField をマウントする。同期先の inputs は次のどちらかで解決する。
 * - `target`（CSS セレクタ）が指定されていれば、それが指す外部の input 群（`context` 配下、
 *   複数マッチ時は全てへ複製同期する）を使う（後方互換のための経路）。
 * - `target` が無ければ、host 自身の子孫に置かれた最初の `input[type="hidden"]` **1つだけ**を
 *   使う（`target` と違い複数への複製は行わない。`<acms-block-editor>` と同じ「自己完結」の
 *   使い方）。
 *
 * React は host 自身ではなく、host 内に動的に追加する専用のマウントコンテナへレンダリングする
 * （block-editor の mountBlockEditorField と同じ理由。target 経由で外部の input を使う場合も
 * 含め、常にこのマウントコンテナ方式に統一する）。
 *
 * host に hidden input・マウントコンテナ以外のコンテンツが既にある場合は、二重マウント（例:
 * static-export の `sortable-item item-template` 行複製で既にマウント済みの subtree が
 * そのまま複製されるケース）とみなし、何もマウントせず null を返す。
 *
 * @throws {Error} target 属性・host 内の hidden input のいずれも無い場合、または target の
 *   参照先の input が見つからない場合
 */
export function mountCategorySelectFromAttributes(
  host: HTMLElement,
  dataset: DOMStringMap,
  context: Document | Element = document
): CategorySelectAttributesResult | null {
  if (hasContentOtherThanHiddenInputsOrMountContainer(host, MOUNT_CONTAINER_MARKER)) {
    return null;
  }

  const {
    target: selector,
    defaultValue = '',
    isCreatable = 'false',
    narrowDown = 'false',
    isClearable = 'true',
    noOption = 'false',
    mtOption = 'false',
    id,
    inputId,
    isDisabled,
    form,
    name,
    menuPortalTarget: menuPortalTargetSelector,
    className,
  } = dataset;

  const inputs = selector
    ? Array.from(context.querySelectorAll<HTMLInputElement>(selector))
    : Array.from(host.querySelectorAll<HTMLInputElement>('input[type="hidden"]')).slice(0, 1);

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

  const mountContainer = appendMountContainer(host, MOUNT_CONTAINER_MARKER);

  const controller = mountCategorySelectField(mountContainer, inputs, {
    defaultValue,
    narrowDown: narrowDown === 'true',
    isCreatable: isCreatable === 'true',
    isClearable: isClearable === 'true',
    mtOption: mtOption === 'true',
    noOption: noOption === 'true',
    id,
    inputId,
    isDisabled: isDisabled === 'true',
    form,
    name,
    menuPortalTarget: menuPortalTargetSelector
      ? document.querySelector<HTMLElement>(menuPortalTargetSelector)
      : undefined,
    className,
  });

  return { controller, inputs };
}
