import { readFile } from "node:fs/promises";
import path from "node:path";
import process from "node:process";
import { pathToFileURL } from "node:url";
import { resolveRealWorkspaceRoot } from "./load-env.mjs";

const rootDir = resolveRealWorkspaceRoot();

export const rules = [
  {
    file: "apps/web/src/server/auth.ts",
    required: [
      {
        pattern: /\bassertSecureRuntimeEnv\s*\(/,
        message: "Auth startup must validate production runtime env before serving requests",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/server/runtime-env.ts",
    required: [
      {
        pattern: /\bDISALLOWED_PRODUCTION_SECRETS\b/,
        message: "runtime env validation must keep a denylist for known development secrets",
      },
      {
        pattern: /\bAUTH_SECRET\b/,
        message: "runtime env validation must check the Auth.js secret environment variables",
      },
      {
        pattern: /\bNEXTAUTH_URL\b/,
        message: "runtime env validation must check the public auth url environment variables",
      },
    ],
    forbidden: [],
  },
  {
    file: "docker-compose.yml",
    required: [
      {
        pattern: /NEXTAUTH_SECRET:\s+\$\{NEXTAUTH_SECRET:\?set NEXTAUTH_SECRET\}/,
        message: "local compose must source NEXTAUTH_SECRET from environment instead of hardcoding it",
      },
    ],
    forbidden: [
      {
        pattern: /NEXTAUTH_SECRET:\s+dev-secret-change-in-production/,
        message: "local compose must not hardcode the development Auth.js secret",
      },
    ],
  },
  {
    file: "packages/api/src/sse/event-bus.ts",
    required: [],
    forbidden: [
      { pattern: /\bRoleSseAudience\b/, message: "role-based SSE audience types must not reappear" },
      { pattern: /\broleAudience\s*\(/, message: "role-derived SSE audiences must not be emitted" },
      { pattern: /\bBROADCAST_SENT\b/, message: "broadcast SSE event resurrection needs explicit architecture review" },
    ],
  },
  {
    file: "packages/api/src/sse/subscription-policy.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bderiveUserSseSubscription\b/,
        message: "subscription derivation must stay centralized in deriveUserSseSubscription",
      },
    ],
    forbidden: [
      { pattern: /\broleAudience\s*\(/, message: "subscription policy must not derive role audiences" },
    ],
  },
  {
    file: "apps/web/src/app/api/sse/timeline/route.ts",
    required: [
      {
        pattern: /\bderiveUserSseSubscription\s*\(/,
        message: "timeline SSE route must derive audiences server-side from the authenticated user",
      },
    ],
    forbidden: [
      { pattern: /\bsearchParams\b/, message: "timeline SSE route must not accept client-provided audience scoping" },
      { pattern: /\baudience\b/, message: "timeline SSE route must not parse raw audience values from the client" },
    ],
  },
  {
    file: "apps/web/src/hooks/useTimelineSSE.ts",
    maxLines: 120,
    required: [
      {
        pattern: /\bgetTimelineSseInvalidationKeys\s*\(/,
        message: "timeline SSE hook must keep invalidation policy delegated to the extracted policy helper",
      },
      {
        pattern: /\bparseTimelineSseEvent\s*\(/,
        message: "timeline SSE hook must keep event parsing delegated to the extracted policy helper",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineLivePreview.ts",
    maxLines: 140,
    required: [
      {
        pattern: /\bexport function scheduleLivePreview\b/,
        message: "timeline live preview helpers must keep frame scheduling centralized",
      },
      {
        pattern: /\bexport function clearLivePreview\b/,
        message: "timeline live preview helpers must keep preview reset logic centralized",
      },
      {
        pattern: /\bexport function preserveLivePreview\b/,
        message: "timeline live preview helpers must keep snapshot preservation centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineTouch.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function getTouchPoint\b/,
        message: "timeline touch helpers must keep touch coordinate fallback centralized",
      },
      {
        pattern: /\bexport function resolveTouchDragDecision\b/,
        message: "timeline touch helpers must keep scroll-vs-drag disambiguation centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineTouchAdapters.ts",
    maxLines: 60,
    required: [
      {
        pattern: /\bexport function createTouchMouseDownEvent\b/,
        message: "timeline touch adapter helpers must keep touch-to-mouse adapter wiring centralized",
      },
      {
        pattern: /\bexport function createTouchCanvasPointerEvent\b/,
        message: "timeline touch adapter helpers must keep canvas pointer adapter wiring centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineTouchEvents.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function forwardTouchStartAsMouseDown\b/,
        message: "timeline touch event helpers must keep touch-start forwarding centralized",
      },
      {
        pattern: /\bexport function forwardCanvasTouchMove\b/,
        message: "timeline touch event helpers must keep touch-move forwarding centralized",
      },
      {
        pattern: /\bexport function forwardCanvasTouchEnd\b/,
        message: "timeline touch event helpers must keep touch-end forwarding centralized",
      },
      {
        pattern: /from "\.\/timelineTouch\.js"/,
        message: "timeline touch event helpers must keep touch policy delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineTouchAdapters\.js"/,
        message: "timeline touch event helpers must keep touch adapter wiring delegated to the extracted helper module",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineMultiSelect.ts",
    maxLines: 90,
    required: [
      {
        pattern: /\bexport function createMultiSelectState\b/,
        message: "timeline multi-select helpers must keep selection bootstrap centralized",
      },
      {
        pattern: /\bexport function finalizeMultiSelectDraft\b/,
        message: "timeline multi-select helpers must keep minimal-drag reset logic centralized",
      },
      {
        pattern: /\bexport function completeMultiSelectDraft\b/,
        message: "timeline multi-select helpers must keep right-click release completion centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineMultiSelectSession.ts",
    maxLines: 90,
    required: [
      {
        pattern: /\bexport function beginCanvasMultiSelectSession\b/,
        message: "timeline multi-select session helpers must keep right-click session lifecycle centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineRangeSelection.ts",
    maxLines: 90,
    required: [
      {
        pattern: /\bexport function createRangeSelectionState\b/,
        message: "timeline range helpers must keep selection bootstrap centralized",
      },
      {
        pattern: /\bexport function updateRangeSelectionDraft\b/,
        message: "timeline range helpers must keep preview date derivation centralized",
      },
      {
        pattern: /\bexport function finalizeRangeSelection\b/,
        message: "timeline range helpers must keep ordered range finalization centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineRangeRelease.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function resolveRangeSelectionRelease\b/,
        message: "timeline range release helpers must keep canvas release resolution centralized",
      },
      {
        pattern: /\bexport function resolveRangeSelectionCancel\b/,
        message: "timeline range release helpers must keep canvas leave cancellation centralized",
      },
      {
        pattern: /from "\.\/timelineRangeSelection\.js"/,
        message: "timeline range release helpers must keep selection finalization delegated to the range helper module",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineOptimisticAllocations.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function reconcileOptimisticEntries\b/,
        message: "timeline optimistic helpers must keep server-reconciliation logic centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelinePreviewSession.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function createProjectPreviewSession\b/,
        message: "timeline preview session helpers must keep project preview target resolution centralized",
      },
      {
        pattern: /\bexport function createAllocationPreviewSession\b/,
        message: "timeline preview session helpers must keep allocation preview target resolution centralized",
      },
      {
        pattern: /from "\.\/timelineLivePreview\.js"/,
        message: "timeline preview session helpers must keep target capture delegated to the live preview helper module",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineAllocationFinalize.ts",
    maxLines: 100,
    required: [
      {
        pattern: /\bexport function hasAllocationDateChange\b/,
        message: "timeline allocation finalize helpers must keep date-change detection centralized",
      },
      {
        pattern: /\bexport function shouldTreatAllocationDragAsClick\b/,
        message: "timeline allocation finalize helpers must keep click-vs-drag classification centralized",
      },
      {
        pattern: /\bexport function requiresAllocationFragmentExtraction\b/,
        message: "timeline allocation finalize helpers must keep segment extraction rules centralized",
      },
      {
        pattern: /\bexport function buildAllocationMovedSnapshot\b/,
        message: "timeline allocation finalize helpers must keep mutation snapshot creation centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineAllocationMultiDrag.ts",
    maxLines: 90,
    required: [
      {
        pattern: /\bexport function isAllocationMultiSelected\b/,
        message: "timeline allocation multi-drag helpers must keep multi-selection eligibility centralized",
      },
      {
        pattern: /\bexport function updateAllocationMultiDrag\b/,
        message: "timeline allocation multi-drag helpers must keep same-day delta suppression centralized",
      },
      {
        pattern: /\bexport function finalizeAllocationMultiDrag\b/,
        message: "timeline allocation multi-drag helpers must keep reset-on-release behavior centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineAllocationMultiDragSession.ts",
    maxLines: 90,
    required: [
      {
        pattern: /\bexport function beginAllocationMultiDragSession\b/,
        message: "timeline allocation multi-drag session helpers must keep document drag lifecycle centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineAllocationActions.ts",
    maxLines: 90,
    required: [
      {
        pattern: /\bexport function buildAllocationBlockClickInfo\b/,
        message: "timeline allocation action helpers must keep popover click payload derivation centralized",
      },
      {
        pattern: /\bexport function buildAllocationMutationPlan\b/,
        message: "timeline allocation action helpers must keep mutation plan derivation centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineAllocationRelease.ts",
    maxLines: 90,
    required: [
      {
        pattern: /\bexport function resolveAllocationRelease\b/,
        message: "timeline allocation release helpers must keep release classification centralized",
      },
      {
        pattern: /from "\.\/timelineAllocationActions\.js"/,
        message: "timeline allocation release helpers must keep click and mutation plan derivation delegated to allocation action helpers",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineDragCleanup.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function cleanupTimelineDragState\b/,
        message: "timeline drag cleanup helpers must keep unmount teardown centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineDragPosition.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function resolveProjectDragPosition\b/,
        message: "timeline drag position helpers must keep project drag date derivation centralized",
      },
      {
        pattern: /\bexport function resolveAllocationDragPosition\b/,
        message: "timeline drag position helpers must keep allocation drag date derivation centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineDocumentDrag.ts",
    maxLines: 50,
    required: [
      {
        pattern: /\bexport function attachDocumentMouseDrag\b/,
        message: "timeline document drag helpers must keep document mouse listener wiring centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineAllocationDragState.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function createAllocationDragState\b/,
        message: "timeline allocation drag state helpers must keep drag bootstrap centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineAllocationDragSession.ts",
    maxLines: 70,
    required: [
      {
        pattern: /\bexport function beginAllocationDragSession\b/,
        message: "timeline allocation drag session helpers must keep document drag lifecycle centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineAllocationReleaseEffects.ts",
    maxLines: 130,
    required: [
      {
        pattern: /\bexport async function finalizeAllocationReleaseEffects\b/,
        message: "timeline allocation release effect helpers must keep release side effects centralized",
      },
      {
        pattern: /from "\.\/timelineAllocationRelease\.js"/,
        message: "timeline allocation release effect helpers must keep release classification delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineLivePreview\.js"/,
        message: "timeline allocation release effect helpers must keep preview lifecycle delegated to the extracted helper module",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineProjectDrag.ts",
    maxLines: 80,
    required: [
      {
        pattern: /\bexport function createProjectDragState\b/,
        message: "timeline project drag helpers must keep drag-state bootstrap centralized",
      },
      {
        pattern: /\bexport function buildProjectShiftMutationInput\b/,
        message: "timeline project drag helpers must keep no-op project-shift mutation gating centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineProjectDragFinalize.ts",
    maxLines: 60,
    required: [
      {
        pattern: /\bexport function finalizeProjectDrag\b/,
        message: "timeline project drag finalize helpers must keep completion flow centralized",
      },
      {
        pattern: /from "\.\/timelineProjectDrag\.js"/,
        message: "timeline project drag finalize helpers must keep mutation gating delegated to the project drag helper module",
      },
      {
        pattern: /from "\.\/timelineLivePreview\.js"/,
        message: "timeline project drag finalize helpers must keep preview preservation delegated to the live preview helper module",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/timelineProjectDragSession.ts",
    maxLines: 70,
    required: [
      {
        pattern: /\bexport function beginProjectDragSession\b/,
        message: "timeline project drag session helpers must keep document drag lifecycle centralized",
      },
    ],
    forbidden: [],
  },
  {
    file: "apps/web/src/hooks/useTimelineDrag.ts",
    required: [
      {
        pattern: /from "\.\/timelineLivePreview\.js"/,
        message: "timeline drag must keep live preview behavior delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineTouchEvents\.js"/,
        message: "timeline drag must keep touch event forwarding delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineMultiSelect\.js"/,
        message: "timeline drag must keep multi-select rectangle lifecycle delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineMultiSelectSession\.js"/,
        message: "timeline drag must keep multi-select document session wiring delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineRangeSelection\.js"/,
        message: "timeline drag must keep range preview and finalization delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineRangeRelease\.js"/,
        message: "timeline drag must keep range release and cancel delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineOptimisticAllocations\.js"/,
        message: "timeline drag must keep optimistic allocation reconciliation delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelinePreviewSession\.js"/,
        message: "timeline drag must keep preview target setup delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineDragCleanup\.js"/,
        message: "timeline drag must keep unmount teardown delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineDragPosition\.js"/,
        message: "timeline drag must keep project and allocation drag position derivation delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineDocumentDrag\.js"/,
        message: "timeline drag must keep document mouse listener lifecycle delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineAllocationMultiDrag\.js"/,
        message: "timeline drag must keep allocation multi-drag rules delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineAllocationMultiDragSession\.js"/,
        message: "timeline drag must keep allocation multi-drag document session wiring delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineAllocationDragState\.js"/,
        message: "timeline drag must keep allocation drag bootstrap delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineAllocationDragSession\.js"/,
        message: "timeline drag must keep allocation drag document session wiring delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineAllocationReleaseEffects\.js"/,
        message: "timeline drag must keep allocation release side effects delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineProjectDrag\.js"/,
        message: "timeline drag must keep project drag bootstrap delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineProjectDragFinalize\.js"/,
        message: "timeline drag must keep project drag completion delegated to the extracted helper module",
      },
      {
        pattern: /from "\.\/timelineProjectDragSession\.js"/,
        message: "timeline drag must keep project drag document session wiring delegated to the extracted helper module",
      },
    ],
    forbidden: [
      {
        pattern: /\bfunction (?:toPxValue|joinTransforms|captureLivePreviewTargets|renderLivePreview|scheduleLivePreview|clearLivePreview|datesMatch|preserveLivePreview|createRangeSelectionState)\b/,
        message: "timeline drag must not re-inline live preview helper implementations",
      },
      {
        pattern: /\b(?:getTouchPoint|resolveTouchDragDecision|createTouchCanvasPointerEvent|createTouchMouseDownEvent)\b/,
        message: "timeline drag must not re-inline extracted touch event forwarding dependencies",
      },
      {
        pattern: /\bfunction (?:hasAllocationDateChange|shouldTreatAllocationDragAsClick|requiresAllocationFragmentExtraction|buildAllocationMovedSnapshot|reconcileOptimisticEntries)\b/,
        message: "timeline drag must not re-inline extracted optimistic or allocation finalize helper implementations",
      },
      {
        pattern: /\bfunction (?:buildAllocationBlockClickInfo|buildAllocationMutationPlan)\b/,
        message: "timeline drag must not re-inline extracted allocation action helper implementations",
      },
      {
        pattern: /\bfunction resolveAllocationRelease\b/,
        message: "timeline drag must not re-inline extracted allocation release helper implementations",
      },
      {
        pattern: /\bfunction attachDocumentMouseDrag\b/,
        message: "timeline drag must not re-inline extracted document listener helper implementations",
      },
      {
        pattern: /\bconst (?:deltaX|pointerDeltaX) = clientX - (?:drag|alloc)\.startMouseX;[\s\S]*computeDragDates\(/,
        message: "timeline drag must not re-inline extracted project or allocation drag position helpers",
      },
      {
        pattern: /\bfunction (?:isAllocationMultiSelected|startAllocationMultiDrag|updateAllocationMultiDrag|finalizeAllocationMultiDrag)\b/,
        message: "timeline drag must not re-inline extracted allocation multi-drag helper implementations",
      },
      {
        pattern: /\bfunction handleMulti(?:Move|Up)\b/,
        message: "timeline drag must not re-inline extracted allocation multi-drag session handlers",
      },
      {
        pattern: /\bfunction createAllocationDragState\b/,
        message: "timeline drag must not re-inline extracted allocation drag bootstrap helpers",
      },
      {
        pattern: /\bfunction handle(?:Move|Up)\b/,
        message: "timeline drag must not re-inline extracted allocation drag session handlers",
      },
      {
        pattern: /\bpendingSnapshotRef\.current = pendingSnapshot\b[\s\S]*updateAllocMutation\.mutate\(/,
        message: "timeline drag must not re-inline extracted allocation release effect mutation wiring",
      },
      {
        pattern: /\bfunction (?:createProjectDragState|buildProjectShiftMutationInput)\b/,
        message: "timeline drag must not re-inline extracted project drag helper implementations",
      },
      {
        pattern: /\bconst mutationInput = buildProjectShiftMutationInput\(finalDrag\)\b[\s\S]*applyShiftMutation\.(?:mutate|mutateAsync)\(/,
        message: "timeline drag must not re-inline extracted project drag finalize flow",
      },
      {
        pattern: /\bdocument\.querySelectorAll<HTMLElement>\([\s\S]*data-timeline-project-id/,
        message: "timeline drag must not re-inline extracted project preview target lookup",
      },
      {
        pattern: /\bcurrentTarget\.closest<HTMLElement>\('\[data-timeline-drag-preview~=\"allocation\"\]'\)/,
        message: "timeline drag must not re-inline extracted allocation preview target lookup",
      },
      {
        pattern: /\bconst selection = finalizeRangeSelection\(/,
        message: "timeline drag must not re-inline extracted range release resolution",
      },
      {
        pattern: /\bif \(rangeStateRef\.current\.isSelecting\)\s*\{[\s\S]*setRangeState\(INITIAL_RANGE_STATE\);[\s\S]*\}/,
        message: "timeline drag must not re-inline extracted range cancel reset flow",
      },
    ],
  },
  {
    file: "docker-compose.prod.yml",
    required: [
      {
        pattern: /image:\s+\$\{APP_IMAGE:\?set APP_IMAGE\}/,
        message: "production compose must deploy the immutable app image",
      },
      {
        pattern: /image:\s+\$\{MIGRATOR_IMAGE:\?set MIGRATOR_IMAGE\}/,
        message: "production compose must deploy the immutable migrator image",
      },
      {
        pattern: /http:\/\/localhost:3000\/api\/ready/,
        message: "production compose must gate app health on the readiness endpoint",
      },
      {
        pattern: /RATE_LIMIT_BACKEND:\s+\$\{RATE_LIMIT_BACKEND:-redis\}/,
        message: "production compose must intentionally pin the Redis-backed rate-limit path",
      },
    ],
    forbidden: [
      { pattern: /\bbuild:/, message: "production compose must not build application images on the host" },
    ],
  },
  {
    file: ".github/workflows/release-image.yml",
    required: [
      {
        pattern: /push:\s*\n\s*branches:\s*\[main\]/,
        message: "image releases must build automatically on pushes to main",
      },
      {
        pattern: /workflow_dispatch:/,
        message: "image release must remain manually callable for rebuilds and tag overrides",
      },
      {
        pattern: /target:\s+runner/,
        message: "release workflow must keep publishing the runner image",
      },
      {
        pattern: /target:\s+migrator/,
        message: "release workflow must keep publishing the migrator image",
      },
    ],
    forbidden: [],
  },
  {
    file: ".github/workflows/deploy-staging.yml",
    required: [
      {
        pattern: /docker-compose\.prod\.yml tooling\/deploy/,
        message: "staging deploy must ship the canonical production compose bundle",
      },
    ],
    forbidden: [],
  },
  {
    file: ".github/workflows/deploy-prod.yml",
    required: [
      {
        pattern: /docker-compose\.prod\.yml tooling\/deploy/,
        message: "production deploy must ship the canonical production compose bundle",
      },
    ],
    forbidden: [],
  },
  {
    file: ".github/workflows/ci.yml",
    required: [
      {
        pattern: /run:\s+pnpm db:generate/,
        message: "CI must route Prisma client generation through the workspace env/schema wrapper",
      },
    ],
    forbidden: [
      {
        pattern: /pnpm --filter @capakraken\/db exec prisma generate/,
        message: "CI must not call prisma generate directly outside the workspace wrapper",
      },
    ],
  },
  {
    file: "tooling/deploy/deploy-compose.sh",
    required: [
      {
        pattern: /COMPOSE_FILE="\$\{COMPOSE_FILE:-docker-compose\.prod\.yml\}"/,
        message: "deploy script must default to the canonical production compose file",
      },
      {
        pattern: /READY_URL="\$\{READY_URL:-http:\/\/127\.0\.0\.1:\$\{APP_HOST_PORT:-3000\}\/api\/ready\}"/,
        message: "deploy script must wait on the readiness endpoint",
      },
      {
        pattern: /docker compose -f "\$\{COMPOSE_FILE\}" config -q/,
        message: "deploy script must validate the rendered compose file before pulling images",
      },
    ],
    forbidden: [],
  },
];

export function countLines(source) {
  return source.split("\n").length;
}

export function evaluateRule(rule, source) {
  const violations = [];

  for (const requirement of rule.required) {
    if (!requirement.pattern.test(source)) {
      violations.push(`${rule.file}: missing guardrail anchor: ${requirement.message}`);
    }
  }

  for (const forbidden of rule.forbidden) {
    if (forbidden.pattern.test(source)) {
      violations.push(`${rule.file}: forbidden pattern matched: ${forbidden.message}`);
    }
  }

  if (typeof rule.maxLines === "number") {
    const lines = countLines(source);
    if (lines > rule.maxLines) {
      violations.push(
        `${rule.file}: file grew to ${lines} lines and exceeds maxLines=${rule.maxLines}; split the ownership surface before expanding it further`,
      );
    }
  }

  return violations;
}

export async function collectArchitectureGuardrailViolations(
  architectureRules = rules,
  { readSource = readFile, workspaceRoot = rootDir } = {},
) {
  const violations = [];

  for (const rule of architectureRules) {
    const absolutePath = path.join(workspaceRoot, rule.file);
    const source = await readSource(absolutePath, "utf8");
    violations.push(...evaluateRule(rule, source));
  }

  return violations;
}

async function main() {
  const violations = await collectArchitectureGuardrailViolations();

  if (violations.length > 0) {
    console.error("Architecture guardrail check failed:");
    for (const violation of violations) {
      console.error(`- ${violation}`);
    }
    process.exit(1);
  }

  console.log("Architecture guardrails passed.");
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  await main();
}
