import type { InventorySource } from "@app/shared-types";

export interface DedupCandidateInput {
  canonicalIngredientId?: string | null;
  displayName: string;
  brand?: string | null;
  quantity: number;
  source: InventorySource;
  createdAt: string;
}

export interface DedupExistingItem extends DedupCandidateInput {
  id: string;
}

export interface DedupCandidate {
  itemId: string;
  score: number;
  reasons: string[];
}

const WINDOW_MS = 3 * 86_400_000; // 3 dygn

/**
 * Dubblettkandidater mellan kvitto, streckkod och bilder (spec §9).
 * Ren heuristik – användaren fattar alltid beslutet. AI-baserad
 * dedup (DEDUPLICATE_INVENTORY-jobbet) kan förfina men aldrig auto-slå ihop
 * utan bekräftelse.
 */
export function findDuplicateCandidates(
  incoming: DedupCandidateInput,
  existing: DedupExistingItem[],
): DedupCandidate[] {
  const incomingTime = Date.parse(incoming.createdAt);
  const results: DedupCandidate[] = [];

  for (const item of existing) {
    const reasons: string[] = [];
    let score = 0;

    const sameCanonical =
      incoming.canonicalIngredientId != null &&
      incoming.canonicalIngredientId === item.canonicalIngredientId;
    const nameMatch = normalizedEquals(incoming.displayName, item.displayName);
    if (sameCanonical) {
      score += 0.45;
      reasons.push("samma ingrediens");
    } else if (nameMatch) {
      score += 0.3;
      reasons.push("liknande namn");
    } else {
      continue;
    }

    const dt = Math.abs(Date.parse(item.createdAt) - incomingTime);
    if (dt <= WINDOW_MS) {
      score += 0.25;
      reasons.push("registrerad inom 3 dygn");
    }

    if (incoming.source !== item.source) {
      score += 0.15;
      reasons.push(`olika källor (${incoming.source} + ${item.source})`);
    }

    const qtyRatio =
      item.quantity > 0 && incoming.quantity > 0
        ? Math.min(incoming.quantity, item.quantity) / Math.max(incoming.quantity, item.quantity)
        : 0;
    if (qtyRatio >= 0.7) {
      score += 0.15;
      reasons.push("liknande mängd");
    }

    if (
      incoming.brand &&
      item.brand &&
      incoming.brand.toLowerCase().trim() === item.brand.toLowerCase().trim()
    ) {
      score += 0.1;
      reasons.push("samma varumärke");
    }

    if (score >= 0.5) {
      results.push({ itemId: item.id, score: Math.min(1, round2(score)), reasons });
    }
  }

  return results.sort((a, b) => b.score - a.score);
}

function normalizedEquals(a: string, b: string): boolean {
  const na = normalize(a);
  const nb = normalize(b);
  if (na === nb) return true;
  return na.length > 3 && nb.length > 3 && (na.includes(nb) || nb.includes(na));
}

function normalize(s: string): string {
  return s
    .toLowerCase()
    .replace(/[^a-zåäö0-9 ]/gi, "")
    .replace(/\s+/g, " ")
    .trim();
}

function round2(v: number): number {
  return Math.round(v * 100) / 100;
}
