'use client';
import { FunctionComponent, ReactNode, useEffect, useState } from 'react';
import { useLocale, useTranslations } from 'next-intl';
import { useParams, useRouter } from 'next/navigation';
import { useQueryClient } from '@tanstack/react-query';
import { useSnackbar } from 'notistack';
import {
  Box,
  Checkbox,
  Dialog,
  Drawer,
  FormControlLabel,
  MenuItem,
  Paper,
  Skeleton,
  Stack,
  Tab,
  Tabs,
  TextField,
  ToggleButton,
  ToggleButtonGroup,
  Typography,
  useMediaQuery,
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { AppButton, AppIcon, ConfirmDialog, EmptyState, PatientHeader, VisitNoteCard } from '@/components';
import { ROUTES, bookingDetailPath } from '@/constants';
import { formatShamsiDate, formatShamsiMonthYear } from '@/utils';
import { bookingKeys } from '@/services/bookings/keys';
import { usePatient } from '@/services/patients';
import { birthDateToAge } from '@/services/patients/age';
import { useRecordAccess, usePatientCareRecord, usePatientHistory, useUpdateCareRecord } from '@/services/patientRecords';
import { CARE_RECORD_TABS, DOSE_UNITS, FREQUENCY_PRESETS, TIME_OF_DAY_CODES } from '@/services/patientRecords/types';
import type {
  CareRecordTab,
  CareTask,
  DoseUnit,
  FamilyCareRecord,
  FrequencyPreset,
  Medication,
  RoutineItem,
  TimeOfDayCode,
  VisitNote,
} from '@/services/patientRecords/types';

/**
 * E2 — Patient care-record viewer (پروندهٔ مراقبت). The **family-owned, patient-scoped** record with four
 * tabs: داروها / روتین / سوابق / وظایف. The customer owns and edits medications/routine/tasks via per-item
 * bottom sheets (never a whole-list edit mode — switching tabs mid-edit loses nothing by construction); the
 * **سوابق** (nurse visit notes) are read-only to everyone, grouped into a Shamsi month-by-month timeline. A
 * persistent ownership banner states the record belongs to the family. The clinical-access check gates the
 * whole screen (a `403` → a clear, non-leaking access-denied card — never partial clinical data).
 */
export default function PatientRecordPage() {
  const t = useTranslations('records');
  // Shared enum labels are reused from the onboarding/patients namespaces — never re-keyed.
  const to = useTranslations('onboarding');
  const tp = useTranslations('patients');

  const params = useParams<{ id: string }>();
  const rawId = Number(params.id);
  const patientId = Number.isInteger(rawId) && rawId > 0 ? rawId : -1;

  const access = useRecordAccess(patientId);
  const canView = access.data?.canView ?? false;
  const patient = usePatient(patientId, { enabled: canView });

  const [tab, setTab] = useState<CareRecordTab>('medications');

  if (access.isLoading) return <RecordSkeleton />;

  // Access-denied — a clear, non-leaking card (never any clinical data).
  if (access.data && !access.data.canView) {
    return (
      <Stack sx={{ gap: 3, maxWidth: 640, mx: 'auto', width: '100%' }}>
        <EmptyState icon="lock" title={t('access_denied_title')} body={t('access_denied_body')} />
        <BackToPatients />
      </Stack>
    );
  }

  if (patient.isLoading) return <RecordSkeleton />;

  if (patient.isError || !patient.data) {
    return (
      <Stack sx={{ gap: 3, maxWidth: 640, mx: 'auto', width: '100%' }}>
        <EmptyState title={t('not_found_title')} body={t('not_found_body')} />
        <BackToPatients />
      </Stack>
    );
  }

  const p = patient.data;
  const age = birthDateToAge(p.birthDate);

  return (
    <Stack sx={{ gap: 3, maxWidth: 640, mx: 'auto', width: '100%' }}>
      <PatientHeader
        displayName={p.displayName}
        relationLabel={p.relation ? to(`relation_${p.relation}`) : undefined}
        genderLabel={to(`gender_${p.gender}`)}
        ageLabel={age == null ? undefined : tp('age_years', { age })}
        conditionLabels={p.conditions.map((code) => to(`condition_${code}`))}
        noConditionsLabel={tp('conditions_none')}
      />

      <Paper
        elevation={0}
        sx={{ p: 2, borderRadius: 2, backgroundColor: 'var(--bal-primary-soft)', display: 'flex', gap: 1, alignItems: 'center' }}
      >
        <AppIcon icon="family" size={22} color="var(--bal-primary)" />
        <Typography variant="body2" sx={{ color: 'var(--bal-primary)', fontWeight: 500 }}>
          {t('ownership_banner')}
        </Typography>
      </Paper>

      <Tabs
        value={tab}
        onChange={(_, next: CareRecordTab) => setTab(next)}
        variant="scrollable"
        scrollButtons="auto"
        allowScrollButtonsMobile
        sx={{ borderBottom: 1, borderColor: 'divider' }}
      >
        {CARE_RECORD_TABS.map((value) => (
          <Tab key={value} value={value} label={t(`tab_${value}`)} sx={{ textTransform: 'none', fontWeight: 700 }} />
        ))}
      </Tabs>

      {tab === 'history' ? (
        <HistoryTab patientId={patientId} />
      ) : (
        <EditableTabs patientId={patientId} tab={tab} canEdit={access.data?.canEdit ?? false} />
      )}
    </Stack>
  );
}

/** The three customer-editable tabs (medications/routine/tasks) share one record read + one save mutation. */
function EditableTabs({ patientId, tab, canEdit }: { patientId: number; tab: CareRecordTab; canEdit: boolean }) {
  const t = useTranslations('records');
  const { enqueueSnackbar } = useSnackbar();
  const record = usePatientCareRecord(patientId);
  const update = useUpdateCareRecord(patientId);

  if (record.isLoading) {
    return (
      <Stack sx={{ gap: 1.5 }}>
        <Skeleton variant="rounded" height={72} />
        <Skeleton variant="rounded" height={72} />
      </Stack>
    );
  }
  if (record.isError || !record.data) {
    return (
      <Typography variant="body2" sx={{ color: 'text.secondary' }}>
        {t('load_error')}
      </Typography>
    );
  }

  const data = record.data;
  const save = (patch: Partial<Pick<FamilyCareRecord, 'medications' | 'routine' | 'tasks'>>, onDone: () => void) => {
    update.mutate(patch, {
      onSuccess: () => {
        enqueueSnackbar(t('saved'), { variant: 'success' });
        onDone();
      },
      onError: () => enqueueSnackbar(t('save_error'), { variant: 'error' }),
    });
  };

  if (tab === 'medications') {
    return (
      <MedicationsTab
        data={data.medications}
        canEdit={canEdit}
        saving={update.isPending}
        onSave={(meds, done) => save({ medications: meds }, done)}
      />
    );
  }
  if (tab === 'routine') {
    return (
      <RoutineTab data={data.routine} canEdit={canEdit} saving={update.isPending} onSave={(items, done) => save({ routine: items }, done)} />
    );
  }
  return <TasksTab data={data.tasks} canEdit={canEdit} saving={update.isPending} onSave={(tasks, done) => save({ tasks }, done)} />;
}

// ── The responsive per-item sheet: Drawer(bottom) on mobile, Dialog on desktop; dirty-gated close ─────────
function RecordItemSheet({
  open,
  title,
  dirty,
  onClose,
  children,
}: {
  open: boolean;
  title: string;
  dirty: boolean;
  onClose: () => void;
  children: ReactNode;
}) {
  const t = useTranslations('common');
  const theme = useTheme();
  const mobile = useMediaQuery(theme.breakpoints.down('sm'));
  const [discardOpen, setDiscardOpen] = useState(false);

  const requestClose = () => {
    if (dirty) setDiscardOpen(true);
    else onClose();
  };

  const body = (
    <Box sx={{ p: 2.5, display: 'flex', flexDirection: 'column', gap: 2 }}>
      <Stack direction="row" sx={{ alignItems: 'center' }}>
        <Typography variant="subtitle1" sx={{ fontWeight: 700, flexGrow: 1 }}>
          {title}
        </Typography>
        <AppButton variant="text" color="inherit" onClick={requestClose} aria-label={t('close')} sx={{ minWidth: 0, p: 1 }}>
          <AppIcon icon="close" size={20} />
        </AppButton>
      </Stack>
      {children}
    </Box>
  );

  return (
    <>
      {mobile ? (
        <Drawer
          anchor="bottom"
          open={open}
          onClose={requestClose}
          slotProps={{ paper: { sx: { borderTopLeftRadius: 'var(--bal-radius-lg)', borderTopRightRadius: 'var(--bal-radius-lg)', maxHeight: '88vh' } } }}
        >
          {body}
        </Drawer>
      ) : (
        <Dialog open={open} onClose={requestClose} fullWidth maxWidth="xs">
          {body}
        </Dialog>
      )}
      <ConfirmDialog
        open={discardOpen}
        title={t('discard_title')}
        body={t('discard_body')}
        confirmLabel={t('discard_confirm')}
        cancelLabel={t('cancel')}
        confirmColor="error"
        onConfirm={() => {
          setDiscardOpen(false);
          onClose();
        }}
        onClose={() => setDiscardOpen(false)}
      />
    </>
  );
}

function SheetActions({
  onCancel,
  onSave,
  onDelete,
  saving,
  canSave,
}: {
  onCancel: () => void;
  onSave: () => void;
  onDelete?: () => void;
  saving: boolean;
  canSave: boolean;
}) {
  const t = useTranslations('records');
  const tc = useTranslations('common');
  return (
    <Stack direction="row" sx={{ gap: 1, justifyContent: onDelete ? 'space-between' : 'flex-end', alignItems: 'center' }}>
      {onDelete ? (
        <AppButton variant="text" color="error" onClick={onDelete} disabled={saving}>
          {t('remove')}
        </AppButton>
      ) : null}
      <Stack direction="row" sx={{ gap: 1 }}>
        <AppButton variant="text" onClick={onCancel} disabled={saving}>
          {tc('cancel')}
        </AppButton>
        <AppButton variant="contained" color="primary" onClick={onSave} disabled={saving || !canSave}>
          {saving ? tc('saving') : tc('save')}
        </AppButton>
      </Stack>
    </Stack>
  );
}

// A tappable row surface shared by the three editable tabs — mirrors PatientCard's press affordance.
function RowCard({ onOpen, children }: { onOpen?: () => void; children: ReactNode }) {
  return (
    <Paper elevation={0} sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 2, overflow: 'hidden' }}>
      {onOpen ? (
        <AppButton
          variant="text"
          color="inherit"
          onClick={onOpen}
          sx={{ width: '100%', p: 1.5, justifyContent: 'flex-start', textAlign: 'start', borderRadius: 0, '&:hover': { bgcolor: 'action.hover' } }}
        >
          <Stack direction="row" sx={{ alignItems: 'center', gap: 1.5, width: '100%' }}>
            <Box sx={{ flexGrow: 1, minWidth: 0 }}>{children}</Box>
            <AppIcon icon="forward" size={18} color="var(--bal-text-secondary)" />
          </Stack>
        </AppButton>
      ) : (
        <Box sx={{ p: 1.5 }}>{children}</Box>
      )}
    </Paper>
  );
}

function TimeOfDayChipRow({
  value,
  onChange,
  disabled,
}: {
  value: TimeOfDayCode[];
  onChange: (next: TimeOfDayCode[]) => void;
  disabled?: boolean;
}) {
  const t = useTranslations('records');
  return (
    <ToggleButtonGroup
      value={value}
      onChange={(_, next: TimeOfDayCode[]) => onChange(next)}
      disabled={disabled}
      sx={{ flexWrap: 'wrap', gap: 1, '& .MuiToggleButtonGroup-grouped': { border: '1px solid', borderColor: 'divider !important', borderRadius: '999px !important', mx: 0 } }}
    >
      {TIME_OF_DAY_CODES.map((code) => (
        <ToggleButton key={code} value={code} size="small" sx={{ textTransform: 'none', px: 1.5, borderRadius: '999px' }}>
          {t(`time_${code}`)}
        </ToggleButton>
      ))}
    </ToggleButtonGroup>
  );
}

// ── Medications ───────────────────────────────────────────────────────────────────────────────────────────
function medicationSummary(m: Medication, t: ReturnType<typeof useTranslations>): string {
  const dose = m.doseAmount ? `${m.doseAmount} ${m.doseUnit ? t(`dose_unit_${m.doseUnit}`) : ''}`.trim() : null;
  const frequency = m.frequencyCode ? t(`frequency_${m.frequencyCode}`) : m.frequencyText;
  return [dose, frequency].filter(Boolean).join(' · ');
}

function MedicationsTab({
  data,
  canEdit,
  saving,
  onSave,
}: {
  data: Medication[];
  canEdit: boolean;
  saving: boolean;
  onSave: (medications: Medication[], onDone: () => void) => void;
}) {
  const t = useTranslations('records');
  const [sheetItem, setSheetItem] = useState<Medication | 'new' | null>(null);
  const [sheetDirty, setSheetDirty] = useState(false);
  const close = () => setSheetItem(null);

  const handleSave = (item: Medication) => {
    const next = sheetItem === 'new' ? [...data, item] : data.map((m) => (m.id === item.id ? item : m));
    onSave(next, close);
  };
  const handleDelete = (id: string) => onSave(data.filter((m) => m.id !== id), close);

  return (
    <Stack sx={{ gap: 1.5 }}>
      {data.length === 0 ? (
        <Typography variant="body2" sx={{ color: 'text.secondary' }}>
          {t('medications_empty')}
        </Typography>
      ) : (
        <Stack sx={{ gap: 1 }}>
          {data.map((m) => (
            <RowCard key={m.id} onOpen={canEdit ? () => setSheetItem(m) : undefined}>
              <Stack direction="row" sx={{ alignItems: 'center', gap: 1 }}>
                <AppIcon icon="medication" size={20} color="var(--bal-primary)" />
                <Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
                  {m.name}
                </Typography>
              </Stack>
              {medicationSummary(m, t) ? (
                <Typography variant="body2" sx={{ color: 'text.secondary', mt: 0.5 }}>
                  {medicationSummary(m, t)}
                </Typography>
              ) : null}
              {m.timeOfDay.length > 0 ? (
                <Stack direction="row" sx={{ gap: 0.5, mt: 0.5, flexWrap: 'wrap' }}>
                  {m.timeOfDay.map((code) => (
                    <Typography
                      key={code}
                      variant="caption"
                      sx={{ px: 1, py: 0.25, borderRadius: '999px', bgcolor: 'var(--bal-primary-soft)', color: 'var(--bal-primary)' }}
                    >
                      {t(`time_${code}`)}
                    </Typography>
                  ))}
                </Stack>
              ) : null}
            </RowCard>
          ))}
        </Stack>
      )}

      {canEdit ? (
        <AppButton variant="outlined" color="primary" startIcon="add" onClick={() => setSheetItem('new')} sx={{ alignSelf: 'flex-start' }}>
          {t('add_medication')}
        </AppButton>
      ) : null}

      <RecordItemSheet open={sheetItem != null} title={sheetItem === 'new' ? t('add_medication') : t('edit_medication')} dirty={sheetDirty} onClose={close}>
        {sheetItem != null ? (
          <MedicationSheetBody
            initial={sheetItem === 'new' ? null : sheetItem}
            saving={saving}
            onCancel={close}
            onSave={handleSave}
            onDelete={sheetItem !== 'new' ? () => handleDelete((sheetItem as Medication).id) : undefined}
            onDirtyChange={setSheetDirty}
          />
        ) : null}
      </RecordItemSheet>
    </Stack>
  );
}

function MedicationSheetBody({
  initial,
  saving,
  onCancel,
  onSave,
  onDelete,
  onDirtyChange,
}: {
  initial: Medication | null;
  saving: boolean;
  onCancel: () => void;
  onSave: (item: Medication) => void;
  onDelete?: () => void;
  onDirtyChange: (dirty: boolean) => void;
}) {
  const t = useTranslations('records');
  const [name, setName] = useState(initial?.name ?? '');
  const [doseAmount, setDoseAmount] = useState(initial?.doseAmount ?? '');
  const [doseUnit, setDoseUnit] = useState<DoseUnit | ''>(initial?.doseUnit ?? '');
  const [frequencyCode, setFrequencyCode] = useState<FrequencyPreset | null>(initial?.frequencyCode ?? null);
  const [frequencyText, setFrequencyText] = useState(initial?.frequencyText ?? '');
  const [timeOfDay, setTimeOfDay] = useState<TimeOfDayCode[]>(initial?.timeOfDay ?? []);
  const [timingNote, setTimingNote] = useState(initial?.timingNote ?? '');

  useEffect(() => {
    const dirty =
      name !== (initial?.name ?? '') ||
      doseAmount !== (initial?.doseAmount ?? '') ||
      doseUnit !== (initial?.doseUnit ?? '') ||
      frequencyCode !== (initial?.frequencyCode ?? null) ||
      frequencyText !== (initial?.frequencyText ?? '') ||
      timingNote !== (initial?.timingNote ?? '') ||
      timeOfDay.length !== (initial?.timeOfDay ?? []).length ||
      timeOfDay.some((code) => !(initial?.timeOfDay ?? []).includes(code));
    onDirtyChange(dirty);
    // eslint-disable-next-line react-hooks/exhaustive-deps -- `initial` is a fresh-mount snapshot (the sheet remounts per open), not reactive state.
  }, [name, doseAmount, doseUnit, frequencyCode, frequencyText, timeOfDay, timingNote]);

  const canSave = name.trim().length > 0;

  const handleSave = () => {
    onSave({
      id: initial?.id ?? `new-${Date.now()}`,
      name: name.trim(),
      doseAmount: doseAmount.trim() || null,
      doseUnit: doseUnit || null,
      frequencyCode,
      frequencyText: frequencyCode ? null : frequencyText.trim() || null,
      timeOfDay,
      timingNote: timingNote.trim() || null,
    });
  };

  return (
    <Stack sx={{ gap: 2 }}>
      <TextField label={t('med_name')} value={name} onChange={(e) => setName(e.target.value)} fullWidth required autoFocus />

      <Stack direction="row" sx={{ gap: 1.5 }}>
        <TextField
          label={t('med_dose_amount')}
          value={doseAmount}
          onChange={(e) => setDoseAmount(e.target.value)}
          sx={{ flex: 1 }}
        />
        <TextField select label={t('med_dose_unit')} value={doseUnit} onChange={(e) => setDoseUnit(e.target.value as DoseUnit)} sx={{ flex: 1 }}>
          <MenuItem value="">{t('med_dose_unit_none')}</MenuItem>
          {DOSE_UNITS.map((unit) => (
            <MenuItem key={unit} value={unit}>
              {t(`dose_unit_${unit}`)}
            </MenuItem>
          ))}
        </TextField>
      </Stack>

      <Stack sx={{ gap: 1 }}>
        <Typography variant="caption" sx={{ color: 'text.secondary' }}>
          {t('med_frequency')}
        </Typography>
        <Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
          {FREQUENCY_PRESETS.map((preset) => (
            <AppButton
              key={preset}
              variant={frequencyCode === preset ? 'contained' : 'outlined'}
              color="primary"
              size="small"
              onClick={() => {
                setFrequencyCode(frequencyCode === preset ? null : preset);
                if (frequencyCode !== preset) setFrequencyText('');
              }}
              sx={{ borderRadius: '999px' }}
            >
              {t(`frequency_${preset}`)}
            </AppButton>
          ))}
        </Stack>
        {!frequencyCode ? (
          <TextField
            label={t('med_frequency_text')}
            value={frequencyText}
            onChange={(e) => setFrequencyText(e.target.value)}
            fullWidth
            size="small"
          />
        ) : null}
      </Stack>

      <Stack sx={{ gap: 1 }}>
        <Typography variant="caption" sx={{ color: 'text.secondary' }}>
          {t('time_of_day')}
        </Typography>
        <TimeOfDayChipRow value={timeOfDay} onChange={setTimeOfDay} />
      </Stack>

      <TextField label={t('med_timing')} value={timingNote} onChange={(e) => setTimingNote(e.target.value)} fullWidth size="small" />

      <SheetActions onCancel={onCancel} onSave={handleSave} onDelete={onDelete} saving={saving} canSave={canSave} />
    </Stack>
  );
}

// ── Routine ───────────────────────────────────────────────────────────────────────────────────────────────
function RoutineTab({
  data,
  canEdit,
  saving,
  onSave,
}: {
  data: RoutineItem[];
  canEdit: boolean;
  saving: boolean;
  onSave: (routine: RoutineItem[], onDone: () => void) => void;
}) {
  const t = useTranslations('records');
  const [sheetItem, setSheetItem] = useState<RoutineItem | 'new' | null>(null);
  const [sheetDirty, setSheetDirty] = useState(false);
  const close = () => setSheetItem(null);

  const handleSave = (item: RoutineItem) => {
    const next = sheetItem === 'new' ? [...data, item] : data.map((r) => (r.id === item.id ? item : r));
    onSave(next, close);
  };
  const handleDelete = (id: string) => onSave(data.filter((r) => r.id !== id), close);

  return (
    <Stack sx={{ gap: 1.5 }}>
      {data.length === 0 ? (
        <Typography variant="body2" sx={{ color: 'text.secondary' }}>
          {t('routine_empty')}
        </Typography>
      ) : (
        <Stack sx={{ gap: 1 }}>
          {data.map((r) => (
            <RowCard key={r.id} onOpen={canEdit ? () => setSheetItem(r) : undefined}>
              <Stack direction="row" sx={{ alignItems: 'center', gap: 1 }}>
                <AppIcon icon="routine" size={20} color="var(--bal-primary)" />
                <Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
                  {r.label}
                </Typography>
              </Stack>
              {r.timeOfDay.length > 0 ? (
                <Stack direction="row" sx={{ gap: 0.5, mt: 0.5, flexWrap: 'wrap' }}>
                  {r.timeOfDay.map((code) => (
                    <Typography
                      key={code}
                      variant="caption"
                      sx={{ px: 1, py: 0.25, borderRadius: '999px', bgcolor: 'var(--bal-primary-soft)', color: 'var(--bal-primary)' }}
                    >
                      {t(`time_${code}`)}
                    </Typography>
                  ))}
                </Stack>
              ) : null}
              {r.note ? (
                <Typography variant="body2" sx={{ color: 'text.secondary', mt: 0.5 }}>
                  {r.note}
                </Typography>
              ) : null}
            </RowCard>
          ))}
        </Stack>
      )}

      {canEdit ? (
        <AppButton variant="outlined" color="primary" startIcon="add" onClick={() => setSheetItem('new')} sx={{ alignSelf: 'flex-start' }}>
          {t('add_routine')}
        </AppButton>
      ) : null}

      <RecordItemSheet open={sheetItem != null} title={sheetItem === 'new' ? t('add_routine') : t('edit_routine')} dirty={sheetDirty} onClose={close}>
        {sheetItem != null ? (
          <RoutineSheetBody
            initial={sheetItem === 'new' ? null : sheetItem}
            saving={saving}
            onCancel={close}
            onSave={handleSave}
            onDelete={sheetItem !== 'new' ? () => handleDelete((sheetItem as RoutineItem).id) : undefined}
            onDirtyChange={setSheetDirty}
          />
        ) : null}
      </RecordItemSheet>
    </Stack>
  );
}

function RoutineSheetBody({
  initial,
  saving,
  onCancel,
  onSave,
  onDelete,
  onDirtyChange,
}: {
  initial: RoutineItem | null;
  saving: boolean;
  onCancel: () => void;
  onSave: (item: RoutineItem) => void;
  onDelete?: () => void;
  onDirtyChange: (dirty: boolean) => void;
}) {
  const t = useTranslations('records');
  const [label, setLabel] = useState(initial?.label ?? '');
  const [timeOfDay, setTimeOfDay] = useState<TimeOfDayCode[]>(initial?.timeOfDay ?? []);
  const [note, setNote] = useState(initial?.note ?? '');

  useEffect(() => {
    const dirty =
      label !== (initial?.label ?? '') ||
      note !== (initial?.note ?? '') ||
      timeOfDay.length !== (initial?.timeOfDay ?? []).length ||
      timeOfDay.some((code) => !(initial?.timeOfDay ?? []).includes(code));
    onDirtyChange(dirty);
    // eslint-disable-next-line react-hooks/exhaustive-deps -- `initial` is a fresh-mount snapshot (the sheet remounts per open), not reactive state.
  }, [label, note, timeOfDay]);

  const canSave = label.trim().length > 0;

  const handleSave = () =>
    onSave({
      id: initial?.id ?? `new-${Date.now()}`,
      label: label.trim(),
      timeOfDay,
      note: note.trim() || null,
    });

  return (
    <Stack sx={{ gap: 2 }}>
      <TextField label={t('routine_label')} value={label} onChange={(e) => setLabel(e.target.value)} fullWidth required autoFocus />
      <Stack sx={{ gap: 1 }}>
        <Typography variant="caption" sx={{ color: 'text.secondary' }}>
          {t('time_of_day')}
        </Typography>
        <TimeOfDayChipRow value={timeOfDay} onChange={setTimeOfDay} />
      </Stack>
      <TextField label={t('routine_note')} value={note} onChange={(e) => setNote(e.target.value)} fullWidth size="small" />
      <SheetActions onCancel={onCancel} onSave={handleSave} onDelete={onDelete} saving={saving} canSave={canSave} />
    </Stack>
  );
}

// ── Tasks ─────────────────────────────────────────────────────────────────────────────────────────────────
function TasksTab({
  data,
  canEdit,
  saving,
  onSave,
}: {
  data: CareTask[];
  canEdit: boolean;
  saving: boolean;
  onSave: (tasks: CareTask[], onDone: () => void) => void;
}) {
  const t = useTranslations('records');
  const [sheetItem, setSheetItem] = useState<CareTask | 'new' | null>(null);
  const [sheetDirty, setSheetDirty] = useState(false);
  const close = () => setSheetItem(null);

  const handleSave = (item: CareTask) => {
    const next = sheetItem === 'new' ? [...data, item] : data.map((task) => (task.id === item.id ? item : task));
    onSave(next, close);
  };
  const handleDelete = (id: string) => onSave(data.filter((task) => task.id !== id), close);
  const toggleDone = (task: CareTask) => onSave(data.map((t2) => (t2.id === task.id ? { ...t2, done: !t2.done } : t2)), () => {});

  return (
    <Stack sx={{ gap: 1.5 }}>
      {data.length === 0 ? (
        <Typography variant="body2" sx={{ color: 'text.secondary' }}>
          {t('tasks_empty')}
        </Typography>
      ) : (
        <Stack sx={{ gap: 1 }}>
          {data.map((task) => (
            <Paper key={task.id} elevation={0} sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 2, p: 1 }}>
              <Stack direction="row" sx={{ alignItems: 'center', gap: 1 }}>
                <FormControlLabel
                  control={<Checkbox checked={task.done} onChange={() => (canEdit ? toggleDone(task) : undefined)} disabled={!canEdit || saving} />}
                  label=""
                  sx={{ m: 0 }}
                  aria-label={t('task_done')}
                />
                <AppButton
                  variant="text"
                  color="inherit"
                  disabled={!canEdit}
                  onClick={() => setSheetItem(task)}
                  sx={{ flexGrow: 1, justifyContent: 'flex-start', textAlign: 'start', minWidth: 0 }}
                >
                  <Typography variant="body2" sx={{ color: task.done ? 'text.secondary' : undefined }}>
                    {task.label}
                  </Typography>
                </AppButton>
                {canEdit ? <AppIcon icon="forward" size={16} color="var(--bal-text-secondary)" /> : null}
              </Stack>
            </Paper>
          ))}
        </Stack>
      )}

      {canEdit ? (
        <AppButton variant="outlined" color="primary" startIcon="add" onClick={() => setSheetItem('new')} sx={{ alignSelf: 'flex-start' }}>
          {t('add_task')}
        </AppButton>
      ) : null}

      <RecordItemSheet open={sheetItem != null} title={sheetItem === 'new' ? t('add_task') : t('edit_task')} dirty={sheetDirty} onClose={close}>
        {sheetItem != null ? (
          <TaskSheetBody
            initial={sheetItem === 'new' ? null : sheetItem}
            saving={saving}
            onCancel={close}
            onSave={handleSave}
            onDelete={sheetItem !== 'new' ? () => handleDelete((sheetItem as CareTask).id) : undefined}
            onDirtyChange={setSheetDirty}
          />
        ) : null}
      </RecordItemSheet>
    </Stack>
  );
}

function TaskSheetBody({
  initial,
  saving,
  onCancel,
  onSave,
  onDelete,
  onDirtyChange,
}: {
  initial: CareTask | null;
  saving: boolean;
  onCancel: () => void;
  onSave: (item: CareTask) => void;
  onDelete?: () => void;
  onDirtyChange: (dirty: boolean) => void;
}) {
  const t = useTranslations('records');
  const [label, setLabel] = useState(initial?.label ?? '');
  const [done, setDone] = useState(initial?.done ?? false);

  useEffect(() => {
    onDirtyChange(label !== (initial?.label ?? '') || done !== (initial?.done ?? false));
    // eslint-disable-next-line react-hooks/exhaustive-deps -- `initial` is a fresh-mount snapshot (the sheet remounts per open), not reactive state.
  }, [label, done]);

  const canSave = label.trim().length > 0;

  return (
    <Stack sx={{ gap: 2 }}>
      <TextField label={t('task_label')} value={label} onChange={(e) => setLabel(e.target.value)} fullWidth required autoFocus />
      <FormControlLabel control={<Checkbox checked={done} onChange={(e) => setDone(e.target.checked)} />} label={t('task_done')} />
      <SheetActions
        onCancel={onCancel}
        onSave={() => onSave({ id: initial?.id ?? `new-${Date.now()}`, label: label.trim(), done })}
        onDelete={onDelete}
        saving={saving}
        canSave={canSave}
      />
    </Stack>
  );
}

// ── History (سوابق) — patient-scoped, read-only, month-grouped, paged ─────────────────────────────────────
// Best-effort read of the frozen variant display name from a cached booking snapshot — mirrors
// BookingDetailView's helper; never fetched (would be an N+1 against the booking detail per note).
function variantName(snapshotJson: string): string | null {
  try {
    const parsed = JSON.parse(snapshotJson) as { displayName?: string };
    return parsed?.displayName ?? null;
  } catch {
    return null;
  }
}

function HistoryTab({ patientId }: { patientId: number }) {
  const t = useTranslations('records');
  const locale = useLocale();
  const router = useRouter();
  const queryClient = useQueryClient();
  const [page, setPage] = useState(1);
  const history = usePatientHistory(patientId, page);

  if (history.isLoading) {
    return (
      <Stack sx={{ gap: 1.5 }}>
        <Skeleton variant="rounded" height={96} />
        <Skeleton variant="rounded" height={96} />
      </Stack>
    );
  }
  if (history.isError || !history.data) {
    return (
      <Typography variant="body2" sx={{ color: 'text.secondary' }}>
        {t('load_error')}
      </Typography>
    );
  }

  const { items, total, pageSize } = history.data;
  if (items.length === 0) {
    return (
      <Typography variant="body2" sx={{ color: 'text.secondary' }}>
        {t('history_empty')}
      </Typography>
    );
  }

  const totalPages = Math.max(1, Math.ceil(total / pageSize));

  // Group this page's notes by Shamsi month (notes already arrive newest-first from the server).
  const groups: Array<{ month: string; notes: VisitNote[] }> = [];
  for (const note of items) {
    const month = formatShamsiMonthYear(note.recordedAt, locale);
    const lastGroup = groups[groups.length - 1];
    if (lastGroup && lastGroup.month === month) lastGroup.notes.push(note);
    else groups.push({ month, notes: [note] });
  }

  return (
    <Stack sx={{ gap: 2 }}>
      {groups.map((group) => (
        <Stack key={group.month} direction="row" sx={{ gap: 1.5 }}>
          <Stack sx={{ alignItems: 'center', pt: 0.5 }}>
            <Box sx={{ width: 8, height: 8, borderRadius: '50%', bgcolor: 'var(--bal-primary)' }} />
            <Box sx={{ width: '1px', flexGrow: 1, bgcolor: 'divider', mt: 0.5 }} />
          </Stack>
          <Stack sx={{ gap: 1.5, pb: 1, flexGrow: 1, minWidth: 0 }}>
            <Typography variant="subtitle2" sx={{ fontWeight: 700, color: 'text.secondary' }}>
              {group.month}
            </Typography>
            {group.notes.map((note) => {
              const doneCount = note.taskResults.filter((task) => task.done).length;
              const booking = note.bookingId != null ? queryClient.getQueryData(bookingKeys.bookingDetail(note.bookingId)) : undefined;
              const service =
                booking && typeof booking === 'object' && 'variantSnapshotJson' in booking
                  ? variantName((booking as { variantSnapshotJson: string }).variantSnapshotJson)
                  : undefined;
              return (
                <VisitNoteCard
                  key={note.id}
                  note={note}
                  dateLabel={formatShamsiDate(note.recordedAt, locale)}
                  authorFallback={t('author_fallback')}
                  taskSummaryLabel={note.taskResults.length > 0 ? t('task_summary', { done: doneCount, total: note.taskResults.length }) : undefined}
                  serviceLabel={service ?? undefined}
                  bookingLinkLabel={note.bookingId != null ? t('view_booking') : undefined}
                  onOpenBooking={note.bookingId != null ? () => router.push(`/${locale}${bookingDetailPath(note.bookingId as number)}`) : undefined}
                />
              );
            })}
          </Stack>
        </Stack>
      ))}
      {totalPages > 1 ? (
        <Stack direction="row" sx={{ gap: 1, justifyContent: 'center', alignItems: 'center' }}>
          <AppButton variant="text" color="primary" disabled={page <= 1 || history.isFetching} onClick={() => setPage((n) => Math.max(1, n - 1))}>
            {t('prev')}
          </AppButton>
          <Typography variant="caption" sx={{ color: 'text.secondary' }}>
            {t('page_of', { page, total: totalPages })}
          </Typography>
          <AppButton variant="text" color="primary" disabled={page >= totalPages || history.isFetching} onClick={() => setPage((n) => n + 1)}>
            {t('next')}
          </AppButton>
        </Stack>
      ) : null}
    </Stack>
  );
}

// ── Small shared bits ─────────────────────────────────────────────────────────────────────────────────────
function BackToPatients() {
  const t = useTranslations('records');
  const router = useRouter();
  const locale = useLocale();
  return (
    <AppButton
      variant="outlined"
      color="primary"
      onClick={() => router.push(`/${locale}${ROUTES.PATIENTS}`)}
      sx={{ alignSelf: 'flex-start' }}
    >
      {t('back_to_patients')}
    </AppButton>
  );
}

function RecordSkeleton() {
  return (
    <Stack sx={{ gap: 3, maxWidth: 640, mx: 'auto', width: '100%' }}>
      <Stack sx={{ gap: 1 }}>
        <Skeleton variant="text" width="50%" height={32} />
        <Skeleton variant="text" width="30%" />
      </Stack>
      <Skeleton variant="rounded" height={56} />
      <Skeleton variant="rounded" height={48} />
      <Skeleton variant="rounded" height={96} />
    </Stack>
  );
}
