import {
  Fragment as Component,
  Fragment as Signal,
  component$,
  useSignal,
  useStore,
} from '@qwik.dev/core';
import { domRender, ssrRenderToDom, trigger, waitForDrain } from '@qwik.dev/core/testing';
import { describe, expect, it } from 'vitest';
import { _useHmr } from '../internal';
import { VNodeFlags } from '../client/types';

const debug = false; //true;
Error.stackTraceLimit = 100;

/**
 * Helper to fire the qHmr document event that triggers HMR re-renders.
 *
 * Uses the kebab-cased `d:q-hmr` form so `trigger` finds elements with the `q-d:q-hmr` attribute
 * set by `useOnDocument('qHmr', …)`.
 */
async function triggerHmr(container: any, files: string[]) {
  const t = Date.now();
  container.document.__hmrT = t;
  await trigger(container.element, null, 'd:q-hmr', {
    detail: { files, t },
  });
  // markVNodeDirty schedules cursor work as a microtask. Ensure it runs.
  await true;
  await waitForDrain(container);
}

// ── domRender tests ─────────────────────────────────────────────────────
// These cover the full end-to-end HMR flow: handler dispatch → markVNodeDirty
// → cursor processing → component re-render with state preservation.

describe('domRender: HMR', () => {
  const render = domRender;

  /**
   * Scenario: **client-rendered component** — the QRL has a live `symbolFn`. Fire HMR _without_ any
   * prior user interaction.
   */
  it('should re-render a fresh client component and preserve initial state', async () => {
    const log: string[] = [];

    const Counter = component$(() => {
      log.push('render');
      const count = useSignal(42);
      _useHmr('test-file.tsx');
      return (
        <div data-qwik-inspector="test-file.tsx:1:1">
          <span>{count.value}</span>
        </div>
      );
    });

    const { vNode, container } = await render(<Counter />, { debug });
    const rendersBeforeHmr = log.length;

    await triggerHmr(container, ['test-file.tsx']);

    expect(log.length).toBeGreaterThan(rendersBeforeHmr);

    // State is preserved (still 42)
    expect(vNode).toMatchVDOM(
      <Component>
        <div data-qwik-inspector="test-file.tsx:1:1">
          <span>
            <Signal>{'42'}</Signal>
          </span>
        </div>
      </Component>
    );
  });

  /**
   * Scenario: **interacted client component** — the QRL has been resolved. Interact first, then
   * fire HMR.
   */
  it('should re-render an interacted component and preserve mutated state', async () => {
    const log: string[] = [];

    const Counter = component$(() => {
      log.push('render');
      const count = useSignal(10);
      _useHmr('counter.tsx');
      return (
        <div data-qwik-inspector="counter.tsx:5:1">
          <button onClick$={() => count.value++}>
            <span>{count.value}</span>
          </button>
        </div>
      );
    });

    const { vNode, container } = await render(<Counter />, { debug });

    // Click to interact — increments count 10 → 11
    await trigger(container.element, 'button', 'click');

    expect(vNode).toMatchVDOM(
      <Component>
        <div data-qwik-inspector="counter.tsx:5:1">
          <button>
            <span>
              <Signal>{'11'}</Signal>
            </span>
          </button>
        </div>
      </Component>
    );

    const rendersBeforeHmr = log.length;

    await triggerHmr(container, ['counter.tsx']);

    expect(log.length).toBeGreaterThan(rendersBeforeHmr);

    // State is preserved at 11
    expect(vNode).toMatchVDOM(
      <Component>
        <div data-qwik-inspector="counter.tsx:5:1">
          <button>
            <span>
              <Signal>{'11'}</Signal>
            </span>
          </button>
        </div>
      </Component>
    );
  });

  it('should preserve store state across HMR', async () => {
    const log: string[] = [];

    const TodoApp = component$(() => {
      log.push('render');
      const store = useStore({ items: ['buy milk'], input: '' });
      _useHmr('todo.tsx');
      return (
        <div data-qwik-inspector="todo.tsx:1:1">
          <ul>
            {store.items.map((item) => (
              <li key={item}>{item}</li>
            ))}
          </ul>
        </div>
      );
    });

    const { vNode, container } = await render(<TodoApp />, { debug });

    const rendersBeforeHmr = log.length;

    await triggerHmr(container, ['todo.tsx']);

    expect(log.length).toBeGreaterThan(rendersBeforeHmr);

    // Store state is preserved
    expect(vNode).toMatchVDOM(
      <Component>
        <div data-qwik-inspector="todo.tsx:1:1">
          <ul>
            <li>{'buy milk'}</li>
          </ul>
        </div>
      </Component>
    );
  });

  it('should ignore HMR events for non-matching files', async () => {
    const log: string[] = [];

    const Counter = component$(() => {
      log.push('render');
      const count = useSignal(0);
      _useHmr('my-component.tsx');
      return (
        <div data-qwik-inspector="my-component.tsx:1:1">
          <span>{count.value}</span>
        </div>
      );
    });

    const { container } = await render(<Counter />, { debug });
    const rendersBeforeHmr = log.length;

    await triggerHmr(container, ['other-file.tsx']);

    // No additional render
    expect(log.length).toBe(rendersBeforeHmr);
  });

  it('should re-render after HMR removes and restores a stateful component', async () => {
    const log: string[] = [];
    const state: {
      phase: 'initial' | 'removed' | 'restored' | 'removed-again' | 'restored-again';
    } = { phase: 'initial' };

    const StatefulChild = component$(() => {
      const store = useStore({ label: 'qwik-state' });
      return (
        <section data-testid={state.phase.startsWith('restored') ? 'hmr-restored' : 'hmr-initial'}>
          {store.label}:{state.phase}
        </section>
      );
    });

    const Parent = component$(() => {
      log.push(state.phase);
      _useHmr('parent.tsx');
      return (
        <div data-qwik-inspector="parent.tsx:1:1">
          {state.phase !== 'removed' && state.phase !== 'removed-again' && <StatefulChild />}
        </div>
      );
    });

    const { container } = await render(<Parent />, { debug });
    expect(container.element.querySelector('[data-testid="hmr-initial"]')).toBeTruthy();

    state.phase = 'removed';
    await triggerHmr(container, ['parent.tsx']);

    expect(log[log.length - 1]).toBe('removed');
    expect(container.element.querySelector('[data-testid="hmr-initial"]')).toBeFalsy();
    container.element.querySelector('[q-d\\:q-hmr]')?.removeAttribute('data-qwik-inspector');

    state.phase = 'restored';
    const rendersBeforeRestore = log.length;

    await triggerHmr(container, ['parent.tsx']);

    expect(log.length).toBeGreaterThan(rendersBeforeRestore);
    expect(container.element.querySelector('[data-testid="hmr-restored"]')?.textContent).toBe(
      'qwik-state:restored'
    );

    state.phase = 'removed-again';
    const rendersBeforeSecondRemove = log.length;

    await triggerHmr(container, ['parent.tsx']);

    expect(log.length).toBeGreaterThan(rendersBeforeSecondRemove);
    expect(log[log.length - 1]).toBe('removed-again');
    expect(container.element.querySelector('[data-testid="hmr-restored"]')).toBeFalsy();

    state.phase = 'restored-again';
    const rendersBeforeSecondRestore = log.length;

    await triggerHmr(container, ['parent.tsx']);

    expect(log.length).toBeGreaterThan(rendersBeforeSecondRestore);
    expect(container.element.querySelector('[data-testid="hmr-restored"]')?.textContent).toBe(
      'qwik-state:restored-again'
    );
  });

  it('should not ack HMR when the captured host has been deleted', async () => {
    const Counter = component$(() => {
      _useHmr('deleted-host.tsx');
      return <div data-qwik-inspector="deleted-host.tsx:1:1">current</div>;
    });

    const { container, vNode } = await render(<Counter />, { debug });
    if (!vNode) {
      throw new Error('Expected rendered VNode');
    }
    vNode.flags |= VNodeFlags.Deleted;

    await triggerHmr(container, ['deleted-host.tsx']);

    expect((container.document as any).__hmrDone).not.toBe((container.document as any).__hmrT);
  });
});

// ── ssrRenderToDom tests ────────────────────────────────────────────────
// SSR tests verify handler registration and file filtering. The full re-render
// flow for SSR-paused components is covered by the E2E test in qwik-cli-e2e.
// (The test cursor system doesn't re-execute paused components after markVNodeDirty.)

describe('ssrRenderToDom: HMR', () => {
  const render = ssrRenderToDom;

  it('should register q-d:q-hmr attribute on the component host element', async () => {
    const Counter = component$(() => {
      const count = useSignal(0);
      _useHmr('my-comp.tsx');
      return (
        <div data-qwik-inspector="my-comp.tsx:1:1">
          <span>{count.value}</span>
        </div>
      );
    });

    const { document } = await render(<Counter />, { debug });

    // The `useOnDocument('qHmr', …)` call should produce a `q-d:q-hmr` attribute
    const hmrElements = document.querySelectorAll('[q-d\\:q-hmr]');
    expect(hmrElements.length).toBe(1);
    expect(hmrElements[0].getAttribute('data-qwik-inspector')).toBe('my-comp.tsx:1:1');
  });

  it('should call _hmr handler with correct element for matching files', async () => {
    const Counter = component$(() => {
      const count = useSignal(0);
      _useHmr('target.tsx');
      return (
        <div data-qwik-inspector="target.tsx:1:1">
          <span>{count.value}</span>
        </div>
      );
    });

    const { container } = await render(<Counter />, { debug });

    // Verify the handler filters correctly — triggering for the right file
    // should NOT throw (it should call _hmr and attempt to mark dirty)
    await triggerHmr(container, ['target.tsx']);

    // Triggering for a wrong file should also not throw
    await triggerHmr(container, ['wrong-file.tsx']);
  });

  it('should preserve state after interaction then HMR', async () => {
    const Counter = component$(() => {
      const count = useSignal(10);
      _useHmr('counter.tsx');
      return (
        <div data-qwik-inspector="counter.tsx:1:1">
          <button onClick$={() => count.value++}>
            <span>{count.value}</span>
          </button>
        </div>
      );
    });

    const { vNode, container } = await render(<Counter />, { debug });

    // Click to resume and increment
    await trigger(container.element, 'button', 'click');

    expect(vNode).toMatchVDOM(
      <Component>
        <div data-qwik-inspector="counter.tsx:1:1">
          <button>
            <span>
              <Signal ssr-required>{'11'}</Signal>
            </span>
          </button>
        </div>
      </Component>
    );

    // Fire HMR — state should remain at 11 (not reset)
    await triggerHmr(container, ['counter.tsx']);

    expect(vNode).toMatchVDOM(
      <Component>
        <div data-qwik-inspector="counter.tsx:1:1">
          <button>
            <span>
              <Signal ssr-required>{'11'}</Signal>
            </span>
          </button>
        </div>
      </Component>
    );
  });
});
