import type { Arch, PatientSide } from '../../common/fdi';

/**
 * What the extraction model is allowed to return.
 *
 * The model emits *intents*, never resolved values: no FDI codes, no ISO dates. Pure,
 * unit-tested resolvers turn intents into domain values, so the two highest-consequence
 * mappings — quadrant mirroring and Jalali conversion — are testable rather than hopeful.
 */

/** A single spoken tooth reference. `spoken` is the transcript span, echoed back to the user. */
export type ToothIntent =
  | { kind: 'explicit'; fdi: string; spoken: string }
  | {
      kind: 'positional';
      arch: Arch;
      side: PatientSide;
      /** 1 = central incisor … 8 = third molar. */
      position: number;
      spoken: string;
    };

/** A spoken deadline. The model never does calendar arithmetic. */
export type DueIntent =
  | { kind: 'weekday'; weekday: Weekday; which: 'this' | 'next' }
  | { kind: 'offset'; unit: 'day' | 'week' | 'month'; amount: number }
  | { kind: 'jalali'; jy: number; jm: number; jd: number }
  | { kind: 'gregorian'; y: number; m: number; d: number };

export const WEEKDAYS = [
  'saturday',
  'sunday',
  'monday',
  'tuesday',
  'wednesday',
  'thursday',
  'friday',
] as const;

export type Weekday = (typeof WEEKDAYS)[number];

/** Two teeth defining an inclusive connected (bridge) span. */
export type ConnectedSpanIntent = { from: ToothIntent; to: ToothIntent };

export type ProsthesisIntent = {
  /** Catalog code applied to every tooth unless overridden. */
  defaultType: string | null;
  overrides: { tooth: ToothIntent; type: string }[];
};

export type VoiceIntent = {
  treatmentType: string | null;
  teeth: ToothIntent[];
  connectedSpans: ConnectedSpanIntent[];
  comment: string | null;
  prosthesis: ProsthesisIntent | null;
  /** Must be one of the linked-lab ids supplied in the prompt, or null. */
  labId: string | null;
  /** False when the spoken name only approximately matched — the UI then requires an explicit tick. */
  labMatchExact: boolean;
  due: DueIntent | null;
};

/** Why a spoken item could not be turned into a domain value. Shown to the user. */
export type UnresolvedReason =
  | 'not_permanent_tooth'
  | 'position_out_of_range'
  /** A position was understood but no quadrant was spoken — four teeth match. */
  | 'tooth_missing_quadrant'
  | 'malformed'
  | 'span_not_same_arch'
  | 'unknown_catalog_code'
  | 'tooth_not_selected'
  | 'invalid_date';

export type UnresolvedItem = {
  /** The transcript span that could not be resolved, so the user can see what was heard. */
  spoken: string;
  reason: UnresolvedReason;
  /**
   * FDI codes still consistent with what was heard — "دو" leaves four, "دو بالا" two. Only
   * `tooth_missing_quadrant` carries them; the sheet offers them as chips.
   */
  candidates?: string[];
};
