import { describe, test, expect, afterEach } from 'vitest';
import type { NodeTree } from '@craftjs/core';
import { getClipboardTree, setClipboardTree } from './clipboard';

function makeTree(rootId: string, props: Record<string, unknown> = {}): NodeTree {
  return {
    rootNodeId: rootId,
    nodes: {
      [rootId]: {
        id: rootId,
        data: {
          type: { resolvedName: 'Container' },
          name: 'Container',
          displayName: 'Container',
          props,
          custom: {},
          isCanvas: false,
          parent: 'wherever-it-originally-lived',
          nodes: [],
          linkedNodes: {},
          hidden: false,
        },
        info: {},
        events: { selected: false, dragged: false, hovered: false },
        dom: null,
        related: {},
        rules: {},
        _hydrationTimestamp: 0,
      } as unknown as NodeTree['nodes'][string],
    },
  };
}

describe('clipboard', () => {
  afterEach(() => {
    setClipboardTree(null);
  });

  test('starts empty', () => {
    expect(getClipboardTree()).toBeNull();
  });

  test('set then get returns a tree with the same root id and shape', () => {
    const tree = makeTree('node-123', { text: 'hello' });
    setClipboardTree(tree);
    const got = getClipboardTree();
    expect(got).not.toBeNull();
    expect(got!.rootNodeId).toBe('node-123');
    expect(got!.nodes['node-123'].data.props).toEqual({ text: 'hello' });
  });

  test('is a shared module-level store -- overwriting replaces the previous value', () => {
    setClipboardTree(makeTree('first'));
    setClipboardTree(makeTree('second'));
    expect(getClipboardTree()!.rootNodeId).toBe('second');
  });

  test('can be cleared back to null', () => {
    setClipboardTree(makeTree('node-123'));
    setClipboardTree(null);
    expect(getClipboardTree()).toBeNull();
  });

  test('deep-clones on set: mutating the original tree after set does not affect the stored snapshot', () => {
    const original = makeTree('node-123', { text: 'original' });
    setClipboardTree(original);

    // Mutate the original tree's props object directly (as if the source
    // node were edited, or the same live node got copied again).
    (original.nodes['node-123'].data.props as Record<string, unknown>).text = 'mutated';

    expect(getClipboardTree()!.nodes['node-123'].data.props).toEqual({ text: 'original' });
  });

  test('deep-clones nested props (arrays/objects), not just the top-level props object', () => {
    const original = makeTree('node-123', { links: [{ url: 'https://example.com' }] });
    setClipboardTree(original);

    (original.nodes['node-123'].data.props as any).links[0].url = 'https://mutated.example.com';

    expect((getClipboardTree()!.nodes['node-123'].data.props as any).links[0].url).toBe(
      'https://example.com',
    );
  });

  test('survives the original tree object being discarded entirely (detached copy, not a live reference)', () => {
    let tree: NodeTree | null = makeTree('node-abc', { text: 'snapshot' });
    setClipboardTree(tree);
    tree = null; // simulate the original page's node/tree going away entirely

    const got = getClipboardTree();
    expect(got).not.toBeNull();
    expect(got!.nodes['node-abc'].data.props).toEqual({ text: 'snapshot' });
  });
});
