import type { OnlyOfficeBridge } from '../src/onlyoffice/bridge';

import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';

import { mount } from '@vue/test-utils';
import Antd, { ConfigProvider, Tooltip } from 'ant-design-vue';
import dayjs from 'dayjs';
import { createPinia } from 'pinia';
import { afterEach, describe, expect, it, vi } from 'vitest';

import App from '../src/App.vue';

const styles = readFileSync(resolve(process.cwd(), 'src/styles.css'), 'utf8');

function bridgeFixture(
  fields: Awaited<ReturnType<OnlyOfficeBridge['inspectFields']>>,
  runtimeOptions: ReturnType<OnlyOfficeBridge['getRuntimeOptions']> | undefined = undefined,
) {
  let ready = () => undefined;
  let changed = (_control: { InternalId?: unknown; Tag?: unknown }) => undefined;
  let focused = (_control: { InternalId?: unknown; Tag?: unknown }) => undefined;
  const bridge: OnlyOfficeBridge = {
    applyFields: vi.fn().mockResolvedValue([]),
    appendRepeatRow: vi.fn().mockResolvedValue([]),
    bindLifecycle: vi.fn((callbacks) => {
      changed = callbacks.onControlChanged;
      focused = callbacks.onControlFocused;
      ready = callbacks.onReady;
      return vi.fn();
    }),
    getRuntimeOptions: () =>
      runtimeOptions ?? {
        apiBaseUrl: 'http://localhost:30000',
        bizDataId: 'record-1',
        bizModule: 'eln.record',
        bizScene: 'eln.record.fill',
        canFill: true,
        documentId: 'file-1',
        pluginVersion: '0.1.0',
        scope: 'onlyoffice:eln-record-fill',
        ticket: 'ticket-1',
      },
    inspectFields: vi.fn().mockResolvedValue(fields),
    removeRepeatRow: vi.fn().mockResolvedValue([]),
    selectControl: vi.fn().mockResolvedValue(undefined),
    setFillOnlyMode: vi.fn().mockResolvedValue(undefined),
  };
  return {
    bridge,
    changed: (control: { InternalId?: unknown; Tag?: unknown }) => changed(control),
    focused: (control: { InternalId?: unknown; Tag?: unknown }) => focused(control),
    ready: () => ready(),
  };
}

describe('App step navigation', () => {
  afterEach(() => {
    vi.restoreAllMocks();
    vi.unstubAllGlobals();
  });

  it('provides Chinese locale to Ant Design and dayjs', () => {
    const wrapper = mount(App, { global: { plugins: [createPinia(), Antd] } });

    const provider = wrapper.getComponent(ConfigProvider);
    const trigger = document.createElement('button');
    wrapper.get('.plugin-shell').element.append(trigger);
    const getPopupContainer = provider.props('getPopupContainer');

    expect(provider.props('locale')).toMatchObject({ locale: 'zh-cn' });
    expect(getPopupContainer).toBeTypeOf('function');
    expect(getPopupContainer?.(trigger)).toBe(wrapper.get('.plugin-shell').element);
    expect(dayjs.locale()).toBe('zh-cn');
    wrapper.unmount();
  });

  it('reports unapplied record drafts to the host window', async () => {
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({ fields: {}, repeatGroups: [] }), { status: 200 })));
    const postMessage = vi.spyOn(window, 'postMessage');
    const runtime = {
      apiBaseUrl: 'http://localhost:30000',
      bizDataId: 'record-1',
      bizModule: 'eln.record' as const,
      bizScene: 'eln.record.fill' as const,
      canFill: true,
      documentId: 'file-1',
      pluginVersion: '0.1.0' as const,
      scope: 'onlyoffice:eln-record-fill' as const,
      ticket: 'ticket-1',
    };
    const fixture = bridgeFixture([{ alias: '检验结果', currentValue: '', internalId: '1', options: [], tag: 'eln.field.result', type: 'text' }], runtime);
    const wrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: fixture.bridge } },
    });
    fixture.ready();
    await vi.waitFor(() => expect(wrapper.text()).toContain('检验结果'));

    await wrapper.get('.field-row input').setValue('合格');

    await vi.waitFor(() =>
      expect(postMessage).toHaveBeenCalledWith(
        {
          bizDataId: 'record-1',
          documentId: 'file-1',
          pendingCount: 1,
          type: 'jnpf:eln-record-fill-state',
          version: 1,
        },
        '*',
      ),
    );
    wrapper.unmount();
  });

  it('uses the annotator status bar and keeps fields in a bounded scroll region', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue(
        new Response(
          JSON.stringify({
            fields: {},
            repeatGroups: [],
            stepStructure: {
              configuration: {
                singleStep: true,
                steps: [{ id: 'step-only', name: '填写', order: 1 }],
              },
              controlSteps: { 'eln.field.sample': 'step-only' },
              repeatGroupSteps: {},
            },
          }),
          { status: 200 },
        ),
      ),
    );
    const { bridge, ready } = bridgeFixture([{ alias: '供试品名称', currentValue: '', internalId: '1', options: [], tag: 'eln.field.sample', type: 'text' }]);
    const wrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: bridge } },
    });
    ready();
    await vi.waitFor(() => expect(wrapper.text()).toContain('供试品名称'));

    expect(wrapper.get('[data-test="connection-status"]').text()).toContain('已连接');
    expect(wrapper.find('[data-test="source-sync-button"]').exists()).toBe(true);
    expect(wrapper.find('.plugin-header .ant-tag').exists()).toBe(false);
    expect(wrapper.get('.plugin-shell').find('[data-test="plugin-scroll-region"]').exists()).toBe(true);
    const toolbar = wrapper.get('[data-test="field-toolbar"]');
    const scrollRegion = wrapper.get('[data-test="plugin-scroll-region"]');
    expect(toolbar.element.parentElement).toBe(wrapper.get('.plugin-shell').element);
    expect(toolbar.element.nextElementSibling).toBe(scrollRegion.element);
    expect(toolbar.get('[data-test="field-filter"]').classes()).not.toContain('ant-input-lg');
    expect(styles).toMatch(/\.plugin-shell\s*\{[^}]*display:\s*flex;/s);
    expect(styles).toMatch(/\.plugin-scroll-region\s*\{[^}]*overflow-y:\s*auto;/s);
    expect(styles).toMatch(/\.field-toolbar\s*\{[^}]*flex:\s*0 0 auto;/s);
    expect(styles).not.toMatch(/\.field-toolbar\s*\{[^}]*position:\s*sticky;/s);
    expect(styles).toMatch(/\.plugin-shell footer\s*\{[^}]*position:\s*static;/s);
  });

  it('locates a document field without scrolling the plugin content', async () => {
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({ fields: {}, repeatGroups: [] }), { status: 200 })));
    const scrollIntoView = vi.spyOn(HTMLElement.prototype, 'scrollIntoView').mockImplementation(() => undefined);
    const fixture = bridgeFixture([
      { alias: '检验结果', currentValue: '', internalId: 'field-1', options: [], tag: 'eln.field.result', type: 'text' },
      { alias: '来源', currentValue: '', internalId: 'field-2', options: [], tag: 'eln.field.source', type: 'text' },
    ]);
    const wrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: fixture.bridge } },
    });
    fixture.ready();
    await vi.waitFor(() => expect(wrapper.text()).toContain('检验结果'));

    await wrapper.get('[aria-label="在文档中定位 检验结果"]').trigger('click');

    await vi.waitFor(() => expect(fixture.bridge.selectControl).toHaveBeenCalledWith('field-1'));
    expect(scrollIntoView).not.toHaveBeenCalled();

    fixture.focused({ InternalId: 'field-2', Tag: 'eln.field.source' });
    await vi.waitFor(() => expect(scrollIntoView).toHaveBeenCalledOnce());
    wrapper.unmount();
  });

  it('distinguishes task and cross-template differences and disables source sync when readonly', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockImplementation(async (url: string) => {
        if (url.endsWith('/sourceSync/preview'))
          return new Response(
            JSON.stringify({
              changedCount: 2,
              differences: [
                {
                  bindingId: 'binding-source',
                  changed: true,
                  currentValue: 'GYDW',
                  dataType: 'TEXT',
                  fieldCode: 'source',
                  fieldName: '来源',
                  previousValue: null,
                  sourceProperty: 'quality_standard.source',
                  sourceType: 'LIMS_TASK',
                },
                {
                  bindingId: 'binding-cross',
                  changed: true,
                  currentValue: '复测合格',
                  dataType: 'TEXT',
                  fieldCode: 'source_result',
                  fieldName: '源检验结果',
                  previousValue: '合格',
                  sourceFieldVersionId: 'source-field-version-2',
                  sourceFetchedAt: '2026-09-02T08:30:00Z',
                  sourceProperty: null,
                  sourceRecordId: 'source-record-1',
                  sourceRevisionId: 'source-revision-2',
                  sourceType: 'CROSS_TEMPLATE',
                  sourceValueId: 'source-value-2',
                },
              ],
              previewToken: 'preview-token',
              recordId: 'record-1',
              recordVersion: 4,
              revisionId: 'revision-1',
            }),
            { status: 200 },
          );
        return new Response(JSON.stringify({ fields: {}, repeatGroups: [] }), { status: 200 });
      }),
    );
    const runtime = {
      apiBaseUrl: 'http://localhost:30000',
      bizDataId: 'record-1',
      bizModule: 'eln.record' as const,
      bizScene: 'eln.record.fill' as const,
      canFill: true,
      documentId: 'file-1',
      pluginVersion: '0.1.0' as const,
      scope: 'onlyoffice:eln-record-fill' as const,
      ticket: 'ticket-1',
    };
    const fixture = bridgeFixture([{ alias: '来源', currentValue: 'GYDW', internalId: '1', options: [], tag: 'eln.field.source', type: 'text' }], runtime);
    const wrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: fixture.bridge } },
    });
    fixture.ready();
    await vi.waitFor(() => expect(wrapper.get('[data-test="connection-status"]').text()).toContain('已连接'));

    expect(wrapper.get('[data-test="field-toolbar"]').find('[data-test="source-sync-button"]').exists()).toBe(true);
    expect(wrapper.find('.plugin-header [data-test="source-sync-button"]').exists()).toBe(false);
    expect(wrapper.get('[data-test="source-sync-button"]').attributes('disabled')).toBeUndefined();
    expect(wrapper.get('[data-test="source-sync-button"]').classes()).not.toContain('ant-btn-lg');
    expect(wrapper.findAllComponents(Tooltip).find((tooltip) => tooltip.props('title') === '重新同步来源快照')?.props('placement')).toBe('topRight');
    await wrapper.get('[aria-label="重新同步来源快照"]').trigger('click');

    await vi.waitFor(() => expect(document.body.textContent).toContain('quality_standard.source'));
    const differences = document.body.querySelector('[data-test="source-sync-differences"]')?.textContent;
    expect(differences).toContain('任务快照');
    expect(differences).toContain('跨模板字段值');
    expect(differences).toContain('source-record-1');
    expect(differences).toContain('source-revision-2');

    const readonlyFixture = bridgeFixture([], { ...runtime, canFill: false });
    const readonlyWrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: readonlyFixture.bridge } },
    });
    readonlyFixture.ready();
    await vi.waitFor(() => expect(readonlyWrapper.get('[data-test="connection-status"]').text()).toContain('当前记录仅可查看'));
    expect(readonlyWrapper.get('[data-test="source-sync-button"]').attributes('disabled')).toBeDefined();

    const emptyEditableFixture = bridgeFixture([], runtime);
    const emptyEditableWrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: emptyEditableFixture.bridge } },
    });
    emptyEditableFixture.ready();
    await vi.waitFor(() => expect(emptyEditableWrapper.get('[data-test="connection-status"]').text()).toContain('已连接'));
    expect(emptyEditableWrapper.find('[data-test="field-toolbar"]').exists()).toBe(false);
    expect(emptyEditableWrapper.get('.plugin-header').find('[data-test="source-sync-button"]').exists()).toBe(true);
  });

  it('navigates multi-step content and returns from the directory to the selected step', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue(
        new Response(
          JSON.stringify({
            fields: {},
            repeatGroups: [],
            stepStructure: {
              configuration: {
                singleStep: false,
                steps: [
                  { id: 'step-prepare', name: '准备', order: 1 },
                  { id: 'step-test', name: '检验', order: 2 },
                ],
              },
              controlSteps: { 'eln.field.sample': 'step-prepare', 'eln.field.result': 'step-test' },
              repeatGroupSteps: {},
            },
          }),
          { status: 200 },
        ),
      ),
    );
    const { bridge, ready } = bridgeFixture([
      { alias: '样品编号', currentValue: '', internalId: '1', options: [], tag: 'eln.field.sample', type: 'text' },
      { alias: '检验结果', currentValue: '', internalId: '2', options: [], tag: 'eln.field.result', type: 'text' },
    ]);
    const wrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: bridge } },
    });
    ready();
    await vi.waitFor(() => expect(wrapper.text()).toContain('样品编号'));

    expect(wrapper.text()).not.toContain('检验结果');
    await wrapper.get('.field-row input').setValue('S-001');
    expect(wrapper.text()).toContain('应用到文档（1）');
    await wrapper.get('[aria-label="下一步骤"]').trigger('click');
    expect(wrapper.text()).toContain('检验结果');
    expect(wrapper.text()).not.toContain('样品编号');

    await wrapper.get('[aria-label="打开填写目录"]').trigger('click');
    expect(wrapper.find('[aria-label="填写目录"]').exists()).toBe(true);
    expect(wrapper.find('.field-row').exists()).toBe(false);
    await wrapper.get('[data-step-id="step-prepare"]').trigger('click');
    expect(wrapper.find('[aria-label="填写目录"]').exists()).toBe(false);
    expect(wrapper.text()).toContain('样品编号');
    expect(wrapper.get('.field-row input').element.getAttribute('value')).toBe('S-001');
    expect(bridge.selectControl).toHaveBeenCalledWith('1');
  });

  it('keeps the compact field layout and hides step terminology for a single-step template', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue(
        new Response(
          JSON.stringify({
            fields: {},
            repeatGroups: [],
            stepStructure: {
              configuration: {
                singleStep: true,
                steps: [{ id: 'step-only', name: '不应显示的唯一步骤', order: 1 }],
              },
              controlSteps: { 'eln.field.sample': 'step-only' },
              repeatGroupSteps: {},
            },
          }),
          { status: 200 },
        ),
      ),
    );
    const { bridge, ready } = bridgeFixture([{ alias: '样品编号', currentValue: '', internalId: '1', options: [], tag: 'eln.field.sample', type: 'text' }]);
    const wrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: bridge } },
    });
    ready();
    await vi.waitFor(() => expect(wrapper.text()).toContain('样品编号'));

    expect(wrapper.find('[aria-label="步骤导航"]').exists()).toBe(false);
    expect(wrapper.find('[aria-label="打开填写目录"]').exists()).toBe(false);
    expect(wrapper.text()).not.toContain('不应显示的唯一步骤');
  });

  it('follows document focus and blocks apply until a cross-step conflict is resolved', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue(
        new Response(
          JSON.stringify({
            fields: {},
            repeatGroups: [],
            stepStructure: {
              configuration: {
                singleStep: false,
                steps: [
                  { id: 'step-first', name: '第一步', order: 1 },
                  { id: 'step-second', name: '第二步', order: 2 },
                ],
              },
              controlSteps: { 'eln.field.first': 'step-first', 'eln.field.second': 'step-second' },
              repeatGroupSteps: {},
            },
          }),
          { status: 200 },
        ),
      ),
    );
    const initial = [
      { alias: '第一项', currentValue: 'A', internalId: '1', options: [], tag: 'eln.field.first', type: 'text' as const },
      { alias: '第二项', currentValue: 'B', internalId: '2', options: [], tag: 'eln.field.second', type: 'text' as const },
    ];
    const fixture = bridgeFixture(initial);
    const wrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: fixture.bridge } },
    });
    fixture.ready();
    await vi.waitFor(() => expect(wrapper.text()).toContain('第一项'));

    fixture.focused({ InternalId: '2', Tag: 'eln.field.second' });
    await vi.waitFor(() => expect(wrapper.text()).toContain('第二项'));
    expect(wrapper.get('[data-field-tag="eln.field.second"]').classes()).toContain('field-row-focused');
    await wrapper.get('.field-row input').setValue('侧栏值');
    vi.mocked(fixture.bridge.inspectFields).mockResolvedValue([initial[0]!, { ...initial[1]!, currentValue: '文档值' }]);
    fixture.changed({ InternalId: '2', Tag: 'eln.field.second' });

    await vi.waitFor(() => expect(wrapper.find('[aria-label="字段冲突"]').exists()).toBe(true));
    expect(wrapper.text()).toContain('1 个字段冲突');
    expect(wrapper.get('footer button').attributes('disabled')).toBeDefined();
    await wrapper.get('[data-conflict-tag="eln.field.second"]').trigger('click');
    expect(fixture.bridge.selectControl).toHaveBeenCalledWith('2');
    await wrapper.get('[data-resolution="draft"]').trigger('click');

    expect(wrapper.find('[aria-label="字段冲突"]').exists()).toBe(false);
    expect(wrapper.get('footer button').attributes('disabled')).toBeUndefined();
    expect(wrapper.text()).toContain('应用到文档（1）');
  });

  it('shows a grouped checkbox error and disables document apply', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue(
        new Response(
          JSON.stringify({
            fields: {
              'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
              'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
            },
            repeatGroups: [],
            stepStructure: {
              configuration: {
                singleStep: true,
                steps: [{ id: 'step-1', name: '检验', order: 1 }],
              },
              controlSteps: {
                'eln.field.failed': 'step-1',
                'eln.field.passed': 'step-1',
              },
              repeatGroupSteps: {},
            },
          }),
          { status: 200 },
        ),
      ),
    );
    const fixture = bridgeFixture([
      { alias: '合格', currentValue: true, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' },
      { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' },
    ]);
    const wrapper = mount(App, {
      global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: fixture.bridge } },
    });
    fixture.ready();

    await vi.waitFor(() => expect(wrapper.text()).toContain('选择组“inspection_result”最多选择 1 项'));
    expect(wrapper.get('footer button').attributes('disabled')).toBeDefined();
  });
});
