import type {
  Detector,
  DetectorContext,
  DetectorOutcome,
  LaneEligibility,
} from "../../../core/src/sdk/detector.js";
import { isIntentionallyExcepted } from "./contracts.js";
import {
  detectHorizontalPageOverflow,
  detectHtmlLanguageDirection,
  detectHydrationError,
  detectInvalidRenderToken,
  detectRawI18nKey,
  detectRequiredContentEmpty,
  detectStuckLoadingState,
  detectUncaughtConsoleError,
  detectUnexpectedRequestFailure,
  hydrationProofConditionMet,
  i18nProofConditionMet,
  overflowIntentAmbiguous,
  requiredContentProofConditionMet,
  stuckLoadingProofConditionMet,
} from "./detect.js";
import { buildUi001Evidence } from "./evidence.js";
import type {
  RenderedCellEvidence,
  RenderedRuleId,
  RenderedViolationFact,
  Ui001EvidenceRecord,
} from "./types.js";
import { RENDERED_DETECTOR_VERSION } from "./types.js";

type RenderedDetectorConfig = {
  id: string;
  className: string;
  ruleId: RenderedRuleId;
  defaultLane: LaneEligibility;
  detect: (evidence: RenderedCellEvidence) => RenderedViolationFact[];
  proofConditionMet?: (
    evidence: RenderedCellEvidence,
    context: DetectorContext,
  ) => boolean;
  classificationBasis: (violation: RenderedViolationFact) => string;
  ambiguousIntent?: (evidence: RenderedCellEvidence) => boolean;
};

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

function buildOutcome(
  config: RenderedDetectorConfig,
  evidence: RenderedCellEvidence,
  violation: RenderedViolationFact,
  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: RENDERED_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}`,
      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;
  }

  if (config.ambiguousIntent?.(evidence) === true) {
    outcome.laneEligibility = "advisory";
    outcome.evidence = [{ truthSource: "observed", payload: ui001 }];
    if (outcome.proofConditionMet !== undefined) {
      outcome.proofConditionMet = false;
    }
  }

  void context;
  return outcome;
}

function evaluateRenderedDetector(
  config: RenderedDetectorConfig,
  evidence: RenderedCellEvidence,
  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: RenderedDetectorConfig): Detector<RenderedCellEvidence, Ui001EvidenceRecord> {
  return {
    id: config.id,
    version: RENDERED_DETECTOR_VERSION,
    defaultLane: config.defaultLane,
    evaluate(evidence, context) {
      return Promise.resolve(evaluateRenderedDetector(config, evidence, context));
    },
  };
}

export const invalidRenderTokenDetector = makeDetector({
  id: "ui-010-invalid-render-token",
  className: "invalid-render-token",
  ruleId: "UI-010",
  defaultLane: "blocking",
  detect: detectInvalidRenderToken,
  classificationBasis: (violation) =>
    `observed visible text contains invalid render token ${String(violation.measurement.token)}`,
});

export const uncaughtConsoleErrorDetector = makeDetector({
  id: "ui-011-uncaught-console-error",
  className: "uncaught-console-error",
  ruleId: "UI-011",
  defaultLane: "blocking",
  detect: detectUncaughtConsoleError,
  classificationBasis: () => "observed uncaught console error with source location",
});

export const unexpectedRequestFailureDetector = makeDetector({
  id: "ui-012-unexpected-request-failure",
  className: "unexpected-request-failure",
  ruleId: "UI-012",
  defaultLane: "advisory",
  detect: detectUnexpectedRequestFailure,
  classificationBasis: (violation) =>
    violation.measurement.declared === true
      ? "failed response contradicts declared network expectation"
      : "undeclared network failure remains advisory",
  ambiguousIntent: (evidence) =>
    detectUnexpectedRequestFailure(evidence).every(
      (violation) => violation.measurement.declared !== true,
    ),
});

export const rawI18nKeyDetector = makeDetector({
  id: "ui-013-raw-i18n-key",
  className: "raw-i18n-key",
  ruleId: "UI-013",
  defaultLane: "blocking-eligible",
  detect: detectRawI18nKey,
  proofConditionMet: (evidence) => i18nProofConditionMet(evidence.cell),
  classificationBasis: () =>
    "visible text matches i18n key grammar and is absent from loaded catalog",
});

export const htmlLanguageDirectionDetector = makeDetector({
  id: "ui-014-html-language-direction",
  className: "html-language-direction",
  ruleId: "UI-014",
  defaultLane: "blocking",
  detect: detectHtmlLanguageDirection,
  classificationBasis: () => "html lang/dir contradict configured cell locale contract",
});

export const requiredContentEmptyDetector = makeDetector({
  id: "ui-015-required-content-empty",
  className: "required-content-empty",
  ruleId: "UI-015",
  defaultLane: "blocking-eligible",
  detect: detectRequiredContentEmpty,
  proofConditionMet: (evidence) => requiredContentProofConditionMet(evidence.cell),
  classificationBasis: () =>
    "confirmed required landmark has no accessible content after settled state",
});

export const stuckLoadingStateDetector = makeDetector({
  id: "ui-016-stuck-loading-state",
  className: "stuck-loading-state",
  ruleId: "UI-016",
  defaultLane: "blocking-eligible",
  detect: detectStuckLoadingState,
  proofConditionMet: (evidence) => stuckLoadingProofConditionMet(evidence),
  classificationBasis: () =>
    "loading indicator remains after declared completion signal and quiet period",
});

export const horizontalPageOverflowDetector = makeDetector({
  id: "ui-017-horizontal-page-overflow",
  className: "horizontal-page-overflow",
  ruleId: "UI-017",
  defaultLane: "advisory",
  detect: detectHorizontalPageOverflow,
  classificationBasis: () =>
    "scrollWidth exceeds clientWidth; advisory until overflow contract excludes intentional document scroll",
  ambiguousIntent: (evidence) => overflowIntentAmbiguous(evidence.cell),
});

export const hydrationErrorDetector = makeDetector({
  id: "ui-018-hydration-error",
  className: "hydration-error",
  ruleId: "UI-018",
  defaultLane: "blocking-eligible",
  detect: detectHydrationError,
  proofConditionMet: (evidence) => hydrationProofConditionMet(evidence.cell),
  classificationBasis: () =>
    "framework/browser hydration diagnostic or identity mismatch under supported adapter",
});

export const renderedDetectors = [
  invalidRenderTokenDetector,
  uncaughtConsoleErrorDetector,
  unexpectedRequestFailureDetector,
  rawI18nKeyDetector,
  htmlLanguageDirectionDetector,
  requiredContentEmptyDetector,
  stuckLoadingStateDetector,
  horizontalPageOverflowDetector,
  hydrationErrorDetector,
] as const;

export const renderedDetectorByRuleId: Record<RenderedRuleId, Detector<RenderedCellEvidence, Ui001EvidenceRecord>> = {
  "UI-010": invalidRenderTokenDetector,
  "UI-011": uncaughtConsoleErrorDetector,
  "UI-012": unexpectedRequestFailureDetector,
  "UI-013": rawI18nKeyDetector,
  "UI-014": htmlLanguageDirectionDetector,
  "UI-015": requiredContentEmptyDetector,
  "UI-016": stuckLoadingStateDetector,
  "UI-017": horizontalPageOverflowDetector,
  "UI-018": hydrationErrorDetector,
};
