import { groupsFromFlatTeeth } from '@/components/treatment/toothSelectionGroups';
import type { FdiToothId } from '@/types/treatment';
import type {
  VoiceApplySelection,
  VoiceExtractionResult,
  VoiceProsthesisResult,
} from '@/types/voice';

/** Which rows the review sheet renders at all — a row with nothing extracted is noise. */
export function voiceRowAvailability(result: VoiceExtractionResult) {
  return {
    treatmentType: result.treatmentType != null,
    teeth: result.teeth.length > 0,
    comment: Boolean(result.comment?.trim()),
    prosthesis: result.prosthesis != null,
    lab: result.labId != null,
    dueDate: result.dueDate != null,
  };
}

/**
 * Everything available ticks itself, with two exceptions: an inexactly-matched lab, because
 * it is the one extracted value whose error leaves the building; and an incomplete
 * prosthesis map, which cannot ship at all and would just move the failure to dispatch.
 */
export function initialVoiceSelection(result: VoiceExtractionResult): VoiceApplySelection {
  const available = voiceRowAvailability(result);
  return {
    treatmentType: available.treatmentType,
    teeth: available.teeth,
    comment: available.comment,
    prosthesis: available.prosthesis && result.prosthesis?.complete === true,
    lab: available.lab && result.labMatchExact,
    dueDate: available.dueDate,
  };
}

/**
 * Intersected with availability rather than counting ticks: a row can be ticked and then lose
 * its content, and "Apply 1 item" that applies nothing is worse than a wrong number.
 */
export function countSelected(
  selection: VoiceApplySelection,
  available: Record<keyof VoiceApplySelection, boolean>,
): number {
  return (Object.keys(selection) as (keyof VoiceApplySelection)[]).filter(
    (key) => selection[key] && available[key],
  ).length;
}

/** Mirrors the backend's rule: every selected tooth needs a code, or the case cannot ship. */
function recheckProsthesis(
  prosthesis: VoiceProsthesisResult,
  teeth: readonly FdiToothId[],
): VoiceProsthesisResult {
  const missingTeeth = teeth.filter((tooth) => !prosthesis.byTooth[tooth]);
  return { ...prosthesis, missingTeeth, complete: missingTeeth.length === 0 };
}

/**
 * Fold the candidate picks into the result, so nothing downstream has to know chips exist.
 *
 * Union rather than toggle: a candidate can coincidentally be a tooth the recording already
 * produced ("۱۲ و دو"), and tapping it must not deselect that one.
 */
export function withChosenTeeth(
  result: VoiceExtractionResult,
  chosen: readonly FdiToothId[],
): VoiceExtractionResult {
  if (chosen.length === 0) return result;

  const teeth = [...new Set([...result.teeth, ...chosen])].sort() as FdiToothId[];

  return {
    ...result,
    teeth,
    toothSelectionGroups: groupsFromFlatTeeth(teeth, result.toothSelectionGroups),
    prosthesis: result.prosthesis ? recheckProsthesis(result.prosthesis, teeth) : null,
  };
}

/** Teeth that are part of a bridge, for the read-only chart's connection marks. */
export function connectedTeethFromResult(result: VoiceExtractionResult): Set<FdiToothId> {
  const connected = new Set<FdiToothId>();
  for (const group of result.toothSelectionGroups) {
    if (group.kind !== 'connected') continue;
    for (const tooth of group.teeth) connected.add(tooth);
  }
  return connected;
}

/** A recording that produced nothing should say so, not show an empty form of checkboxes. */
export function hasAnythingToApply(result: VoiceExtractionResult): boolean {
  return Object.values(voiceRowAvailability(result)).some(Boolean);
}
