import { describe, test, expect } from 'vitest';
import React from 'react';
import { renderEditorHarness } from '../../test-utils/editorHarness';
import { EMPTY_CANVAS, PageProvider, usePages } from '../../state/PageContext';
import { PagesPanel } from './PagesPanel';

const withHeading = JSON.stringify({
  ROOT: {
    type: { resolvedName: 'Container' }, isCanvas: true,
    props: { style: {}, tag: 'div' }, displayName: 'Container',
    custom: {}, hidden: false, nodes: ['h1'], linkedNodes: {}, parent: null,
  },
  h1: {
    type: { resolvedName: 'Heading' }, isCanvas: false,
    props: { text: 'Keep me', level: 2 }, displayName: 'Heading',
    custom: {}, hidden: false, nodes: [], linkedNodes: {}, parent: 'ROOT',
  },
});

/** Same real-timer-flush pattern as `PageContext.orphan-repair-wiring.test.tsx`
 *  -- `PageContext.loadState`/this feature's own reset handler both schedule
 *  their `actions.deserialize()` calls via `setTimeout(..., 0)` rather than
 *  applying them synchronously, so tests that exercise either path need to
 *  let a real macrotask turn run before asserting on the live Frame. */
async function flushTimers(harness: ReturnType<typeof renderEditorHarness>): Promise<void> {
  await harness.act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 10));
  });
}

function clickByLabel(container: HTMLElement, label: string): HTMLButtonElement {
  const btn = container.querySelector(`[aria-label="${label}"]`) as HTMLButtonElement | null;
  if (!btn) throw new Error(`No button found with aria-label "${label}"`);
  return btn;
}

function clickByText(container: HTMLElement, text: string): HTMLButtonElement {
  const btn = Array.from(container.querySelectorAll('button')).find(
    (b) => b.textContent === text,
  ) as HTMLButtonElement | undefined;
  if (!btn) throw new Error(`No button found with text "${text}"`);
  return btn;
}

describe('resetting a page to EMPTY_CANVAS (Craft.js characterisation)', () => {
  test('clears the canvas', () => {
    const harness = renderEditorHarness({ initialState: withHeading });
    expect(JSON.parse(harness.getSerialized()).ROOT.nodes).toEqual(['h1']);

    harness.act(() => { harness.actions.deserialize(EMPTY_CANVAS); });
    expect(JSON.parse(harness.getSerialized()).ROOT.nodes).toEqual([]);
    harness.unmount();
  });

  test('the reset is undoable through Craft history', () => {
    const harness = renderEditorHarness({ initialState: withHeading });
    harness.act(() => { harness.actions.deserialize(EMPTY_CANVAS); });
    harness.act(() => { harness.actions.history.undo(); });
    expect(JSON.parse(harness.getSerialized()).ROOT.nodes).toEqual(['h1']);
    harness.unmount();
  });
});

describe('PagesPanel Reset Page control', () => {
  test('a Reset button is rendered for every page row, and the dialog names the page', () => {
    const harness = renderEditorHarness();
    harness.mountChild(
      <PageProvider>
        <PagesPanel />
      </PageProvider>,
    );

    // Fresh PageProvider starts with exactly one page: "Home".
    const erasers = harness.container.querySelectorAll('.fa-eraser');
    expect(erasers.length).toBe(1);
    expect(clickByLabel(harness.container, 'Reset Home to blank')).toBeTruthy();

    harness.act(() => {
      clickByLabel(harness.container, 'Reset Home to blank').click();
    });
    expect(harness.container.textContent).toContain('Clear every element from "Home"?');
    expect(harness.container.textContent).toContain('Ctrl+Z undoes this.');

    harness.unmount();
  });

  test('the Header and Footer zone rows never grow a Reset control', () => {
    const harness = renderEditorHarness();
    harness.mountChild(
      <PageProvider>
        <PagesPanel />
      </PageProvider>,
    );

    const zoneRows = harness.container.querySelectorAll('.zone-row');
    expect(zoneRows.length).toBe(2); // Header, Footer
    zoneRows.forEach((row) => {
      expect(row.querySelector('.fa-eraser')).toBeNull();
    });
    // No aria-label anywhere in the panel offers to reset the header/footer.
    expect(harness.container.querySelector('[aria-label="Reset Header to blank"]')).toBeNull();
    expect(harness.container.querySelector('[aria-label="Reset Footer to blank"]')).toBeNull();

    harness.unmount();
  });

  test('clicking Reset then Cancel leaves the active page untouched', () => {
    const harness = renderEditorHarness({ initialState: withHeading });
    harness.mountChild(
      <PageProvider>
        <PagesPanel />
      </PageProvider>,
    );

    harness.act(() => {
      clickByLabel(harness.container, 'Reset Home to blank').click();
    });
    harness.act(() => {
      clickByText(harness.container, 'Cancel').click();
    });

    expect(JSON.parse(harness.getSerialized()).ROOT.nodes).toEqual(['h1']);
    harness.unmount();
  });

  test('clicking Reset then confirming blanks the ACTIVE page', async () => {
    const harness = renderEditorHarness({ initialState: withHeading });
    harness.mountChild(
      <PageProvider>
        <PagesPanel />
      </PageProvider>,
    );

    harness.act(() => {
      clickByLabel(harness.container, 'Reset Home to blank').click();
    });
    harness.act(() => {
      clickByText(harness.container, 'Reset page').click();
    });
    await flushTimers(harness);

    expect(JSON.parse(harness.getSerialized()).ROOT.nodes).toEqual([]);
    harness.unmount();
  });

  test('resetting a page that is NOT on screen switches to it first, then blanks it -- ' +
    'the switch-in load must resolve BEFORE the blank, or the blank would just get ' +
    'overwritten by the page\'s real content', async () => {
    let ctx: ReturnType<typeof usePages> | null = null;
    const Probe: React.FC = () => {
      ctx = usePages();
      return null;
    };

    const harness = renderEditorHarness({ initialState: withHeading });
    harness.mountChild(
      <PageProvider>
        <Probe />
        <PagesPanel />
      </PageProvider>,
    );

    // addPage() saves the live Frame (withHeading) into Home's slot, adds
    // "About", and switches the live Frame to About's (empty) canvas.
    harness.act(() => { ctx!.addPage('About', 'about'); });
    await flushTimers(harness);
    expect(ctx!.activePageId).not.toBe('home');
    expect(JSON.parse(harness.getSerialized()).ROOT.nodes).toEqual([]); // About is blank

    // Now reset Home while About is the active/on-screen page.
    harness.act(() => {
      clickByLabel(harness.container, 'Reset Home to blank').click();
    });
    harness.act(() => {
      clickByText(harness.container, 'Reset page').click();
    });
    await flushTimers(harness);

    // The live Frame now shows Home (switchPage ran) and it's blank (reset ran
    // after the switch-in load, not before it).
    expect(ctx!.activePageId).toBe('home');
    expect(JSON.parse(harness.getSerialized()).ROOT.nodes).toEqual([]);

    // And the blank state actually persisted into Home's stored slot, not
    // just transiently on the Frame: switch away and back, still blank.
    const aboutId = ctx!.pages.find((p) => p.id !== 'home')!.id;
    harness.act(() => { ctx!.switchPage(aboutId); });
    await flushTimers(harness);
    harness.act(() => { ctx!.switchPage('home'); });
    await flushTimers(harness);
    expect(JSON.parse(harness.getSerialized()).ROOT.nodes).toEqual([]);

    harness.unmount();
  });
});
