// tools/generate-tests/generators/local-first-test-generator.ts — SPEC-047
// Generates local-first property-based tests and offline test scaffolds.
const PBT_COMMENT = '// Property-based tests — complement with fast-check or similar library';
/**
 * Returns true when the project has local-first architecture detected.
 */
export function isLocalFirstProject(knowledge) {
    return knowledge.localFirst?.isLocalFirst === true;
}
/**
 * Generate local-first test definitions (CRDT property-based + offline tests).
 */
export function generateLocalFirstTestDefs(title, testDir) {
    return [
        {
            name: `${title} — CRDT Convergence: same ops in different order produce same state`,
            type: 'integration',
            file: `${testDir}/local-first/`,
            description: 'Property: given any sequence of concurrent operations, applying them in different orders produces the same final state (commutativity)',
            priority: 'critical',
            automatable: true,
        },
        {
            name: `${title} — CRDT Idempotency: applying same ops multiple times gives same result`,
            type: 'unit',
            file: `${testDir}/local-first/`,
            description: 'Property: applying the same CRDT operation multiple times gives the same result as applying it once (idempotency)',
            priority: 'critical',
            automatable: true,
        },
        {
            name: `${title} — Offline Queue: operations enqueue when offline`,
            type: 'integration',
            file: `${testDir}/local-first/`,
            description: 'Simulate network disconnection — verify operations are enqueued locally and not lost',
            priority: 'high',
            automatable: true,
        },
        {
            name: `${title} — Offline Merge: conflicting changes sync without data loss`,
            type: 'integration',
            file: `${testDir}/local-first/`,
            description: 'User A and User B both edit offline, then sync — verify merge produces no data loss',
            priority: 'critical',
            automatable: true,
        },
        {
            name: `${title} — Schema Migration: v1 document opens in v2`,
            type: 'unit',
            file: `${testDir}/local-first/`,
            description: 'A document created with schema version 1.0 opens correctly in schema version 2.0 (backward-compatible migration)',
            priority: 'high',
            automatable: true,
        },
    ];
}
// === Language-specific test content generators ===
function generateNodeLocalFirstTest(title) {
    const slug = title.toLowerCase().replace(/\s+/g, '-');
    return `${PBT_COMMENT}
// Local-First Tests for ${title}
// Install fast-check for property-based tests: pnpm add -D fast-check
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// import * as fc from 'fast-check'; // Uncomment when fast-check is installed

// TODO: Import your CRDT document type here
// import { Y } from 'yjs'; // For Yjs projects
// import * as Automerge from '@automerge/automerge'; // For Automerge projects

describe('${title} — CRDT Convergence', () => {
  it('should produce same state regardless of operation order (commutativity)', () => {
    // Property: applyOps([op1, op2]) === applyOps([op2, op1])
    // TODO: Replace with your actual CRDT document and operations
    const applyOps = (ops: Array<{ key: string; value: string }>): Record<string, string> => {
      const state: Record<string, string> = {};
      for (const op of ops) {
        state[op.key] = op.value;
      }
      return state;
    };

    const op1 = { key: 'name', value: 'Alice' };
    const op2 = { key: 'role', value: 'admin' };

    const stateA = applyOps([op1, op2]);
    const stateB = applyOps([op2, op1]);

    expect(stateA).toEqual(stateB);
  });

  it('should produce same state when same operation applied multiple times (idempotency)', () => {
    // Property: applyOp(applyOp(state, op)) === applyOp(state, op)
    // TODO: Replace with your actual CRDT apply function
    const applyOp = (state: Record<string, string>, key: string, value: string): Record<string, string> => ({
      ...state,
      [key]: value,
    });

    const initial: Record<string, string> = {};
    const once = applyOp(initial, 'key', 'value');
    const twice = applyOp(once, 'key', 'value');

    expect(once).toEqual(twice);
  });
});

describe('${title} — Offline Queue', () => {
  let onlineStatus = true;
  const operationQueue: Array<{ key: string; value: string }> = [];

  beforeEach(() => {
    onlineStatus = true;
    operationQueue.length = 0;
    vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(true);
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should enqueue operations when offline', () => {
    // Simulate going offline
    onlineStatus = false;
    vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(false);

    // TODO: Replace with your actual operation dispatch function
    const dispatchOperation = (key: string, value: string): void => {
      if (!onlineStatus) {
        operationQueue.push({ key, value });
        return;
      }
      // would sync to server
    };

    dispatchOperation('document-1', 'updated content');
    dispatchOperation('document-2', 'another update');

    expect(operationQueue).toHaveLength(2);
  });

  it('should drain queue on reconnect without data loss', async () => {
    // Queue some ops while offline
    onlineStatus = false;
    operationQueue.push({ key: 'doc-${slug}', value: 'offline change' });

    // Go back online
    onlineStatus = true;
    const syncedOps: Array<{ key: string; value: string }> = [];

    const drainQueue = async (): Promise<void> => {
      while (operationQueue.length > 0) {
        const op = operationQueue.shift();
        if (op) {
          syncedOps.push(op);
        }
      }
    };

    await drainQueue();

    expect(syncedOps).toHaveLength(1);
    expect(syncedOps[0]?.key).toBe('doc-${slug}');
    expect(operationQueue).toHaveLength(0);
  });
});

describe('${title} — Schema Migration', () => {
  it('should open v1 document in v2 schema without data loss', () => {
    // TODO: Replace with your actual schema migration function
    interface DocV1 { name: string; version: number; }
    interface DocV2 { name: string; displayName: string; version: number; }

    const migrateV1toV2 = (doc: DocV1): DocV2 => ({
      ...doc,
      displayName: doc.name, // backward-compatible: populate new field from old
      version: 2,
    });

    const v1Doc: DocV1 = { name: 'My Document', version: 1 };
    const v2Doc = migrateV1toV2(v1Doc);

    expect(v2Doc.name).toBe('My Document');
    expect(v2Doc.displayName).toBe('My Document');
    expect(v2Doc.version).toBe(2);
  });
});
`;
}
function generatePythonLocalFirstTest(title) {
    const slug = title.toLowerCase().replace(/ /g, '_');
    return `${PBT_COMMENT.replace('//', '#')}
"""Local-First Tests for ${title}."""
# Install hypothesis for property-based tests: pip install hypothesis
import pytest
# from hypothesis import given, strategies as st  # Uncomment when hypothesis is installed


class TestCrdtConvergence:
    """CRDT convergence property tests."""

    def test_${slug}_operations_commutative(self) -> None:
        """Same ops in different order produce the same state."""
        # TODO: Replace with your actual CRDT apply function
        def apply_ops(ops: list[tuple[str, str]]) -> dict[str, str]:
            state: dict[str, str] = {}
            for key, value in ops:
                state[key] = value
            return state

        op1 = ("name", "Alice")
        op2 = ("role", "admin")

        state_a = apply_ops([op1, op2])
        state_b = apply_ops([op2, op1])

        assert state_a == state_b

    def test_${slug}_operations_idempotent(self) -> None:
        """Applying same operation multiple times gives same result."""

        def apply_op(state: dict[str, str], key: str, value: str) -> dict[str, str]:
            return {**state, key: value}

        initial: dict[str, str] = {}
        once = apply_op(initial, "key", "value")
        twice = apply_op(once, "key", "value")

        assert once == twice


class TestOfflineQueue:
    """Offline operation queue tests."""

    def test_${slug}_enqueues_when_offline(self) -> None:
        """Operations are enqueued when offline."""
        online = False
        queue: list[dict[str, str]] = []

        def dispatch(key: str, value: str) -> None:
            if not online:
                queue.append({"key": key, "value": value})

        dispatch("doc-1", "offline change")
        assert len(queue) == 1

    def test_${slug}_drains_queue_on_reconnect(self) -> None:
        """Queue drains without data loss on reconnect."""
        queue: list[dict[str, str]] = [{"key": "doc-${slug}", "value": "pending"}]
        synced: list[dict[str, str]] = []

        while queue:
            synced.append(queue.pop(0))

        assert len(synced) == 1
        assert synced[0]["key"] == "doc-${slug}"
        assert len(queue) == 0


class TestSchemaMigration:
    """Schema evolution migration tests."""

    def test_${slug}_v1_opens_in_v2(self) -> None:
        """V1 document opens in V2 schema without data loss."""

        def migrate_v1_to_v2(doc: dict[str, object]) -> dict[str, object]:
            return {**doc, "display_name": doc.get("name", ""), "version": 2}

        v1_doc = {"name": "My Document", "version": 1}
        v2_doc = migrate_v1_to_v2(v1_doc)

        assert v2_doc["name"] == "My Document"
        assert v2_doc["display_name"] == "My Document"
        assert v2_doc["version"] == 2
`;
}
function generateLocalFirstTestContent(title, language) {
    if (language === 'python') {
        return generatePythonLocalFirstTest(title);
    }
    return generateNodeLocalFirstTest(title);
}
function getFileExtension(language) {
    const exts = {
        python: 'py',
        go: 'go',
        ruby: 'rb',
        java: 'java',
        javascript: 'test.ts',
        typescript: 'test.ts',
    };
    return exts[language] ?? 'test.ts';
}
/**
 * Generate local-first test files for the given spec.
 */
export function generateLocalFirstTestFiles(spec, testDir, language, autoGenerate) {
    const slug = spec.id.toLowerCase().replace(/[^a-z0-9]/g, '-');
    const ext = getFileExtension(language);
    const content = generateLocalFirstTestContent(spec.title, language);
    const framework = language === 'python' ? 'pytest' : 'vitest';
    return [
        {
            path: `${testDir}/local-first/${slug}.local-first.${ext}`,
            framework,
            content,
            ready: autoGenerate,
        },
    ];
}
//# sourceMappingURL=local-first-test-generator.js.map