import { normalizeTeamName } from "~/lib/normalize-team-name";

export type ReconciliationParticipant = {
  id: string;
  name: string;
  externalId?: string | null;
};

export type MatchConfidence = "exact" | "partial" | "review" | "none";

export type CandidateMatch = {
  participantId: string;
  participantName: string;
  confidence: MatchConfidence;
  score: number;
};

export type UnmatchedTeamResolutionView = {
  suggestedParticipantId: string | null;
  suggestedParticipantName: string | null;
  confidence: MatchConfidence;
  topCandidates: CandidateMatch[];
  orderedParticipants: ReconciliationParticipant[];
};

function tokenSet(value: string): Set<string> {
  return new Set(
    normalizeTeamName(value)
      .split(" ")
      .map((token) => token.trim())
      .filter(Boolean)
  );
}

function reviewScore(apiTeamName: string, participantName: string): number {
  const normalizedApi = normalizeTeamName(apiTeamName);
  const normalizedParticipant = normalizeTeamName(participantName);

  if (!normalizedApi || !normalizedParticipant) return 0;

  const apiTokens = tokenSet(apiTeamName);
  const participantTokens = tokenSet(participantName);
  const sharedTokens = [...apiTokens].filter((token) => participantTokens.has(token));

  let score = 0;

  if (sharedTokens.length > 0) {
    score += sharedTokens.length * 20;
  }

  if (normalizedApi.startsWith(normalizedParticipant) || normalizedParticipant.startsWith(normalizedApi)) {
    score += 12;
  }

  if (normalizedApi[0] === normalizedParticipant[0]) {
    score += 4;
  }

  const lengthDelta = Math.abs(normalizedApi.length - normalizedParticipant.length);
  score -= Math.min(lengthDelta, 10);

  return Math.max(score, 0);
}

export function rankParticipantMatches(
  apiTeamName: string,
  participants: ReconciliationParticipant[]
): CandidateMatch[] {
  const normalizedApi = normalizeTeamName(apiTeamName);

  return participants
    .map((participant) => {
      const normalizedParticipant = normalizeTeamName(participant.name);

      if (!normalizedApi || !normalizedParticipant) {
        return {
          participantId: participant.id,
          participantName: participant.name,
          confidence: "none" as const,
          score: 0,
        };
      }

      if (normalizedParticipant === normalizedApi) {
        return {
          participantId: participant.id,
          participantName: participant.name,
          confidence: "exact" as const,
          score: 100,
        };
      }

      if (
        normalizedApi.length >= 4 &&
        normalizedParticipant.length >= 4 &&
        (normalizedApi.includes(normalizedParticipant) ||
          normalizedParticipant.includes(normalizedApi))
      ) {
        return {
          participantId: participant.id,
          participantName: participant.name,
          confidence: "partial" as const,
          score: 80,
        };
      }

      return {
        participantId: participant.id,
        participantName: participant.name,
        confidence: "review" as const,
        score: reviewScore(apiTeamName, participant.name),
      };
    })
    .toSorted((a, b) => b.score - a.score || a.participantName.localeCompare(b.participantName));
}

export function buildUnmatchedTeamResolutionView(
  apiTeamName: string,
  participants: ReconciliationParticipant[]
): UnmatchedTeamResolutionView {
  const rankedCandidates = rankParticipantMatches(apiTeamName, participants);
  const topCandidates = rankedCandidates.filter((candidate) => candidate.score > 0).slice(0, 3);
  const bestCandidate = rankedCandidates[0];

  const confidence =
    bestCandidate?.confidence === "exact" || bestCandidate?.confidence === "partial"
      ? bestCandidate.confidence
      : topCandidates.length > 0
        ? "review"
        : "none";

  const suggestedParticipantId =
    confidence === "exact" || confidence === "partial" ? bestCandidate.participantId : null;
  const suggestedParticipantName =
    confidence === "none" ? null : (bestCandidate?.participantName ?? null);

  const topCandidateIds = new Set(topCandidates.map((candidate) => candidate.participantId));
  const orderedParticipants = [
    ...topCandidates
      .map((candidate) =>
        participants.find((participant) => participant.id === candidate.participantId) ?? null
      )
      .filter((participant): participant is ReconciliationParticipant => participant !== null),
    ...participants.filter((participant) => !topCandidateIds.has(participant.id)),
  ];

  return {
    suggestedParticipantId,
    suggestedParticipantName,
    confidence,
    topCandidates,
    orderedParticipants,
  };
}
