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

const {
  EVENTS_MODULE,
  API_MODULE,
  APP_MODULE,
  COMPONENTS_MODULE,
} = vi.hoisted(() => ({
  EVENTS_MODULE: new URL('../../../web/comfyui/loras_widget_events.js', import.meta.url).pathname,
  API_MODULE: new URL('../../../scripts/api.js', import.meta.url).pathname,
  APP_MODULE: new URL('../../../scripts/app.js', import.meta.url).pathname,
  COMPONENTS_MODULE: new URL('../../../web/comfyui/loras_widget_components.js', import.meta.url).pathname,
}));

vi.mock(API_MODULE, () => ({
  api: {},
}));

vi.mock(APP_MODULE, () => ({
  app: {},
}));

vi.mock(COMPONENTS_MODULE, () => ({
  createMenuItem: vi.fn(),
  createDropIndicator: vi.fn(),
}));

describe('LoRA widget drag interactions', () => {
  beforeEach(() => {
    document.body.innerHTML = '';
    const dragStyle = document.getElementById('lm-lora-shared-styles');
    if (dragStyle) {
      dragStyle.remove();
    }
  });

  afterEach(() => {
    document.body.classList.remove('lm-lora-strength-dragging');
  });

  it('adjusts a single LoRA strength while syncing collapsed clip strength', async () => {
    const { handleStrengthDrag } = await import(EVENTS_MODULE);

    const widget = {
      value: [
        { name: 'Test', strength: 0.5, clipStrength: 0.25, expanded: false },
      ],
      callback: vi.fn(),
    };

    handleStrengthDrag('Test', 0.5, 100, { clientX: 140 }, widget, false);

    expect(widget.value[0].strength).toBeCloseTo(0.54, 2);
    expect(widget.value[0].clipStrength).toBeCloseTo(0.54, 2);
    expect(widget.callback).toHaveBeenCalledWith(widget.value);
  });

  it('applies proportional drag updates to all LoRAs', async () => {
    const { handleAllStrengthsDrag } = await import(EVENTS_MODULE);

    const widget = {
      value: [
        { name: 'A', strength: 0.4, clipStrength: 0.4 },
        { name: 'B', strength: 0.6, clipStrength: 0.6 },
      ],
      callback: vi.fn(),
    };

    const initialStrengths = [
      { modelStrength: 0.4, clipStrength: 0.4 },
      { modelStrength: 0.6, clipStrength: 0.6 },
    ];

    handleAllStrengthsDrag(initialStrengths, 100, { clientX: 160 }, widget);

    expect(widget.value[0].strength).toBeCloseTo(0.41, 2);
    expect(widget.value[1].strength).toBeCloseTo(0.62, 2);
    expect(widget.callback).toHaveBeenCalledWith(widget.value);
  });

  it('initiates drag gestures, updates strength, and clears cursor state on pointerup', async () => {
    const module = await import(EVENTS_MODULE);
    const renderSpy = vi.fn();
    const previewSpy = { hide: vi.fn() };
    const onDragStart = vi.fn();
    const onDragEnd = vi.fn();

    const dragEl = document.createElement('div');
    dragEl.className = 'lm-lora-entry';
    document.body.append(dragEl);

    const widget = {
      value: [{ name: 'Test', strength: 0.5, clipStrength: 0.5 }],
      callback: vi.fn(),
    };

    module.initDrag(dragEl, 'Test', widget, false, previewSpy, renderSpy, {
      onDragStart,
      onDragEnd,
    });

    dragEl.dispatchEvent(new PointerEvent('pointerdown', { clientX: 50, bubbles: true, pointerId: 1 }));
    expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(false);
    expect(onDragStart).not.toHaveBeenCalled();

    dragEl.dispatchEvent(new PointerEvent('pointermove', { clientX: 70, bubbles: true, pointerId: 1 }));
    expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(true);
    expect(onDragStart).toHaveBeenCalledTimes(1);
    expect(previewSpy.hide).toHaveBeenCalled();
    expect(widget.value[0].strength).not.toBe(0.5);
    expect(widget.callback).not.toHaveBeenCalled();
    expect(renderSpy).not.toHaveBeenCalled();

    dragEl.dispatchEvent(new PointerEvent('pointerup', { pointerId: 1 }));
    expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(false);
    expect(onDragEnd).toHaveBeenCalledTimes(1);
    // 454210a4 replaced renderFunction() with widget.value setter + widget.callback()
    expect(widget.callback).toHaveBeenCalledWith(widget.value);
  });

  it('deletes the selected LoRA when backspace is pressed outside of strength inputs', async () => {
    const { handleKeyboardNavigation } = await import(EVENTS_MODULE);
    const widget = {
      value: [{ name: 'Test', strength: 0.5, clipStrength: 0.5 }],
      callback: vi.fn(),
    };
    const preventDefault = vi.fn();
    const renderSpy = vi.fn();
    const selectLora = vi.fn();
    const event = {
      key: 'Backspace',
      preventDefault,
      ctrlKey: false,
      metaKey: false,
      target: document.createElement('div'),
    };

    const handled = handleKeyboardNavigation(event, 'Test', widget, renderSpy, selectLora);

    expect(handled).toBe(true);
    expect(preventDefault).toHaveBeenCalled();
    expect(widget.value).toEqual([]);
    expect(widget.callback).toHaveBeenCalledWith([]);
    expect(renderSpy).toHaveBeenCalledWith([], widget);
    expect(selectLora).toHaveBeenCalledWith(null);
  });

  it('keeps the LoRA entry when editing strength input and pressing backspace', async () => {
    const { handleKeyboardNavigation } = await import(EVENTS_MODULE);
    const strengthInput = document.createElement('input');
    strengthInput.classList.add('lm-lora-strength-input');

    const widget = {
      value: [{ name: 'Test', strength: 0.5, clipStrength: 0.5 }],
      callback: vi.fn(),
    };
    const preventDefault = vi.fn();
    const renderSpy = vi.fn();
    const selectLora = vi.fn();

    const event = {
      key: 'Backspace',
      preventDefault,
      ctrlKey: false,
      metaKey: false,
      target: strengthInput,
    };

    const handled = handleKeyboardNavigation(event, 'Test', widget, renderSpy, selectLora);

    expect(handled).toBe(false);
    expect(preventDefault).not.toHaveBeenCalled();
    expect(widget.value).toHaveLength(1);
    expect(widget.callback).not.toHaveBeenCalled();
    expect(renderSpy).not.toHaveBeenCalled();
    expect(selectLora).not.toHaveBeenCalled();
  });
});
