import { useEffect, useMemo, useRef, useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useParams, useSearchParams, Link, useNavigate } from 'react-router-dom';
import {
  ChevronRightIcon, PencilIcon, ClipboardDocumentCheckIcon, DocumentTextIcon, RectangleStackIcon,
  ArrowPathRoundedSquareIcon,
  CalendarDaysIcon, CreditCardIcon, BanknotesIcon, ChatBubbleLeftRightIcon,
  PhoneArrowUpRightIcon, PaperClipIcon, ClipboardDocumentListIcon,
  ArrowUpTrayIcon, TrashIcon, DocumentIcon, UserIcon,
} from '@heroicons/react/24/outline';
import { PlusIcon } from '@heroicons/react/24/outline';
import { usePackages, usePatientPackages } from '../hooks/usePackages';
import { useCourseProtocols, usePatientCourses } from '../hooks/useCourses';
import { usePatientNoShows } from '../hooks/useCancellation';
import { toast } from 'sonner';
import { api } from '../lib/api';
import type { ApiResponse } from '../lib/api';
import type { PatientRecord } from '../types';
import { useAuthStore } from '../stores/authStore';
import { formatDate, formatDateTime, formatRial, formatTime, formatNumber, unixToIso } from '../lib/utils';
import DataTable, { type Column } from '../components/ui/DataTable';
import type { WalletTxn } from '../hooks/usePatientWallet';
import type { WalletModalSubmit } from '../components/WalletTransactionModal';
import Modal from '../components/ui/Modal';
import ConfirmDialog from '../components/ui/ConfirmDialog';
import PersianDateInput from '../components/ui/PersianDateInput';
import PatientCaseBanner, { Breadcrumb } from '../components/PatientCaseBanner';
import SessionServiceCard, { type SessionCardData } from '../components/SessionServiceCard';
import { useIssueInvoice } from '../hooks/useIssueInvoice';
import AppointmentTurnCard, { type AppointmentCardData } from '../components/AppointmentTurnCard';
import SessionPaymentAccordion, { type SessionPaymentData } from '../components/SessionPaymentAccordion';
import InvoiceSummaryModal from '../components/InvoiceSummaryModal';
import SessionAuditModal from '../components/session/SessionAuditModal';
import SearchableSelect from '../components/ui/SearchableSelect';
import { TurnsFilter, AddTurn } from '../components/icons/FilesToolbarIcons';
import {
  TabServices, TabInfo, TabCalendar, TabCard, TabWallet, TabCall, TabAttach, TabBody,
} from '../components/icons/FilesServiceIcons';
import PatientRecordInfoForm from '../components/PatientRecordInfoForm';
import WalletTransactionModal from '../components/WalletTransactionModal';
import { usePatientWallet } from '../hooks/usePatientWallet';
import {
  profileToFormValues, formValuesToPayload,
  GENDER_OPTS, MARITAL_OPTS, EDUCATION_OPTS, REFERRAL_OPTS,
} from '../lib/patientForm';
import { usePermissions } from '../hooks/usePermissions';

type TabKey = 'services' | 'info' | 'appointments' | 'payments' | 'wallet' | 'packages' | 'courses' | 'notes' | 'callcenter' | 'attach' | 'records';

const TABS: { key: TabKey; label: string; icon: (c: string) => React.ReactNode }[] = [
  { key: 'services',     label: 'سرویس‌ها',      icon: (c) => <TabServices color={c} /> },
  { key: 'info',         label: 'اطلاعات پرونده', icon: (c) => <TabInfo color={c} /> },
  { key: 'appointments', label: 'نوبت‌ها',        icon: (c) => <TabCalendar color={c} /> },
  { key: 'payments',     label: 'پرداخت‌ها',      icon: (c) => <TabCard color={c} /> },
  { key: 'wallet',       label: 'کیف پول',        icon: (c) => <TabWallet color={c} /> },
  { key: 'packages',     label: 'پکیج‌ها',         icon: (c) => <RectangleStackIcon style={{ width: 18, color: c }} /> },
  { key: 'courses',      label: 'دوره‌های درمان',  icon: (c) => <ArrowPathRoundedSquareIcon style={{ width: 18, color: c }} /> },
  { key: 'notes',        label: 'یادداشت‌ها',     icon: (c) => <DocumentTextIcon style={{ width: 18, color: c }} /> },
  { key: 'callcenter',   label: 'کال سنتر',       icon: (c) => <TabCall color={c} /> },
  { key: 'attach',       label: 'ضمیمه',          icon: (c) => <TabAttach color={c} /> },
  { key: 'records',      label: 'پرونده پزشکی',   icon: (c) => <TabBody color={c} /> },
];

function Placeholder({ label }: { label: string }) {
  return (
    <div style={{ padding: '48px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>
      محتوای «{label}» به‌زودی تکمیل می‌شود.
    </div>
  );
}

/** پرونده — the tabbed patient case-file (Figma "جزئیات پرونده"). */
export default function PatientDetailPage() {
  const { uuid } = useParams<{ uuid: string }>();
  // مجوزهای منشی؛ برای owner/پزشک همیشه true (usePermissions بدون context آزاد است).
  const { can } = usePermissions();
  const canUpdate = can('patients', 'update');
  // ?tab=wallet etc. lets other pages (e.g. appointment forms) deep-link a tab
  const [searchParams] = useSearchParams();
  const requested = searchParams.get('tab') as TabKey | null;
  const [tab, setTab] = useState<TabKey>(
    requested && TABS.some((t) => t.key === requested) ? requested : 'services',
  );

  const { data, isLoading } = useQuery<ApiResponse<PatientRecord>>({
    queryKey: ['patient', uuid],
    queryFn: () => api.get(`/api/v1/patient/${uuid}`),
    enabled: !!uuid,
  });
  const record = data?.data;

  const qc = useQueryClient();
  const nav = useNavigate();
  const [invoiceUuid, setInvoiceUuid] = useState<string | null>(null);
  const [auditSessionUuid, setAuditSessionUuid] = useState<string | null>(null);
  const [sessionFilter, setSessionFilter] = useState<'active' | 'all' | 'archived'>('active');

  // «مشاهده فاکتور»: اگر session هنوز فاکتور ندارد، اول صادر می‌شود (idempotent).
  const issueInvoiceMut = useIssueInvoice(uuid);
  const viewInvoice = (s: SessionCardData) => {
    if (s.invoice_uuid) { setInvoiceUuid(s.invoice_uuid); return; }
    issueInvoiceMut.mutate(s.uuid, {
      onSuccess: (iv) => setInvoiceUuid(iv),
      onError: () => toast.error('صدور فاکتور ناموفق بود'),
    });
  };

  const archiveMut = useMutation({
    mutationFn: ({ sessionUuid, archived }: { sessionUuid: string; archived: boolean }) =>
      api.patch(`/api/v1/session/${sessionUuid}`, { archived }),
    onSuccess: (_r, v) => {
      qc.invalidateQueries({ queryKey: ['patient-sessions', uuid] });
      toast.success(v.archived ? 'مراجعه آرشیو شد' : 'مراجعه از آرشیو خارج شد');
    },
    onError: (e: any) => toast.error(e?.message || 'خطا در آرشیو'),
  });

  // ── فرم «اطلاعات پرونده» (اینلاین، معادل tauri FileInfoSection) ──────────────
  // استان/شهر (Location) و بیمهٔ پایه (insurance-pricing) برای گزینه‌های فرم.
  const [editProvinceId, setEditProvinceId] = useState<number | null>(null);
  const seededProvince = useRef(false);
  useEffect(() => {
    if (!seededProvince.current && record?.profile) {
      setEditProvinceId(record.profile.province_id ?? null);
      seededProvince.current = true;
    }
  }, [record]);

  const provincesQ = useQuery<any>({
    queryKey: ['provinces'],
    queryFn: () => api.get('/api/v1/provinces'),
    staleTime: 600_000,
  });
  const citiesQ = useQuery<any>({
    queryKey: ['cities', editProvinceId],
    queryFn: () => api.get(`/api/v1/cities${editProvinceId ? `?province_id=${editProvinceId}` : ''}`),
    staleTime: 300_000,
  });
  const pricingQ = useQuery<ApiResponse<any>>({
    queryKey: ['insurance-pricing'],
    queryFn: () => api.get('/api/v1/insurance-pricing'),
    enabled: !!uuid,
  });
  const locOpts = (raw: any) =>
    (raw?.data?.data ?? raw?.data ?? []).map((x: any) => ({ value: Number(x.id), label: x.name }));
  const baseInsuranceOptions = ((pricingQ.data?.data as any)?.insurances ?? [])
    .filter((i: any) => i.type === 'basic')
    .map((i: any) => ({ value: String(i.insurance_id), label: i.insurance_name }));
  const suppInsuranceOptions = ((pricingQ.data?.data as any)?.insurances ?? [])
    .filter((i: any) => i.type === 'supplementary')
    .map((i: any) => ({ value: String(i.insurance_id), label: i.insurance_name }));

  const updateProfileMut = useMutation({
    mutationFn: (body: object) => api.patch(`/api/v1/patient/${uuid}`, body),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['patient', uuid] });
      toast.success('اطلاعات بیمار به‌روزرسانی شد');
    },
    onError: (e: any) => toast.error(e?.message || 'خطا در به‌روزرسانی اطلاعات بیمار'),
  });

  // sessions + appointments load eagerly so the banner (debt / next visit) is ready.
  const sessionsQ = useQuery<ApiResponse<SessionCardData[]>>({
    queryKey: ['patient-sessions', uuid, sessionFilter],
    queryFn: () => api.get(`/api/v1/patient/${uuid}/sessions?filter=${sessionFilter}`),
    enabled: !!uuid,
  });
  const appointmentsQ = useQuery<ApiResponse<any[]>>({
    queryKey: ['patient-appointments', uuid],
    queryFn: () => api.get(`/api/v1/patient/${uuid}/appointments`),
    enabled: !!uuid,
  });
  const sessions = sessionsQ.data?.data ?? [];
  const hasDebt = sessions.some((s) => !s.is_paid);
  const nowSec = Math.floor(Date.now() / 1000);
  const { noShows } = usePatientNoShows(uuid);

  const nextAppointment = (appointmentsQ.data?.data ?? [])
    .filter((a: any) => (a.starts_at ?? 0) >= nowSec && !String(a.status).startsWith('cancelled'))
    .sort((a: any, b: any) => a.starts_at - b.starts_at)[0]?.starts_at ?? null;

  return (
    <div className="fade-in">
      {/* breadcrumb + patient banner (tauri BreadcrumbHeader + FileServicesHeader) */}
      <Breadcrumb name={record?.user_name || 'پرونده'} backTo="/admin/patients" />
      <PatientCaseBanner
        name={record?.user_name || 'پرونده'}
        recordNumber={record?.record_number}
        mobile={record?.user_mobile}
        createdAt={(record as any)?.created_at}
        tags={(record as any)?.tags}
        nextAppointment={nextAppointment}
        hasDebt={hasDebt}
        noShows={noShows}
        onAddNote={() => setTab('notes')}
      />

      {/* tab bar */}
      <div style={{ display: 'flex', gap: 4, overflowX: 'auto', borderBottom: '1px solid var(--border)', marginBottom: 18 }}>
        {TABS.map((t) => {
          const on = t.key === tab;
          return (
            <button key={t.key} onClick={() => setTab(t.key)} style={{
              display: 'inline-flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap',
              padding: '10px 14px', border: 'none', background: 'none', cursor: 'pointer',
              fontFamily: 'inherit', fontSize: 13, fontWeight: on ? 700 : 500,
              color: on ? 'var(--primary)' : 'var(--text-2)',
              borderBottom: `2px solid ${on ? 'var(--primary)' : 'transparent'}`,
            }}>
              {t.icon(on ? 'var(--primary)' : 'var(--text-2)')} {t.label}
            </button>
          );
        })}
      </div>

      {/* tab content */}
      {isLoading ? (
        <div style={{ padding: 24, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
      ) : tab === 'info' ? (
        <div style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--r-lg)', padding: 20 }}>
          <PatientRecordInfoForm
            defaultValues={profileToFormValues(record?.profile ?? null)}
            recordNumber={record?.record_number}
            options={{
              gender: GENDER_OPTS,
              marital: MARITAL_OPTS,
              education: EDUCATION_OPTS,
              referral: REFERRAL_OPTS,
              insurance: baseInsuranceOptions,
              supplementary: suppInsuranceOptions,
              province: locOpts(provincesQ.data),
              city: locOpts(citiesQ.data),
            }}
            onSubmit={(v) => updateProfileMut.mutate(formValuesToPayload(v))}
            isSubmitting={updateProfileMut.isPending}
            onProvinceChange={setEditProvinceId}
          />
        </div>
      ) : tab === 'services' ? (
        <div>
          {/* filter (همه/فعال/آرشیو) + new-service */}
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, marginBottom: 16, flexWrap: 'wrap' }}>
            <div className="seg">
              <button className={sessionFilter === 'active'   ? 'on' : ''} onClick={() => setSessionFilter('active')}>فعال‌ها</button>
              <button className={sessionFilter === 'all'      ? 'on' : ''} onClick={() => setSessionFilter('all')}>همه</button>
              <button className={sessionFilter === 'archived' ? 'on' : ''} onClick={() => setSessionFilter('archived')}>آرشیو</button>
            </div>
            {canUpdate && (
              <Link to={`/admin/patients/${uuid}/session/new`} className="flex items-center justify-center gap-2 rounded-[8px]" style={{ height: 48, minWidth: 137, background: 'var(--primary)', color: 'var(--on-primary)', textDecoration: 'none', padding: '0 16px', fontSize: 14 }}>
                <AddTurn color="var(--on-primary)" /> سرویس جدید
              </Link>
            )}
          </div>
          {sessionsQ.isLoading ? (
            <div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
          ) : sessions.length === 0 ? (
            <div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>سرویسی ثبت نشده است</div>
          ) : (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', columnGap: 6, rowGap: 10, alignItems: 'stretch' }}>
              {sessions.map((s) => (
                <SessionServiceCard key={s.uuid} session={s} onSettle={(u) => nav(`/admin/patients/${uuid}/session/${u}/pay`)} onViewInvoice={viewInvoice} onEdit={(sess) => nav(`/admin/patients/${uuid}/session/${sess.uuid}/edit`)} onViewAudit={(sess) => setAuditSessionUuid(sess.uuid)} onArchive={(sess, archived) => archiveMut.mutate({ sessionUuid: sess.uuid, archived })} issuing={issueInvoiceMut.isPending} />
              ))}
            </div>
          )}
        </div>
      ) : tab === 'appointments' ? (
        <AppointmentsTab uuid={uuid!} q={appointmentsQ} />
      ) : tab === 'payments' ? (
        <PaymentsTab q={sessionsQ} />
      ) : tab === 'wallet' ? (
        <WalletTab uuid={uuid!} />
      ) : tab === 'packages' ? (
        <PackagesTab uuid={uuid!} />
      ) : tab === 'courses' ? (
        <CoursesTab uuid={uuid!} />
      ) : tab === 'callcenter' ? (
        <CallCenterTab uuid={uuid!} />
      ) : tab === 'attach' ? (
        <AttachmentsTab uuid={uuid!} />
      ) : tab === 'records' ? (
        <MedicalRecordsTab uuid={uuid!} />
      ) : tab === 'notes' ? (
        <NotesTab uuid={uuid!} />
      ) : (
        <Placeholder label={TABS.find((t) => t.key === tab)!.label} />
      )}

      <InvoiceSummaryModal invoiceUuid={invoiceUuid} onClose={() => setInvoiceUuid(null)} />
      <SessionAuditModal sessionUuid={auditSessionUuid} onClose={() => setAuditSessionUuid(null)} />
    </div>
  );
}

interface Attachment { uuid: string; name: string; url: string; mime?: string | null; size?: number | null }

const formatBytes = (n?: number | null) => {
  if (!n) return '';
  if (n < 1024) return `${n} B`;
  if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
  return `${(n / 1024 / 1024).toFixed(1)} MB`;
};

/** ضمیمه — patient attachments: upload (raw body), list, delete. */
function AttachmentsTab({ uuid }: { uuid: string }) {
  const qc = useQueryClient();
  const { can } = usePermissions();
  const canUpdate = can('patients', 'update');
  const canDelete = can('patients', 'delete');
  const fileRef = useRef<HTMLInputElement>(null);
  const [uploading, setUploading] = useState(false);

  const { data, isLoading } = useQuery<ApiResponse<Attachment[]>>({
    queryKey: ['patient-attachments', uuid],
    queryFn: () => api.get(`/api/v1/patient/${uuid}/attachments`),
  });
  const items = data?.data ?? [];

  const del = useMutation({
    mutationFn: (attUuid: string) => api.delete(`/api/v1/patient/attachment/${attUuid}`),
    onSuccess: () => { qc.invalidateQueries({ queryKey: ['patient-attachments', uuid] }); toast.success('ضمیمه حذف شد'); },
    onError: (e: any) => toast.error(e.message),
  });

  const onFile = async (file: File) => {
    setUploading(true);
    try {
      const token = useAuthStore.getState().token;
      const res = await fetch(`/api/v1/patient/${uuid}/attachment?name=${encodeURIComponent(file.name)}`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/octet-stream',
          'Content-Disposition': `attachment; filename="${file.name}"`,
          ...(token ? { Authorization: `Bearer ${token}` } : {}),
        },
        body: file,
      });
      if (!res.ok) throw new Error('خطا در آپلود فایل');
      qc.invalidateQueries({ queryKey: ['patient-attachments', uuid] });
      toast.success('فایل آپلود شد');
    } catch (e: any) {
      toast.error(e.message);
    } finally {
      setUploading(false);
      if (fileRef.current) fileRef.current.value = '';
    }
  };

  return (
    <div>
      {canUpdate && (
        <div style={{ marginBottom: 14 }}>
          <input ref={fileRef} type="file" hidden onChange={(e) => e.target.files?.[0] && onFile(e.target.files[0])} />
          <button className="btn primary" disabled={uploading} onClick={() => fileRef.current?.click()}>
            <ArrowUpTrayIcon style={{ width: 16 }} /> {uploading ? 'در حال آپلود...' : 'آپلود فایل جدید'}
          </button>
        </div>
      )}

      {isLoading ? (
        <div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
      ) : items.length === 0 ? (
        <div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>هنوز فایلی ضمیمه نشده است.</div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {items.map((a) => (
            <div key={a.uuid} style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--r-lg)', padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12 }}>
              <DocumentIcon style={{ width: 22, color: 'var(--primary)', flexShrink: 0 }} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <a href={a.url} target="_blank" rel="noreferrer" style={{ fontWeight: 600, fontSize: 14, color: 'var(--text)', textDecoration: 'none', wordBreak: 'break-all' }}>{a.name}</a>
                {a.size ? <div style={{ fontSize: 12, color: 'var(--text-3)' }}>{formatBytes(a.size)}</div> : null}
              </div>
              {canDelete && (
                <button className="btn sm ghost" aria-label="حذف" style={{ color: 'var(--danger)' }} onClick={() => del.mutate(a.uuid)}><TrashIcon style={{ width: 16 }} /></button>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

interface MedicalItem { uuid: string; title: string; body?: string | null; recorded_at: number }

/** پرونده پزشکی — medical exam entries: list + add/edit modal + delete. */
function MedicalRecordsTab({ uuid }: { uuid: string }) {
  const qc = useQueryClient();
  const { can } = usePermissions();
  const canUpdate = can('patients', 'update');
  const canDelete = can('patients', 'delete');
  const [modal, setModal] = useState<'create' | MedicalItem | null>(null);
  const [delTarget, setDelTarget] = useState<MedicalItem | null>(null);
  const [title, setTitle] = useState('');
  const [date, setDate] = useState('');
  const [body, setBody] = useState('');

  const { data, isLoading } = useQuery<ApiResponse<MedicalItem[]>>({
    queryKey: ['patient-medical', uuid],
    queryFn: () => api.get(`/api/v1/patient/${uuid}/medical-records`),
  });
  const items = data?.data ?? [];
  const invalidate = () => qc.invalidateQueries({ queryKey: ['patient-medical', uuid] });

  const open = (m?: MedicalItem) => {
    setTitle(m?.title ?? ''); setBody(m?.body ?? '');
    setDate(unixToIso(m?.recorded_at));
    setModal(m ?? 'create');
  };

  const save = useMutation({
    mutationFn: () => {
      const payload = { title, body: body || null, recorded_at: date ? Math.floor(new Date(date).getTime() / 1000) : undefined };
      return modal === 'create'
        ? api.post(`/api/v1/patient/${uuid}/medical-record`, payload)
        : api.patch(`/api/v1/patient/medical-record/${(modal as MedicalItem).uuid}`, payload);
    },
    onSuccess: () => { invalidate(); setModal(null); toast.success('ذخیره شد'); },
    onError: (e: any) => toast.error(e.message),
  });
  const del = useMutation({
    mutationFn: (u: string) => api.delete(`/api/v1/patient/medical-record/${u}`),
    onSuccess: () => { invalidate(); setDelTarget(null); toast.success('حذف شد'); },
    onError: (e: any) => { toast.error(e.message); setDelTarget(null); },
  });

  return (
    <div>
      {canUpdate && (
        <div style={{ marginBottom: 14 }}>
          <button className="btn primary" onClick={() => open()}><PlusIcon style={{ width: 16 }} /> ثبت معاینه جدید</button>
        </div>
      )}

      {isLoading ? (
        <div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
      ) : items.length === 0 ? (
        <div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>معاینه‌ای ثبت نشده است.</div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {items.map((m) => (
            <div key={m.uuid} style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--r-lg)', padding: '14px 16px' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10 }}>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 700, fontSize: 14 }}>{m.title}</div>
                  <div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 2 }}>{formatDate(m.recorded_at)}</div>
                  {m.body && <div style={{ fontSize: 13, color: 'var(--text-2)', marginTop: 8, whiteSpace: 'pre-wrap' }}>{m.body}</div>}
                </div>
                <div style={{ display: 'flex', gap: 6 }}>
                  {canUpdate && (
                    <button className="btn sm ghost" aria-label="ویرایش" style={{ color: 'var(--accent)' }} onClick={() => open(m)}><PencilIcon style={{ width: 15 }} /></button>
                  )}
                  {canDelete && (
                    <button className="btn sm ghost" aria-label="حذف" style={{ color: 'var(--danger)' }} onClick={() => setDelTarget(m)}><TrashIcon style={{ width: 15 }} /></button>
                  )}
                </div>
              </div>
            </div>
          ))}
        </div>
      )}

      <Modal open={modal !== null} onClose={() => setModal(null)} title={modal === 'create' ? 'ثبت معاینه جدید' : 'ویرایش معاینه'}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div>
            <label className="field-label">عنوان *</label>
            <div className="field"><input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="مثلاً: معاینه اولیه" autoFocus /></div>
          </div>
          <div>
            <label className="field-label">تاریخ</label>
            <PersianDateInput value={date} onChange={setDate} />
          </div>
          <div>
            <label className="field-label">شرح</label>
            <div className="field" style={{ height: 'auto' }}><textarea value={body} onChange={(e) => setBody(e.target.value)} rows={4} placeholder="شرح معاینه" style={{ width: '100%', border: 'none', background: 'transparent', fontFamily: 'inherit', resize: 'vertical' }} /></div>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn primary" disabled={!title.trim() || save.isPending} onClick={() => save.mutate()}>ذخیره</button>
            <button className="btn" onClick={() => setModal(null)}>انصراف</button>
          </div>
        </div>
      </Modal>

      <ConfirmDialog
        open={!!delTarget}
        title="حذف معاینه"
        message={`آیا از حذف «${delTarget?.title}» مطمئن هستید؟`}
        confirmLabel="حذف"
        onConfirm={() => delTarget && del.mutate(delTarget.uuid)}
        onCancel={() => setDelTarget(null)}
        loading={del.isPending}
      />
    </div>
  );
}

interface Note { uuid: string; body: string; pinned: boolean; author: string | null; created_at: number; updated_at: number | null }

/** آیکن سنجاق (پین) — پرشده وقتی یادداشت پین است. Heroicons سنجاق ندارد. */
function PinIcon({ filled, color, size = 15 }: { filled?: boolean; color: string; size?: number }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={filled ? color : 'none'} stroke="currentColor" strokeWidth={filled ? 0 : 1.8} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={{ color }}>
      <path d="M9 4h6l-1 5 3 3v2H7v-2l3-3-1-5Z" />
      <line x1="12" y1="14" x2="12" y2="21" stroke="currentColor" strokeWidth={1.8} />
    </svg>
  );
}

type NoteSort = 'newest' | 'oldest';

/**
 * یادداشت‌ها — personal, pinnable staff notes on the record (replaces the old
 * پیام‌ها tab). Compose box on top, then the list: pinned notes float first
 * (accent rail + filled pin), each with author + Jalali date and pin/edit/delete.
 */
function NotesTab({ uuid }: { uuid: string }) {
  const qc = useQueryClient();
  const { can } = usePermissions();
  const canUpdate = can('patients', 'update');
  const canDelete = can('patients', 'delete');
  const [body, setBody] = useState('');
  const [sort, setSort] = useState<NoteSort>('newest');
  const [editTarget, setEditTarget] = useState<Note | null>(null);
  const [editBody, setEditBody] = useState('');
  const [delTarget, setDelTarget] = useState<Note | null>(null);

  const { data, isLoading } = useQuery<ApiResponse<Note[]>>({
    queryKey: ['patient-notes', uuid],
    queryFn: () => api.get(`/api/v1/patient/${uuid}/notes`),
  });
  const items = data?.data ?? [];
  const invalidate = () => qc.invalidateQueries({ queryKey: ['patient-notes', uuid] });

  // سرور «پین‌شده‌ها اول، سپس جدیدترین» می‌دهد؛ برای «قدیمی‌ترین» ترتیبِ غیرپین را
  // معکوس می‌کنیم ولی پین‌شده‌ها همیشه بالا می‌مانند.
  const shown = useMemo(() => {
    const pinned = items.filter((n) => n.pinned);
    const rest = items.filter((n) => !n.pinned);
    const dir = (a: Note, b: Note) => (sort === 'oldest' ? a.created_at - b.created_at : b.created_at - a.created_at);
    return [...pinned].sort(dir).concat([...rest].sort(dir));
  }, [items, sort]);

  const create = useMutation({
    mutationFn: () => api.post(`/api/v1/patient/${uuid}/note`, { body: body.trim() }),
    onSuccess: () => { invalidate(); setBody(''); toast.success('یادداشت ثبت شد'); },
    onError: (e: any) => toast.error(e?.message || 'خطا در ثبت یادداشت'),
  });
  const update = useMutation({
    mutationFn: ({ u, patch }: { u: string; patch: { body?: string; pinned?: boolean } }) =>
      api.patch(`/api/v1/patient/note/${u}`, patch),
    onSuccess: () => { invalidate(); setEditTarget(null); },
    onError: (e: any) => toast.error(e?.message || 'خطا در ویرایش یادداشت'),
  });
  const del = useMutation({
    mutationFn: (u: string) => api.delete(`/api/v1/patient/note/${u}`),
    onSuccess: () => { invalidate(); setDelTarget(null); toast.success('یادداشت حذف شد'); },
    onError: (e: any) => { toast.error(e?.message || 'خطا در حذف یادداشت'); setDelTarget(null); },
  });

  const openEdit = (n: Note) => { setEditTarget(n); setEditBody(n.body); };

  return (
    <div>
      {/* افزودن یادداشت جدید */}
      <div style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--r-lg)', padding: 18, marginBottom: 18 }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)', marginBottom: 12 }}>افزودن یادداشت جدید</div>
        <div className="field" style={{ height: 'auto', marginBottom: 12 }}>
          <textarea
            value={body}
            onChange={(e) => setBody(e.target.value)}
            rows={4}
            placeholder="یادداشت خود را بنویسید..."
            style={{ width: '100%', border: 'none', background: 'transparent', fontFamily: 'inherit', resize: 'vertical', color: 'var(--text)' }}
          />
        </div>
        {canUpdate && (
          <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
            <button className="btn primary" disabled={!body.trim() || create.isPending} onClick={() => create.mutate()}>
              <PlusIcon style={{ width: 16 }} /> ذخیره یادداشت
            </button>
          </div>
        )}
      </div>

      {/* سرآیند + مرتب‌سازی */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14, flexWrap: 'wrap', gap: 10 }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-2)' }}>
          یادداشت‌های قبلی ({formatNumber(items.length)})
        </div>
        {items.length > 0 && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <span style={{ fontSize: 12.5, color: 'var(--text-3)' }}>مرتب‌سازی:</span>
            {([['newest', 'جدیدترین'], ['oldest', 'قدیمی‌ترین']] as [NoteSort, string][]).map(([key, label]) => {
              const on = sort === key;
              return (
                <button key={key} onClick={() => setSort(key)} style={{
                  background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13,
                  padding: '2px 2px 4px', fontWeight: on ? 700 : 500,
                  color: on ? 'var(--accent)' : 'var(--text-3)',
                  borderBottom: `2px solid ${on ? 'var(--accent)' : 'transparent'}`,
                }}>{label}</button>
              );
            })}
          </div>
        )}
      </div>

      {/* لیست */}
      {isLoading ? (
        <div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
      ) : items.length === 0 ? (
        <div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>یادداشتی ثبت نشده است.</div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {shown.map((n) => (
            <div key={n.uuid} style={{
              background: n.pinned ? 'var(--accent-bg)' : 'var(--surface)',
              border: '1px solid var(--border)',
              borderInlineStart: `4px solid ${n.pinned ? 'var(--accent)' : 'transparent'}`,
              borderRadius: 'var(--r-lg)', padding: '14px 16px',
            }}>
              <div style={{ fontSize: 13.5, lineHeight: 1.9, color: 'var(--text)', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{n.body}</div>

              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 12, flexWrap: 'wrap', gap: 8 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 14, fontSize: 12, color: 'var(--text-3)' }}>
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                    <UserIcon style={{ width: 15 }} /> {n.author || 'نامشخص'}
                  </span>
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                    <CalendarDaysIcon style={{ width: 15 }} /> {formatDate(n.created_at)}
                  </span>
                  {n.updated_at && <span style={{ fontSize: 11 }}>(ویرایش‌شده)</span>}
                </div>
                <div style={{ display: 'flex', gap: 6 }}>
                  {canUpdate && (
                    <button
                      className="btn sm ghost"
                      aria-label={n.pinned ? 'برداشتن پین' : 'پین کردن'}
                      title={n.pinned ? 'برداشتن پین' : 'پین کردن'}
                      style={{ color: n.pinned ? 'var(--accent)' : 'var(--text-3)' }}
                      disabled={update.isPending}
                      onClick={() => update.mutate({ u: n.uuid, patch: { pinned: !n.pinned } })}
                    >
                      <PinIcon filled={n.pinned} color="currentColor" />
                    </button>
                  )}
                  {canUpdate && (
                    <button className="btn sm ghost" aria-label="ویرایش" style={{ color: 'var(--accent)' }} onClick={() => openEdit(n)}><PencilIcon style={{ width: 15 }} /></button>
                  )}
                  {canDelete && (
                    <button className="btn sm ghost" aria-label="حذف" style={{ color: 'var(--danger)' }} onClick={() => setDelTarget(n)}><TrashIcon style={{ width: 15 }} /></button>
                  )}
                </div>
              </div>
            </div>
          ))}
        </div>
      )}

      {/* ویرایش */}
      <Modal open={editTarget !== null} onClose={() => setEditTarget(null)} title="ویرایش یادداشت">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div className="field" style={{ height: 'auto' }}>
            <textarea value={editBody} onChange={(e) => setEditBody(e.target.value)} rows={4} autoFocus placeholder="متن یادداشت" style={{ width: '100%', border: 'none', background: 'transparent', fontFamily: 'inherit', resize: 'vertical', color: 'var(--text)' }} />
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn primary" disabled={!editBody.trim() || update.isPending} onClick={() => editTarget && update.mutate({ u: editTarget.uuid, patch: { body: editBody.trim() } })}>ذخیره</button>
            <button className="btn" onClick={() => setEditTarget(null)}>انصراف</button>
          </div>
        </div>
      </Modal>

      <ConfirmDialog
        open={!!delTarget}
        title="حذف یادداشت"
        message="آیا از حذف این یادداشت مطمئن هستید؟"
        confirmLabel="حذف"
        onConfirm={() => delTarget && del.mutate(delTarget.uuid)}
        onCancel={() => setDelTarget(null)}
        loading={del.isPending}
      />
    </div>
  );
}

interface Call { uuid: string; subject: string; summary?: string | null; outcome: string; called_at: number; personnel?: string | null }

const pad2 = (n: number) => String(n).padStart(2, '0');
/** تاریخ امروز میلادی به فرمت YYYY-MM-DD (ورودی PersianDateInput). */
const nowDate = () => { const d = new Date(); return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`; };
/** ساعت الان به فرمت HH:MM. */
const nowTime = () => { const d = new Date(); return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`; };

/** کال سنتر — patient call log: register a call + filterable history (all / success / missed). */
function CallCenterTab({ uuid }: { uuid: string }) {
  const qc = useQueryClient();
  const { can } = usePermissions();
  const canUpdate = can('patients', 'update');
  const canDelete = can('patients', 'delete');
  const userName = useAuthStore((s) => s.userName);
  const [filter, setFilter] = useState<'all' | 'success' | 'missed'>('all');
  const [date, setDate] = useState(nowDate);
  const [time, setTime] = useState(nowTime);
  const [subject, setSubject] = useState('');
  const [summary, setSummary] = useState('');
  const [outcome, setOutcome] = useState<'success' | 'missed'>('success');

  // Keep تاریخ/ساعت تماس live while the user hasn't manually edited them, so a
  // long-open tab doesn't submit a stale time without a page refresh.
  const dateTouched = useRef(false);
  const timeTouched = useRef(false);
  useEffect(() => {
    const sync = () => {
      if (!dateTouched.current) setDate(nowDate());
      if (!timeTouched.current) setTime(nowTime());
    };
    const id = window.setInterval(sync, 30_000);
    window.addEventListener('focus', sync);
    return () => { window.clearInterval(id); window.removeEventListener('focus', sync); };
  }, []);

  const { data, isLoading } = useQuery<ApiResponse<Call[]>>({
    queryKey: ['patient-calls', uuid],
    queryFn: () => api.get(`/api/v1/patient/${uuid}/calls`),
  });
  const calls = data?.data ?? [];
  const shown = filter === 'all' ? calls : calls.filter((c) => c.outcome === filter);
  const successCount = calls.filter((c) => c.outcome === 'success').length;
  const missedCount = calls.filter((c) => c.outcome === 'missed').length;
  const invalidate = () => qc.invalidateQueries({ queryKey: ['patient-calls', uuid] });

  const create = useMutation({
    mutationFn: () => {
      const iso = date ? `${date}T${time || '00:00'}` : null;
      const calledAt = iso ? Math.floor(new Date(iso).getTime() / 1000) : Math.floor(Date.now() / 1000);
      return api.post(`/api/v1/patient/${uuid}/call`, { subject, summary, outcome, called_at: calledAt, personnel: userName });
    },
    onSuccess: () => { invalidate(); dateTouched.current = false; timeTouched.current = false; setDate(nowDate()); setTime(nowTime()); setSubject(''); setSummary(''); setOutcome('success'); toast.success('تماس ثبت شد'); },
    onError: (e: any) => toast.error(e.message),
  });
  const del = useMutation({
    mutationFn: (u: string) => api.delete(`/api/v1/patient/call/${u}`),
    onSuccess: () => { invalidate(); toast.success('تماس حذف شد'); },
    onError: (e: any) => toast.error(e.message),
  });

  const chip = (key: 'all' | 'success' | 'missed', label: string) => (
    <button onClick={() => setFilter(key)} style={{
      padding: '6px 14px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', cursor: 'pointer',
      fontFamily: 'inherit', fontSize: 13, fontWeight: filter === key ? 700 : 500,
      background: filter === key ? 'var(--primary-soft)' : 'var(--surface)',
      color: filter === key ? 'var(--primary)' : 'var(--text-2)',
    }}>{label}</button>
  );

  return (
    <div style={{ display: 'flex', gap: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}>
      {/* register form */}
      <div style={{ width: 320, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--r-lg)', padding: 18 }}>
        <div style={{ fontSize: 14, fontWeight: 700, textAlign: 'center', marginBottom: 16 }}>ثبت تماس جدید</div>
        <label style={{ fontSize: 12.5, color: 'var(--text-3)' }}>تاریخ تماس</label>
        <div style={{ margin: '6px 0 12px' }}><PersianDateInput value={date} onChange={(v) => { dateTouched.current = true; setDate(v); }} /></div>
        <label style={{ fontSize: 12.5, color: 'var(--text-3)' }}>ساعت تماس</label>
        <div className="field" style={{ margin: '6px 0 12px' }}><input type="time" value={time} onChange={(e) => { timeTouched.current = true; setTime(e.target.value); }} dir="ltr" /></div>
        <label style={{ fontSize: 12.5, color: 'var(--text-3)' }}>موضوع تماس</label>
        <div className="field" style={{ margin: '6px 0 12px' }}><input value={subject} onChange={(e) => setSubject(e.target.value)} placeholder="موضوع تماس" /></div>
        <label style={{ fontSize: 12.5, color: 'var(--text-3)' }}>خلاصه تماس</label>
        <div className="field" style={{ height: 'auto', margin: '6px 0 12px' }}><textarea value={summary} onChange={(e) => setSummary(e.target.value)} rows={3} placeholder="خلاصه تماس" style={{ width: '100%', border: 'none', background: 'transparent', fontFamily: 'inherit', resize: 'vertical' }} /></div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 14 }}>
          <button onClick={() => setOutcome('success')} style={{ flex: 1, padding: '8px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, background: outcome === 'success' ? 'var(--success-bg)' : 'var(--surface)', color: outcome === 'success' ? 'var(--success)' : 'var(--text-2)', fontWeight: outcome === 'success' ? 700 : 500 }}>موفق</button>
          <button onClick={() => setOutcome('missed')} style={{ flex: 1, padding: '8px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, background: outcome === 'missed' ? 'var(--danger-bg)' : 'var(--surface)', color: outcome === 'missed' ? 'var(--danger)' : 'var(--text-2)', fontWeight: outcome === 'missed' ? 700 : 500 }}>بی‌پاسخ</button>
        </div>
        {canUpdate && (
          <button className="btn primary" style={{ width: '100%' }} disabled={!subject.trim() || create.isPending} onClick={() => create.mutate()}><PlusIcon style={{ width: 16 }} /> ثبت تماس</button>
        )}
      </div>

      {/* history */}
      <div style={{ flex: 1, minWidth: 320 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14, flexWrap: 'wrap', gap: 8 }}>
          <div style={{ display: 'flex', gap: 6 }}>
            {chip('all', 'همه')}
            {chip('success', `تماس‌های موفق (${successCount})`)}
            {chip('missed', `بی‌پاسخ (${missedCount})`)}
          </div>
          <div style={{ fontSize: 14, fontWeight: 700 }}>تاریخچه تماس‌ها</div>
        </div>

        {isLoading ? (
          <div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
        ) : shown.length === 0 ? (
          <div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>تماسی ثبت نشده است.</div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {shown.map((c) => {
              const ok = c.outcome === 'success';
              return (
                <div key={c.uuid} style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderInlineStart: `4px solid ${ok ? 'var(--success)' : 'var(--danger)'}`, borderRadius: 'var(--r-lg)', padding: '14px 16px', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10 }}>
                  <div style={{ flex: 1 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                      <PhoneArrowUpRightIcon style={{ width: 16, color: ok ? 'var(--success)' : 'var(--danger)' }} />
                      <span style={{ fontSize: 13.5, fontWeight: 700 }}>{c.subject}</span>
                    </div>
                    {c.summary && <div style={{ fontSize: 12.5, color: 'var(--text-2)', marginTop: 4 }}>{c.summary}</div>}
                  </div>
                  <div style={{ textAlign: 'end', minWidth: 120 }}>
                    <div style={{ fontSize: 12, color: 'var(--text-3)' }}>{formatDateTime(c.called_at)}</div>
                    {c.personnel && <div style={{ fontSize: 12, color: 'var(--text-2)', marginTop: 2 }}>{c.personnel}</div>}
                    {canDelete && (
                      <button className="btn sm ghost" aria-label="حذف" style={{ color: 'var(--danger)', marginTop: 4 }} onClick={() => del.mutate(c.uuid)}><TrashIcon style={{ width: 14 }} /></button>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}

type WalletFilter = 'all' | 'credit' | 'debit';

const WALLET_FILTERS: { key: WalletFilter; label: string }[] = [
  { key: 'all', label: 'همه' },
  { key: 'credit', label: 'واریزی' },
  { key: 'debit', label: 'برداشت' },
];

const METHOD_LABEL: Record<string, string> = {
  card: 'کارت به کارت', pos: 'کارت‌خوان', cash: 'نقدی', wallet: 'کیف پول', gateway: 'درگاه اینترنتی',
};
const STATUS_LABEL: Record<string, string> = { confirmed: 'تأیید شده' };

type WalletRow = WalletTxn & { row_no: number };

/**
 * کیف پول — کارت موجودی + مودال شارژ/برداشت (روش پرداخت واقعی) + فیلتر
 * همه/واریزی/برداشت روی دفترِ کاملِ تراکنش‌ها (DataTable با ستون ثبت‌کننده،
 * روش پرداخت، دلیل و وضعیت). طراحی مطابق پنل.
 */
function WalletTab({ uuid }: { uuid: string }) {
  const { can } = usePermissions();
  const canUpdate = can('patients', 'update');
  const { balanceRials, transactions, isLoading, charge, withdraw } = usePatientWallet(uuid);
  const [modalOpen, setModalOpen] = useState(false);
  const [filter, setFilter] = useState<WalletFilter>('all');

  const submitting = charge.isPending || withdraw.isPending;

  const handleSubmit = ({ mode, ...body }: WalletModalSubmit) => {
    const mut = mode === 'charge' ? charge : withdraw;
    mut.mutate(body, {
      onSuccess: () => {
        toast.success(mode === 'charge' ? 'کیف پول شارژ شد' : 'برداشت از کیف پول انجام شد');
        setModalOpen(false);
      },
      onError: (e: any) => toast.error(e?.message || 'خطا در ثبت تراکنش'),
    });
  };

  const shown = filter === 'all' ? transactions : transactions.filter((t) => t.type === filter);
  const rows: WalletRow[] = shown.map((t, i) => ({ ...t, row_no: i + 1 }));

  const columns: Column<WalletRow>[] = [
    { key: 'row_no', header: 'ردیف', className: 'w-[60px]', render: (r) => formatNumber(r.row_no) },
    {
      key: 'amount', header: 'مبلغ', render: (r) => (
        <span style={{ fontWeight: 700, direction: 'ltr', color: r.type === 'credit' ? 'var(--success)' : 'var(--danger)' }}>
          {r.type === 'credit' ? '+' : '−'}{formatRial(r.amount_rials)}
        </span>
      ),
    },
    {
      key: 'type', header: 'نوع تراکنش', render: (r) => (
        <span className={`badge ${r.type === 'credit' ? 'green' : 'red'}`}>{r.type === 'credit' ? 'واریز' : 'برداشت'}</span>
      ),
    },
    { key: 'method', header: 'روش پرداخت', render: (r) => (r.payment_method ? METHOD_LABEL[r.payment_method] ?? r.payment_method : '—') },
    { key: 'reason', header: 'دلیل', render: (r) => r.description || '—' },
    { key: 'by', header: 'ثبت‌کننده', render: (r) => r.created_by_name || '—' },
    { key: 'date', header: 'تاریخ', render: (r) => formatDate(r.created_at) },
    { key: 'time', header: 'ساعت', render: (r) => formatTime(r.created_at) },
    { key: 'status', header: 'وضعیت', render: (r) => <span className="badge gray">{STATUS_LABEL[r.status ?? ''] ?? 'تأیید شده'}</span> },
  ];

  return (
    <div>
      {/* موجودی + دکمه تراکنش جدید */}
      <div className="card card-pad" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, marginBottom: 16, flexWrap: 'wrap' }}>
        <div>
          <div style={{ fontSize: 12.5, color: 'var(--text-3)', marginBottom: 6 }}>موجودی کیف پول</div>
          <div style={{ fontSize: 24, fontWeight: 800, color: 'var(--primary)', direction: 'ltr' }}>{formatRial(balanceRials)}</div>
          {/* موجودی کلِ بیمار است ولی فهرست فقط تراکنش‌های همین محیط را می‌آورد؛
              بدون این توضیح، اختلافِ جمعِ سطرها با موجودی شبیه باگ دیده می‌شود. */}
          <div style={{ fontSize: 11.5, color: 'var(--text-3)', marginTop: 6 }}>
            موجودی کل بیمار؛ تراکنش‌های زیر فقط مربوط به همین محل کار است
          </div>
        </div>
        {canUpdate && (
          <button className="btn primary" onClick={() => setModalOpen(true)}>
            <PlusIcon style={{ width: 16 }} /> شارژ کیف پول
          </button>
        )}
      </div>

      {/* فیلتر تراکنش‌ها */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}>
        <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-2)' }}>تراکنش‌ها:</span>
        {WALLET_FILTERS.map((f) => {
          const on = filter === f.key;
          return (
            <button key={f.key} onClick={() => setFilter(f.key)} style={{
              padding: '6px 14px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', cursor: 'pointer',
              fontFamily: 'inherit', fontSize: 13, fontWeight: on ? 700 : 500,
              background: on ? 'var(--primary-soft)' : 'var(--surface)',
              color: on ? 'var(--primary)' : 'var(--text-2)',
            }}>{f.label}</button>
          );
        })}
      </div>

      <WalletTransactionModal
        open={modalOpen}
        balanceRials={balanceRials}
        submitting={submitting}
        onClose={() => setModalOpen(false)}
        onSubmit={handleSubmit}
      />

      <DataTable<WalletRow>
        columns={columns}
        data={rows}
        loading={isLoading}
        emptyMessage="تراکنشی ثبت نشده است"
      />
    </div>
  );
}

/**
 * پرداخت‌ها — settlement history grouped by مراجعه (session), ported from tauri
 * PaymentsSection. Reuses the already-fetched sessions; each session is an
 * accordion showing its settlement line or the empty message.
 */
function PaymentsTab({ q }: { q: { data?: ApiResponse<any[]>; isLoading: boolean } }) {
  const items = (q.data?.data ?? []) as SessionPaymentData[];
  // undefined = default (first panel open, matching tauri); null = user closed all.
  const [expanded, setExpanded] = useState<string | null | undefined>(undefined);
  const openUuid = expanded === undefined ? (items[0]?.uuid ?? null) : expanded;

  if (q.isLoading) return <div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>;
  if (items.length === 0) return <div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>پرداختی ثبت نشده است</div>;

  return (
    <div>
      {items.map((s) => (
        <SessionPaymentAccordion
          key={s.uuid}
          session={s}
          expanded={openUuid === s.uuid}
          onToggle={() => setExpanded(openUuid === s.uuid ? null : s.uuid)}
        />
      ))}
    </div>
  );
}

const APPT_SORT_OPTS = [
  { value: 'newest', label: 'جدیدترین' },
  { value: 'oldest', label: 'قدیمی‌ترین' },
  { value: 'reserved', label: 'رزرو شده' },
  { value: 'done', label: 'انجام شده' },
  { value: 'cancelled', label: 'لغو شده' },
];

const CANCELLED_STATUSES = ['cancelled_by_doctor', 'cancelled_by_user', 'no_show', 'expired'];

/**
 * نوبت‌ها — the appointments tab, ported from tauri TurnsSection: a sort/filter
 * toolbar + reserve/new buttons, over a grid of AppointmentTurnCard. Sorting and
 * filtering are client-side over the already-fetched list (tauri leaves them inert).
 */
function AppointmentsTab({ uuid, q }: {
  uuid: string;
  q: { data?: ApiResponse<AppointmentCardData[]>; isLoading: boolean };
}) {
  const [sort, setSort] = useState('newest');
  const items = q.data?.data ?? [];
  const queryKey = ['patient-appointments', uuid];

  const shown = useMemo(() => {
    let list = [...items];
    if (sort === 'reserved') list = list.filter((a) => a.status !== 'completed' && !CANCELLED_STATUSES.includes(a.status));
    else if (sort === 'done') list = list.filter((a) => a.status === 'completed');
    else if (sort === 'cancelled') list = list.filter((a) => CANCELLED_STATUSES.includes(a.status));
    list.sort((a, b) => (sort === 'oldest' ? a.starts_at - b.starts_at : b.starts_at - a.starts_at));
    return list;
  }, [items, sort]);

  return (
    <div>
      {/* toolbar: sort + filter (right of RTL) · reserve/new buttons (left) */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, marginBottom: 16, flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ width: 320, maxWidth: '100%' }}>
            <SearchableSelect options={APPT_SORT_OPTS} value={sort} onChange={(v) => setSort(String(v ?? 'newest'))} height={48} />
          </div>
          <button type="button" aria-label="فیلتر" className="flex items-center justify-center rounded-[4px] cursor-pointer" style={{ width: 62, height: 48, border: '1px solid var(--primary)', background: 'transparent' }}>
            <TurnsFilter color="var(--primary)" />
          </button>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <Link to="/admin/appointments/reserve" className="flex items-center justify-center gap-2 rounded-[8px]" style={{ height: 48, minWidth: 137, border: '1px solid var(--primary)', color: 'var(--primary)', textDecoration: 'none', padding: '0 16px', fontSize: 14 }}>
            <AddTurn color="var(--primary)" /> نوبت رزرو
          </Link>
          {/* `record` یعنی مراجعه‌کننده از همین پرونده می‌آید؛ صفحهٔ ثبت نوبت دیگر جستجو نمی‌خواهد. */}
          <Link to={`/admin/appointments/new?record=${uuid}`} className="flex items-center justify-center gap-2 rounded-[8px]" style={{ height: 48, minWidth: 137, background: 'var(--primary)', color: 'var(--on-primary)', textDecoration: 'none', padding: '0 16px', fontSize: 14 }}>
            <AddTurn color="var(--on-primary)" /> نوبت جدید
          </Link>
        </div>
      </div>

      {q.isLoading ? (
        <div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
      ) : shown.length === 0 ? (
        <div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>نوبتی ثبت نشده است</div>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', columnGap: 6, rowGap: 10, alignItems: 'stretch' }}>
          {shown.map((a) => <AppointmentTurnCard key={a.uuid} appointment={a} queryKey={queryKey} />)}
        </div>
      )}
    </div>
  );
}


/**
 * پکیج‌های بیمار.
 *
 * مانده از دفتر می‌آید نه از شمارنده؛ لینک «دفتر» همان تاریخچه را نشان می‌دهد تا
 * معلوم باشد عدد از کجا آمده.
 */
function PackagesTab({ uuid }: { uuid: string }) {
  const { patientPackages, loading, sell } = usePatientPackages(uuid);
  const { packages } = usePackages();
  const [selected, setSelected] = useState('');

  const active = packages.filter((p) => p.active);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div className="card" style={{ display: 'flex', alignItems: 'flex-end', gap: 10, flexWrap: 'wrap' }}>
        <div className="field" style={{ minWidth: 240, margin: 0 }}>
          <label>فروش پکیج تازه</label>
          <SearchableSelect
            value={selected}
            onChange={(v) => setSelected(String(v ?? ''))}
            options={active.map((p) => ({
              value: p.uuid,
              label: `${p.name} — ${p.session_count} جلسه`,
            }))}
            placeholder="انتخاب پکیج"
          />
        </div>
        <button
          type="button"
          className="btn primary sm"
          disabled={selected === '' || sell.isPending}
          onClick={async () => {
            await sell.mutateAsync({ package_uuid: selected });
            setSelected('');
          }}
        >
          ثبت خرید
        </button>
      </div>

      {loading ? (
        <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)' }}>در حال بارگذاری…</div>
      ) : patientPackages.length === 0 ? (
        <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>
          این بیمار هنوز پکیجی نخریده است
        </div>
      ) : (
        patientPackages.map((p) => (
          <div key={p.uuid} className="card" style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
              <span style={{ fontWeight: 600 }}>{p.package_name}</span>
              <span style={{ fontSize: 12, color: 'var(--text-3)' }}>
                خرید {formatDate(p.purchased_at)}
                {p.valid_to !== null && ` · اعتبار تا ${formatDate(p.valid_to)}`}
              </span>
            </div>

            <span style={{ fontSize: 14 }}>
              مانده: <strong>{p.balance}</strong> از {p.session_count}
            </span>

            {p.expired && <span className="badge red"><span className="bdot" />منقضی</span>}
            {!p.expired && p.balance === 0 && (
              <span style={{ fontSize: 12, color: 'var(--text-2)' }}>
                اعتبار پکیج تمام شده؛ نوبت بعدی نقدی محاسبه می‌شود.
              </span>
            )}

            <Link
              className="btn secondary sm"
              style={{ marginRight: 'auto' }}
              to={`/admin/patient-package/${p.uuid}/ledger`}
            >
              دفتر اعتبار
            </Link>
          </div>
        ))
      )}
    </div>
  );
}

/**
 * دوره‌های درمان بیمار.
 *
 * پیشرفت از سرور می‌آید («۳ از ۸»)؛ فرانت نمی‌شمارد، چون وضعیت جلسات آن‌جاست.
 */
function CoursesTab({ uuid }: { uuid: string }) {
  const { courses, loading, start } = usePatientCourses(uuid);
  const { protocols } = useCourseProtocols();
  const [selected, setSelected] = useState('');

  const active = protocols.filter((p) => p.active);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div className="card" style={{ display: 'flex', alignItems: 'flex-end', gap: 10, flexWrap: 'wrap' }}>
        <div className="field" style={{ minWidth: 240, margin: 0 }}>
          <label>شروع دورهٔ تازه</label>
          <SearchableSelect
            value={selected}
            onChange={(v) => setSelected(String(v ?? ''))}
            options={active.map((p) => ({
              value: p.uuid,
              label: `${p.service_name} — ${p.session_count} جلسه`,
            }))}
            placeholder="انتخاب پروتکل"
          />
        </div>
        <button
          type="button"
          className="btn primary sm"
          disabled={selected === '' || start.isPending}
          onClick={async () => {
            await start.mutateAsync({ protocol_uuid: selected });
            setSelected('');
          }}
        >
          شروع دوره
        </button>
      </div>

      {loading ? (
        <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)' }}>در حال بارگذاری…</div>
      ) : courses.length === 0 ? (
        <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>
          این بیمار دورهٔ درمانی ندارد
        </div>
      ) : (
        courses.map((c) => (
          <div key={c.uuid} className="card" style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
              <span style={{ fontWeight: 600 }}>{c.service_name}</span>
              <span style={{ fontSize: 12, color: 'var(--text-3)' }}>شروع {formatDate(c.started_at)}</span>
            </div>

            <span style={{ fontSize: 14 }}>
              جلسهٔ <strong>{c.progress.completed}</strong> از {c.progress.total}
            </span>

            {c.status === 'active' ? (
              <span className="badge green"><span className="bdot" />در جریان</span>
            ) : (
              <span className="badge"><span className="bdot" />{c.status === 'completed' ? 'تمام‌شده' : 'رهاشده'}</span>
            )}

            <Link className="btn secondary sm" style={{ marginRight: 'auto' }} to={`/admin/treatment-course/${c.uuid}`}>
              جزئیات دوره
            </Link>
          </div>
        ))
      )}
    </div>
  );
}
