import { describe, it, expect } from 'vitest';
import { _jsxSplit } from './jsx-internal';
import type { QRL } from '../qrl/qrl.public';

describe('_jsxSplit', () => {
  describe('event name conversion', () => {
    it('should convert onClick$ to q-e:click in varProps', () => {
      const node = _jsxSplit('button', { onClick$: (() => {}) as any as QRL }, null, null, 0);
      expect(node.varProps['onClick$']).toBeUndefined();
      expect(node.varProps['q-e:click']).toBeDefined();
    });

    it('should convert onClick$ to q-e:click in constProps', () => {
      const node = _jsxSplit('button', null, { onClick$: (() => {}) as any as QRL }, null, 0);
      expect(node.constProps?.['onClick$']).toBeUndefined();
      expect(node.constProps?.['q-e:click']).toBeDefined();
    });

    it('should convert onInput$ to q-e:input in varProps', () => {
      const node = _jsxSplit('input', { onInput$: (() => {}) as any as QRL }, null, null, 0);
      expect(node.varProps['onInput$']).toBeUndefined();
      expect(node.varProps['q-e:input']).toBeDefined();
    });

    it('should convert event handlers to passive attributes when marked passive', () => {
      const node = _jsxSplit(
        'button',
        {
          'passive:click': true,
          'passive:scroll': true,
          'passive:touchstart': true,
          'passive:mouseover': true,
          onClick$: (() => {}) as any as QRL,
          'window:onScroll$': (() => {}) as any as QRL,
          'document:onTouchStart$': (() => {}) as any as QRL,
        },
        null,
        null,
        0
      );

      expect(node.varProps['onClick$']).toBeUndefined();
      expect(node.varProps['window:onScroll$']).toBeUndefined();
      expect(node.varProps['document:onTouchStart$']).toBeUndefined();
      expect(node.varProps['q-ep:click']).toBeDefined();
      expect(node.varProps['q-wp:scroll']).toBeDefined();
      expect(node.varProps['q-dp:touchstart']).toBeDefined();
      expect(node.varProps['passive:click']).toBeUndefined();
      expect(node.varProps['passive:scroll']).toBeUndefined();
      expect(node.varProps['passive:touchstart']).toBeUndefined();
      expect(node.varProps['passive:mouseover']).toBeUndefined();
      expect(node.varProps['q-ep:mouseover']).toBeUndefined();
    });

    it('should ignore passive markers when there is no matching event handler', () => {
      const node = _jsxSplit(
        'button',
        {
          'passive:click': true,
          'passive:scroll': true,
        },
        null,
        null,
        0
      );

      expect(node.varProps['passive:click']).toBeUndefined();
      expect(node.varProps['passive:scroll']).toBeUndefined();
      expect(node.varProps['q-ep:click']).toBeUndefined();
      expect(node.varProps['q-wp:scroll']).toBeUndefined();
      expect(node.constProps).toBeNull();
    });

    it('should drop preventdefault markers when the same event is passive', () => {
      const node = _jsxSplit(
        'button',
        {
          'preventdefault:click': true,
          onClick$: (() => {}) as any as QRL,
        },
        {
          'passive:click': true,
        },
        null,
        0
      );

      expect(node.varProps['preventdefault:click']).toBeUndefined();
      expect(node.varProps['q-ep:click']).toBeDefined();
      expect(node.constProps?.['passive:click']).toBeUndefined();
    });

    it('should drop preventdefault markers when passive and preventdefault are both in varProps', () => {
      const node = _jsxSplit(
        'button',
        {
          'passive:click': true,
          'preventdefault:click': true,
          onClick$: (() => {}) as any as QRL,
        },
        null,
        null,
        0
      );

      expect(node.varProps['passive:click']).toBeUndefined();
      expect(node.varProps['preventdefault:click']).toBeUndefined();
      expect(node.varProps['q-ep:click']).toBeDefined();
    });

    it('should drop preventdefault markers when passive is in varProps and preventdefault is in constProps', () => {
      const node = _jsxSplit(
        'button',
        {
          'passive:click': true,
          onClick$: (() => {}) as any as QRL,
        },
        {
          'preventdefault:click': true,
        },
        null,
        0
      );

      expect(node.varProps['passive:click']).toBeUndefined();
      expect(node.varProps['q-ep:click']).toBeDefined();
      expect(node.constProps?.['preventdefault:click']).toBeUndefined();
    });

    it('should drop preventdefault markers when passive is in constProps and preventdefault is in constProps', () => {
      const node = _jsxSplit(
        'button',
        {
          onClick$: (() => {}) as any as QRL,
        },
        {
          'passive:click': true,
          'preventdefault:click': true,
        },
        null,
        0
      );

      expect(node.varProps['q-ep:click']).toBeDefined();
      expect(node.constProps?.['passive:click']).toBeUndefined();
      expect(node.constProps?.['preventdefault:click']).toBeUndefined();
    });

    it('should keep preventdefault markers when there is no matching passive event', () => {
      const node = _jsxSplit(
        'button',
        {
          'preventdefault:click': true,
          onClick$: (() => {}) as any as QRL,
        },
        null,
        null,
        0
      );

      expect(node.varProps['preventdefault:click']).toBe(true);
      expect(node.varProps['q-e:click']).toBeDefined();
    });

    it('should preserve capture markers while lowering event handlers', () => {
      const node = _jsxSplit(
        'button',
        {
          'capture:click': true,
          onClick$: (() => {}) as any as QRL,
        },
        null,
        null,
        0
      );

      expect(node.varProps['capture:click']).toBe(true);
      expect(node.varProps['q-e:click']).toBeDefined();
    });

    it('should preserve capture markers with passive events', () => {
      const node = _jsxSplit(
        'button',
        {
          'capture:touchstart': true,
          'passive:touchstart': true,
          onTouchStart$: (() => {}) as any as QRL,
        },
        null,
        null,
        0
      );

      expect(node.varProps['capture:touchstart']).toBe(true);
      expect(node.varProps['passive:touchstart']).toBeUndefined();
      expect(node.varProps['q-ep:touchstart']).toBeDefined();
    });

    it('should convert multiple event handlers', () => {
      const node = _jsxSplit(
        'button',
        {
          onClick$: (() => {}) as any as QRL,
          onMouseOver$: (() => {}) as any as QRL,
        },
        null,
        null,
        0
      );
      expect(node.varProps['onClick$']).toBeUndefined();
      expect(node.varProps['onMouseOver$']).toBeUndefined();
      expect(node.varProps['q-e:click']).toBeDefined();
      expect(node.varProps['q-e:mouseover']).toBeDefined();
    });
  });

  it('should override the same event with different name conventions in var props case 1', () => {
    const clickHandler1 = (() => {}) as any as QRL;
    const clickHandler2 = (() => {}) as any as QRL;

    const node = _jsxSplit(
      'button',
      { onClick$: clickHandler1, 'q-e:click': clickHandler2 },
      null,
      null,
      0
    );

    expect(node.varProps['onClick$']).toBeUndefined();
    expect(node.varProps['q-e:click']).toBe(clickHandler2);
  });

  it('should override the same event with different name conventions in var props case 2', () => {
    const clickHandler1 = (() => {}) as any as QRL;
    const clickHandler2 = (() => {}) as any as QRL;

    const node = _jsxSplit(
      'button',
      { 'q-e:click': clickHandler2, onClick$: clickHandler1 },
      null,
      null,
      0
    );

    expect(node.varProps['onClick$']).toBeUndefined();
    expect(node.varProps['q-e:click']).toBe(clickHandler1);
  });

  it('should override the same event with different name conventions in const props case 1', () => {
    const clickHandler1 = (() => {}) as any as QRL;
    const clickHandler2 = (() => {}) as any as QRL;

    const node = _jsxSplit(
      'button',
      {},
      { onClick$: clickHandler1, 'q-e:click': clickHandler2 },
      null,
      0
    );

    expect(node.constProps?.['onClick$']).toBeUndefined();
    expect(node.constProps?.['q-e:click']).toBe(clickHandler2);
  });

  it('should override the same event with different name conventions in const props case 2', () => {
    const clickHandler1 = (() => {}) as any as QRL;
    const clickHandler2 = (() => {}) as any as QRL;

    const node = _jsxSplit(
      'button',
      {},
      { 'q-e:click': clickHandler2, onClick$: clickHandler1 },
      null,
      0
    );

    expect(node.constProps?.['onClick$']).toBeUndefined();
    expect(node.constProps?.['q-e:click']).toBe(clickHandler1);
  });

  describe('event handling between constProps and varProps', () => {
    it('should let constProps overwrite varProps when both have same event', () => {
      const constHandler = (() => {}) as any as QRL;
      const varHandler = (() => {}) as any as QRL;

      const node = _jsxSplit(
        'input',
        { onInput$: varHandler },
        { 'q-e:input': constHandler },
        null,
        0
      );

      // varProps should be transformed but then removed during deduplication
      expect(node.varProps['onInput$']).toBeUndefined();
      expect(node.varProps['q-e:input']).toBeUndefined();

      // constProps keeps its handler (no merging)
      expect(node.constProps?.['q-e:input']).toBe(constHandler);
    });

    it('should let constProps overwrite varProps after transformation', () => {
      const constHandler = (() => {}) as any as QRL;
      const varHandler = (() => {}) as any as QRL;

      const node = _jsxSplit(
        'input',
        { 'q-e:input': varHandler },
        { onInput$: constHandler },
        null,
        0
      );

      // constProps transformed and constProps wins
      expect(node.constProps?.['onInput$']).toBeUndefined();
      expect(node.constProps?.['q-e:input']).toBe(constHandler);
      expect(node.varProps['q-e:input']).toBeUndefined();
    });

    it('should handle multiple different events in both props', () => {
      const clickHandler1 = (() => {}) as any as QRL;
      const clickHandler2 = (() => {}) as any as QRL;
      const inputHandler1 = (() => {}) as any as QRL;
      const inputHandler2 = (() => {}) as any as QRL;

      const node = _jsxSplit(
        'input',
        {
          onClick$: clickHandler2,
          onInput$: inputHandler2,
        },
        {
          onClick$: clickHandler1,
          'q-e:input': inputHandler1,
        },
        null,
        0
      );

      // constProps wins for both (no merging)
      expect(node.constProps?.['q-e:click']).toBe(clickHandler1);
      expect(node.constProps?.['q-e:input']).toBe(inputHandler1);
      expect(node.varProps['q-e:click']).toBeUndefined();
      expect(node.varProps['q-e:input']).toBeUndefined();
    });

    it('should keep events separate when no overlap', () => {
      const clickHandler = (() => {}) as any as QRL;
      const inputHandler = (() => {}) as any as QRL;

      const node = _jsxSplit(
        'input',
        { onClick$: clickHandler },
        { onInput$: inputHandler },
        null,
        0
      );

      // Both should be kept in their respective locations
      expect(node.varProps['q-e:click']).toBe(clickHandler);
      expect(node.constProps?.['q-e:input']).toBe(inputHandler);
    });
  });

  describe('bind:value integration with events', () => {
    it('should handle bind:value with onInput$ in varProps', () => {
      const handler = (() => {}) as any as QRL;
      const signal = { value: 'test' };

      const node = _jsxSplit(
        'input',
        {
          'bind:value': signal,
          onInput$: handler,
        },
        null,
        null,
        0
      );

      expect(node.varProps['bind:value']).toBeUndefined();
      expect(node.varProps.value).toBe(signal);

      const merged = node.varProps['q-e:input'];
      expect(merged).toBeDefined();
      expect(Array.isArray(merged)).toBe(true);
      if (Array.isArray(merged)) {
        // Should have bind handler + user handler
        expect(merged.length).toBeGreaterThanOrEqual(2);
      }
    });

    it('should move q-e:input from constProps to varProps when bind:value is in varProps', () => {
      const handler = (() => {}) as any as QRL;
      const signal = { value: 'test' };

      const node = _jsxSplit(
        'input',
        {
          'bind:value': signal,
        },
        {
          'q-e:input': handler,
        },
        null,
        0
      );

      expect(node.varProps['bind:value']).toBeUndefined();
      expect(node.varProps.value).toBe(signal);

      // q-e:input should be moved from constProps to varProps and merged with bind handler
      expect(node.constProps?.['q-e:input']).toBeUndefined();
      const merged = node.varProps['q-e:input'];
      expect(merged).toBeDefined();
      expect(Array.isArray(merged)).toBe(true);
      if (Array.isArray(merged)) {
        expect(merged).toHaveLength(2);
        expect(merged[0]).toBe(handler); // Original handler from constProps (moved)
        // merged[1] should be the bind handler
      }
    });

    it('should move onInput$ from constProps to varProps and merge when bind:value is truthy', () => {
      const handler = 2 as any as QRL;
      const signal = { value: 'test' };
      const doBind = true;

      const node = _jsxSplit(
        'button',
        {
          'bind:value': doBind && signal,
        },
        {
          onInput$: handler,
        },
        null,
        0
      );

      expect(node.varProps['bind:value']).toBeUndefined();
      expect(node.varProps.value).toBe(signal);

      // onInput$ should be converted to q-e:input, moved from constProps to varProps, and merged
      expect(node.constProps?.['onInput$']).toBeUndefined();
      expect(node.constProps?.['q-e:input']).toBeUndefined();
      const merged = node.varProps['q-e:input'];
      expect(merged).toBeDefined();
      expect(Array.isArray(merged)).toBe(true);
      if (Array.isArray(merged)) {
        expect(merged).toHaveLength(2);
        expect(merged[0]).toBe(handler); // Original handler from constProps (moved and converted)
        // merged[1] should be the bind handler
      }
    });

    it('should keep onInput$ in constProps when bind:value is falsy', () => {
      const handler = 2 as any as QRL;
      const signal = { value: 'test' };
      const doBind = false;

      const node = _jsxSplit(
        'button',
        {
          'bind:value': doBind && signal,
        },
        {
          onInput$: handler,
        },
        null,
        0
      );

      // bind:value is falsy, so no binding should occur
      expect(node.varProps['bind:value']).toBe(false);
      expect(node.varProps.value).toBeUndefined();

      // onInput$ should remain in constProps, converted to q-e:input
      expect(node.constProps?.['onInput$']).toBeUndefined();
      expect(node.constProps?.['q-e:input']).toBe(handler);
      expect(node.varProps['q-e:input']).toBeUndefined();
    });

    it('should move bind:value from const props to var props', async () => {
      const signal = { value: 'test' };
      const node = _jsxSplit(
        'input',
        null,
        {
          'bind:value': signal,
        },
        null,
        0
      );
      expect(node.varProps['bind:value']).toBeUndefined();
      expect(node.constProps?.['onInput$']).toBeUndefined();
      expect(node.constProps?.['q-e:input']).toBeUndefined();

      expect(node.varProps.value).toBe(signal);
      expect(node.varProps['q-e:input']).toBeDefined();
    });

    it('should process bind:value for var props', async () => {
      const signal = { value: 'test' };
      const node = _jsxSplit(
        'input',
        {
          'bind:value': signal,
        },
        null,
        null,
        0
      );
      expect(node.varProps['bind:value']).toBeUndefined();
      expect(node.constProps?.['onInput$']).toBeUndefined();
      expect(node.constProps?.['q-e:input']).toBeUndefined();

      expect(node.varProps.value).toBe(signal);
      expect(node.varProps['q-e:input']).toBeDefined();
    });
  });

  describe('className to class conversion', () => {
    it('should convert className to class in varProps', () => {
      const node = _jsxSplit('div', { className: 'my-class' }, null, null, 0);

      expect(node.varProps.className).toBeUndefined();
      expect(node.varProps.class).toBe('my-class');
    });

    it('should convert className to class in constProps', () => {
      const node = _jsxSplit('div', null, { className: 'my-class' }, null, 0);

      expect(node.constProps?.className).toBeUndefined();
      expect(node.constProps?.class).toBe('my-class');
    });
  });

  describe('children and key extraction', () => {
    it('should extract children from varProps', () => {
      const children = 'Hello';
      const node = _jsxSplit('div', { children }, null, null, 0);

      expect(node.varProps.children).toBeUndefined();
      expect(node.children).toBe(children);
    });

    it('should extract key from varProps', () => {
      const node = _jsxSplit('div', { key: 'my-key' }, null, null, 0);

      expect(node.varProps.key).toBeUndefined();
      expect(node.key).toBe('my-key');
    });

    it('should prefer explicit children parameter over varProps', () => {
      const explicitChildren = 'Explicit';
      const propsChildren = 'Props';

      const node = _jsxSplit('div', { children: propsChildren }, null, explicitChildren, 0);

      expect(node.children).toBe(explicitChildren);
    });
  });

  describe('deduplication between varProps and constProps', () => {
    it('should remove duplicate keys from varProps that exist in constProps', () => {
      const node = _jsxSplit(
        'div',
        { id: 'var-id', class: 'var-class' },
        { id: 'const-id' },
        null,
        0
      );

      expect(node.varProps.id).toBeUndefined();
      expect(node.varProps.class).toBe('var-class');
      expect(node.constProps?.id).toBe('const-id');
    });
  });

  describe('non-HTML elements', () => {
    it('should not convert events for function components', () => {
      const Component = () => null;
      const handler = (() => {}) as any as QRL;

      const node = _jsxSplit(Component as any, { onClick$: handler }, null, null, 0);

      // For components, events should not be converted
      expect(node.varProps['onClick$']).toBeDefined();
      expect(node.varProps['q-e:click']).toBeUndefined();
    });
  });

  describe('copy-on-write optimization', () => {
    it('should not mutate original varProps when converting events', () => {
      const handler = (() => {}) as any as QRL;
      const originalVarProps = { onClick$: handler, id: 'test' };
      const originalRef = originalVarProps;

      const node = _jsxSplit('button', originalVarProps, null, null, 0);

      // Original object should not be mutated
      expect(originalRef.onClick$).toBe(handler);
      expect((originalRef as any)['q-e:click']).toBeUndefined();
      expect(originalRef.id).toBe('test');

      // Result should have converted event
      expect(node.varProps['q-e:click']).toBeDefined();
      expect(node.varProps.id).toBe('test');
    });

    it('should not mutate original constProps when converting events', () => {
      const handler = (() => {}) as any as QRL;
      const originalConstProps = { onClick$: handler, id: 'test' };
      const originalRef = originalConstProps;

      const node = _jsxSplit('button', null, originalConstProps, null, 0);

      // Original object should not be mutated
      expect(originalRef.onClick$).toBe(handler);
      expect((originalRef as any)['q-e:click']).toBeUndefined();
      expect(originalRef.id).toBe('test');

      // Result should have converted event
      expect(node.constProps?.['q-e:click']).toBeDefined();
      expect(node.constProps?.id).toBe('test');
    });

    it('should not mutate original varProps when handling bind:checked', () => {
      const signal = { value: true } as any;
      const originalVarProps = { 'bind:checked': signal, id: 'test' };
      const originalRef = originalVarProps;

      const node = _jsxSplit('input', originalVarProps, null, null, 0);

      // Original object should not be mutated
      expect(originalRef['bind:checked']).toBe(signal);
      expect(originalRef.id).toBe('test');
      expect((originalRef as any).checked).toBeUndefined();
      expect((originalRef as any)['q-e:input']).toBeUndefined();

      // Result should have checked and q-e:input (not q-e:change)
      expect(node.varProps.checked).toBeDefined();
      expect(node.varProps['q-e:input']).toBeDefined();
      expect(node.varProps['bind:checked']).toBeUndefined();
      expect(node.varProps.id).toBe('test');
    });

    it('should not mutate original varProps when deduplicating with constProps', () => {
      const originalVarProps = { id: 'var-id', class: 'var-class' };
      const originalRef = originalVarProps;

      const node = _jsxSplit('div', originalVarProps, { id: 'const-id' }, null, 0);

      // Original object should not be mutated
      expect(originalRef.id).toBe('var-id');
      expect(originalRef.class).toBe('var-class');

      // Result should have deduplicated (id removed from varProps)
      expect(node.varProps.id).toBeUndefined();
      expect(node.varProps.class).toBe('var-class');
      expect(node.constProps?.id).toBe('const-id');
    });

    it('should not mutate original varProps when removing children', () => {
      const originalVarProps = { id: 'test', children: 'child content' };
      const originalRef = originalVarProps;

      const node = _jsxSplit('div', originalVarProps, null, null, 0);

      // Original object should not be mutated
      expect(originalRef.children).toBe('child content');
      expect(originalRef.id).toBe('test');

      // Result should have children extracted and id preserved
      expect(node.varProps.children).toBeUndefined();
      expect(node.varProps.id).toBe('test');
      expect(node.children).toBe('child content');
    });

    it('should not mutate original varProps when removing key', () => {
      const originalVarProps = { id: 'test', key: 'my-key' };
      const originalRef = originalVarProps;

      const node = _jsxSplit('div', originalVarProps, null, null, 0);

      // Original object should not be mutated
      expect(originalRef.key).toBe('my-key');
      expect(originalRef.id).toBe('test');

      // Result should have key extracted and id preserved
      expect(node.varProps.key).toBeUndefined();
      expect(node.varProps.id).toBe('test');
      expect(node.key).toBe('my-key');
    });

    it('should not mutate original varProps when converting className', () => {
      const originalVarProps = { className: 'test-class', id: 'test' };
      const originalRef = originalVarProps;

      const node = _jsxSplit('div', originalVarProps, null, null, 0);

      // Original object should not be mutated
      expect(originalRef.className).toBe('test-class');
      expect(originalRef.id).toBe('test');

      // Result should have class instead of className
      expect(node.varProps.className).toBeUndefined();
      expect(node.varProps.class).toBe('test-class');
      expect(node.varProps.id).toBe('test');
    });

    it('should not copy when no modifications are needed', () => {
      const originalVarProps = { id: 'test', 'data-value': '123' };

      const node = _jsxSplit('div', originalVarProps, null, null, 0);

      // When no modifications are needed, should use same reference
      expect(node.varProps).toBe(originalVarProps);
      expect(node.varProps.id).toBe('test');
      expect(node.varProps['data-value']).toBe('123');
    });

    it('should not copy constProps when no modifications are needed', () => {
      const originalConstProps = { id: 'test', 'data-value': '123' };

      const node = _jsxSplit('div', null, originalConstProps, null, 0);

      // When no modifications are needed, should use same reference
      expect(node.constProps).toBe(originalConstProps);
      expect(node.constProps?.id).toBe('test');
      expect(node.constProps?.['data-value']).toBe('123');
    });

    it('should handle multiple modifications with single copy', () => {
      const handler1 = (() => {}) as any as QRL;
      const handler2 = (() => {}) as any as QRL;
      const originalVarProps = {
        onClick$: handler1,
        onInput$: handler2,
        className: 'test-class',
        key: 'my-key',
        id: 'test',
      };
      const originalRef = originalVarProps;

      const node = _jsxSplit('input', originalVarProps, null, null, 0);

      // Original should be untouched
      expect(originalRef.onClick$).toBe(handler1);
      expect(originalRef.onInput$).toBe(handler2);
      expect(originalRef.className).toBe('test-class');
      expect(originalRef.key).toBe('my-key');
      expect(originalRef.id).toBe('test');

      // Result should have all transformations applied
      expect(node.varProps['q-e:click']).toBeDefined();
      expect(node.varProps['q-e:input']).toBeDefined();
      expect(node.varProps.class).toBe('test-class');
      expect(node.varProps.className).toBeUndefined();
      expect(node.varProps.key).toBeUndefined();
      expect(node.varProps.id).toBe('test');
      expect(node.key).toBe('my-key');

      // Should be a different object (only one copy made)
      expect(node.varProps).not.toBe(originalRef);
    });
  });
});
