import { describe, it, expect, vi, afterEach } from 'vitest';
import { waitFor, fireEvent } from '@testing-library/react';
import { createSubCategoryDefaultValue, mountSubCategorySelectFromAttributes } from './mount-sub-category-select';
import type { SubCategoryOption } from './types';

vi.mock('./components/sub-category-select/sub-category-select', () => ({
  default: ({
    defaultValue,
    onChange,
    mainCategoryId,
    limit,
  }: {
    defaultValue: SubCategoryOption[];
    onChange: (value: SubCategoryOption[]) => void;
    mainCategoryId?: number;
    limit?: number;
  }) => (
    <div data-testid="sub-category-select-stub" data-main-category-id={mainCategoryId} data-limit={limit}>
      <span data-testid="default-value">{JSON.stringify(defaultValue)}</span>
      <button type="button" onClick={() => onChange([{ label: 'new', value: 'new' }])}>
        change
      </button>
    </div>
  ),
}));

describe('createSubCategoryDefaultValue', () => {
  it('カンマ区切りの value / label を SubCategoryOption[] に変換する', () => {
    expect(createSubCategoryDefaultValue('1,2', 'ラベル1,ラベル2')).toEqual([
      { label: 'ラベル1', value: '1' },
      { label: 'ラベル2', value: '2' },
    ]);
  });
});

describe('mountSubCategorySelectFromAttributes', () => {
  afterEach(() => {
    document.body.innerHTML = '';
    vi.clearAllMocks();
  });

  it('target も host 内の hidden input も無い場合はエラーを投げる', () => {
    const host = document.createElement('div');
    expect(() => mountSubCategorySelectFromAttributes(host, {})).toThrow(
      'Not found target attribute or hidden input element!'
    );
  });

  it('target 属性の参照先の input が見つからない場合はエラーを投げる', () => {
    const host = document.createElement('div');
    expect(() => mountSubCategorySelectFromAttributes(host, { target: '#missing' })).toThrow(
      'Not found input element!'
    );
  });

  // Why: target 属性を使わず、<acms-sub-category-select> 自身の子孫に hidden input を直接
  // 置くだけで動くようにする（block-editor と同じ「自己完結」の使い方を選べるようにするため）。
  it('target が無くても host 内の hidden input を検出して使う', async () => {
    document.body.innerHTML = `<input name="category_id" value="7" />`;
    const host = document.createElement('div');
    host.innerHTML = `<input type="hidden" name="sub_category" value="" />`;
    document.body.appendChild(host);
    const input = host.querySelector('input') as HTMLInputElement;

    const result = mountSubCategorySelectFromAttributes(host, { defaultValue: '1', defaultLabel: 'ラベル1' });

    expect(result.input).toBe(input);

    await waitFor(() => {
      expect(document.querySelector('[data-testid="sub-category-select-stub"]')).not.toBeNull();
    });
    expect(input.isConnected).toBe(true);

    fireEvent.click(document.querySelector('button')!);
    expect(input.value).toBe('new');
  });

  it('data-category-id をセレクタとして解決し、data-default-value / data-default-label を使う', async () => {
    document.body.innerHTML = `
      <input id="hidden-sub-category" value="" />
      <input id="main-category" value="3" />
    `;
    const host = document.createElement('div');
    document.body.appendChild(host);

    mountSubCategorySelectFromAttributes(host, {
      target: '#hidden-sub-category',
      categoryId: '#main-category',
      defaultValue: '1,2',
      defaultLabel: 'ラベル1,ラベル2',
      limit: '3',
    });

    await waitFor(() => {
      expect(document.querySelector('[data-testid="sub-category-select-stub"]')).not.toBeNull();
    });

    const stub = document.querySelector('[data-testid="sub-category-select-stub"]')!;
    expect(stub.getAttribute('data-main-category-id')).toBe('3');
    expect(stub.getAttribute('data-limit')).toBe('3');
    expect(JSON.parse(document.querySelector('[data-testid="default-value"]')?.textContent || '[]')).toEqual([
      { label: 'ラベル1', value: '1' },
      { label: 'ラベル2', value: '2' },
    ]);
  });

  it('data-category-id 省略時は既定セレクタ [name=category_id] を使う', async () => {
    document.body.innerHTML = `
      <input name="category_id" value="7" />
      <input id="hidden-sub-category" value="" />
    `;
    const host = document.createElement('div');
    document.body.appendChild(host);

    mountSubCategorySelectFromAttributes(host, { target: '#hidden-sub-category' });

    await waitFor(() => {
      expect(
        document.querySelector('[data-testid="sub-category-select-stub"]')?.getAttribute('data-main-category-id')
      ).toBe('7');
    });
  });

  it('category_id が acms.category.change を発火すると mainCategoryId が更新される', async () => {
    document.body.innerHTML = `
      <input name="category_id" value="7" />
      <input id="hidden-sub-category" value="" />
    `;
    const host = document.createElement('div');
    document.body.appendChild(host);
    const categoryInput = document.querySelector<HTMLInputElement>('[name=category_id]')!;

    mountSubCategorySelectFromAttributes(host, { target: '#hidden-sub-category' });

    await waitFor(() => {
      expect(document.querySelector('[data-testid="sub-category-select-stub"]')).not.toBeNull();
    });

    categoryInput.value = '42';

    // Why not: 発火を waitFor の外で一度だけにすると CI で flaky になる。購読は
    // SubCategorySelectField の useEffect（passive effect）で登録されるため、stub が DOM に
    // 現れた時点ではまだ登録されておらず、登録前に流れた一発きりのイベントは失われて二度と
    // 反映されない。waitFor の中で発火し直せば購読が有効になるまでリトライできる（同じ値の
    // 再発火は冪等で、購読されなければタイムアウトして失敗するため検知力は落ちない）。
    await waitFor(() => {
      fireEvent(categoryInput, new CustomEvent('acms.category.change', { bubbles: true }));
      expect(
        document.querySelector('[data-testid="sub-category-select-stub"]')?.getAttribute('data-main-category-id')
      ).toBe('42');
    });
  });

  it('選択変更で参照先の hidden input が更新され、acms.sub-category.change が発火する', async () => {
    document.body.innerHTML = `<input id="hidden-sub-category" value="" />`;
    const host = document.createElement('div');
    document.body.appendChild(host);
    const input = document.getElementById('hidden-sub-category') as HTMLInputElement;
    const handleChange = vi.fn();
    input.addEventListener('acms.sub-category.change', handleChange);

    mountSubCategorySelectFromAttributes(host, { target: '#hidden-sub-category' });

    await waitFor(() => {
      expect(document.querySelector('[data-testid="sub-category-select-stub"]')).not.toBeNull();
    });

    fireEvent.click(document.querySelector('button')!);

    expect(input.value).toBe('new');
    expect(handleChange).toHaveBeenCalledTimes(1);
  });
});
