import type {
  Detector,
  DetectorContext,
  DetectorOutcome,
  LaneEligibility,
} from "../../../core/src/sdk/detector.js";
import { isIntentionallyExcepted } from "./contracts.js";
import {
  detectLargeEmptyContainers,
  detectMissingBlockedImages,
  detectUnexplainedDisabledStyling,
  disabledStylingContractProofMet,
  requiredContentContractProofMet,
} from "./detect.js";
import { buildUi001Evidence } from "./evidence.js";
import type {
  AssetsCellEvidence,
  AssetsRuleId,
  AssetsViolationFact,
  Ui001EvidenceRecord,
} from "./types.js";
import { ASSETS_DETECTOR_VERSION } from "./types.js";

type AssetsDetectorConfig = {
  id: string;
  className: string;
  ruleId: AssetsRuleId;
  defaultLane: LaneEligibility;
  detect: (evidence: AssetsCellEvidence) => AssetsViolationFact[];
  proofConditionMet?: (
    evidence: AssetsCellEvidence,
    context: DetectorContext,
  ) => boolean;
  classificationBasis: (violation: AssetsViolationFact) => string;
};

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

function buildOutcome(
  config: AssetsDetectorConfig,
  evidence: AssetsCellEvidence,
  violation: AssetsViolationFact,
  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: ASSETS_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 evaluateAssetsDetector(
  config: AssetsDetectorConfig,
  evidence: AssetsCellEvidence,
  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: AssetsDetectorConfig,
): Detector<AssetsCellEvidence, Ui001EvidenceRecord> {
  return {
    id: config.id,
    version: ASSETS_DETECTOR_VERSION,
    defaultLane: config.defaultLane,
    evaluate(evidence, context) {
      return Promise.resolve(evaluateAssetsDetector(config, evidence, context));
    },
  };
}

export const missingBlockedImageDetector = makeDetector({
  id: "ui-070-missing-blocked-image",
  className: "missing-blocked-image",
  ruleId: "UI-070",
  defaultLane: "blocking",
  detect: detectMissingBlockedImages,
  classificationBasis: (violation) =>
    `image failure ${String(violation.measurement.failureKind)} from request evidence, intrinsic size, or visible fallback state`,
});

export const largeEmptyContainerDetector = makeDetector({
  id: "ui-071-large-empty-container",
  className: "large-empty-container",
  ruleId: "UI-071",
  defaultLane: "advisory",
  detect: detectLargeEmptyContainers,
  proofConditionMet: (evidence) => requiredContentContractProofMet(evidence.cell),
  classificationBasis: () =>
    "large empty container with dimensions, descendants, background, and associated failure evidence; advisory until required content contract exists",
});

export const unexplainedDisabledStylingDetector = makeDetector({
  id: "ui-072-unexplained-disabled-styling",
  className: "unexplained-disabled-styling",
  ruleId: "UI-072",
  defaultLane: "advisory",
  detect: detectUnexplainedDisabledStyling,
  proofConditionMet: (evidence) => disabledStylingContractProofMet(evidence.cell),
  classificationBasis: () =>
    "disabled-looking styling without semantic disabled state or accessible explanation compared to sibling norms; advisory until confirmed",
});

export const assetsDetectors = [
  missingBlockedImageDetector,
  largeEmptyContainerDetector,
  unexplainedDisabledStylingDetector,
] as const;

export const assetsDetectorByRuleId: Record<
  AssetsRuleId,
  Detector<AssetsCellEvidence, Ui001EvidenceRecord>
> = {
  "UI-070": missingBlockedImageDetector,
  "UI-071": largeEmptyContainerDetector,
  "UI-072": unexplainedDisabledStylingDetector,
};
