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

vi.mock('./components/category-select/category-select', () => ({
  default: ({
    defaultValue,
    onChange,
    narrowDown,
    isCreatable,
    noOption,
    id,
    name,
  }: {
    defaultValue?: string;
    onChange: (value: CategoryOption | null) => void;
    narrowDown?: boolean;
    isCreatable?: boolean;
    noOption?: boolean;
    id?: string;
    name?: string;
  }) => (
    <div
      data-testid="category-select-stub"
      data-narrow-down={String(!!narrowDown)}
      data-is-creatable={String(!!isCreatable)}
      data-no-option={String(!!noOption)}
      data-id={id}
      data-name={name}
    >
      <span data-testid="default-value">{defaultValue ?? ''}</span>
      <button type="button" onClick={() => onChange({ label: 'new', value: '99' })}>
        change
      </button>
      <button type="button" data-testid="clear" onClick={() => onChange(null)}>
        clear
      </button>
    </div>
  ),
}));

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

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

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

  // Why: target 属性を使わず、<acms-category-select> 自身の子孫に hidden input を直接置くだけで
  // 動くようにする（block-editor と同じ「自己完結」の使い方を選べるようにするため）。
  // target と違い複数 input への複製は行わず、最初の1つだけを対象にする。
  it('target が無くても host 内の最初の hidden input を検出して使う', async () => {
    const host = document.createElement('div');
    host.innerHTML = `
      <input type="hidden" name="category_id" value="" />
      <input type="hidden" name="other" value="" />
    `;
    document.body.appendChild(host);
    const [firstInput] = Array.from(host.querySelectorAll<HTMLInputElement>('input[type="hidden"]'));

    const { inputs: resolvedInputs } = mountCategorySelectFromAttributes(host, { defaultValue: '12' })!;

    expect(resolvedInputs).toEqual([firstInput]);

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

    fireEvent.click(document.querySelector('button')!);
    expect(firstInput.value).toBe('99');
  });

  // Why: static-export のようなテンプレート行複製パターン（`sortable-item item-template`）では、
  // 既にマウント済み（React の描画結果を含む）DOM がそのまま複製され、複製先の connectedCallback で
  // 再度マウントしてしまうと二重マウントになる。host に hidden input 以外のコンテンツが既にある
  // 場合は null を返してマウントしないことで防ぐ（block-editor の mountBlockEditorField と同じ
  // ガード）。
  it('host に既にマウント済みのコンテンツがある場合は null を返しマウントしない（二重マウント防止）', () => {
    const host = document.createElement('div');
    host.innerHTML = `<input type="hidden" name="category_id" value="" /><div>already mounted</div>`;
    document.body.appendChild(host);

    const result = mountCategorySelectFromAttributes(host, {});

    expect(result).toBeNull();
  });

  it('data-default-value を defaultValue として使用する', async () => {
    document.body.innerHTML = `<input id="hidden-category" value="should-not-be-used" />`;
    const host = document.createElement('div');
    document.body.appendChild(host);

    mountCategorySelectFromAttributes(host, {
      target: '#hidden-category',
      defaultValue: '12',
      narrowDown: 'true',
      isCreatable: 'true',
      id: 'cat-id',
      name: 'cat-name',
    });

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

    expect(document.querySelector('[data-testid="default-value"]')?.textContent).toBe('12');
    const stub = document.querySelector('[data-testid="category-select-stub"]')!;
    expect(stub.getAttribute('data-narrow-down')).toBe('true');
    expect(stub.getAttribute('data-is-creatable')).toBe('true');
    expect(stub.getAttribute('data-id')).toBe('cat-id');
    expect(stub.getAttribute('data-name')).toBe('cat-name');
  });

  it('data-target が複数の input にマッチする場合、選択変更で全ての input が更新される', async () => {
    document.body.innerHTML = `
      <input class="cat-input" value="" />
      <input class="cat-input" value="" />
    `;
    const host = document.createElement('div');
    document.body.appendChild(host);
    const inputs = Array.from(document.querySelectorAll<HTMLInputElement>('.cat-input'));
    const handleChange = vi.fn();
    inputs.forEach((input) => input.addEventListener('acms.category.change', handleChange));

    const { inputs: resolvedInputs } = mountCategorySelectFromAttributes(host, { target: '.cat-input' })!;
    expect(resolvedInputs).toHaveLength(2);

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

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

    inputs.forEach((input) => expect(input.value).toBe('99'));
    expect(handleChange).toHaveBeenCalledTimes(2);
  });

  it('選択解除（null）で input.value が空文字になる', async () => {
    document.body.innerHTML = `<input id="hidden-category" value="5" />`;
    const host = document.createElement('div');
    document.body.appendChild(host);
    const input = document.getElementById('hidden-category') as HTMLInputElement;

    mountCategorySelectFromAttributes(host, { target: '#hidden-category', defaultValue: '5' });

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

    fireEvent.click(document.querySelector('[data-testid="clear"]')!);

    expect(input.value).toBe('');
  });
});
