import type { EditorView } from '@codemirror/view';
import { flushSync, mount, unmount } from 'svelte';
import { afterEach, describe, expect, it, vi } from 'vitest';
import ParamTokenEditor from './ParamTokenEditor.svelte';

type RenderResult = {
  host: HTMLDivElement;
  view: EditorView;
  destroy: () => Promise<void>;
};

const PARAMS = [
  {
    name: 'start.user_input',
    displayName: '开始节点 > 用户问题',
    refType: 'input',
  },
];

async function renderEditor(props: Record<string, unknown> = {}): Promise<RenderResult> {
  const host = document.createElement('div');
  document.body.appendChild(host);

  const app = mount(ParamTokenEditor, {
    target: host,
    props: {
      mode: 'textarea',
      value: '',
      parameters: PARAMS,
      ...props,
    },
  });
  flushSync();

  const shell = host.querySelector('.param-token-editor-shell') as HTMLDivElement & {
    __paramTokenEditorView?: EditorView;
  };

  if (!shell?.__paramTokenEditorView) {
    throw new Error('ParamTokenEditor view not found');
  }

  return {
    host,
    view: shell.__paramTokenEditorView,
    destroy: async () => {
      await unmount(app);
      host.remove();
    },
  };
}

async function pressKey(view: EditorView, key: string) {
  view.focus();
  view.contentDOM.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }));
  flushSync();
}

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

describe('ParamTokenEditor', () => {
  it('should render chip text and parameter panel with displayName only', async () => {
    const { host, destroy } = await renderEditor({
      value: '前缀 {{start.user_input}}',
    });

    expect(host.querySelector('.param-token-chip-text')?.textContent).toBe('开始节点 > 用户问题');
    expect(host.querySelector('.param-token-item-label')?.textContent).toBe('开始节点 > 用户问题');
    expect(host.textContent).not.toContain('start.user_input');

    await destroy();
  });

  it('should insert raw token text and move cursor after token', async () => {
    const oninput = vi.fn();
    const onchange = vi.fn();
    const { host, view, destroy } = await renderEditor({
      oninput,
      onchange,
    });

    (host.querySelector('.param-token-item') as HTMLButtonElement).click();
    flushSync();

    expect(view.state.doc.toString()).toBe('{{start.user_input}}');
    expect(view.state.selection.main.head).toBe('{{start.user_input}}'.length);
    expect(oninput).toHaveBeenCalled();
    expect(onchange).toHaveBeenCalledWith({
      target: {
        value: '{{start.user_input}}',
      },
    });

    await destroy();
  });

  it('should remove the whole token on backspace and delete at token boundaries', async () => {
    const token = '{{start.user_input}}';
    const { view, destroy } = await renderEditor({
      value: `A ${token} B`,
    });

    view.dispatch({
      selection: {
        anchor: `A ${token}`.length,
      },
    });
    await pressKey(view, 'Backspace');
    expect(view.state.doc.toString()).toBe('A  B');

    view.dispatch({
      changes: {
        from: 0,
        to: view.state.doc.length,
        insert: `A ${token} B`,
      },
      selection: {
        anchor: 2,
      },
    });
    flushSync();
    await pressKey(view, 'Delete');
    expect(view.state.doc.toString()).toBe('A  B');

    await destroy();
  });

  it('should block newline insertion in single-line mode', async () => {
    const { view, destroy } = await renderEditor({
      mode: 'input',
      value: 'abc',
    });

    await pressKey(view, 'Enter');
    expect(view.state.doc.toString()).toBe('abc');

    await destroy();
  });

  it('should render invalid token as disconnected parameter label without undefined hint', async () => {
    const { host, destroy } = await renderEditor({
      value: '{{missing.ref}}',
    });

    expect(host.querySelector('.param-token-chip-text')?.textContent).toBe('ref（已断开连接）');
    expect(host.textContent).not.toContain('未定义参数');

    await destroy();
  });

  it('should prefer stored display name for disconnected references', async () => {
    const { host, destroy } = await renderEditor({
      value: '{{start.user_input}}',
      parameters: [
        {
          name: 'start.user_input',
          displayName: '开始节点 > 用户问题',
          resolved: false,
          disconnected: true,
        },
      ],
    });

    expect(host.querySelector('.param-token-chip-text')?.textContent).toBe(
      '开始节点 > 用户问题（已断开连接）',
    );
    expect(host.querySelector('.param-token-invalid .param-token-chip-text')).not.toBeNull();

    await destroy();
  });
});
