import { describe, it, expect } from 'vitest';
import { planEdit } from '../pending_changes.js';

// planEdit is the pure core of applyOne's edit splice. These tests pin the
// idempotency guards that stop the "block stamped 2-3x" corruption: applying the
// same queued edit more than once must be a no-op, never a duplicate.

describe('planEdit — normal application', () => {
  it('applies a unique exact edit', () => {
    const content = 'a\nfoo\nb\n';
    const plan = planEdit(content, 'foo', 'bar');
    expect(plan).toEqual({ kind: 'apply', updated: 'a\nbar\nb\n' });
  });

  it('reports ambiguous when old_string occurs more than once', () => {
    const content = 'foo\nx\nfoo\n';
    const plan = planEdit(content, 'foo', 'bar');
    expect(plan).toEqual({ kind: 'ambiguous', count: 2 });
  });

  it('reports not_found when old_string is absent and new is not present', () => {
    const content = 'alpha\nbeta\n';
    const plan = planEdit(content, 'gamma that is clearly nowhere', 'delta');
    expect(plan).toEqual({ kind: 'not_found' });
  });
});

describe('planEdit — idempotency (the corruption guard)', () => {
  it('treats a re-applied anchored insert as already-applied (no duplicate)', () => {
    // The exact mechanism that tripled `const recordFormats` in settings.html:
    // an anchored insert (old=anchor, new=anchor+block) where the anchor still
    // matches uniquely after the first apply.
    const oldStr = '<script>';
    const newStr = '<script>\nconst recordFormats = ["gif","mp4"];';
    const before = '<script>\nfunction render() {}\n</script>\n';

    const first = planEdit(before, oldStr, newStr);
    expect(first.kind).toBe('apply');
    const after = first.kind === 'apply' ? first.updated : '';
    expect((after.match(/const recordFormats/g) || []).length).toBe(1);

    // Re-applying the identical edit to the already-edited content is a no-op.
    const second = planEdit(after, oldStr, newStr);
    expect(second).toEqual({ kind: 'noop', reason: 'already-applied' });
  });

  it('treats an edit whose old_string is gone but new_string is present as already-applied', () => {
    const content = 'const total = sum + tax;\n';
    const plan = planEdit(content, 'const subtotal = sum;', 'const total = sum + tax;');
    expect(plan).toEqual({ kind: 'noop', reason: 'already-applied' });
  });

  it('treats a no-change splice as a noop', () => {
    const content = 'a\nfoo\nb\n';
    const plan = planEdit(content, 'foo', 'foo');
    expect(plan).toEqual({ kind: 'noop', reason: 'identical' });
  });

  it('does not duplicate across three repeated applications', () => {
    const oldStr = 'function f() {';
    const newStr = 'function f() {\n  const x = 1;';
    let content = 'function f() {\n  return x;\n}\n';
    for (let i = 0; i < 3; i++) {
      const plan = planEdit(content, oldStr, newStr);
      if (plan.kind === 'apply') content = plan.updated;
    }
    expect((content.match(/const x = 1;/g) || []).length).toBe(1);
  });
});
