const candidateOrder = Object.freeze([
  "RUN_PROJECT_TESTS",
  "REVIEW_DEPENDENCY_CHANGES",
  "REVIEW_SECURITY_SENSITIVE_PATHS",
  "REVIEW_PYTHON_IMPORTS",
  "MANUAL_REVIEW"
]);

const dependencyBasenames = new Set([
  "cargo.lock",
  "cargo.toml",
  "composer.json",
  "composer.lock",
  "deno.json",
  "deno.lock",
  "gemfile",
  "gemfile.lock",
  "go.mod",
  "go.sum",
  "gradle.lockfile",
  "package-lock.json",
  "package.json",
  "pom.xml",
  "pnpm-lock.yaml",
  "poetry.lock",
  "pyproject.toml",
  "requirements.in",
  "requirements.txt",
  "uv.lock",
  "yarn.lock"
]);
const securityPathParts = new Set([
  "auth",
  "authentication",
  "authorization",
  "crypto",
  "iam",
  "permissions",
  "policy",
  "security",
  "secrets"
]);
const testPathParts = new Set(["test", "tests", "__tests__", "spec", "specs"]);
const deploymentPathParts = new Set([".github", ".gitlab", "deploy", "deployment", "infra", "infrastructure", "terraform"]);
const migrationPathParts = new Set(["migration", "migrations", "schema"]);
const sourceExtensions = new Set([
  ".c",
  ".cpp",
  ".go",
  ".java",
  ".cjs",
  ".cts",
  ".js",
  ".jsx",
  ".mjs",
  ".mts",
  ".py",
  ".rb",
  ".rs",
  ".sh",
  ".ts",
  ".tsx"
]);
const documentationExtensions = new Set([".md", ".mdx", ".rst", ".txt"]);

function normalizedParts(relativePath) {
  return String(relativePath || "")
    .replaceAll("\\", "/")
    .toLowerCase()
    .split("/")
    .filter(Boolean);
}

function safePolicyPath(relativePath) {
  if (typeof relativePath !== "string" || relativePath.length < 1 || relativePath.length > 240) return false;
  if (relativePath.startsWith("/") || relativePath.includes("\\") || /[\0-\x1f\x7f\u202a-\u202e\u2066-\u2069]/u.test(relativePath)) return false;
  const parts = relativePath.split("/");
  return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
}

function basename(relativePath) {
  return normalizedParts(relativePath).at(-1) || "";
}

function extension(relativePath) {
  const name = basename(relativePath);
  const dot = name.lastIndexOf(".");
  return dot > 0 ? name.slice(dot) : "";
}

function hasPart(relativePath, parts) {
  return normalizedParts(relativePath).some((part) => parts.has(part));
}

function isDependencyPath(relativePath) {
  const name = basename(relativePath);
  return dependencyBasenames.has(name) || /^requirements(?:[-_.][a-z0-9-]+)?\.(?:in|txt)$/i.test(name);
}

function isTestPath(relativePath) {
  const parts = normalizedParts(relativePath);
  const name = parts.at(-1) || "";
  return parts.some((part) => testPathParts.has(part)) ||
    /(?:^test[_-]|[_-]test\.|\.test\.|\.spec\.|[_-]spec\.)/i.test(name);
}

function isSecurityPath(relativePath) {
  const parts = normalizedParts(relativePath);
  const name = parts.at(-1) || "";
  const stem = name.split(".")[0];
  return parts.some((part) => securityPathParts.has(part)) ||
    /^(?:auth|authentication|authorization|crypto|iam|permission|permissions|policy|security|secret|secrets)(?:[-_].*)?$/i.test(stem);
}

function isDeploymentPath(relativePath) {
  const name = basename(relativePath);
  return hasPart(relativePath, deploymentPathParts) ||
    /^(?:dockerfile|docker-compose(?:\.[a-z0-9-]+)?\.ya?ml|compose(?:\.[a-z0-9-]+)?\.ya?ml)$/i.test(name);
}

function isMigrationPath(relativePath) {
  const name = basename(relativePath);
  return hasPart(relativePath, migrationPathParts) || /^(?:schema|migration)[._-]/i.test(name);
}

function countPaths(paths, predicate) {
  return paths.reduce((count, relativePath) => count + (predicate(relativePath) ? 1 : 0), 0);
}

function finding(id, severity, title, detail, fileCount) {
  return Object.freeze({ id, severity, title, detail, fileCount });
}

export function deriveRunrailReviewDecision({
  changedFiles,
  diffLineCount,
  verifierPassed,
  reviewContract = null
}) {
  if (!Array.isArray(changedFiles) || changedFiles.length < 1 || changedFiles.length > 100) {
    throw new TypeError("changedFiles must contain between 1 and 100 bounded file rows.");
  }
  if (!Number.isSafeInteger(diffLineCount) || diffLineCount < 1 || diffLineCount > 5_000) {
    throw new TypeError("diffLineCount must be a bounded positive integer.");
  }
  if (typeof verifierPassed !== "boolean") throw new TypeError("verifierPassed must be a boolean.");
  if (
    reviewContract !== null &&
    (
      typeof reviewContract !== "object" ||
      Array.isArray(reviewContract) ||
      reviewContract.profile !== "runrail-review-contract-v1" ||
      reviewContract.baselineBound !== true ||
      reviewContract.candidateCouldWeakenBaseline !== false ||
      !Array.isArray(reviewContract.matches) ||
      !Array.isArray(reviewContract.requiredEvidence) ||
      !Array.isArray(reviewContract.requiredReviewLanes) ||
      typeof reviewContract.passed !== "boolean"
    )
  ) {
    throw new TypeError("reviewContract must be a verified Runrail review-contract evaluation.");
  }
  const paths = changedFiles.map((row) => {
    if (!row || !new Set(["A", "M", "D"]).has(row.status) || !safePolicyPath(row.path)) {
      throw new TypeError("Each changed file must contain an A, M, or D status and a path.");
    }
    return row.path;
  });
  const securityCount = countPaths(paths, isSecurityPath);
  const dependencyCount = countPaths(paths, isDependencyPath);
  const deploymentCount = countPaths(paths, isDeploymentPath);
  const migrationCount = countPaths(paths, isMigrationPath);
  const testCount = countPaths(paths, isTestPath);
  const pythonCount = countPaths(paths, (relativePath) => extension(relativePath) === ".py");
  const sourceCount = countPaths(paths, (relativePath) => sourceExtensions.has(extension(relativePath)));
  const docsCount = countPaths(paths, (relativePath) => documentationExtensions.has(extension(relativePath)));
  const findings = [];
  const candidates = new Set();

  if (reviewContract?.candidateContractChanged) {
    findings.push(finding(
      "REVIEW_CONTRACT_CHANGED",
      "high",
      "Baseline review contract changed",
      "The candidate changes the review contract, but this review remains bound to the unchanged base contract. A policy owner must review and baseline the contract separately.",
      1
    ));
    candidates.add("MANUAL_REVIEW");
  }
  if (reviewContract?.changedFileBudgetExceeded || reviewContract?.diffLineBudgetExceeded) {
    findings.push(finding(
      "REVIEW_CONTRACT_BUDGET_EXCEEDED",
      "high",
      "Repository review budget exceeded",
      "The candidate exceeds the baseline contract's changed-file or diff-line budget and must be reduced or reviewed through the repository's exception process.",
      paths.length
    ));
    candidates.add("MANUAL_REVIEW");
  }
  if ((reviewContract?.matchedRuleCount || 0) > 0) {
    const contractSeverity = reviewContract.matches.some((match) => match.severity === "high")
      ? "high"
      : "medium";
    findings.push(finding(
      "REVIEW_CONTRACT_RULE_MATCH",
      contractSeverity,
      "Baseline review contract matched",
      "Repository-owned path rules require the recorded review lanes and evidence before a human merge decision.",
      Math.max(1, reviewContract.matchedFileCount)
    ));
    candidates.add("MANUAL_REVIEW");
  }
  if ((reviewContract?.missingCompanionGroupCount || 0) > 0) {
    const missingHighCompanion = reviewContract.matches.some(
      (match) => match.severity === "high" && match.missingCompanionGroupCount > 0
    );
    findings.push(finding(
      "REVIEW_CONTRACT_COMPANION_MISSING",
      missingHighCompanion ? "high" : "medium",
      "Required companion change missing",
      "A baseline contract rule matched, but one or more required companion path groups have no changed file in this candidate.",
      Math.min(paths.length, reviewContract.missingCompanionGroupCount)
    ));
    candidates.add("MANUAL_REVIEW");
  }

  if (securityCount > 0) {
    findings.push(finding(
      "SECURITY_SENSITIVE_PATHS",
      "high",
      "Security-sensitive paths changed",
      "Authentication, authorization, cryptography, permissions, policy, or security paths require specialist review.",
      securityCount
    ));
    candidates.add("REVIEW_SECURITY_SENSITIVE_PATHS");
  }
  if (dependencyCount > 0) {
    findings.push(finding(
      "DEPENDENCY_FILES_CHANGED",
      "high",
      "Dependency definition changed",
      "Dependency manifests or lockfiles require an explicit dependency and supply-chain review.",
      dependencyCount
    ));
    candidates.add("REVIEW_DEPENDENCY_CHANGES");
  }
  if (deploymentCount > 0) {
    findings.push(finding(
      "DELIVERY_CONFIGURATION_CHANGED",
      "high",
      "Delivery configuration changed",
      "CI, deployment, or infrastructure changes require an operator review outside the syntax preflight.",
      deploymentCount
    ));
    candidates.add("MANUAL_REVIEW");
  }
  if (migrationCount > 0) {
    findings.push(finding(
      "DATA_MIGRATION_CHANGED",
      "high",
      "Data migration or schema changed",
      "Migration and schema changes require data-impact and rollback review before merge.",
      migrationCount
    ));
    candidates.add("MANUAL_REVIEW");
  }
  if (sourceCount > 0 && testCount === 0) {
    findings.push(finding(
      "NO_TEST_FILES_CHANGED",
      "medium",
      "Source changed without test changes",
      "Run the repository's approved project tests and inspect whether the changed behavior needs new or updated tests.",
      sourceCount
    ));
    candidates.add("RUN_PROJECT_TESTS");
  }
  if (paths.length >= 20 || diffLineCount >= 1_000) {
    findings.push(finding(
      "LARGE_CHANGESET",
      "medium",
      "Large changeset",
      "The change size increases review uncertainty and should be split or reviewed in smaller units when practical.",
      paths.length
    ));
    candidates.add("MANUAL_REVIEW");
  }
  if (!verifierPassed && (reviewContract !== null || pythonCount > 0)) {
    findings.push(finding(
      reviewContract === null ? "PYTHON_SYNTAX_PREFLIGHT_FAILED" : "STATIC_SYNTAX_PREFLIGHT_FAILED",
      "high",
      reviewContract === null ? "Python syntax preflight failed" : "Static syntax preflight failed",
      reviewContract === null
        ? "The isolated Python syntax verifier failed. Stop before merge and inspect the candidate patch locally."
        : "At least one bounded Python, JavaScript, or JSON syntax check failed. Stop before merge and inspect the candidate patch locally.",
      reviewContract === null ? pythonCount : paths.length
    ));
    candidates.add("MANUAL_REVIEW");
  }
  if (pythonCount > 0 && verifierPassed) {
    findings.push(finding(
      "PYTHON_FILES_CHANGED",
      "info",
      "Python syntax preflight passed",
      "The isolated Python syntax preflight passed; imports and runtime behavior still require project-context review.",
      pythonCount
    ));
    candidates.add("REVIEW_PYTHON_IMPORTS");
  }
  if (testCount > 0) {
    findings.push(finding(
      "TEST_FILES_CHANGED",
      "info",
      "Test files changed",
      "Inspect the test intent and run the repository's approved project test command locally.",
      testCount
    ));
    candidates.add("RUN_PROJECT_TESTS");
  }
  if (
    docsCount === paths.length &&
    dependencyCount === 0 &&
    securityCount === 0 &&
    deploymentCount === 0 &&
    migrationCount === 0
  ) {
    findings.push(finding(
      "DOCUMENTATION_ONLY_CHANGE",
      "info",
      "Documentation-only changeset",
      "Review rendered documentation, links, and product wording before approval.",
      docsCount
    ));
    candidates.add("MANUAL_REVIEW");
  }
  if (findings.length === 0) {
    findings.push(finding(
      "BOUNDED_CHANGESET",
      "info",
      "Bounded changeset",
      "The deterministic policy found no specialized path category; manual review and project checks remain required.",
      paths.length
    ));
    candidates.add("MANUAL_REVIEW");
  }

  const severityOrder = { high: 0, medium: 1, info: 2 };
  findings.sort((left, right) => severityOrder[left.severity] - severityOrder[right.severity]);
  const findingCounts = {
    high: findings.filter((entry) => entry.severity === "high").length,
    medium: findings.filter((entry) => entry.severity === "medium").length,
    info: findings.filter((entry) => entry.severity === "info").length
  };
  const riskLevel = findingCounts.high > 0 ? "high" : findingCounts.medium > 0 ? "medium" : "low";
  const recommendation = riskLevel === "high"
    ? "specialist-review"
    : riskLevel === "medium"
      ? "project-tests-required"
      : "manual-review";
  return {
    policyProfile: "runrail-review-policy-v1",
    riskLevel,
    recommendation,
    candidateIds: candidateOrder.filter((candidateId) => candidates.has(candidateId)),
    humanApprovalRequired: true,
    mergeReady: false,
    projectTestsRequired: true,
    specialistReviewRequired: riskLevel === "high",
    findingCounts,
    findings
  };
}
