import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
import { waitFor, fireEvent } from '@testing-library/react';
import { fetchClient } from '@ablogcms/fetch-client';
import { getDefaultValue, getSettings, mountUnitEditorField, mountUnitEditorFromAttributes } from './mount-unit-editor';

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

vi.mock('@ablogcms/unit-editor/components/unit-editor', () => ({
  default: ({
    defaultValue,
    onCreate,
    onContentChange,
  }: {
    defaultValue: unknown[];
    onCreate: (editor: unknown) => void;
    onContentChange: (data: unknown[]) => void;
  }) => (
    <div data-testid="unit-editor-stub">
      <span data-testid="default-value">{JSON.stringify(defaultValue)}</span>
      <button
        type="button"
        onClick={() =>
          onCreate({
            id: 'fake-editor',
            createUnit: vi.fn(() => ({ id: 'more-1', type: 'more' })),
            commands: { insertUnit: vi.fn() },
          })
        }
      >
        create
      </button>
      <button type="button" onClick={() => onContentChange([{ id: '1', type: 'text', children: [] }])}>
        transaction
      </button>
      <button type="button" onClick={() => onContentChange([{ id: '1', type: 'more', children: [] }])}>
        transaction-with-more
      </button>
    </div>
  ),
}));

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

describe('getDefaultValue', () => {
  afterEach(() => {
    document.body.innerHTML = '';
  });

  it('default-value が無ければ空配列になる', () => {
    const element = document.createElement('div');
    expect(getDefaultValue(element)).toEqual([]);
  });

  it('default-value の JSON をパースする', () => {
    const element = document.createElement('div');
    element.setAttribute('default-value', '[{"id":"1","type":"text","children":[]}]');
    expect(getDefaultValue(element)).toEqual([{ id: '1', type: 'text', children: [] }]);
  });

  it('#unit-html-repository に対応する data-unit-id があれば defaultHtml を差し込む', () => {
    document.body.innerHTML = `
      <template id="unit-html-repository">
        <div data-unit-id="1">stored html</div>
      </template>
    `;
    const element = document.createElement('div');
    element.setAttribute('default-value', '[{"id":"1","type":"custom","children":[]}]');

    const result = getDefaultValue(element);

    expect(result[0].defaultHtml).toContain('stored html');
  });
});

describe('getSettings', () => {
  it('settings が無ければ既定値を使う', () => {
    const element = document.createElement('div');
    const settings = getSettings(element);
    expect(settings).toBeDefined();
  });

  it('settings の JSON を既定値にマージする', () => {
    const element = document.createElement('div');
    element.setAttribute('settings', '{"unitGroup":{"enable":false,"options":[]}}');
    const settings = getSettings(element);
    expect(settings.unitGroup).toEqual({ enable: false, options: [] });
  });
});

describe('mountUnitEditorField / mountUnitEditorFromAttributes', () => {
  beforeEach(() => {
    // UnitEditor 配下は BlockEditorSettingsProvider（コア側）でラップされ、ブロックエディター設定を
    // 取得する。ここでは値そのものは検証対象ではないため、既定値で解決させておく。
    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('defaultValue と settings を UnitEditor に渡してマウントする', async () => {
    const target = document.createElement('div');
    document.body.appendChild(target);

    mountUnitEditorField(
      target,
      [{ id: '1', type: 'text', children: [] }] as never,
      {
        unitGroup: { enable: false, options: [] },
      } as never
    );

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });
    expect(document.querySelector('[data-testid="default-value"]')?.textContent).toBe(
      JSON.stringify([{ id: '1', type: 'text', children: [] }])
    );
  });

  it('onCreate 相当（editor 生成）で onEditorReady が呼ばれる', async () => {
    const target = document.createElement('div');
    document.body.appendChild(target);
    const onEditorReady = vi.fn();

    mountUnitEditorField(target, [] as never, {} as never, { onEditorReady });

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

    fireEvent.click(document.querySelectorAll('button')[0]);

    expect(onEditorReady).toHaveBeenCalledWith(expect.objectContaining({ id: 'fake-editor' }));
  });

  it('onContentChange 相当（transaction）で onChange が呼ばれ、summary_range が更新される', async () => {
    document.body.innerHTML = `<input name="summary_range" value="" />`;
    const target = document.createElement('div');
    document.body.appendChild(target);
    const onChange = vi.fn();

    mountUnitEditorField(target, [] as never, {} as never, { onChange });

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

    fireEvent.click(document.querySelectorAll('button')[2]);

    expect(onChange).toHaveBeenCalledWith([{ id: '1', type: 'more', children: [] }]);
    const summaryRangeInput = document.querySelector<HTMLInputElement>('[name="summary_range"]')!;
    expect(summaryRangeInput.value).toBe('0');
  });

  it('mountUnitEditorFromAttributes は element 自身をマウント先にする', async () => {
    const element = document.createElement('div');
    element.setAttribute('default-value', '[{"id":"1","type":"text","children":[]}]');
    document.body.appendChild(element);

    mountUnitEditorFromAttributes(element);

    await waitFor(() => {
      expect(element.querySelector('[data-testid="unit-editor-stub"]')).not.toBeNull();
    });
  });

  it('unmount するとコンテナが空になる', async () => {
    const target = document.createElement('div');
    document.body.appendChild(target);

    const controller = mountUnitEditorField(target, [] as never, {} as never);

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

    controller.unmount();

    expect(getStub()).toBeNull();
  });
});
