'use client';

import { useMemo, useState } from 'react';
import { useTranslations } from 'next-intl';
import { AlertTriangle } from 'lucide-react';
import { Button } from '@/components/ui/shared/Button';
import { Checkbox } from '@/components/ui/shared/Checkbox';
import {
  ResponsiveDialogOverlay,
  ResponsiveDialogPanel,
} from '@/components/ui/shared/ResponsiveDialog';
import { FdiToothChart } from '@/components/ui/treatment/FdiToothChart';
import {
  connectedTeethFromResult,
  countSelected,
  hasAnythingToApply,
  initialVoiceSelection,
  voiceRowAvailability,
  withChosenTeeth,
} from '@/components/treatment/voiceReviewRows';
import { useLocale } from 'next-intl';
import { useAppFormatters } from '@/lib/hooks/useAppFormatters';
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
import type { ProsthesisCatalogEntry } from '@/types/treatment-catalog';
import type { FdiToothId, LinkedOrganizationOption } from '@/types/treatment';
import type { VoiceApplySelection, VoiceExtractionResult } from '@/types/voice';

interface VoiceReviewSheetProps {
  result: VoiceExtractionResult;
  treatmentCatalog: TreatmentCatalogEntry[];
  prosthesisCatalog: ProsthesisCatalogEntry[];
  labs: LinkedOrganizationOption[];
  /** The result is handed back because the sheet may have added teeth the model missed. */
  onApply: (selection: VoiceApplySelection, result: VoiceExtractionResult) => void;
  onDiscard: () => void;
}

/**
 * Confirmation step between the model's output and the form.
 *
 * Modal on desktop, bottom sheet on mobile — an overlay and not a route, because navigating
 * would unmount TreatmentWorkspace and destroy the in-progress draft.
 */
export function VoiceReviewSheet({
  result,
  treatmentCatalog,
  prosthesisCatalog,
  labs,
  onApply,
  onDiscard,
}: VoiceReviewSheetProps) {
  const t = useTranslations('treatment');
  const locale = useLocale();
  const { formatDate } = useAppFormatters();
  const [selection, setSelection] = useState<VoiceApplySelection>(() =>
    initialVoiceSelection(result),
  );
  const [chosen, setChosen] = useState<FdiToothId[]>([]);

  // Everything below renders from `effective`, never from `result` — a tooth picked from
  // the candidate chips has to reach the rows, the chart and the apply count alike.
  const effective = useMemo(() => withChosenTeeth(result, chosen), [result, chosen]);

  const available = useMemo(() => voiceRowAvailability(effective), [effective]);
  const connectedTeeth = useMemo(() => connectedTeethFromResult(effective), [effective]);
  const selectedTeeth = useMemo(() => new Set(effective.teeth), [effective.teeth]);
  const nothingToApply = !hasAnythingToApply(effective);
  const selectedCount = countSelected(selection, available);

  const pickCandidate = (tooth: FdiToothId) => {
    const nextChosen = chosen.includes(tooth)
      ? chosen.filter((t) => t !== tooth)
      : [...chosen, tooth];
    setChosen(nextChosen);
    setSelection((prev) => ({
      ...prev,
      // The teeth row starts unticked whenever the recording produced no teeth of its own,
      // and a picked tooth that is not ticked applies nothing.
      teeth: true,
      // A picked tooth has no prosthesis type, so the map is no longer shippable — leaving the
      // row ticked would apply a map dispatch rejects. Only ever unticks; re-ticking is the
      // clinician's call.
      prosthesis:
        prev.prosthesis &&
        withChosenTeeth(result, nextChosen).prosthesis?.complete !== false,
    }));
  };

  const labelFor = (code: string | null, catalog: { code: string; label: string }[]) =>
    catalog.find((entry) => entry.code === code)?.label ?? code ?? '';

  const toggle = (key: keyof VoiceApplySelection) => (checked: boolean) =>
    setSelection((prev) => ({ ...prev, [key]: checked }));

  return (
    <ResponsiveDialogOverlay onBackdropClick={onDiscard}>
      <ResponsiveDialogPanel
        role="dialog"
        aria-modal="true"
        aria-labelledby="voice-review-title"
        maxWidthClass="sm:max-w-xl"
      >
        <h2 id="voice-review-title" className="text-base font-semibold text-text-primary">
          {t('voiceReviewTitle')}
        </h2>

        <p className="mt-2 rounded-[var(--radius-md)] bg-background-card/60 px-3 py-2 text-sm text-text-secondary">
          {effective.transcript}
        </p>

        {nothingToApply ? (
          <p className="mt-4 text-sm text-text-secondary">{t('voiceNothingExtracted')}</p>
        ) : (
          <div className="mt-4 space-y-3">
            {available.treatmentType ? (
              <Row
                label={t('treatmentType')}
                checked={selection.treatmentType}
                onChange={toggle('treatmentType')}
              >
                <span className="text-sm text-text-primary">
                  {labelFor(effective.treatmentType, treatmentCatalog)}
                </span>
              </Row>
            ) : null}

            {available.teeth ? (
              <Row label={t('entryStepTeeth')} checked={selection.teeth} onChange={toggle('teeth')}>
                <div className="mt-1">
                  <FdiToothChart
                    readOnly
                    compact
                    scale={0.55}
                    selected={selectedTeeth}
                    connectedTeeth={connectedTeeth}
                  />
                </div>
              </Row>
            ) : null}

            {available.comment ? (
              <Row
                label={t('comments')}
                checked={selection.comment}
                onChange={toggle('comment')}
              >
                <span className="text-sm whitespace-pre-wrap text-text-primary">
                  {effective.comment}
                </span>
              </Row>
            ) : null}

            {available.prosthesis && effective.prosthesis ? (
              <Row
                label={t('prosthesisColType')}
                checked={selection.prosthesis}
                onChange={toggle('prosthesis')}
                warning={
                  effective.prosthesis.complete
                    ? undefined
                    : t('voiceProsthesisIncomplete', {
                        teeth: formatToothList(effective.prosthesis.missingTeeth, locale),
                      })
                }
              >
                <span className="text-sm text-text-primary">
                  {Object.entries(effective.prosthesis.byTooth)
                    .map(
                      ([tooth, code]) => `${tooth}: ${labelFor(code, prosthesisCatalog)}`,
                    )
                    .join(' · ')}
                </span>
              </Row>
            ) : null}

            {available.lab ? (
              <Row
                label={t('entryStepLab')}
                checked={selection.lab}
                onChange={toggle('lab')}
                warning={effective.labMatchExact ? undefined : t('voiceLabInexact')}
              >
                <span className="text-sm text-text-primary">
                  {labs.find((lab) => lab.id === effective.labId)?.name ?? effective.labId}
                </span>
              </Row>
            ) : null}

            {available.dueDate && effective.dueDate ? (
              <Row
                label={t('dueDateLabel')}
                checked={selection.dueDate}
                onChange={toggle('dueDate')}
              >
                <span className="text-sm text-text-primary">
                  {formatDate(civilDateToLocalDate(effective.dueDate))}
                </span>
              </Row>
            ) : null}
          </div>
        )}

        {effective.unresolved.length > 0 ? (
          <div className="mt-4 rounded-[var(--radius-md)] border border-amber-500/40 bg-amber-500/10 px-3 py-2">
            <p className="text-xs font-medium text-amber-700 dark:text-amber-400">
              {t('voiceNotUnderstood')}
            </p>
            <ul className="mt-1 space-y-0.5">
              {effective.unresolved.map((item, index) => (
                <li key={`${item.spoken}-${index}`} className="text-xs text-text-secondary">
                  {item.spoken ? `“${item.spoken}” — ` : ''}
                  {t(`voiceUnresolved.${item.reason}`)}
                  {item.candidates && item.candidates.length > 0 ? (
                    <span className="mt-1 flex flex-wrap items-center gap-1">
                      <span className="text-text-muted">{t('voicePickTooth')}</span>
                      {item.candidates.map((tooth) => {
                        const picked = chosen.includes(tooth as FdiToothId);
                        return (
                          <button
                            key={tooth}
                            type="button"
                            aria-pressed={picked}
                            aria-label={t('toothAria', { fdi: tooth })}
                            onClick={() => pickCandidate(tooth as FdiToothId)}
                            className={`rounded-full border px-2 py-0.5 text-xs transition-colors ${
                              picked
                                ? 'border-transparent bg-primary text-white'
                                : 'border-border text-text-primary hover:border-border-strong'
                            }`}
                          >
                            {tooth}
                          </button>
                        );
                      })}
                    </span>
                  ) : null}
                </li>
              ))}
            </ul>
          </div>
        ) : null}

        <div className="mt-5 flex flex-col gap-2 sm:flex-row sm:justify-end">
          <Button type="button" variant="secondary" onClick={onDiscard} fullWidth className="sm:w-auto">
            {t('voiceDiscard')}
          </Button>
          <Button
            type="button"
            variant="primary"
            disabled={selectedCount === 0}
            onClick={() => onApply(selection, effective)}
            fullWidth
            className="sm:w-auto"
          >
            {t('voiceApply', { count: selectedCount })}
          </Button>
        </div>
      </ResponsiveDialogPanel>
    </ResponsiveDialogOverlay>
  );
}

function Row({
  label,
  checked,
  onChange,
  warning,
  children,
}: {
  label: string;
  checked: boolean;
  onChange: (checked: boolean) => void;
  warning?: string;
  children: React.ReactNode;
}) {
  return (
    <div className="rounded-[var(--radius-md)] border border-border/70 px-3 py-2">
      <Checkbox checked={checked} onChange={onChange} label={label} />
      <div className="mt-1 ps-7 min-w-0">{children}</div>
      {warning ? (
        <p className="mt-1 ps-7 flex items-start gap-1 text-xs text-amber-700 dark:text-amber-400">
          <AlertTriangle className="mt-0.5 h-3 w-3 shrink-0" aria-hidden />
          {warning}
        </p>
      ) : null}
    </div>
  );
}

/**
 * `new Date('2025-10-17')` parses a civil date as UTC midnight, which renders as the 16th
 * west of Greenwich. Build it from its parts so it means the same day everywhere.
 */
function civilDateToLocalDate(iso: string): Date {
  const [year, month, day] = iso.split('-').map(Number);
  return new Date(year, (month ?? 1) - 1, day ?? 1);
}

/** Locale-aware list separator — the Arabic comma is not correct in en or nl. */
function formatToothList(teeth: readonly string[], locale: string): string {
  try {
    return new Intl.ListFormat(locale, { style: 'short', type: 'unit' }).format([...teeth]);
  } catch {
    return teeth.join(', ');
  }
}
