export interface DedupCandidate {
  id?: string;
  player_name?: string | null;
  player?: string | null;
  forecast_type?: string | null;
  forecast_payload?: Record<string, any> | null;
  recommendation?: string | null;
  stat_type?: string | null;
  edge?: number | string | null;
  line?: number | string | null;
  market_type?: string | null;
  odds?: number | string | null;
}

function normalizeText(value: unknown): string {
  return String(value ?? '')
    .normalize('NFKD')
    .replace(/[\u0300-\u036f]/g, '')
    .trim()
    .toLowerCase()
    .replace(/\s+/g, ' ');
}

function toNumber(value: unknown): number | null {
  if (value == null || value === '') return null;
  const parsed = typeof value === 'number' ? value : parseFloat(String(value));
  return Number.isFinite(parsed) ? parsed : null;
}

function getPayload(prop: DedupCandidate): Record<string, any> {
  return prop.forecast_payload && typeof prop.forecast_payload === 'object'
    ? prop.forecast_payload
    : {};
}

function getPlayerCanonical(prop: DedupCandidate): string {
  return normalizeText(prop.player_name ?? prop.player ?? getPayload(prop).player);
}

function getStatType(prop: DedupCandidate): string {
  const payload = getPayload(prop);
  return normalizeText(prop.stat_type ?? payload.stat_type ?? payload.prop);
}

function getDirection(prop: DedupCandidate): 'over' | 'under' | 'unknown' {
  const payload = getPayload(prop);
  const normalized = normalizeText(prop.recommendation ?? payload.recommendation);
  if (normalized === 'over' || normalized === 'o') return 'over';
  if (normalized === 'under' || normalized === 'u') return 'under';
  return 'unknown';
}

function getMarketType(prop: DedupCandidate): string {
  const payload = getPayload(prop);
  return normalizeText(prop.market_type ?? payload.market_type ?? prop.forecast_type ?? 'PLAYER_PROP');
}

function getLine(prop: DedupCandidate): number | null {
  const payload = getPayload(prop);
  return toNumber(prop.line ?? payload.market_line_value ?? payload.line);
}

function getEdge(prop: DedupCandidate): number {
  const payload = getPayload(prop);
  return toNumber(prop.edge ?? payload.edge) ?? Number.NEGATIVE_INFINITY;
}

function americanOddsToCost(value: number | null): number | null {
  if (value == null) return null;
  if (value === 0) return null;
  if (value > 0) return 100 / (value + 100);
  const abs = Math.abs(value);
  return abs / (abs + 100);
}

function getVigCost(prop: DedupCandidate): number {
  const payload = getPayload(prop);
  const odds = toNumber(prop.odds ?? payload.odds ?? payload.book_odds ?? payload.juice);
  return americanOddsToCost(odds) ?? Number.POSITIVE_INFINITY;
}

export function buildDedupIdentity(prop: DedupCandidate): string {
  return [
    getPlayerCanonical(prop),
    getStatType(prop),
    getDirection(prop),
    getMarketType(prop),
  ].join(':');
}

function compareCandidates(a: { item: DedupCandidate; index: number }, b: { item: DedupCandidate; index: number }): number {
  const edgeA = getEdge(a.item);
  const edgeB = getEdge(b.item);
  if (edgeA !== edgeB) return edgeB - edgeA;

  const direction = getDirection(a.item);
  const lineA = getLine(a.item);
  const lineB = getLine(b.item);
  if (lineA != null && lineB != null && lineA !== lineB) {
    if (direction === 'over') return lineA - lineB;
    if (direction === 'under') return lineB - lineA;
  }
  if (lineA == null && lineB != null) return 1;
  if (lineA != null && lineB == null) return -1;

  const vigA = getVigCost(a.item);
  const vigB = getVigCost(b.item);
  if (vigA !== vigB) return vigA - vigB;

  return a.index - b.index;
}

export function chooseSiblingWinner<T extends DedupCandidate>(props: T[]): { winner: T; losers: T[] } {
  const ranked = props.map((item, index) => ({ item, index })).sort(compareCandidates);
  return {
    winner: ranked[0].item,
    losers: ranked.slice(1).map((entry) => entry.item),
  };
}

export function deduplicateProps<T extends DedupCandidate>(props: T[]): T[] {
  const groups = new Map<string, Array<{ item: T; index: number }>>();

  props.forEach((item, index) => {
    const identity = buildDedupIdentity(item);
    const existing = groups.get(identity) ?? [];
    existing.push({ item, index });
    groups.set(identity, existing);
  });

  const keepIds = new Set<T>();
  groups.forEach((entries) => {
    if (entries.length === 1) {
      keepIds.add(entries[0].item);
      return;
    }
    const { winner } = chooseSiblingWinner(entries.map((entry) => entry.item));
    keepIds.add(winner);
  });

  return props.filter((item) => keepIds.has(item));
}
