import { z } from "zod";

export const truthSourceSchema = z.enum([
  "observed",
  "inferred",
  "legacy",
  "confirmed",
  "universal",
]);

export type TruthSource = z.infer<typeof truthSourceSchema>;

export const sourceRefSchema = z
  .object({
    uri: z.string(),
    excerpt: z.string().optional(),
  })
  .strict();

export type SourceRef = z.infer<typeof sourceRefSchema>;

export const evidenceNodeKindSchema = z.enum([
  "actor",
  "action",
  "state",
  "record",
  "message",
  "external-effect",
]);

export type EvidenceNodeKind = z.infer<typeof evidenceNodeKindSchema>;

export const evidenceConfidenceSchema = z.enum(["observed", "inferred", "legacy"]);

export type EvidenceConfidence = z.infer<typeof evidenceConfidenceSchema>;

export const evidenceNodeSchema = z
  .object({
    id: z.string(),
    kind: evidenceNodeKindSchema,
    label: z.string(),
    provenance: z.array(sourceRefSchema),
  })
  .strict();

export type EvidenceNode = z.infer<typeof evidenceNodeSchema>;

export const evidenceEdgeSchema = z
  .object({
    from: z.string(),
    to: z.string(),
    kind: z.string(),
    provenance: z.array(sourceRefSchema),
    confidence: evidenceConfidenceSchema,
  })
  .strict();

export type EvidenceEdge = z.infer<typeof evidenceEdgeSchema>;

export const evidenceGraphSchema = z
  .object({
    nodes: z.array(evidenceNodeSchema),
    edges: z.array(evidenceEdgeSchema),
  })
  .strict();

export type EvidenceGraph = z.infer<typeof evidenceGraphSchema>;

export type AdjudicationItem = {
  kind: "path-contradiction";
  from: string;
  edgeKind: string;
  conflictingOutcomes: Array<{
    to: string;
    confidence: EvidenceConfidence;
    provenance: SourceRef[];
  }>;
};

function parseStrict<T>(schema: z.ZodType<T>, value: unknown, label: string): T {
  const result = schema.safeParse(value);
  if (!result.success) {
    const issue = result.error.issues[0];
    throw new Error(
      `${label}: ${issue?.path.join(".") ?? "$"}: ${issue?.message ?? "validation failed"}`,
    );
  }
  return result.data;
}

export function parseEvidenceGraph(value: unknown): EvidenceGraph {
  return parseStrict(evidenceGraphSchema, value, "evidence graph");
}

function groupKey(edge: EvidenceEdge): string {
  return `${edge.from}\0${edge.kind}`;
}

export function detectContradictions(graph: EvidenceGraph): AdjudicationItem[] {
  const parsed = parseEvidenceGraph(graph);
  const groups = new Map<string, EvidenceEdge[]>();

  for (const edge of parsed.edges) {
    const key = groupKey(edge);
    const existing = groups.get(key);
    if (existing === undefined) {
      groups.set(key, [edge]);
    } else {
      existing.push(edge);
    }
  }

  const items: AdjudicationItem[] = [];

  for (const edges of groups.values()) {
    const uniqueOutcomes = new Map<string, EvidenceEdge>();
    for (const edge of edges) {
      if (!uniqueOutcomes.has(edge.to)) {
        uniqueOutcomes.set(edge.to, edge);
      }
    }

    if (uniqueOutcomes.size <= 1) {
      continue;
    }

    const [first] = edges;
    if (first === undefined) {
      continue;
    }

    items.push({
      kind: "path-contradiction",
      from: first.from,
      edgeKind: first.kind,
      conflictingOutcomes: [...uniqueOutcomes.values()].map((edge) => ({
        to: edge.to,
        confidence: edge.confidence,
        provenance: edge.provenance,
      })),
    });
  }

  return items;
}
