/**
 * Knowledge graph helpers for palace triple management.
 *
 * Triples are (subject, predicate, object) with temporal validity.
 * This module provides pure functions for contradiction detection,
 * merging, and currency checking.
 */

/**
 * A lightweight triple for comparison (does not require full doc fields).
 */
export interface TripleInput {
  subject: string;
  predicate: string;
  object: string;
  validFrom: string;
  validTo?: string;
  confidence?: number;
}

/**
 * Find contradictions between existing triples and incoming ones.
 *
 * A contradiction exists when:
 * - Same subject + predicate, different object
 * - Both are currently valid (no validTo or validTo in the future)
 *
 * @returns Array of { existing, incoming } contradiction pairs
 */
export function findContradictions(
  existing: TripleInput[],
  incoming: TripleInput[],
  asOf: Date = new Date()
): Array<{ existing: TripleInput; incoming: TripleInput }> {
  const contradictions: Array<{ existing: TripleInput; incoming: TripleInput }> = [];

  for (const inc of incoming) {
    for (const ext of existing) {
      if (
        normalizeEntity(ext.subject) === normalizeEntity(inc.subject) &&
        normalizeEntity(ext.predicate) === normalizeEntity(inc.predicate) &&
        normalizeEntity(ext.object) !== normalizeEntity(inc.object) &&
        isTripleCurrent(ext, asOf) &&
        isTripleCurrent(inc, asOf)
      ) {
        contradictions.push({ existing: ext, incoming: inc });
      }
    }
  }

  return contradictions;
}

/**
 * Merge incoming triples into existing set.
 *
 * - If an incoming triple contradicts an existing one, the existing triple
 *   is invalidated (validTo set) and the incoming one is kept.
 * - If an incoming triple is a duplicate (same S/P/O), it is skipped.
 * - Otherwise, the incoming triple is added.
 *
 * @returns { merged, invalidated, added, skipped } counts
 */
export function mergeTriples(
  existing: TripleInput[],
  incoming: TripleInput[],
  asOf: Date = new Date()
): {
  merged: TripleInput[];
  invalidated: TripleInput[];
  added: TripleInput[];
  skipped: TripleInput[];
} {
  const invalidated: TripleInput[] = [];
  const added: TripleInput[] = [];
  const skipped: TripleInput[] = [];

  const merged = [...existing];

  for (const inc of incoming) {
    // Check for exact duplicate
    const isDuplicate = merged.some(
      ext =>
        normalizeEntity(ext.subject) === normalizeEntity(inc.subject) &&
        normalizeEntity(ext.predicate) === normalizeEntity(inc.predicate) &&
        normalizeEntity(ext.object) === normalizeEntity(inc.object) &&
        isTripleCurrent(ext, asOf)
    );

    if (isDuplicate) {
      skipped.push(inc);
      continue;
    }

    // Check for contradiction
    const contradictIdx = merged.findIndex(
      ext =>
        normalizeEntity(ext.subject) === normalizeEntity(inc.subject) &&
        normalizeEntity(ext.predicate) === normalizeEntity(inc.predicate) &&
        normalizeEntity(ext.object) !== normalizeEntity(inc.object) &&
        isTripleCurrent(ext, asOf)
    );

    if (contradictIdx >= 0) {
      // Invalidate the old triple
      const old = merged[contradictIdx];
      merged[contradictIdx] = { ...old, validTo: asOf.toISOString() };
      invalidated.push(old);
    }

    merged.push(inc);
    added.push(inc);
  }

  return { merged, invalidated, added, skipped };
}

/**
 * Check if a triple is currently valid.
 *
 * @param triple - The triple to check
 * @param asOf - Reference time (default: now)
 * @returns true if the triple has no validTo or validTo is in the future
 */
export function isTripleCurrent(
  triple: Pick<TripleInput, 'validTo'>,
  asOf: Date = new Date()
): boolean {
  if (!triple.validTo) return true;
  return new Date(triple.validTo).getTime() > asOf.getTime();
}

/**
 * Normalize an entity string for comparison (lowercase, trim, collapse whitespace).
 */
function normalizeEntity(s: string): string {
  return s.toLowerCase().trim().replace(/\s+/g, ' ');
}
