// Tests for makeElementId — ID generation for design elements.
//
// What this guards
// ────────────────
// makeElementId is called from inside hot paths: every addElement,
// every duplicateElement, every replaceElements iteration during
// session restoration, every template-loading entry. A regression
// that produced colliding ids would cause React key warnings, lost
// drag/selection state when keys collide on re-render, and confusion
// in undo/redo (history snapshots would have two entries with the
// same id).
//
// We test:
//   • The id format is grep-able (starts with "element-") so existing
//     log filters and the diagnostic-history `summarizeElements`
//     helper continue to recognise our ids.
//   • Suffix-less ids are unique enough for non-loop callers.
//   • Suffix-mixed ids are unique even in a tight loop where
//     Date.now() can return the same millisecond for every iteration.
// The suffix-uniqueness test is the regression test for the bug the
// suffix parameter was added to fix — see the docblock in makeId.js.

import { makeElementId } from './makeId';

describe('makeElementId', () => {
  describe('format', () => {
    it('produces an id starting with "element-"', () => {
      // Required so the `summarizeElements` helper in useDesignEditor's
      // diagnostic logging produces useful output, and so call sites
      // that filter logs by `[element-` find what they expect.
      expect(makeElementId()).toMatch(/^element-/);
    });

    it('embeds the timestamp', () => {
      // Format: element-<ms-timestamp>-<random>. Pinning the timestamp
      // position helps debugging — if an id appears in two different
      // snapshots, the timestamps tell you which mount produced it.
      const before = Date.now();
      const id = makeElementId();
      const after = Date.now();
      // Extract the timestamp segment.
      const match = id.match(/^element-(\d+)-/);
      expect(match).not.toBeNull();
      const ts = Number(match[1]);
      expect(ts).toBeGreaterThanOrEqual(before);
      expect(ts).toBeLessThanOrEqual(after);
    });

    it('embeds the suffix when provided', () => {
      // Suffix lands between the timestamp and the random segment, so
      // a caller passing a loop index produces something like
      // `element-1779491484151-3-ip8fyoztd`. The position is documented
      // in the makeId.js docblock and visible in the history-log
      // snapshots from the May 22 crop+undo investigation.
      const id = makeElementId(3);
      expect(id).toMatch(/^element-\d+-3-[a-z0-9]+$/);
    });

    it('handles string suffixes (not just numeric)', () => {
      // The signature accepts `number|string`. We don't currently have
      // a caller using a string suffix, but the public type allows it
      // so we test that it doesn't break.
      const id = makeElementId('slot');
      expect(id).toMatch(/^element-\d+-slot-[a-z0-9]+$/);
    });
  });

  describe('uniqueness', () => {
    it('two suffix-less calls produce different ids', () => {
      // Date.now() may return the same value for two close calls on
      // fast machines, but the 9-char random suffix makes a collision
      // astronomically unlikely (2^46 entropy per id).
      const a = makeElementId();
      const b = makeElementId();
      expect(a).not.toBe(b);
    });

    it('a tight loop produces all-unique ids when suffix is used (regression test)', () => {
      // The bug that motivated the suffix parameter: a `for` loop
      // creating elements in one tick produces multiple ids that
      // share the same Date.now() value. Pre-suffix, the random
      // segment alone made collisions rare-but-possible; the suffix
      // makes them impossible (each iteration has a different
      // suffix), even if Math.random somehow returned the same
      // sequence twice in a row.
      const ids = new Set();
      for (let i = 0; i < 1000; i++) {
        ids.add(makeElementId(i));
      }
      expect(ids.size).toBe(1000);
    });

    it('suffix-less ids in a tight loop are very-likely-unique (statistical, not guaranteed)', () => {
      // 1000 ids without suffix. With ~2^46 random entropy, the
      // birthday-paradox collision probability at N=1000 is
      // approximately N^2 / (2 * 2^46) = 1.4e-8. Astronomically
      // unlikely, but pin the statistical expectation here so a
      // future regression that, say, dropped Math.random precision
      // or hardcoded a constant random suffix gets caught.
      const ids = new Set();
      for (let i = 0; i < 1000; i++) {
        ids.add(makeElementId());
      }
      expect(ids.size).toBe(1000);
    });
  });
});
