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,
  };
}

/**
 * Which rows start ticked.
 *
 * Everything available ticks itself, with two deliberate exceptions:
 *
 * - **lab, when the name only approximately matched.** Shipping a case to a lab is the one
 *   extracted value whose error leaves the building, so it always requires a deliberate tick.
 * - **prosthesis, when the map is incomplete.** A prosthesis detail with an untyped tooth
 *   cannot ship at all, so applying it 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,
  };
}

/**
 * How many rows will actually be applied — drives the confirm button's label.
 *
 * Intersected with availability rather than counting ticks: a row can be ticked and then
 * lose its content (the last candidate tooth un-picked), 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 clinician's candidate picks into the extracted result.
 *
 * Everything downstream reads a `VoiceExtractionResult` — row availability, the mini
 * chart, the prosthesis warning, `applyVoiceResult` — so resolving the picks into one here
 * means none of them has to know the chips exist.
 *
 * Union rather than toggle, for two reasons: a candidate can coincidentally be a tooth the
 * recording already produced ("۱۲ و دو"), where tapping it must not deselect that tooth;
 * and `groupsFromFlatTeeth` keeps the bridges intact while giving every remaining tooth a
 * single group, so no tooth can be lost on the way through.
 */
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;
}

/**
 * Whether the sheet has anything worth showing.
 *
 * A recording that produced nothing usable should say so plainly rather than present an
 * empty form of checkboxes.
 */
export function hasAnythingToApply(result: VoiceExtractionResult): boolean {
  return Object.values(voiceRowAvailability(result)).some(Boolean);
}
