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

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

const DEFAULT_CATEGORY_TARGET = '[name=category_id]';

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

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

interface SubCategorySelectFieldProps {
  input: HTMLInputElement;
  categoryInput: HTMLInputElement | null;
  options: SubCategorySelectFieldOptions;
}

const isValidLimit = (value: unknown): value is number => {
  const num = typeof value === 'string' ? Number(value) : value;
  return typeof num === 'number' && !isNaN(num) && isFinite(num) && num >= 0;
};

/**
 * カテゴリー選択（categoryInput）の acms.category.change を購読し、mainCategoryId に反映しつつ
 * SubCategorySelect をレンダリングする。`<acms-sub-category-select>` から呼ばれる共有ロジック。
 */
export function SubCategorySelectField({ input, categoryInput, options }: SubCategorySelectFieldProps) {
  const [categoryId, setCategoryId] = useState(parseInt(categoryInput?.value || '0', 10));

  useEffect(() => {
    function handleChange(event: Event) {
      if (!(event.target instanceof HTMLInputElement)) {
        return;
      }
      setCategoryId(parseInt(event.target.value, 10));
    }

    categoryInput?.addEventListener('acms.category.change', handleChange);

    return () => {
      categoryInput?.removeEventListener('acms.category.change', handleChange);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const { defaultValue: defaultValueString = '', defaultLabel: defaultLabelString = '', limit, ...rest } = options;
  const defaultValue = createSubCategoryDefaultValue(defaultValueString, defaultLabelString);

  return (
    <SubCategorySelect
      mainCategoryId={categoryId}
      defaultValue={defaultValue}
      onChange={(newValue) => {
        input.value = newValue.map((option) => option.value).join(',');
        void emit('sub-category.select.change', { target: input });
        triggerEvent(input, 'acms.sub-category.change', { bubbles: true });
      }}
      {...(isValidLimit(limit) && { limit })}
      {...rest}
    />
  );
}

export interface SubCategorySelectFieldController {
  update: (options: SubCategorySelectFieldOptions) => void;
  unmount: () => void;
}

/**
 * mountContainer に SubCategorySelectField をマウントする。input（フォーム送信の実体となる
 * hidden input）へ選択値を同期する。
 * `<acms-sub-category-select>`（elements/sub-category-select-element.ts）から呼ばれる共有ロジック。
 */
export function mountSubCategorySelectField(
  mountContainer: HTMLElement,
  input: HTMLInputElement,
  categoryInput: HTMLInputElement | null,
  initialOptions: SubCategorySelectFieldOptions = {}
): SubCategorySelectFieldController {
  let key = 0;

  const buildElement = (options: SubCategorySelectFieldOptions) => (
    <Suspense key={key} fallback={null}>
      <SubCategorySelectField input={input} categoryInput={categoryInput} options={options} />
    </Suspense>
  );

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

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

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

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

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

  const controller = mountSubCategorySelectField(mountContainer, input, categoryInput, {
    defaultValue,
    defaultLabel,
    id,
    inputId,
    isDisabled: isDisabled === 'true',
    form,
    name,
    menuPortalTarget: menuPortalTargetSelector
      ? document.querySelector<HTMLElement>(menuPortalTargetSelector)
      : undefined,
    ...(limitString !== undefined && { limit: Number(limitString) }),
    className,
  });

  return { controller, input };
}
