import type {
  Detector,
  DetectorContext,
  DetectorOutcome,
} from "../../../core/src/sdk/detector.js";
import { isIntentionallyExcepted } from "./contracts.js";
import { detectAlignmentLineOutliers } from "./detect-alignment.js";
import { detectAdjacentRegionClearanceAnomalies } from "./detect-clearance.js";
import { detectComponentBoxOutliers } from "./detect-component-box.js";
import { detectGridTrackOutliers } from "./detect-grid-track.js";
import { detectContentGutterOutliers } from "./detect-gutter.js";
import { detectPairedInsetAsymmetryOutliers } from "./detect-paired-inset.js";
import { detectSectionRhythm } from "./detect-section-rhythm.js";
import { detectRepeatedSiblingGapOutliers } from "./detect-sibling-gap.js";
import { buildUi001Evidence } from "./evidence.js";
import type {
  MeasuredLayoutCellEvidence,
  MeasuredLayoutMatrixEvidence,
  MeasuredLayoutRuleId,
  MeasuredLayoutViolationFact,
  Ui001EvidenceRecord,
} from "./types.js";
import { MEASURED_LAYOUT_DETECTOR_VERSION } from "./types.js";

type MeasuredLayoutDetectorConfig = {
  id: string;
  className: string;
  ruleId: MeasuredLayoutRuleId;
  detect: (matrix: MeasuredLayoutMatrixEvidence) => MeasuredLayoutViolationFact[];
};

function cellForViolation(
  matrix: MeasuredLayoutMatrixEvidence,
  violation: MeasuredLayoutViolationFact,
): MeasuredLayoutCellEvidence {
  const cell = matrix.cells.find((candidate) => candidate.cell.id === violation.cellId);
  if (cell === undefined) {
    throw new Error(
      `measured layout violation ${violation.ruleId} names cell ${violation.cellId}, which is absent from matrix ${matrix.matrixId}`,
    );
  }
  return cell;
}

function buildOutcome(
  config: MeasuredLayoutDetectorConfig,
  matrix: MeasuredLayoutMatrixEvidence,
  violation: MeasuredLayoutViolationFact,
): DetectorOutcome<Ui001EvidenceRecord> {
  const targetCell = cellForViolation(matrix, violation);
  const ui001 = buildUi001Evidence(
    targetCell,
    config.ruleId,
    violation,
    `${violation.kind} is an observed measured-layout deviation from its peer mode`,
  );
  return {
    detector: { id: config.id, version: MEASURED_LAYOUT_DETECTOR_VERSION },
    class: config.className,
    severity: "medium",
    target: { kind: "dom", canonical: violation.blamedIdentity },
    context: { kind: "browser", cell: { id: targetCell.cell.id } },
    summary: violation.summary,
    evidence: [{ truthSource: "observed", payload: ui001 }],
    artifacts: [],
    laneEligibility: "advisory",
    scope: {
      id: `${config.id}:${matrix.matrixId}:${violation.locator}:${violation.kind}`,
      detectorId: config.id,
      surfaceId: targetCell.cell.id,
    },
    violation,
    contextDimensions: {
      route: targetCell.cell.routeId,
      role: targetCell.cell.role,
      locale: targetCell.cell.locale,
      rule: config.ruleId,
    },
  };
}

function evaluateDetector(
  config: MeasuredLayoutDetectorConfig,
  matrix: MeasuredLayoutMatrixEvidence,
  context: DetectorContext,
): DetectorOutcome<Ui001EvidenceRecord>[] {
  return config.detect(matrix)
    .filter((violation) => {
      const cell = cellForViolation(matrix, violation);
      return !isIntentionallyExcepted(context, cell.cell, config.id, config.ruleId);
    })
    .map((violation) => buildOutcome(config, matrix, violation));
}

function makeDetector(
  config: MeasuredLayoutDetectorConfig,
): Detector<MeasuredLayoutMatrixEvidence, Ui001EvidenceRecord> {
  return {
    id: config.id,
    version: MEASURED_LAYOUT_DETECTOR_VERSION,
    defaultLane: "advisory",
    evaluate(matrix, context) {
      return Promise.resolve(evaluateDetector(config, matrix, context));
    },
  };
}

const contentGutterOutlierDetector = makeDetector({
  id: "ui-091-content-gutter-outlier",
  className: "content-gutter-outlier",
  ruleId: "UI-091",
  detect: detectContentGutterOutliers,
});

const siblingGapOutlierDetector = makeDetector({
  id: "ui-092-sibling-gap-outlier",
  className: "sibling-gap-outlier",
  ruleId: "UI-092",
  detect: detectRepeatedSiblingGapOutliers,
});

const regionClearanceAnomalyDetector = makeDetector({
  id: "ui-093-region-clearance-anomaly",
  className: "region-clearance-anomaly",
  ruleId: "UI-093",
  detect: (matrix) => matrix.cells.flatMap((cell) => detectAdjacentRegionClearanceAnomalies(cell)),
});

const alignmentLineOutlierDetector = makeDetector({
  id: "ui-094-alignment-line-outlier",
  className: "alignment-line-outlier",
  ruleId: "UI-094",
  detect: detectAlignmentLineOutliers,
});

const componentBoxOutlierDetector = makeDetector({
  id: "ui-095-component-box-outlier",
  className: "component-box-outlier",
  ruleId: "UI-095",
  detect: detectComponentBoxOutliers,
});

const gridTrackOutlierDetector = makeDetector({
  id: "ui-096-grid-track-outlier",
  className: "grid-track-outlier",
  ruleId: "UI-096",
  detect: detectGridTrackOutliers,
});

const sectionRhythmOutlierDetector = makeDetector({
  id: "ui-097-section-rhythm-outlier",
  className: "section-rhythm-outlier",
  ruleId: "UI-097",
  detect: detectSectionRhythm,
});

const pairedInsetAsymmetryDetector = makeDetector({
  id: "ui-098-paired-inset-asymmetry",
  className: "paired-inset-asymmetry",
  ruleId: "UI-098",
  detect: detectPairedInsetAsymmetryOutliers,
});

export const measuredLayoutDetectors = [
  contentGutterOutlierDetector,
  siblingGapOutlierDetector,
  regionClearanceAnomalyDetector,
  alignmentLineOutlierDetector,
  componentBoxOutlierDetector,
  gridTrackOutlierDetector,
  sectionRhythmOutlierDetector,
  pairedInsetAsymmetryDetector,
] as const;

export const measuredLayoutDetectorByRuleId: Record<
  MeasuredLayoutRuleId,
  Detector<MeasuredLayoutMatrixEvidence, Ui001EvidenceRecord>
> = {
  "UI-091": contentGutterOutlierDetector,
  "UI-092": siblingGapOutlierDetector,
  "UI-093": regionClearanceAnomalyDetector,
  "UI-094": alignmentLineOutlierDetector,
  "UI-095": componentBoxOutlierDetector,
  "UI-096": gridTrackOutlierDetector,
  "UI-097": sectionRhythmOutlierDetector,
  "UI-098": pairedInsetAsymmetryDetector,
};
