import { flushPromises, mount, shallowMount } from '@vue/test-utils';
import { defineComponent, nextTick } from 'vue';

import { beforeEach, describe, expect, it, vi } from 'vitest';

import VersionDrawer from '../VersionDrawer.vue';
import drawerSource from '../VersionDrawer.vue?raw';

const apiMocks = vi.hoisted(() => ({
  createFormulaVersion: vi.fn(),
  getFormulaDetail: vi.fn(),
  getFormulaFunctions: vi.fn(),
  getFormulaTemplateReferences: vi.fn(),
  preReleaseFormulaVersion: vi.fn(),
  updateFormulaVersion: vi.fn(),
}));
const drawerState = vi.hoisted(() => ({ closeDrawer: vi.fn(), init: undefined as any }));
const tableState = vi.hoisted(() => ({ config: undefined as any, referenceConfig: undefined as any, referenceReload: vi.fn() }));
const InputSearch = defineComponent({
  name: 'AInputSearch',
  props: ['value'],
  emits: ['search', 'update:value'],
  template:
    '<div><input data-test="reference-search-input" :value="value" @input="$emit(\'update:value\', $event.target.value)" /><button data-test="reference-search-submit" @click="$emit(\'search\', value)">查询</button></div>',
});
const layoutStubs = { 'a-button': true, 'a-input-search': true };

function deferred<T>() {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>((promiseResolve) => {
    resolve = promiseResolve;
  });
  return { promise, resolve };
}

vi.mock('#/api/x/eln/formulas', () => ({
  cancelFormulaVersion: vi.fn(),
  createFormulaVersion: apiMocks.createFormulaVersion,
  getFormulaDetail: apiMocks.getFormulaDetail,
  getFormulaFunctions: apiMocks.getFormulaFunctions,
  getFormulaTemplateReferences: apiMocks.getFormulaTemplateReferences,
  preReleaseFormulaVersion: apiMocks.preReleaseFormulaVersion,
  publishFormulaVersion: vi.fn(),
  switchCurrentFormulaVersion: vi.fn(),
  updateFormulaVersion: apiMocks.updateFormulaVersion,
}));
vi.mock('@jnpf/hooks', () => ({ useMessage: () => ({ createMessage: { success: vi.fn(), warning: vi.fn() } }) }));
vi.mock('@jnpf/ui/drawer', () => ({
  BasicDrawer: { name: 'BasicDrawer', template: '<div><slot /></div>' },
  useDrawerInner: (init: any) => {
    drawerState.init = init;
    return [vi.fn(), { changeLoading: vi.fn(), closeDrawer: drawerState.closeDrawer }];
  },
}));
vi.mock('@jnpf/ui/modal', () => ({
  BasicModal: {
    name: 'BasicModal',
    emits: ['heightChange', 'ok'],
    props: ['afterClose', 'confirmLoading', 'height', 'minHeight', 'showOkBtn', 'title'],
    template: '<div><slot /></div>',
  },
  useModal: () => [vi.fn(), { closeModal: vi.fn(), openModal: vi.fn() }],
}));
vi.mock('@jnpf/ui/vxeTable', () => ({
  BasicVxeTable: {
    name: 'BasicVxeTable',
    props: ['dataSource'],
    template: '<div><div v-for="record in dataSource"><slot :record="record" name="jieguoLeixing" /><slot :record="record" name="action" /></div></div>',
  },
  TableAction: {
    name: 'TableAction',
    props: ['actions'],
    template:
      '<button v-for="action in actions" :key="action.label" @click="action.onClick ? action.onClick() : action.modelConfirm?.onOk()">{{ action.label }}</button>',
  },
  useVxeTable: (config: any) => {
    if (config.actionColumn) {
      tableState.config = config;
      return [vi.fn()];
    }
    tableState.referenceConfig = config;
    tableState.referenceReload = vi.fn(async (options: any = {}) => {
      const raw = { currentPage: options.page || 1, pageSize: 20, ...options.searchInfo };
      const query = config.beforeFetch ? config.beforeFetch(raw) : raw;
      return config.api?.(query);
    });
    return [vi.fn(), { reload: tableState.referenceReload }];
  },
}));

describe('eLN formula version drawer', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    apiMocks.getFormulaDetail.mockResolvedValue({
      data: {
        bizEnabled: 'enabled',
        gongshiBianma: 'hanliang_abc123',
        id: 'f1',
        mubanYinyongShu: 12,
        templateReferences: [
          { bindingId: 'b1', bindingStatus: 'ACTIVE', formulaVersionId: 'v1', templateId: 't1', templateName: '含量模板', templateStatus: 'PUBLISHED' },
        ],
        versions: [
          {
            banbenHao: 1,
            bizStatus: 'PUBLISHED',
            gongshiDingyiId: 'f1',
            id: 'v1',
            jieguoLeixing: 'TEXT',
            parameters: [{ canshuBianma: 'a', canshuMingcheng: '样品量', paixu: 1, shifouBitian: 1, shujuLeixing: 'DECIMAL' }],
          },
          {
            banbenHao: 2,
            biaodashi: 'amount * 2',
            bizStatus: 'DRAFT',
            gongshiDingyiId: 'f1',
            id: 'v2',
            jieguoLeixing: 'DECIMAL',
            parameters: [],
            xiuyueMoshi: 'HALF_UP',
            xiuyueXiaoshuWei: 2,
          },
        ],
      },
    });
    apiMocks.getFormulaFunctions.mockResolvedValue([]);
    apiMocks.getFormulaTemplateReferences.mockResolvedValue({
      data: {
        list: [{ bindingId: 'b1', bindingStatus: 'ACTIVE', formulaVersionId: 'v1', templateId: 't1', templateName: '含量模板', templateStatus: 'PUBLISHED' }],
        pagination: { currentPage: 1, pageSize: 20, total: 12 },
      },
    });
  });

  it('loads versions and template reference summaries from the detail endpoint', async () => {
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': true,
          'a-form-item': true,
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          ElnFormulaParameterTable: true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1' });
    await flushPromises();
    expect(apiMocks.getFormulaDetail).toHaveBeenCalledWith('f1');
    expect(tableState.config.dataSource.value).toHaveLength(2);
    expect(drawerSource).toContain('templateReferences');
    expect(drawerSource).toContain('预发布，仅供模板验证');
    expect(wrapper.text()).toContain('含量模板 · 已发布 · 已生效');
    expect(wrapper.text()).toContain('文本');
    expect(wrapper.text()).toContain('数值');
    expect(wrapper.text()).not.toContain('DECIMAL');
    expect(wrapper.text()).not.toContain('PUBLISHED · ACTIVE');
  });

  it('does not show the pre-release warning when the current version is published', async () => {
    apiMocks.getFormulaDetail.mockResolvedValueOnce({
      data: {
        bizEnabled: 'enabled',
        dangqianGongshiBanbenId: 'v1',
        gongshiBianma: 'hanliang_abc123',
        id: 'f1',
        versions: [
          { banbenHao: 1, bizStatus: 'PUBLISHED', gongshiDingyiId: 'f1', id: 'v1', parameters: [] },
          { banbenHao: 2, bizStatus: 'PRE_RELEASE', gongshiDingyiId: 'f1', id: 'v2', parameters: [] },
        ],
      },
    });
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': { props: ['message'], template: '<div>{{ message }}</div>' },
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': true,
          'a-form-item': true,
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
        },
      },
    });

    await drawerState.init({ formulaId: 'f1' });
    await flushPromises();

    expect(wrapper.find('.formula-summary .pre-release-warning').exists()).toBe(false);
  });

  it('uses the reference total and opens a paged view for summaries beyond the first five', async () => {
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot name="extra" /><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': true,
          'a-form-item': true,
          'a-input-number': true,
          'a-input-search': InputSearch,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1' });
    await flushPromises();

    expect(wrapper.text()).toContain('12');
    const viewAll = wrapper.find('[data-test="view-all-references"]');
    expect(viewAll.exists()).toBe(true);
    await viewAll.trigger('click');
    await flushPromises();

    expect(apiMocks.getFormulaTemplateReferences).toHaveBeenCalledWith('f1', { currentPage: 1, pageSize: 20 });
    expect(tableState.referenceConfig.pagination).toMatchObject({ pageSize: 20 });
  });

  it('trims template reference searches and returns to the first page', async () => {
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot name="extra" /><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': true,
          'a-form-item': true,
          'a-input-number': true,
          'a-input-search': InputSearch,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1' });
    await flushPromises();
    await wrapper.find('[data-test="view-all-references"]').trigger('click');
    await wrapper.find('[data-test="reference-search-input"]').setValue(' 含量模板 ');
    await nextTick();
    await wrapper.find('[data-test="reference-search-submit"]').trigger('click');
    await flushPromises();

    expect(tableState.referenceReload).toHaveBeenLastCalledWith({ page: 1, searchInfo: { keyword: '含量模板' } });
    expect(apiMocks.getFormulaTemplateReferences).toHaveBeenLastCalledWith('f1', {
      currentPage: 1,
      keyword: '含量模板',
      pageSize: 20,
    });
  });

  it('keeps the formula description summary away from the drawer edges', () => {
    expect(drawerSource).toMatch(/\.formula-summary\s*\{[^}]*padding:\s*16px 24px;/);
  });

  it('keeps the template references away from the drawer edges', () => {
    expect(drawerSource).toMatch(/\.formula-references\s*\{[^}]*padding:\s*12px 24px;/);
  });

  it('keeps the version table away from the drawer edges', () => {
    expect(drawerSource).toMatch(/\.version-table\s*\{[^}]*padding:\s*12px 24px 0;/);
  });

  it('fills the drawer body so the version table can use the remaining height', () => {
    expect(drawerSource).toMatch(/<BasicDrawer[^>]*class="full-drawer"/);
  });

  it('lets the creation-time column absorb remaining table width', () => {
    const creationTimeColumn = tableState.config.columns.find((column: any) => column.dataIndex === 'chuangjianShijian');
    expect(creationTimeColumn).toMatchObject({ minWidth: 165 });
    expect(creationTimeColumn).not.toHaveProperty('width');
  });

  it('shows the last modification time in the version list', () => {
    const lastModifiedColumn = tableState.config.columns.find((column: any) => column.dataIndex === 'gengxinShijian');
    expect(lastModifiedColumn).toMatchObject({ format: 'date|YYYY-MM-DD HH:mm:ss', title: '最后修改时间', width: 165 });
  });

  it('keeps the newest formula detail when an older request finishes last', async () => {
    const first = deferred<any>();
    const second = deferred<any>();
    apiMocks.getFormulaDetail.mockImplementationOnce(() => first.promise).mockImplementationOnce(() => second.promise);
    shallowMount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': true,
          'a-form-item': true,
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
        },
      },
    });

    const firstInit = drawerState.init({ formulaId: 'formula-a' });
    const secondInit = drawerState.init({ formulaId: 'formula-b' });
    second.resolve({ data: { bizEnabled: 'enabled', gongshiBianma: 'formula_b', id: 'formula-b', versions: [{ id: 'version-b' }] } });
    await secondInit;
    await nextTick();
    first.resolve({ data: { bizEnabled: 'enabled', gongshiBianma: 'formula_a', id: 'formula-a', versions: [{ id: 'version-a' }] } });
    await firstInit;
    await nextTick();

    expect(tableState.config.dataSource.value.map((version: { id: string }) => version.id)).toEqual(['version-b']);
  });

  it('ignores a stale reload after the drawer has switched to another formula', async () => {
    const staleReload = deferred<any>();
    apiMocks.getFormulaDetail
      .mockResolvedValueOnce({
        data: {
          bizEnabled: 'enabled',
          gongshiBianma: 'formula_a',
          id: 'formula-a',
          versions: [{ banbenHao: 1, bizStatus: 'DRAFT', gongshiDingyiId: 'formula-a', id: 'version-a', parameters: [] }],
        },
      })
      .mockImplementationOnce(() => staleReload.promise)
      .mockResolvedValueOnce({ data: { bizEnabled: 'enabled', gongshiBianma: 'formula_b', id: 'formula-b', versions: [{ id: 'version-b' }] } });
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': true,
          'a-form-item': true,
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
        },
      },
    });
    await drawerState.init({ formulaId: 'formula-a' });
    await nextTick();
    const preReleaseButton = wrapper.findAll('button').find((button) => button.text() === '提交预发布');
    expect(preReleaseButton).toBeDefined();
    void preReleaseButton!.trigger('click');
    await Promise.resolve();

    await drawerState.init({ formulaId: 'formula-b' });
    staleReload.resolve({ data: { bizEnabled: 'enabled', gongshiBianma: 'formula_a', id: 'formula-a', versions: [{ id: 'version-a' }] } });
    await flushPromises();

    expect(tableState.config.dataSource.value.map((version: { id: string }) => version.id)).toEqual(['version-b']);
    expect(wrapper.emitted('reload')).toBeUndefined();
  });

  it('delegates copying to the backend by source version and keeps the drawer open on API failure', async () => {
    apiMocks.createFormulaVersion.mockRejectedValue(new Error('network error'));
    const wrapper = shallowMount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': true,
          'a-descriptions-item': true,
          'a-form': true,
          'a-form-item': true,
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1' });
    const copy = tableState.config.actionColumn;
    expect(drawerSource).toContain('sourceVersionId: version.id');
    expect(drawerSource).toContain('await createFormulaVersion');
    expect(drawerSource).not.toContain('closeDrawer()');
    expect(copy).toBeTruthy();
    expect(wrapper.exists()).toBe(true);
    expect(drawerState.closeDrawer).not.toHaveBeenCalled();
  });

  it('edits only supported result rules on a draft version', () => {
    expect(drawerSource).not.toContain("{ fullName: '数值集合', id: 'DECIMAL_LIST' }");
    expect(drawerSource).toContain('label="修约方式"');
    expect(drawerSource).toContain("{ fullName: '四舍六入五成双', id: 'HALF_EVEN' }");
    expect(drawerSource).not.toContain('银行家舍入');
    expect(drawerSource).toContain(':max="12"');
  });

  it('provides placeholders for every standard draft-version control', () => {
    expect(drawerSource).toContain('placeholder="请选择结果类型"');
    expect(drawerSource).toContain('placeholder="请输入修约小数位"');
    expect(drawerSource).toContain('placeholder="请选择修约方式"');
  });

  it('keeps bottom padding inside the draft-version modal content', () => {
    expect(drawerSource).toContain('class="draft-version-content"');
    expect(drawerSource).toMatch(/\.draft-version-content\s*\{[^}]*padding-bottom:\s*24px;/);
  });

  it('locks the measured collapsed height while parameter configuration toggles', async () => {
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': { template: '<form><slot /></form>' },
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          FormulaEditor: true,
          FormulaPreview: true,
          ParameterTable: true,
          TrialPanel: true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1', openDraft: true });
    await flushPromises();

    const editModal = wrapper.findAllComponents({ name: 'BasicModal' })[0]!;
    expect(editModal.props('height')).toBeUndefined();
    expect(editModal.props('minHeight')).toBeUndefined();

    editModal.vm.$emit('heightChange', 648);
    await nextTick();
    expect(editModal.props('height')).toBe(648);
    expect(editModal.props('minHeight')).toBe(648);

    await wrapper.find('[data-test="toggle-parameters"]').trigger('click');
    await nextTick();
    expect(editModal.props('height')).toBe(648);
    expect(editModal.props('minHeight')).toBe(648);
  });

  it('uses a responsive two-pane workspace with a full-width collapsible parameter section', () => {
    expect(drawerSource).toContain('width="min(1200px, calc(100vw - 48px))"');
    expect(drawerSource).toContain('class="draft-version-workspace"');
    expect(drawerSource).toContain('class="draft-version-editor"');
    expect(drawerSource).toContain('class="draft-version-validation"');
    expect(drawerSource).toContain('class="draft-version-preview"');
    expect(drawerSource).toContain('class="draft-version-preview-title"');
    expect(drawerSource).toContain('class="draft-version-trial"');
    expect(drawerSource).toContain('class="draft-version-trial-title"');
    expect(drawerSource).not.toContain('<a-tabs');
    expect(drawerSource).toMatch(/\.draft-version-preview\s*\{[^}]*margin-top:\s*12px;[^}]*\}/);
    expect(drawerSource).not.toMatch(/\.draft-version-preview\s*\{[^}]*border:/);
    expect(drawerSource).not.toMatch(/\.draft-version-preview-title\s*\{[^}]*border:/);
    expect(drawerSource).not.toMatch(/\.draft-version-trial-title\s*\{[^}]*border:/);
    expect(drawerSource.indexOf('<FormulaPreview')).toBeLessThan(drawerSource.indexOf('class="draft-version-validation"'));
    expect(drawerSource.indexOf('<TrialPanel')).toBeGreaterThan(drawerSource.indexOf('class="draft-version-validation"'));
    expect(drawerSource).toContain('parametersExpanded');
    expect(drawerSource).toContain('data-test="toggle-parameters"');
    expect(drawerSource).toContain('DownOutlined v-if="parametersExpanded"');
    expect(drawerSource).toContain('UpOutlined v-else');
    expect(drawerSource.indexOf("parametersExpanded ? '收起配置' : '展开配置'")).toBeLessThan(drawerSource.indexOf('DownOutlined v-if="parametersExpanded"'));
    expect(drawerSource).not.toMatch(/\.draft-version-content\s*\{[^}]*(?:height|max-height):/);
  });

  it('does not turn the formula editor column into a nested scroll container', () => {
    expect(drawerSource).not.toMatch(/\.draft-version-editor\s*\{[^}]*overflow:\s*auto;/);
    expect(drawerSource).not.toMatch(/\.draft-version-editor\s*\{[^}]*overflow:\s*hidden;/);
  });

  it('lets the expanded parameter table replace the entire modal workspace', async () => {
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': { template: '<form><slot /></form>' },
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          FormulaEditor: true,
          FormulaPreview: true,
          ParameterTable: {
            name: 'ElnFormulaParameterTable',
            props: { fullHeight: Boolean, modelValue: Array },
            template: '<div data-test="parameter-table" />',
          },
          TrialPanel: true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1', openDraft: true });
    await flushPromises();

    expect(wrapper.find('.draft-version-workspace').exists()).toBe(true);
    await wrapper.find('[data-test="toggle-parameters"]').trigger('click');
    await nextTick();

    expect(wrapper.find('.draft-version-workspace').exists()).toBe(false);
    expect(wrapper.find('.draft-version-parameters.is-expanded').exists()).toBe(true);
    expect(wrapper.findComponent({ name: 'ElnFormulaParameterTable' }).props('fullHeight')).toBe(true);
  });

  it('recreates the trial panel after the edit modal closes', async () => {
    let trialPanelInstance = 0;
    const TrialPanelStub = defineComponent({
      name: 'ElnFormulaTrialPanel',
      props: ['rule'],
      setup() {
        return { instance: ++trialPanelInstance };
      },
      template: '<div data-test="trial-panel">{{ instance }}</div>',
    });
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': { template: '<form><slot /></form>' },
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          FormulaEditor: true,
          FormulaPreview: true,
          ParameterTable: true,
          TrialPanel: TrialPanelStub,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1', openDraft: true });
    await flushPromises();

    expect(wrapper.find('[data-test="trial-panel"]').text()).toBe('1');
    const editModal = wrapper.findAllComponents({ name: 'BasicModal' })[0]!;
    await editModal.props('afterClose')();
    await nextTick();

    expect(wrapper.find('[data-test="trial-panel"]').text()).toBe('2');
  });

  it('shows confirmation loading while a draft version is being saved', async () => {
    const saveRequest = deferred<any>();
    apiMocks.updateFormulaVersion.mockReturnValueOnce(saveRequest.promise);
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': { template: '<form><slot /></form>' },
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          FormulaEditor: true,
          FormulaPreview: true,
          ParameterTable: true,
          TrialPanel: true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1', openDraft: true });
    await flushPromises();

    const editModal = wrapper.findAllComponents({ name: 'BasicModal' })[0]!;
    editModal.vm.$emit('ok');
    await nextTick();
    expect(editModal.props('confirmLoading')).toBe(true);

    saveRequest.resolve({ data: {} });
    await flushPromises();
    expect(editModal.props('confirmLoading')).toBe(false);
  });

  it('opens published versions in a readonly preview modal', async () => {
    const validateEditor = vi.fn();
    const FormulaEditorStub = defineComponent({
      name: 'ElnFormulaEditor',
      props: ['functions', 'modelValue', 'parameters', 'readonly', 'rule'],
      setup(_, { expose }) {
        expose({ validate: validateEditor });
        return {};
      },
      template: '<div class="formula-editor-stub" />',
    });
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': { template: '<form><slot /></form>' },
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          FormulaEditor: FormulaEditorStub,
          FormulaPreview: true,
          ParameterTable: true,
          TrialPanel: true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1' });
    await flushPromises();

    const preview = wrapper.findAll('button').find((button) => button.text() === '预览');
    expect(preview).toBeDefined();
    await preview!.trigger('click');
    await flushPromises();

    const editor = wrapper.findComponent({ name: 'ElnFormulaEditor' });
    const versionModal = wrapper.findAllComponents({ name: 'BasicModal' })[0]!;
    expect(editor.props('readonly')).toBe(true);
    expect(versionModal.props('title')).toBe('预览公式版本 - V1');
    expect(versionModal.props('showOkBtn')).toBe(false);
    expect(validateEditor).toHaveBeenCalledTimes(1);
  });

  it('loads function assistance and mounts the controlled editor for a draft version', async () => {
    const validateEditor = vi.fn();
    const FormulaEditorStub = defineComponent({
      name: 'ElnFormulaEditor',
      props: ['functions', 'modelValue', 'parameters', 'rule'],
      setup(_, { expose }) {
        expose({ validate: validateEditor });
        return {};
      },
      template: '<div class="formula-editor-stub" />',
    });
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': { template: '<form><slot /></form>' },
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          'a-button': true,
          FormulaEditor: FormulaEditorStub,
          FormulaPreview: true,
          ParameterTable: true,
          TrialPanel: true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1' });
    await flushPromises();

    const edit = wrapper.findAll('button').find((button) => button.text() === '编辑');
    await edit!.trigger('click');
    await flushPromises();

    const editor = wrapper.findComponent({ name: 'ElnFormulaEditor' });
    expect(wrapper.findAllComponents({ name: 'BasicModal' })[0]!.props('title')).toBe('编辑公式版本 - V2');
    expect(apiMocks.getFormulaFunctions).toHaveBeenCalledTimes(1);
    expect(editor.exists()).toBe(true);
    expect(editor.props('parameters')).toEqual([]);
    expect(editor.props('rule')).toEqual(expect.objectContaining({ biaodashi: 'amount * 2', jieguoLeixing: 'DECIMAL', parameters: [] }));
    expect(validateEditor).toHaveBeenCalledTimes(1);
  });

  it('shows the target version in reason modal titles', async () => {
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': true,
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          FormulaEditor: true,
          FormulaPreview: true,
          ParameterTable: true,
          TrialPanel: true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1' });
    await flushPromises();

    const reasonModal = wrapper.findAllComponents({ name: 'BasicModal' })[1]!;
    const cancel = wrapper.findAll('button').find((button) => button.text() === '作废');
    await cancel!.trigger('click');
    expect(reasonModal.props('title')).toBe('作废公式版本 - V2');

    const setCurrent = wrapper.findAll('button').find((button) => button.text() === '设为当前');
    await setCurrent!.trigger('click');
    expect(reasonModal.props('title')).toBe('切换当前版本 - V1');
  });

  it('opens the earliest draft editor immediately when initialized after draft creation', async () => {
    apiMocks.getFormulaDetail.mockResolvedValueOnce({
      data: {
        bizEnabled: 'enabled',
        gongshiBianma: 'hanliang_draft',
        id: 'formula-draft-1',
        versions: [
          {
            banbenHao: 1,
            biaodashi: '',
            bizStatus: 'DRAFT',
            gongshiDingyiId: 'formula-draft-1',
            id: 'version-draft-1',
            jieguoLeixing: 'DECIMAL',
            parameters: [],
          },
        ],
      },
    });
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': { template: '<form><slot /></form>' },
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': true,
          'a-select': true,
          'a-tag': true,
          'a-textarea': true,
          FormulaEditor: { name: 'ElnFormulaEditor', props: ['functions', 'parameters', 'rule'], template: '<div class="formula-editor-stub" />' },
          FormulaPreview: true,
          ParameterTable: { props: ['modelValue'], template: '<div data-test="parameter-table">{{ modelValue.length }}</div>' },
          TrialPanel: true,
        },
      },
    });

    await drawerState.init({ formulaId: 'formula-draft-1', openDraft: true });
    await flushPromises();

    expect(apiMocks.getFormulaFunctions).toHaveBeenCalledTimes(1);
    expect(wrapper.findComponent({ name: 'ElnFormulaEditor' }).props('rule')).toEqual(
      expect.objectContaining({ biaodashi: '', jieguoLeixing: 'DECIMAL', parameters: [], xiuyueMoshi: 'HALF_UP', xiuyueXiaoshuWei: 2 }),
    );
    expect(wrapper.find('.draft-version-preview').exists()).toBe(true);
    expect(wrapper.find('.draft-version-trial').exists()).toBe(true);
    expect(wrapper.text()).toContain('公式预览');
    expect(wrapper.text()).toContain('临时试算');
    expect(wrapper.find('[data-test="parameter-table"]').exists()).toBe(false);

    await wrapper.find('[data-test="toggle-parameters"]').trigger('click');
    await nextTick();
    expect(wrapper.find('[data-test="parameter-table"]').exists()).toBe(true);

    await drawerState.init({ formulaId: 'f1', openDraft: true });
    await flushPromises();
    expect(wrapper.find('.draft-version-preview').exists()).toBe(true);
    expect(wrapper.find('.draft-version-trial').exists()).toBe(true);
    expect(wrapper.find('[data-test="parameter-table"]').exists()).toBe(false);
  });

  it('clears and hides rounding settings for non-decimal results', async () => {
    const Select = defineComponent({ name: 'ASelect', props: ['allowClear', 'fieldNames', 'options', 'value'], emits: ['update:value'], template: '<div />' });
    const InputNumber = defineComponent({ name: 'AInputNumber', props: ['value'], template: '<input :value="value" />' });
    const wrapper = mount(VersionDrawer, {
      global: {
        stubs: {
          ...layoutStubs,
          'a-alert': true,
          'a-descriptions': { template: '<div><slot /></div>' },
          'a-descriptions-item': { template: '<span><slot /></span>' },
          'a-form': { template: '<form><slot /></form>' },
          'a-form-item': { template: '<div><slot /></div>' },
          'a-input-number': InputNumber,
          'a-select': Select,
          'a-tag': true,
          'a-textarea': true,
          FormulaEditor: true,
          FormulaPreview: true,
          ParameterTable: true,
          TrialPanel: true,
        },
      },
    });
    await drawerState.init({ formulaId: 'f1', openDraft: true });
    await flushPromises();

    const selects = wrapper.findAllComponents({ name: 'ASelect' });
    expect(selects).toHaveLength(2);
    expect(selects.every((select) => JSON.stringify(select.props('fieldNames')) === JSON.stringify({ label: 'fullName', value: 'id' }))).toBe(true);
    await selects[0]!.vm.$emit('update:value', 'BOOLEAN');
    await nextTick();

    expect(wrapper.findAllComponents({ name: 'AInputNumber' })).toHaveLength(0);
    expect(wrapper.findAllComponents({ name: 'ASelect' })).toHaveLength(1);

    await wrapper.findComponent({ name: 'ASelect' }).vm.$emit('update:value', 'DECIMAL');
    await nextTick();
    expect(wrapper.findComponent({ name: 'AInputNumber' }).props('value')).toBe(2);
    expect(wrapper.findAllComponents({ name: 'ASelect' }).at(1)!.props('value')).toBe('HALF_UP');
  });
});
