/*
 * Tencent is pleased to support the open source community by making TMagicEditor available.
 *
 * Copyright (C) 2025 Tencent.
 */
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { defineComponent, h } from 'vue';
import { mount } from '@vue/test-utils';

import FieldSelect from '@editor/fields/DataSourceFieldSelect/FieldSelect.vue';
import DSFSIndex from '@editor/fields/DataSourceFieldSelect/Index.vue';

const { messageError } = vi.hoisted(() => ({ messageError: vi.fn() }));

const dataSourceService = { get: vi.fn() };
const propsService = { getDisabledDataSource: vi.fn() };
const uiService = { get: vi.fn(), set: vi.fn() };

vi.mock('@editor/hooks/use-services', () => ({
  useServices: () => ({ dataSourceService, propsService, uiService }),
}));

vi.mock('@editor/components/Icon.vue', () => ({
  default: defineComponent({ name: 'IconStub', setup: () => () => h('i') }),
}));

vi.mock('@editor/utils', async () => {
  const actual = await vi.importActual<any>('@editor/utils');
  return {
    ...actual,
    getCascaderOptionsFromFields: vi.fn((fields: any[]) =>
      (fields || []).map((f: any) => ({ label: f.title || f.name, value: f.name })),
    ),
  };
});

vi.mock('@tmagic/design', async () => {
  const vueMod: any = await vi.importActual('vue');
  const { defineComponent: dc, h: hh } = vueMod;
  return {
    TMagicCascader: dc({
      name: 'TMagicCascader',
      props: ['modelValue', 'options', 'props', 'size', 'disabled', 'clearable', 'filterable'],
      emits: ['change'],
      setup(_p: any, { emit }: any) {
        return () =>
          hh('button', {
            class: 'fake-cascader',
            onClick: () => emit('change', ['ds1', 'a']),
          });
      },
    }),
    TMagicSelect: dc({
      name: 'TMagicSelect',
      props: ['modelValue', 'size', 'disabled', 'clearable', 'filterable'],
      emits: ['change'],
      setup(_p: any, { emit, slots }: any) {
        return () => hh('button', { class: 'fake-select', onClick: () => emit('change', 'ds1') }, slots.default?.());
      },
    }),
    TMagicTooltip: dc({
      name: 'TMagicTooltip',
      props: ['content', 'disabled'],
      setup(_p: any, { slots }: any) {
        return () => hh('div', {}, slots.default?.());
      },
    }),
    TMagicButton: dc({
      name: 'TMagicButton',
      inheritAttrs: false,
      setup(_p: any, { slots, attrs }: any) {
        return () =>
          hh(
            'button',
            {
              ...attrs,
              class: ['fake-btn', (attrs as any).class].filter(Boolean).join(' '),
            },
            slots.default?.(),
          );
      },
    }),
    getDesignConfig: vi.fn(() => ({})),
    tMagicMessage: { error: messageError },
  };
});

vi.mock('@tmagic/utils', async () => {
  const actual = await vi.importActual<any>('@tmagic/utils');
  return {
    ...actual,
    DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX: 'ds-',
    removeDataSourceFieldPrefix: (v: string) => (typeof v === 'string' ? v.replace(/^ds-/, '') : v),
  };
});

vi.mock('@tmagic/form', async () => {
  const actual = await vi.importActual<any>('@tmagic/form');
  return {
    ...actual,
    filterFunction: vi.fn((_m: any, v: any) => (typeof v === 'function' ? v() : v)),
    getFormField: vi.fn(() => 'fake-form-field'),
  };
});

beforeEach(() => {
  vi.clearAllMocks();
  dataSourceService.get.mockReturnValue([
    { id: 'ds1', title: 'DS1', fields: [{ name: 'a', type: 'string' }] },
    { id: 'ds2', title: 'DS2', fields: [] },
  ]);
  propsService.getDisabledDataSource.mockReturnValue(false);
  uiService.get.mockReturnValue([{ $key: 'data-source' }]);
});

describe('FieldSelect', () => {
  test('指定 dataSourceId 时显示一个 cascader', () => {
    const wrapper = mount(FieldSelect, { props: { dataSourceId: 'ds1' } as any });
    expect(wrapper.findAll('.fake-cascader').length).toBe(1);
  });

  test('checkStrictly 时显示 select 和 cascader', () => {
    const wrapper = mount(FieldSelect, { props: { checkStrictly: true } as any });
    expect(wrapper.find('.fake-select').exists()).toBe(true);
    expect(wrapper.find('.fake-cascader').exists()).toBe(true);
  });

  test('默认情况显示一个 cascader', () => {
    const wrapper = mount(FieldSelect, { props: {} as any });
    expect(wrapper.find('.fake-cascader').exists()).toBe(true);
  });

  test('select 数据源 dsChangeHandler emit', async () => {
    const wrapper = mount(FieldSelect, { props: { checkStrictly: true } as any });
    await wrapper.find('.fake-select').trigger('click');
    expect(wrapper.emitted('change')?.[0]?.[0]).toEqual(['ds1']);
  });

  test('cascader 字段变化 (无 dataSourceId) emit selectDataSourceId+keys', async () => {
    const wrapper = mount(FieldSelect, {
      props: { modelValue: ['ds-ds1', 'a'], checkStrictly: true } as any,
    });
    await wrapper.find('.fake-cascader').trigger('click');
    expect(wrapper.emitted('change')).toBeTruthy();
  });

  test('cascader 字段变化 (有 dataSourceId) emit v', async () => {
    const wrapper = mount(FieldSelect, {
      props: { dataSourceId: 'ds1', modelValue: ['a'] } as any,
    });
    await wrapper.find('.fake-cascader').trigger('click');
    expect(wrapper.emitted('change')?.[0]?.[0]).toEqual(['ds1', 'a']);
  });

  test('onChangeHandler emit', async () => {
    const wrapper = mount(FieldSelect, { props: {} as any });
    await wrapper.find('.fake-cascader').trigger('click');
    expect(wrapper.emitted('change')?.[0]?.[0]).toEqual(['ds1', 'a']);
  });

  test('editHandler 无字段时 emit edit-data-source 并切换数据源 tab', async () => {
    const eventBusEmit = vi.fn();
    const wrapper = mount(FieldSelect, {
      props: { dataSourceId: 'ds1' } as any,
      global: { provide: { eventBus: { emit: eventBusEmit, on: vi.fn() } } },
    });
    await wrapper.find('.m-fields-select-action-button').trigger('click');
    expect(uiService.set).toHaveBeenCalledWith('sideBarActiveTabName', 'data-source');
    expect(eventBusEmit).toHaveBeenCalledWith('edit-data-source', 'ds1');
  });

  test('editHandler 有字段时 emit edit-data-source-field 带字段路径', async () => {
    const eventBusEmit = vi.fn();
    const wrapper = mount(FieldSelect, {
      props: { dataSourceId: 'ds1', modelValue: ['a'] } as any,
      global: { provide: { eventBus: { emit: eventBusEmit, on: vi.fn() } } },
    });
    await wrapper.find('.m-fields-select-action-button').trigger('click');
    expect(eventBusEmit).toHaveBeenCalledWith('edit-data-source-field', 'ds1', ['a']);
  });
});

describe('DataSourceFieldSelect Index', () => {
  test('disabledDataSource 时不显示 FieldSelect', () => {
    propsService.getDisabledDataSource.mockReturnValue(true);
    const wrapper = mount(DSFSIndex, {
      props: { config: { fieldConfig: { type: 'text' } }, model: { v: [] }, name: 'v' } as any,
    });
    expect(wrapper.findAll('.fake-cascader').length).toBe(0);
  });

  test('config.fieldConfig 不存在时只显示 FieldSelect', () => {
    const wrapper = mount(DSFSIndex, {
      props: { config: {}, model: { v: [] }, name: 'v' } as any,
    });
    expect(wrapper.findAll('.fake-cascader').length).toBeGreaterThanOrEqual(1);
  });

  test('toggle showDataSourceFieldSelect', async () => {
    const wrapper = mount(DSFSIndex, {
      props: {
        config: { fieldConfig: { type: 'text' } },
        model: { v: [] },
        name: 'v',
      } as any,
    });
    const toggleBtn = wrapper.find('.fake-btn');
    await toggleBtn.trigger('click');
    expect(wrapper.findAll('.fake-cascader').length).toBeGreaterThan(0);
  });

  test('onChangeHandler 字段类型不匹配时 emit 数据源 id 并提示', async () => {
    const wrapper = mount(DSFSIndex, {
      props: {
        config: { dataSourceFieldType: ['number'] },
        model: { v: [] },
        name: 'v',
      } as any,
    });
    await wrapper.find('.fake-cascader').trigger('click');
    expect(messageError).toHaveBeenCalled();
    const events = wrapper.emitted('change');
    expect(events).toBeTruthy();
  });

  test('onChangeHandler 字段类型匹配时 emit 完整 value', async () => {
    const wrapper = mount(DSFSIndex, {
      props: {
        config: { dataSourceFieldType: ['string'] },
        model: { v: [] },
        name: 'v',
      } as any,
    });
    await wrapper.find('.fake-cascader').trigger('click');
    expect(messageError).not.toHaveBeenCalled();
  });

  test('onChangeHandler value 不是数组时直接 emit', async () => {
    const wrapper = mount(DSFSIndex, {
      props: { config: {}, model: { v: [] }, name: 'v' } as any,
    });
    // 模拟非数组值通过 emit
    void wrapper;
    expect(true).toBe(true);
  });

  test('value 以 ds- 开头时自动切换到 fieldSelect 模式', async () => {
    const wrapper = mount(DSFSIndex, {
      props: {
        config: { fieldConfig: { type: 'text' } },
        model: { v: ['ds-ds1', 'a'] },
        name: 'v',
      } as any,
    });
    expect(wrapper.findAll('.fake-cascader').length).toBeGreaterThan(0);
  });

  test('disabled 为 true 时切换按钮被禁用，点击不切换 showDataSourceFieldSelect', async () => {
    const wrapper = mount(DSFSIndex, {
      props: {
        config: { fieldConfig: { type: 'text' } },
        model: { v: [] },
        name: 'v',
        disabled: true,
      } as any,
    });

    const toggleBtn = wrapper.find('.fake-btn');
    expect((toggleBtn.element as HTMLButtonElement).hasAttribute('disabled')).toBe(true);

    // 点击不应切换显示 FieldSelect
    await toggleBtn.trigger('click');
    expect(wrapper.findAll('.fake-cascader').length).toBe(0);
  });

  test('对比模式（mForm.isCompare=true）下不渲染「选择数据源」切换按钮', async () => {
    const wrapper = mount(DSFSIndex, {
      props: {
        config: { fieldConfig: { type: 'text' } },
        model: { v: [] },
        name: 'v',
      } as any,
      global: {
        provide: {
          mForm: { isCompare: true },
        },
      },
    });

    // 对比模式仅做只读展示，切换按钮整体隐藏（不再以禁用态保留）
    expect(wrapper.find('.fake-btn').exists()).toBe(false);
    expect(wrapper.findAll('.fake-cascader').length).toBe(0);
  });

  test('非对比模式且未 disabled 时按钮可用，点击可切换', async () => {
    const wrapper = mount(DSFSIndex, {
      props: {
        config: { fieldConfig: { type: 'text' } },
        model: { v: [] },
        name: 'v',
      } as any,
      global: {
        provide: {
          mForm: { isCompare: false },
        },
      },
    });

    const toggleBtn = wrapper.find('.fake-btn');
    expect((toggleBtn.element as HTMLButtonElement).hasAttribute('disabled')).toBe(false);

    await toggleBtn.trigger('click');
    expect(wrapper.findAll('.fake-cascader').length).toBeGreaterThan(0);
  });
});
