import type { MatrixCell } from "../../../schema/src/records/context.js";
import {
  clusterFingerprints,
  dominantCluster,
  groupComparableCells,
  sharedChromeMemberIdentities,
  structuralDiff,
} from "./fingerprint.js";
import type {
  ConsistencyCellEvidence,
  ConsistencyMatrixEvidence,
  ConsistencyViolationFact,
  CohortKind,
  FingerprintCluster,
  StructuralDiffEntry,
} from "./types.js";
import { COHORT_KINDS } from "./types.js";

const COHORT_SET = new Set<string>(COHORT_KINDS);

function parseCohortKind(value: string): CohortKind | undefined {
  const normalized = value.trim().toLowerCase();
  if (!COHORT_SET.has(normalized)) {
    return undefined;
  }
  return normalized as CohortKind;
}

export function chromeContractProofMet(cell: MatrixCell): boolean {
  return cell.state.uiChromeContract === "confirmed";
}

export function cohortContractProofMet(cell: MatrixCell): boolean {
  return cell.state.uiCohortContract === "confirmed";
}

export function styleCohortContractProofMet(cell: MatrixCell): boolean {
  return cell.state.uiStyleCohortContract === "confirmed";
}

function expectedLandmarks(cell: MatrixCell): string[] {
  const raw = cell.state.uiExpectedLandmarks;
  if (raw === undefined || raw.trim().length === 0) {
    return [];
  }
  return raw
    .split(",")
    .map((entry) => entry.trim())
    .filter((entry) => entry.length > 0);
}

const ROLE_TOLERANT_COLLECTIONS = [
  "landmarks",
  "accessibleLabels",
  "links",
  "authControls",
  "headings",
  "componentSignatures",
] as const;

const ROLE_CORROBORATION_MINIMUM_CELLS = 2;

function roleConditionedMemberPaths(
  cells: ConsistencyCellEvidence[],
): Map<string, Map<string, boolean>> {
  const cellsByRole = new Map<string, number>();
  for (const cell of cells) {
    cellsByRole.set(cell.cell.role, (cellsByRole.get(cell.cell.role) ?? 0) + 1);
  }
  const roles = [...cellsByRole.keys()].sort();
  const corroboratedRoles = roles.filter(
    (role) => (cellsByRole.get(role) ?? 0) >= ROLE_CORROBORATION_MINIMUM_CELLS,
  );
  if (corroboratedRoles.length < 2) {
    return new Map();
  }
  const majorityPresenceByPath = new Map<string, Map<string, boolean>>();
  for (const collection of ROLE_TOLERANT_COLLECTIONS) {
    const presenceByIdentity = new Map<string, Map<string, number>>();
    for (const cell of cells) {
      const role = cell.cell.role;
      const members = new Set(
        sharedChromeMemberIdentities(collection, cell.consistencyProbe.sharedChrome[collection]),
      );
      for (const identity of members) {
        const counts = presenceByIdentity.get(identity) ?? new Map<string, number>();
        counts.set(role, (counts.get(role) ?? 0) + 1);
        presenceByIdentity.set(identity, counts);
      }
    }
    for (const [identity, counts] of presenceByIdentity) {
      const majorityPresenceByRole = new Map(
        roles.map((role) => [
          role,
          (counts.get(role) ?? 0) * 2 > (cellsByRole.get(role) ?? 0),
        ]),
      );
      const corroboratedPresence = new Set(
        corroboratedRoles.map((role) => majorityPresenceByRole.get(role)),
      );
      if (corroboratedPresence.size > 1) {
        majorityPresenceByPath.set(`${collection}[${identity}]`, majorityPresenceByRole);
      }
    }
  }
  return majorityPresenceByPath;
}

function partitionRoleConditionedDiffs(
  structural: StructuralDiffEntry[],
  cells: ConsistencyCellEvidence[],
  roles: Iterable<string>,
): { visible: StructuralDiffEntry[]; roleConditioned: StructuralDiffEntry[] } {
  const tolerated = roleConditionedMemberPaths(cells);
  const targetRoles = [...roles];
  const visible: StructuralDiffEntry[] = [];
  const roleConditioned: StructuralDiffEntry[] = [];
  for (const entry of structural) {
    const majorityPresenceByRole = tolerated.get(entry.path);
    const isRoleConditioned =
      majorityPresenceByRole !== undefined &&
      ((entry.kind === "missing" && targetRoles.every((role) => majorityPresenceByRole.get(role) === false)) ||
        (entry.kind === "unexpected" && targetRoles.every((role) => majorityPresenceByRole.get(role) === true)));
    if (isRoleConditioned) {
      roleConditioned.push(entry);
    } else {
      visible.push(entry);
    }
  }
  return { visible, roleConditioned };
}

function diffValue(entry: StructuralDiffEntry): string {
  const value = entry.outlier ?? entry.dominant;
  if (typeof value === "string") {
    return value;
  }
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
    const record = value as Record<string, unknown>;
    for (const field of ["label", "locator", "href", "kind", "level"]) {
      if (typeof record[field] === "string" || typeof record[field] === "number") {
        return String(record[field]);
      }
    }
  }
  return JSON.stringify(value);
}

function summaryDiff(entry: StructuralDiffEntry): string {
  const collection = entry.path.split("[")[0] ?? entry.path;
  const label = {
    landmarks: "landmark",
    accessibleLabels: "accessible label",
    links: "link",
    authControls: "auth control",
    headings: "heading",
    componentSignatures: "component signature",
  }[collection] ?? collection;
  if (entry.kind === "reordered") {
    return `reordered ${label}`;
  }
  if (entry.kind === "changed") {
    return `changed ${label} "${diffValue(entry)}"`;
  }
  return `${entry.kind ?? "changed"} ${label} "${diffValue(entry)}"`;
}

function summarizeDiffs(structural: StructuralDiffEntry[]): string {
  const visible = structural.slice(0, 3).map(summaryDiff);
  const remainder = structural.length - visible.length;
  return `${visible.join(", ")}${remainder > 0 ? `, and ${String(remainder)} more` : ""}`;
}

function comparisonEvidence(
  outlier: ConsistencyCellEvidence,
  reference: ConsistencyCellEvidence,
): Record<string, unknown> {
  return {
    outlier: { cellId: outlier.cell.id, memberGeometry: outlier.consistencyProbe.memberGeometry },
    reference: { cellId: reference.cell.id, memberGeometry: reference.consistencyProbe.memberGeometry },
  };
}

function cellById(cells: ConsistencyCellEvidence[], ids: string[]): ConsistencyCellEvidence | undefined {
  const id = [...ids].sort()[0];
  return id === undefined ? undefined : cells.find((cell) => cell.cell.id === id);
}

type ReportableFingerprintCluster = {
  cohort: CohortKind | undefined;
  comparisonGroup: string;
  cluster: FingerprintCluster;
  dominant: FingerprintCluster;
  clusterCell: ConsistencyCellEvidence;
  dominantCell: ConsistencyCellEvidence;
  structural: StructuralDiffEntry[];
  roleConditioned: StructuralDiffEntry[];
};

function reportedFingerprintClusters(
  matrix: ConsistencyMatrixEvidence,
  comparableCellsByGroup: Map<string, ConsistencyCellEvidence[]>,
): ReportableFingerprintCluster[] {
  const reported: ReportableFingerprintCluster[] = [];
  for (const [comparisonGroup, clusters] of Object.entries(matrix.clustersByCohort)) {
    const cells = comparableCellsByGroup.get(comparisonGroup) ?? [];
    const cohort = cells[0]?.consistencyProbe.cohortKind;
    const dominant = matrix.dominantClusterByCohort[comparisonGroup];
    if (clusters.length <= 1 || dominant === undefined) {
      continue;
    }

    for (const cluster of clusters) {
      if (cluster.fingerprintHash === dominant.fingerprintHash || cluster.cellIds.length < 2) {
        continue;
      }
      const dominantCell = cellById(cells, dominant.cellIds);
      const clusterCell = cellById(cells, cluster.cellIds);
      if (dominantCell === undefined || clusterCell === undefined) {
        throw new Error(`fingerprint cluster ${cluster.fingerprintHash} has no cell evidence`);
      }
      const clusterRoles = cluster.cellIds.map((id) => cells.find((cell) => cell.cell.id === id)?.cell.role);
      if (clusterRoles.some((role) => role === undefined)) {
        throw new Error(`fingerprint cluster ${cluster.fingerprintHash} has no cell evidence`);
      }
      const roles = clusterRoles.filter((role): role is string => role !== undefined);
      const { visible: structural, roleConditioned } = partitionRoleConditionedDiffs(
        structuralDiff(dominantCell.consistencyProbe.sharedChrome, clusterCell.consistencyProbe.sharedChrome),
        cells,
        roles,
      );
      if (structural.length > 0) {
        reported.push({
          cohort,
          comparisonGroup,
          cluster,
          dominant,
          clusterCell,
          dominantCell,
          structural,
          roleConditioned,
        });
      }
    }
  }
  return reported;
}

export function detectSharedChromeFingerprintIssues(
  evidence: ConsistencyCellEvidence,
  comparableCells: ConsistencyCellEvidence[] = [],
): ConsistencyViolationFact[] {
  const missingLandmarks = expectedLandmarks(evidence.cell).filter(
    (landmark) => !evidence.consistencyProbe.sharedChrome.landmarks.includes(landmark),
  );
  if (missingLandmarks.length === 0) {
    return [];
  }
  const reference = comparableCells
    .filter(
      (cell) =>
        cell.cell.id !== evidence.cell.id &&
        missingLandmarks.every((landmark) => cell.consistencyProbe.sharedChrome.landmarks.includes(landmark)),
    )
    .sort((left, right) => left.cell.id.localeCompare(right.cell.id))[0];
  const comparison = reference === undefined
    ? {
        visualComparisonUnavailable:
          "No comparable reference page exists with the configured landmark.",
      }
    : {
        structuralDiff: structuralDiff(
          reference.consistencyProbe.sharedChrome,
          evidence.consistencyProbe.sharedChrome,
        ).filter(
          (entry) =>
            entry.kind === "missing" &&
            missingLandmarks.some((landmark) => entry.path === `landmarks[${landmark}]`),
        ),
        comparison: comparisonEvidence(evidence, reference),
      };

  return [
    {
      ruleId: "UI-040",
      kind: "shared-chrome-incomplete",
      locator: "document",
      summary: `shared chrome fingerprint missing configured landmarks: ${missingLandmarks.join(", ")}`,
      measurement: {
        missingLandmarks,
        fingerprint: evidence.consistencyProbe.sharedChrome,
        fingerprintHash: evidence.consistencyProbe.fingerprintHash,
        ...comparison,
      },
    },
  ];
}

export function buildMatrixEvidence(
  matrixId: string,
  cells: ConsistencyCellEvidence[],
): ConsistencyMatrixEvidence {
  const clustersByCohort: Record<string, FingerprintCluster[]> = {};
  const dominantClusterByCohort: Record<string, FingerprintCluster | undefined> = {};

  for (const [comparisonGroup, comparableCells] of groupComparableCells(cells)) {
    const clusters = clusterFingerprints(comparableCells);
    clustersByCohort[comparisonGroup] = clusters;
    dominantClusterByCohort[comparisonGroup] = dominantCluster(clusters);
  }

  return {
    matrixId,
    cells,
    clustersByCohort,
    dominantClusterByCohort,
  };
}

export function detectFingerprintClusters(
  matrix: ConsistencyMatrixEvidence,
): ConsistencyViolationFact[] {
  const violations: ConsistencyViolationFact[] = [];
  const comparableCellsByGroup = groupComparableCells(matrix.cells);

  for (const report of reportedFingerprintClusters(matrix, comparableCellsByGroup)) {
    const {
      cohort,
      comparisonGroup,
      cluster,
      dominant,
      clusterCell,
      dominantCell,
      structural,
      roleConditioned,
    } = report;
    violations.push({
      ruleId: "UI-041",
      kind: "provisional-cluster",
      locator: cluster.cellIds[0] ?? comparisonGroup,
      summary: `${String(cluster.cellIds.length)} pages share a page-shell layout that differs from the ${String(dominant.cellIds.length)}-page majority in this group: ${summarizeDiffs(structural)}.`,
      measurement: {
        cohort,
        comparisonGroup,
        cluster,
        dominantCluster: dominant,
        structuralDiff: structural,
        roleConditionedDiff: roleConditioned,
        provisionalOnly: true,
        comparison: comparisonEvidence(clusterCell, dominantCell),
      },
    });
  }

  return violations;
}

export function detectFingerprintOutliers(
  matrix: ConsistencyMatrixEvidence,
): ConsistencyViolationFact[] {
  const violations: ConsistencyViolationFact[] = [];
  const comparableCellsByGroup = groupComparableCells(matrix.cells);
  const reportedClusterCellIds = new Set(
    reportedFingerprintClusters(matrix, comparableCellsByGroup).flatMap((report) => report.cluster.cellIds),
  );

  for (const [comparisonGroup, clusters] of Object.entries(matrix.clustersByCohort)) {
    const comparableCells = comparableCellsByGroup.get(comparisonGroup) ?? [];
    const cohort = comparableCells[0]?.consistencyProbe.cohortKind;
    const dominant = matrix.dominantClusterByCohort[comparisonGroup];
    if (dominant === undefined || clusters.length <= 1) {
      continue;
    }

    const dominantCell = cellById(comparableCells, dominant.cellIds);
    if (dominantCell === undefined) {
      continue;
    }

    for (const cell of comparableCells) {
      if (dominant.cellIds.includes(cell.cell.id)) {
        continue;
      }
      if (reportedClusterCellIds.has(cell.cell.id)) {
        continue;
      }

      const structural = structuralDiff(
        dominantCell.consistencyProbe.sharedChrome,
        cell.consistencyProbe.sharedChrome,
      );
      const { visible: visibleStructural, roleConditioned } = partitionRoleConditionedDiffs(
        structural,
        comparableCells,
        [cell.cell.role],
      );
      if (visibleStructural.length === 0) {
        continue;
      }
      violations.push({
        ruleId: "UI-042",
        kind: "fingerprint-outlier",
        locator: cell.cell.id,
        summary: `page shell on ${cell.cell.url} differs from the ${String(dominant.cellIds.length)} other pages in this group: ${summarizeDiffs(visibleStructural)}.`,
        measurement: {
          cohort,
          comparisonGroup,
          outlierCellId: cell.cell.id,
          dominantCluster: dominant,
          prevalence: dominant.prevalence,
          structuralDiff: visibleStructural,
          roleConditionedDiff: roleConditioned,
          fingerprintHash: cell.consistencyProbe.fingerprintHash,
          dominantFingerprintHash: dominant.fingerprintHash,
          comparison: comparisonEvidence(cell, dominantCell),
        },
      });
    }
  }

  return violations;
}

export function detectCohortSeparationIssues(
  matrix: ConsistencyMatrixEvidence,
): ConsistencyViolationFact[] {
  const violations: ConsistencyViolationFact[] = [];
  const cohorts = new Set(matrix.cells.map((cell) => cell.consistencyProbe.cohortKind));

  for (const cell of matrix.cells) {
    const configured = cell.cell.state.uiCohortKind;
    if (configured === undefined || configured.trim().length === 0) {
      continue;
    }
    const configuredCohort = parseCohortKind(configured);
    if (
      configuredCohort !== undefined &&
      configuredCohort !== cell.consistencyProbe.observedCohortKind
    ) {
      violations.push({
        ruleId: "UI-043",
        kind: "cohort-evidence-mismatch",
        locator: cell.cell.id,
        summary: `configured cohort ${configuredCohort} does not match observed cohort ${cell.consistencyProbe.observedCohortKind}`,
        measurement: {
          configuredCohort,
          observedCohort: cell.consistencyProbe.observedCohortKind,
          resolvedCohort: cell.consistencyProbe.cohortKind,
          separatedCohorts: [...cohorts].sort(),
          visualComparisonUnavailable: "No comparable reference page exists for this cohort classification mismatch.",
        },
      });
    }
  }

  const grouped = groupComparableCells(matrix.cells);
  for (const [comparisonGroup, cells] of grouped) {
    const cohort = cells[0]?.consistencyProbe.cohortKind;
    if (cells.length < 2) {
      continue;
    }
    const hashes = new Set(cells.map((cell) => cell.consistencyProbe.fingerprintHash));
    if (hashes.size <= 1) {
      continue;
    }
    const crossCohortRisk = matrix.cells.some(
      (cell) =>
        cell.consistencyProbe.cohortKind !== cells[0]?.consistencyProbe.cohortKind &&
        cell.consistencyProbe.fingerprintHash !== cells[0]?.consistencyProbe.fingerprintHash,
    );
    if (!crossCohortRisk) {
      continue;
    }
    violations.push({
      ruleId: "UI-043",
      kind: "cohort-separated-comparison",
      locator: comparisonGroup,
      summary: `comparable cell group ${comparisonGroup} kept separate from other layout surfaces during fingerprint comparison`,
      measurement: {
        cohort,
        comparisonGroup,
        cellIds: cells.map((cell) => cell.cell.id).sort(),
        separatedCohorts: [...cohorts].sort(),
        withinCohortFingerprintCount: hashes.size,
        visualComparisonUnavailable: "This finding confirms separate comparison groups; it does not compare an outlier with a reference page.",
      },
    });
  }

  return violations;
}

export function detectComputedStyleOutliers(
  matrix: ConsistencyMatrixEvidence,
): ConsistencyViolationFact[] {
  const violations: ConsistencyViolationFact[] = [];

  for (const [comparisonGroup, cells] of groupComparableCells(matrix.cells)) {
    const cohort = cells[0]?.consistencyProbe.cohortKind;
    const styleCells = cells.filter(
      (cell) => cell.consistencyProbe.styleFingerprintHash !== undefined,
    );
    if (styleCells.length < 2) {
      continue;
    }

    const grouped = new Map<string, ConsistencyCellEvidence[]>();
    for (const cell of styleCells) {
      const hash = cell.consistencyProbe.styleFingerprintHash;
      if (hash === undefined) {
        continue;
      }
      const bucket = grouped.get(hash) ?? [];
      bucket.push(cell);
      grouped.set(hash, bucket);
    }

    const clusters = [...grouped.entries()]
      .map(([fingerprintHash, clusterCells]) => ({
        fingerprintHash,
        cellIds: clusterCells.map((cell) => cell.cell.id).sort(),
        prevalence: clusterCells.length / styleCells.length,
      }))
      .sort((left, right) => right.cellIds.length - left.cellIds.length || left.fingerprintHash.localeCompare(right.fingerprintHash));

    const dominant = clusters[0];
    if (dominant === undefined || clusters.length <= 1) {
      continue;
    }

    const dominantCell = cellById(styleCells, dominant.cellIds);
    if (dominantCell?.consistencyProbe.computedStyle === undefined) {
      continue;
    }

    for (const cell of styleCells) {
      if (dominant.cellIds.includes(cell.cell.id)) {
        continue;
      }
      if (cell.consistencyProbe.computedStyle === undefined) {
        continue;
      }

      const structural = structuralDiff(
        dominantCell.consistencyProbe.computedStyle,
        cell.consistencyProbe.computedStyle,
      );
      violations.push({
        ruleId: "UI-044",
        kind: "computed-style-outlier",
        locator: cell.cell.id,
        summary: `computed-style fingerprint outlier in comparable cell group ${comparisonGroup}`,
        measurement: {
          cohort,
          comparisonGroup,
          uniRule: "UNI-094",
          outlierCellId: cell.cell.id,
          dominantCluster: dominant,
          prevalence: dominant.prevalence,
          structuralDiff: structural,
          styleFingerprintHash: cell.consistencyProbe.styleFingerprintHash,
          dominantStyleFingerprintHash: dominant.fingerprintHash,
          advisoryUntilStyleContract: true,
          comparison: comparisonEvidence(cell, dominantCell),
        },
      });
    }
  }

  return violations;
}
