import type {
  Detector,
  DetectorContext,
  DetectorOutcome,
  LaneEligibility,
} from "../../../core/src/sdk/detector.js";
import { isIntentionallyExcepted } from "./contracts.js";
import {
  detectActionCheckpointViolations,
  detectAggregateCls,
  detectSkeletonSettledDiff,
  performanceContractProofMet,
  skeletonContractProofMet,
  transitionContractProofMet,
} from "./detect.js";
import { buildUi001Evidence } from "./evidence.js";
import type {
  LayoutCellEvidence,
  LayoutRuleId,
  LayoutViolationFact,
  Ui001EvidenceRecord,
} from "./types.js";
import { LAYOUT_DETECTOR_VERSION } from "./types.js";

type LayoutDetectorConfig = {
  id: string;
  className: string;
  ruleId: LayoutRuleId;
  defaultLane: LaneEligibility;
  detect: (evidence: LayoutCellEvidence) => LayoutViolationFact[];
  proofConditionMet?: (
    evidence: LayoutCellEvidence,
    context: DetectorContext,
  ) => boolean;
  classificationBasis: (violation: LayoutViolationFact) => string;
};

function browserContext(cellId: string) {
  return { kind: "browser" as const, cell: { id: cellId } };
}

function buildOutcome(
  config: LayoutDetectorConfig,
  evidence: LayoutCellEvidence,
  violation: LayoutViolationFact,
  context: DetectorContext,
  proofConditionMet?: boolean,
): DetectorOutcome<Ui001EvidenceRecord> {
  const ui001 = buildUi001Evidence(
    evidence,
    config.ruleId,
    violation,
    config.classificationBasis(violation),
  );

  const effectiveProof =
    proofConditionMet ??
    (config.defaultLane === "blocking"
      ? true
      : config.defaultLane === "blocking-eligible"
        ? false
        : false);

  const truthSource =
    config.defaultLane === "advisory"
      ? "observed"
      : effectiveProof
        ? "universal"
        : "observed";

  const outcome: DetectorOutcome<Ui001EvidenceRecord> = {
    detector: { id: config.id, version: LAYOUT_DETECTOR_VERSION },
    class: config.className,
    severity: "high",
    target: { kind: "dom", canonical: violation.locator },
    context: browserContext(evidence.cell.id),
    summary: violation.summary,
    evidence: [{ truthSource, payload: ui001 }],
    artifacts: [],
    laneEligibility: config.defaultLane,
    scope: {
      id: `${config.id}:${evidence.cell.id}:${violation.locator}:${violation.kind}`,
      detectorId: config.id,
      surfaceId: evidence.cell.id,
    },
    violation,
    contextDimensions: {
      route: evidence.cell.routeId,
      role: evidence.cell.role,
      locale: evidence.cell.locale,
    },
  };

  if (config.defaultLane === "blocking-eligible") {
    outcome.proofConditionMet = effectiveProof;
  }

  void context;
  return outcome;
}

function evaluateLayoutDetector(
  config: LayoutDetectorConfig,
  evidence: LayoutCellEvidence,
  context: DetectorContext,
): DetectorOutcome<Ui001EvidenceRecord>[] {
  if (isIntentionallyExcepted(context, evidence.cell, config.id, config.ruleId)) {
    return [];
  }

  const violations = config.detect(evidence);
  if (violations.length === 0) {
    return [];
  }

  return violations.map((violation) => {
    const proof =
      config.proofConditionMet === undefined
        ? undefined
        : config.proofConditionMet(evidence, context);
    return buildOutcome(config, evidence, violation, context, proof);
  });
}

function makeDetector(
  config: LayoutDetectorConfig,
): Detector<LayoutCellEvidence, Ui001EvidenceRecord> {
  return {
    id: config.id,
    version: LAYOUT_DETECTOR_VERSION,
    defaultLane: config.defaultLane,
    evaluate(evidence, context) {
      return Promise.resolve(evaluateLayoutDetector(config, evidence, context));
    },
  };
}

export const aggregateClsDetector = makeDetector({
  id: "ui-030-aggregate-cls",
  className: "aggregate-cls-exceeded",
  ruleId: "UI-030",
  defaultLane: "advisory",
  detect: detectAggregateCls,
  proofConditionMet: (evidence) => performanceContractProofMet(evidence.cell),
  classificationBasis: (violation) =>
    `aggregate CLS ${String(violation.measurement.aggregateCls)} exceeds CWV good threshold ${String(violation.measurement.clsThreshold)} excluding recent-input shifts`,
});

export const skeletonSettledDiffDetector = makeDetector({
  id: "ui-031-skeleton-settled-diff",
  className: "skeleton-shape-replacement",
  ruleId: "UI-031",
  defaultLane: "advisory",
  detect: detectSkeletonSettledDiff,
  proofConditionMet: (evidence) => skeletonContractProofMet(evidence.cell),
  classificationBasis: () =>
    "skeleton and settled geometry differ; reported separately from aggregate CLS",
});

export const actionCheckpointDetector = makeDetector({
  id: "ui-032-action-checkpoint",
  className: "action-checkpoint-transition",
  ruleId: "UI-032",
  defaultLane: "advisory",
  detect: detectActionCheckpointViolations,
  proofConditionMet: (evidence) => transitionContractProofMet(evidence.cell),
  classificationBasis: (violation) =>
    `action checkpoint detected ${violation.kind.replaceAll("-", " ")}`,
});

export const layoutDetectors = [
  aggregateClsDetector,
  skeletonSettledDiffDetector,
  actionCheckpointDetector,
] as const;

export const layoutDetectorByRuleId: Record<
  LayoutRuleId,
  Detector<LayoutCellEvidence, Ui001EvidenceRecord>
> = {
  "UI-030": aggregateClsDetector,
  "UI-031": skeletonSettledDiffDetector,
  "UI-032": actionCheckpointDetector,
};
