import { ratchet } from "../../../../core/src/ratchet/ratchet.js";
import type {
  ContractMigration,
  OracleDeletionRatchetInput,
  OracleDeletionRatchetResult,
  OracleDeletionViolation,
  OwnedOracleSpec,
} from "./types.js";

function indexOracles(
  oracles: readonly OwnedOracleSpec[],
): Map<string, OwnedOracleSpec> {
  return new Map(oracles.map((oracle) => [oracle.oracleId, oracle]));
}

function retiredOracleIds(migrations: readonly ContractMigration[] | undefined): Set<string> {
  const retired = new Set<string>();
  for (const migration of migrations ?? []) {
    for (const oracleId of migration.retiredOracleIds) {
      retired.add(oracleId);
    }
  }
  return retired;
}

function detectViolations(
  baselineOracles: readonly OwnedOracleSpec[],
  currentOracles: readonly OwnedOracleSpec[],
  migrations: readonly ContractMigration[] | undefined,
): OracleDeletionViolation[] {
  const currentById = indexOracles(currentOracles);
  const retired = retiredOracleIds(migrations);
  const violations: OracleDeletionViolation[] = [];

  for (const baselineOracle of baselineOracles) {
    if (retired.has(baselineOracle.oracleId)) {
      continue;
    }

    const current = currentById.get(baselineOracle.oracleId);
    if (current === undefined) {
      violations.push({
        scopeId: baselineOracle.scopeId,
        oracleId: baselineOracle.oracleId,
        contractClass: baselineOracle.contractClass,
        reason: "removed",
      });
      continue;
    }

    const removedAssertions = baselineOracle.assertions.filter(
      (assertion) => !current.assertions.includes(assertion),
    );
    if (removedAssertions.length > 0) {
      violations.push({
        scopeId: baselineOracle.scopeId,
        oracleId: baselineOracle.oracleId,
        contractClass: baselineOracle.contractClass,
        reason: "weakened",
        removedAssertions,
      });
    }
  }

  return violations;
}

export function evaluateOracleDeletionRatchet(
  input: OracleDeletionRatchetInput,
): OracleDeletionRatchetResult {
  const violations = detectViolations(
    input.baselineOracles,
    input.currentOracles,
    input.migrations,
  );

  const ratchetResult = ratchet(input.baseline, input.currentRun);

  return {
    passes: violations.length === 0,
    violations,
    ratchet: ratchetResult,
    currentRun: input.currentRun,
  };
}
