import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

/**
 * Parity guard between the two copies of the provider snapshot types:
 *   apps/coder/src/services/provider-types.ts  (backend source of truth)
 *   apps/web/src/api/types.ts                  (web wire copy)
 *
 * APPROACH: text-identity of each shared type block (mirrors the repo's existing
 * ws-frames.test.ts byte-parity convention). A compile-time bidirectional-
 * assignability check was attempted first (a web-side file importing coder's
 * import-free provider-types.ts), but apps/web/tsconfig.app.json is a composite
 * project and rejects out-of-include files with TS6307 — so cross-project type
 * import is structurally blocked. This runtime guard FAILS on any field
 * add/remove/rename/loosen in either copy, including the nested model/mode/
 * command types that ProviderSnapshotEntry references. Single-source-of-truth
 * (shared workspace package) is deferred as a Tier-2 follow-up.
 */
const here = dirname(fileURLToPath(import.meta.url));
const coderSrc = readFileSync(resolve(here, '../provider-types.ts'), 'utf8');
const webSrc = readFileSync(resolve(here, '../../../../web/src/api/types.ts'), 'utf8');

function extractBlock(src: string, name: string): string {
  const iface = src.match(new RegExp(`export interface ${name} \\{[\\s\\S]*?\\n\\}`));
  const alias = src.match(new RegExp(`export type ${name} =[^;]*;`));
  const block = iface?.[0] ?? alias?.[0];
  if (!block) throw new Error(`type block '${name}' not found`);
  // Normalize to type structure: drop blank + comment lines (//, /* */, *),
  // trim each line. Field add/remove/rename/loosen still changes a field line.
  return block
    .split('\n')
    .map((l) => l.trim())
    .filter(
      (l) =>
        l.length > 0 &&
        !l.startsWith('//') &&
        !l.startsWith('/*') &&
        !l.startsWith('*'),
    )
    .join('\n');
}

describe('provider snapshot type parity (coder ↔ web)', () => {
  // Includes the nested types ProviderSnapshotEntry references, so structural
  // drift anywhere in the snapshot surface is caught.
  const names = [
    'ProviderSnapshotStatus',
    'ProviderSnapshotEntry',
    'ProviderModel',
    'ProviderMode',
    'ThinkingOption',
    'AgentCommand',
  ];
  for (const name of names) {
    it(`${name} is identical in both copies`, () => {
      expect(
        extractBlock(webSrc, name),
        `${name} drifted between apps/coder/src/services/provider-types.ts and apps/web/src/api/types.ts`,
      ).toBe(extractBlock(coderSrc, name));
    });
  }
});
