import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, waitFor } from '@testing-library/react';
import type { UnitTreeNode } from '@ablogcms/unit-editor/core/types/unit';
import type { Editor } from '@ablogcms/unit-editor/core';
import dispatchAtableField from '../../../../dispatch/dispatch-a-table-field';
import dispatchMediaField from '../../../../dispatch/media/dispatch-media-field';
import { CustomUnitContent } from './edit';
import { renderFixtureFile } from './__fixtures__/render-fixture';

vi.mock('../../../../dispatch/dispatch-a-table-field', () => ({ default: vi.fn() }));
vi.mock('../../../../dispatch/dispatch-resize-image-cf', () => ({ default: vi.fn() }));
vi.mock('../../../../dispatch/dispatch-flatpicker', () => ({ default: vi.fn() }));
vi.mock('../../../../dispatch/dispatch-lite-editor-field', () => ({ default: vi.fn() }));
vi.mock('../../../../elements/upgrade-legacy-marks', () => ({ upgradeLegacyBlockEditors: vi.fn() }));
vi.mock('../../../../dispatch/dispatch-select2', () => ({ default: vi.fn() }));
vi.mock('../../../../dispatch/media/dispatch-media-field', () => ({ default: vi.fn() }));
vi.mock('../../../../dispatch/dispatch-rich-editor', () => ({ default: vi.fn() }));

/**
 * What: custom ユニットが SSR（ServerSideRenderer）経由でサーバーの生成HTML（を近似したフィクスチャ）を
 * 受け取り、その HTML を実際にマウントした要素に対して組み込みJS（use-buildin-js.ts の apply()）まで
 * 一通り到達することを保証する統合テスト。use-buildin-js.test.ts が個々の dispatch を直接 apply() 経由で
 * 検証しているのに対し、こちらは「fetch → SSR の innerHTML 反映 → onRender → apply()」という
 * CustomUnitContent 全体の配線が繋がっていることを検証する。
 */

let unitIdSeq = 0;
function createUnit(overrides: Partial<UnitTreeNode> = {}): UnitTreeNode {
  unitIdSeq += 1;
  return {
    id: `custom-unit-${unitIdSeq}`,
    name: 'カスタム',
    type: 'custom',
    status: 'open',
    collapsed: false,
    children: [],
    attributes: {},
    ...overrides,
  };
}

function createMockEditor(): Editor {
  return {
    emit: vi.fn(),
    on: vi.fn(),
    off: vi.fn(),
  } as unknown as Editor;
}

describe('CustomUnitContent（custom ユニットの SSR → 組み込みJS 統合）', () => {
  beforeEach(() => {
    (window as unknown as { ACMS: { Config: Record<string, unknown>; Library: Record<string, unknown> } }).ACMS = {
      Config: {
        aTableFieldMark: '.js-editable-table-field',
        LiteEditorMark: '.js-lite-editor-field',
        SmartBlockMark: '.js-smartblock,.js-paper-editor',
        blockEditorMark: '.js-block-editor',
        mediaFieldMark: '.js-media-field',
        select2Mark: '.js-select2',
        select2Threshold: 10,
        flatDatePicker: '.js-datepicker2',
        flatTimePicker: '.js-timepicker',
        resizeImageTargetMarkCF: '.js-img_resize_cf',
        fieldgroupSortableMark: '.js-fieldgroup-sortable',
      },
      Library: {
        acmsLink: (params: { searchParams?: Record<string, unknown> }) =>
          `/ajax/unit/render.html?${new URLSearchParams(params.searchParams as Record<string, string>).toString()}`,
      },
    };
    window.csrfToken = 'test-csrf-token';
    // fieldgroupSortable / wysiwyg は ACMS グローバル経由の呼び出しのため、ここでもスパイを生やす
    (window.ACMS as unknown as { Dispatch: Record<string, unknown> }).Dispatch = {
      fieldgroupSortable: vi.fn(),
      wysiwyg: { dispatch: vi.fn() },
    };
    vi.stubGlobal('$', (selector: string, context: Element) => ({
      each(callback: (this: Element, index: number, element: Element) => unknown) {
        Array.from(context.querySelectorAll(selector)).forEach((element, index) => {
          callback.call(element, index, element);
        });
      },
    }));
  });

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

  it('media.html をサーバーHTMLとして受け取ると、media-field の組み込みJSまで到達し .js-media-field 要素を検出する', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      text: async () => renderFixtureFile('media.html'),
    });
    vi.stubGlobal('fetch', fetchMock);
    const editor = createMockEditor();
    const unit = createUnit();

    const { container } = render(<CustomUnitContent unit={unit} editor={editor} />);

    await waitFor(() => expect(dispatchMediaField).toHaveBeenCalled());

    const [calledElement] = vi.mocked(dispatchMediaField).mock.calls[0];
    expect((calledElement as Element).querySelector('.js-media-field')).not.toBeNull();
    expect(container.querySelector('.js-media-field')).not.toBeNull();
  });

  it('table.html をサーバーHTMLとして受け取ると、a-table の組み込みJSまで到達し .js-editable-table-field 要素を検出する', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      ok: true,
      text: async () => renderFixtureFile('table.html'),
    });
    vi.stubGlobal('fetch', fetchMock);
    const editor = createMockEditor();
    const unit = createUnit();

    render(<CustomUnitContent unit={unit} editor={editor} />);

    await waitFor(() => expect(dispatchAtableField).toHaveBeenCalled());

    const [calledElement] = vi.mocked(dispatchAtableField).mock.calls[0];
    expect((calledElement as Element).querySelector('.js-editable-table-field')).not.toBeNull();
  });

  it('SSR のフェッチが失敗した場合は html が空のまま据え置かれ、組み込みJSの初期化（apply）は発火しない', async () => {
    const fetchMock = vi.fn().mockResolvedValue({ ok: false, text: async () => '' });
    vi.stubGlobal('fetch', fetchMock);
    const editor = createMockEditor();
    const unit = createUnit();

    render(<CustomUnitContent unit={unit} editor={editor} />);

    await waitFor(() =>
      expect(editor.emit).toHaveBeenCalledWith('error', expect.objectContaining({ code: 'unitRenderFetchFailed' }))
    );

    expect(dispatchMediaField).not.toHaveBeenCalled();
    expect(dispatchAtableField).not.toHaveBeenCalled();
  });
});
