import { describe, it, expect, vi, afterEach, beforeAll, beforeEach } from 'vitest';
import { waitFor } from '@testing-library/react';
import { fetchClient } from '@ablogcms/fetch-client';
import { registerAcmsElements } from './register';
import type { UnitConfigEditorElement } from './unit-config-editor-element';

vi.mock('@ablogcms/fetch-client', () => ({
  fetchClient: { get: vi.fn() },
}));

vi.mock('@ablogcms/unit-editor/components/config-editor', () => ({
  default: ({ id, label, className }: { id: string; label: string; className?: string }) => (
    <div data-testid="unit-config-editor-stub" className={className}>
      <span data-testid="id">{id}</span>
      <span data-testid="label">{label}</span>
    </div>
  ),
}));

const getStub = () => document.querySelector('[data-testid="unit-config-editor-stub"]');

const createElement = (attrs: Record<string, string> = {}): UnitConfigEditorElement => {
  const el = document.createElement('acms-unit-config-editor') as UnitConfigEditorElement;
  Object.entries(attrs).forEach(([key, value]) => el.setAttribute(key, value));
  return el;
};

describe('acms-unit-config-editor', () => {
  beforeAll(() => {
    registerAcmsElements();
  });

  beforeEach(() => {
    vi.mocked(fetchClient.get).mockReset();
    vi.mocked(fetchClient.get).mockResolvedValue({
      data: {},
      status: 200,
      statusText: 'OK',
      headers: new Headers(),
    });
    (window as unknown as { ACMS: { Config: Record<string, unknown>; Library: unknown } }).ACMS = {
      Config: { bid: 1, cid: 2 },
      Library: { acmsLink: vi.fn(() => 'ajax/edit/block-editor/settings.json') },
    };
  });

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

  it('data-id / label / default-value を渡して UnitConfigEditor を自身にマウントする', async () => {
    const el = createElement({
      'data-id': 'config-1',
      label: '設定',
      'default-value': '[]',
    });
    document.body.appendChild(el);

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });
    expect(el.querySelector('[data-testid="id"]')?.textContent).toBe('config-1');
    expect(el.querySelector('[data-testid="label"]')?.textContent).toBe('設定');
  });

  it('host の root-class 属性が実際にレンダリングされる UnitConfigEditor のルート要素へ転送される', async () => {
    const el = createElement({ 'data-id': 'config-1', label: '設定' });
    el.setAttribute('root-class', 'my-custom-class');
    document.body.appendChild(el);

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });

    expect(getStub()?.className).toBe('my-custom-class');
    // root-class は class と違い host 自身の見た目に意味を持つ標準属性ではないため取り除かない。
    expect(el.getAttribute('root-class')).toBe('my-custom-class');
  });

  it('同一ページに複数存在しても、それぞれ独立してマウントする', async () => {
    const el1 = createElement({ 'data-id': 'insert', label: '新規エントリー作成' });
    const el2 = createElement({ 'data-id': 'add_text', label: 'テキストユニット追加' });
    document.body.appendChild(el1);
    document.body.appendChild(el2);

    await waitFor(() => {
      expect(el1.querySelector('[data-testid="id"]')?.textContent).toBe('insert');
      expect(el2.querySelector('[data-testid="id"]')?.textContent).toBe('add_text');
    });
  });

  it('disconnectedCallback でアンマウントし、再接続すると再マウントする', async () => {
    const el = createElement({ 'data-id': 'config-1', label: '設定' });
    document.body.appendChild(el);

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });

    el.remove();
    // unmount はマイクロタスクへ遅延される（物理的な DOM 移動と区別するため。詳細は
    // disconnectedCallback のコメント参照）。
    await waitFor(() => {
      expect(getStub()).toBeNull();
    });

    document.body.appendChild(el);
    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });
  });

  // Why not: この要素がカスタムユニットの中に埋め込まれる等で物理的に DOM 移動されると、
  // disconnectedCallback → connectedCallback が同期的に（マイクロタスクが実行される前に）
  // 再発火する。マイクロタスクの中で this.isConnected を見て「移動で戻ってきた」場合は
  // 何もしないことで、既存の root を壊さず・二重マウントもしないことを特徴づける。
  it('マイクロタスクが実行される前に再接続された場合（物理的な DOM 移動）は unmount せず、二重マウントもしない', async () => {
    const el = createElement({ 'data-id': 'config-1', label: '設定' });
    const parent = document.createElement('div');
    document.body.appendChild(parent);
    parent.appendChild(el);

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });

    parent.removeChild(el);
    parent.appendChild(el);

    await Promise.resolve();
    await Promise.resolve();

    expect(document.querySelectorAll('[data-testid="unit-config-editor-stub"]').length).toBe(1);
  });

  it('data-id が無ければ例外を投げずに console.error に留める', async () => {
    const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
    const el = createElement({ label: '設定' });

    expect(() => document.body.appendChild(el)).not.toThrow();
    expect(consoleErrorSpy).toHaveBeenCalled();
    consoleErrorSpy.mockRestore();
  });
});
