import React, { useEffect, useRef, useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Link, useNavigate, useSearchParams } from 'react-router';
import {
  PlusIcon, ChevronRightIcon, ChevronLeftIcon, ChevronDownIcon, CalendarDaysIcon,
  AdjustmentsHorizontalIcon,
} from '@heroicons/react/24/outline';
import { api } from '../lib/api';
import type { PaginatedResponse, ApiResponse } from '../lib/api';
import type { Appointment, ClinicResource } from '../types';
import { formatDate, toGregorianDate, todayIso, formatTime } from '../lib/utils';
import { useAuthStore } from '../stores/authStore';
import { useClinicContext } from '../hooks/useClinicContext';
import Pagination from '../components/ui/Pagination';
import AppointmentFiltersModal, { applyAppointmentFilters, EMPTY_FILTERS } from '../components/AppointmentFiltersModal';
import type { AppointmentFilters } from '../components/AppointmentFiltersModal';
import PersianCalendar from '../components/ui/PersianCalendar';
import SearchableSelect from '../components/ui/SearchableSelect';
// اجزای طرح نوبت‌های tauri
import TurnsStatInfo from '../components/appointments/TurnsStatInfo';
import TurnsViewToggle from '../components/appointments/TurnsViewToggle';
import type { TurnsViewMode } from '../components/appointments/TurnsViewToggle';
import DoctorTabs from '../components/appointments/DoctorTabs';
import { useResources } from '../hooks/useResources';
import ResourceDayPanel from '../components/appointments/ResourceDayPanel';
import { useResourceBookingServices } from '../hooks/useResourceBookingServices';
import { useUrlState } from '../hooks/useUrlState';
import TurnsTimeline from '../components/appointments/TurnsTimeline';
import TurnsTable from '../components/appointments/TurnsTable';
import { usePermissions } from '../hooks/usePermissions';
import NewAppointmentModal from '../components/appointments/NewAppointmentModal';
import type { BookingSlot } from '../components/appointments/NewAppointmentModal';
import { useDoctorBookingServices } from '../hooks/useDoctorBookingServices';
import { buildServiceTimeline } from '../components/appointments/serviceTimeline';
import { CANCELLED_STATUSES } from '../components/appointments/turnStatus';
import type { TimelineSlot } from '../components/appointments/types';

const EMPTY_ARR: Appointment[] = [];

// ─────────────────────────────────────────────────────────────────────────────
// Date Navigator (طرح tauri — ناوبری روزانه)
// ─────────────────────────────────────────────────────────────────────────────

const WEEK_DAYS_FA = ['یک‌شنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه', 'شنبه'];

function getPersianWeekDay(gregorianDate: string): string {
  return WEEK_DAYS_FA[new Date(gregorianDate + 'T12:00:00').getDay()];
}

const navBtnSx: React.CSSProperties = {
  background: 'var(--surface)', border: '1px solid var(--border)',
  borderRadius: 'var(--r-sm)', height: 36, width: 36,
  display: 'flex', alignItems: 'center', justifyContent: 'center',
  cursor: 'pointer', color: 'var(--text-2)',
};

function DateNavigator({ date, onChange }: { date: string; onChange: (d: string) => void }) {
  const [showCal, setShowCal] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  const weekDay = getPersianWeekDay(date);
  const isFriday = new Date(date + 'T12:00:00').getDay() === 5;

  function addDays(n: number) {
    const d = new Date(date + 'T12:00:00');
    d.setDate(d.getDate() + n);
    onChange(toGregorianDate(d));
  }

  return (
    <div ref={ref} style={{ display: 'flex', alignItems: 'center', gap: 2, position: 'relative' }}>
      <button className="btn sm" style={navBtnSx} onClick={() => addDays(1)}>
        <ChevronRightIcon style={{ width: 15, height: 15 }} />
      </button>
      <div style={{
        padding: '0 14px', height: 44,
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        background: 'var(--surface)', border: '1px solid var(--border)',
        borderRadius: 'var(--r-sm)', minWidth: 130, gap: 1,
      }}>
        <span style={{ fontSize: 13, fontWeight: 700, lineHeight: 1.2, color: isFriday ? 'var(--danger)' : 'var(--text)' }}>
          {weekDay}
        </span>
        <span style={{ fontSize: 11, color: 'var(--text-2)', lineHeight: 1.2 }}>
          {formatDate(date)}
        </span>
      </div>
      <button className="btn sm" style={navBtnSx} onClick={() => addDays(-1)}>
        <ChevronLeftIcon style={{ width: 15, height: 15 }} />
      </button>
      <button className="btn sm" style={navBtnSx} onClick={() => setShowCal(c => !c)}>
        <CalendarDaysIcon style={{ width: 15, height: 15 }} />
      </button>
      {showCal && (
        <PersianCalendar value={date} onChange={v => { onChange(v); setShowCal(false); }} onClose={() => setShowCal(false)} />
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Main Page — «نوبت‌ ها» (طرح tauri)
// ─────────────────────────────────────────────────────────────────────────────

interface TodayStats { total: number; completed: number; waiting: number; cancelled: number; }

export default function AppointmentsPage() {
  const navigate = useNavigate();
  const primaryRole = useAuthStore(s => s.primaryRole);
  const dbUuid = useAuthStore(s => s.dbUuid);
  // در محیط کلینیک، dbUuid شناسهٔ کلینیک است نه پزشک؛ uuid پزشک فقط از doctorUuid می‌آید.
  const doctorUuid = useAuthStore(s => s.doctorUuid);
  const clinicUuid = useClinicContext();
  const scope = useAuthStore(s => s.context?.scope);
  const isAdmin = primaryRole === 'admin';
  const isClinic = primaryRole === 'clinic';
  const isDoctor = primaryRole === 'doctor';
  const isRepresentation = primaryRole === 'representation';
  // منشی در هر دو ساختار باید تایم‌لاین ببیند: کلینیک چندپزشکه (تب پزشکانِ
  // تخصیص‌یافته) و پزشک مستقل (همان یک پزشک). لیست در هر دو حالت از اندپوینتِ
  // احرازشدهٔ /my/clinic-doctors می‌آید که خودش هر دو سناریو را resolve می‌کند.
  const isSecretary = primaryRole === 'secretary';
  const isClinicScopedSecretary = isSecretary && scope === 'clinic';
  // مجوزهای منشی روی نوبت‌ها؛ برای owner/پزشک همیشه true.
  const { can } = usePermissions();
  const canCreateAppt = can('appointments', 'create');
  const canManageAppt = can('appointments', 'update_status');
  const canCancelAppt = can('appointments', 'cancel');

  const [params] = useSearchParams();
  const today = todayIso();
  // پس از ویرایش/ثبت، صفحه با ?date=... باز می‌شود تا همان روز نمایش داده شود.
  const [selectedDate, setSelectedDate] = useState(params.get('date') || today);
  const [viewMode, setViewMode] = useState<TurnsViewMode>('timeline');
  const [selectedDoctorUuid, setSelectedDoctorUuid] = useState<string>(isDoctor ? (doctorUuid ?? '') : '');

  /**
   * تب منبع در URL می‌نشیند تا «بازگشت» و رفرش همان تب را برگردانند — همان قاعده‌ای که
   * `CLAUDE.md` برای وضعیت لیست‌ها می‌گذارد. (تب پزشک هنوز `useState` است؛ رفعش
   * refactor جداست و اینجا دست نمی‌خورد.)
   */
  const [urlState, setUrlState] = useUrlState({ resource: '' });
  const selectedResourceUuid = urlState.resource;
  const { resources: bookableResources } = useResources({ active: '1' });

  /**
   * منابعی که پزشکِ انتخاب‌شده ناظرشان است.
   *
   * وقتی خودِ تب منبع فعال است `selectedDoctorUuid` خالی می‌شود، پس ناظرِ همان منبع
   * مبنا قرار می‌گیرد — وگرنه نوار منابع زیر پای کاربر خالی می‌شد.
   */
  const supervisedResources = selectedDoctorUuid
    ? bookableResources.filter((r) => r.supervisor?.uuid === selectedDoctorUuid)
    : [];

  const [bookingResource, setBookingResource] = useState<ClinicResource | null>(null);
  const activeResource = bookableResources.find((r) => r.uuid === selectedResourceUuid) ?? null;
  // سرویس‌های همان منبع، نه کل کاتالوگ: منبعی که سرویسی را نمی‌دهد نباید در فهرست
  // بیاید — سرور هم همان را با ۴۲۲ رد می‌کند.
  const { services: resourceServices } = useResourceBookingServices(bookingResource?.uuid);

  /**
   * منبع زیرمجموعهٔ پزشک است، نه رقیبش: انتخاب یک منبع فقط نما را داخل همان پزشک
   * تنگ می‌کند. پاک کردن پزشک، نوبت‌دهی خودِ او را از دسترس خارج می‌کرد.
   */
  const selectResource = (uuid: string) => setUrlState({ resource: uuid });

  /** برگشت به خودِ پزشک: نمای منبع بسته می‌شود. */
  const selectDoctor = (uuid: string) => {
    setSelectedDoctorUuid(uuid);
    if (selectedResourceUuid) setUrlState({ resource: '' });
  };
  const [bookingSlot, setBookingSlot] = useState<BookingSlot | null>(null);
  const [filtersOpen, setFiltersOpen] = useState(false);
  const [filters, setFilters] = useState<AppointmentFilters>(EMPTY_FILTERS);
  const qc = useQueryClient();

  // ── Today stats
  const statsQuery = useQuery<ApiResponse<TodayStats>>({
    queryKey: ['appt-today-stats', selectedDate, isAdmin],
    queryFn: () => api.get(
      isAdmin
        ? `/api/v1/admin/appointments/today-stats?date=${selectedDate}`
        : `/api/v1/my/appointments/today-stats?date=${selectedDate}`
    ),
  });
  const stats = statsQuery.data?.data ?? { total: 0, completed: 0, waiting: 0, cancelled: 0 };

  // ── Appointments query
  const apptEndpoint = isAdmin
    ? '/api/v1/admin/appointments'
    : isRepresentation
      ? '/api/v1/representation/appointments'
      : '/api/v1/my/appointments';
  const apptQueryKey = ['appointments', apptEndpoint, selectedDate, selectedDoctorUuid, selectedResourceUuid];
  const apptParams = new URLSearchParams({ date: selectedDate, limit: '500' });
  // تب منبع جای تب پزشک را می‌گیرد، نه اینکه رویش سوار شود: «نوبت‌های لیزر CO2» یعنی
  // همهٔ نوبت‌های آن دستگاه، از هر پزشکی.
  if (selectedResourceUuid) apptParams.set('resource_uuid', selectedResourceUuid);
  else if (selectedDoctorUuid) apptParams.set('doctor_uuid', selectedDoctorUuid);

  const apptQuery = useQuery<PaginatedResponse<Appointment>>({
    queryKey: apptQueryKey,
    queryFn: () => api.get(`${apptEndpoint}?${apptParams}`),
  });
  const appointments: Appointment[] = apptQuery.data?.data ?? EMPTY_ARR;
  const filteredAppointments = applyAppointmentFilters(appointments, filters);
  const filtersActive = filters !== EMPTY_FILTERS && JSON.stringify(filters) !== JSON.stringify(EMPTY_FILTERS);

  // Table pagination — client-side (full day stays loaded for timeline + doctor tabs).
  const TABLE_PAGE_SIZE = 20;
  const [tablePage, setTablePage] = useState(1);
  useEffect(() => { setTablePage(1); }, [selectedDate, selectedDoctorUuid, filters]);
  const pagedAppointments = filteredAppointments.slice((tablePage - 1) * TABLE_PAGE_SIZE, tablePage * TABLE_PAGE_SIZE);

  // ── Clinic doctors (authoritative list for tabs)
  const clinicDoctorsQuery = useQuery<ApiResponse<{ data: { uuid: string; name: string; has_schedule?: boolean }[] }>>({
    queryKey: ['clinic-doctors', dbUuid, isSecretary, isClinic, isDoctor],
    // منشی، کلینیک و خودِ پزشک از اندپوینتِ احرازشده می‌گیرند: هم فقط پزشکانِ مجاز را
    // می‌دهد و هم `has_schedule`ِ همین محیط را. پزشک هم لازمش دارد — بدون آن،
    // پروفایل خودش نمی‌داند که «ساعت کاری تنظیم نشده» و فقط تایم‌لاین خالی می‌بیند.
    // ادمین از لیستِ کلینیکِ انتخاب‌شده می‌خواند (آن اندپوینت نقش ادمین را پوشش
    // نمی‌دهد) و آنجا فلگ نمی‌آید.
    queryFn: () => api.get(
      isSecretary || isClinic || isDoctor
        ? '/api/v1/my/clinic-doctors'
        : `/api/v1/clinic/doctor-list/${dbUuid}`,
    ),
    enabled: isSecretary || isDoctor || (isClinic && !!dbUuid),
  });
  const clinicDoctorsList = clinicDoctorsQuery.data?.data?.data ?? [];

  const doctors = React.useMemo(() => {
    const map = new Map<string, { name: string; hasSchedule: boolean }>();
    // پزشکِ بدون برنامهٔ کاری هم تب می‌گیرد: منابعِ تحت نظرش تقویم مستقلِ خودشان را
    // دارند و قابل نوبت‌دهی‌اند، پس پنهان‌کردنِ پزشک آن منابع را هم از دسترس خارج
    // می‌کرد. به‌جای حذف، تبش برچسب «بدون ساعت کاری» می‌گیرد.
    // نبودِ فیلد (مسیر ادمین) یعنی «نمی‌دانیم»، پس برچسب نمی‌خورد.
    clinicDoctorsList.forEach(d => map.set(d.uuid, { name: d.name, hasSchedule: d.has_schedule !== false }));
    appointments.forEach(a => {
      if (a.doctor_uuid && a.doctor_name && !map.has(a.doctor_uuid)) {
        map.set(a.doctor_uuid, { name: a.doctor_name, hasSchedule: true });
      }
    });
    return Array.from(map.entries()).map(([uuid, v]) => ({ uuid, name: v.name, hasSchedule: v.hasSchedule }));
  }, [appointments, clinicDoctorsList]);

  const selectedDoctor = doctors.find(d => d.uuid === selectedDoctorUuid) ?? null;
  /** پزشکِ انتخاب‌شده برنامهٔ هفتگی ندارد ⇒ تایم‌لاین جای خود را به راهنما می‌دهد. */
  const doctorHasNoSchedule = selectedDoctor?.hasSchedule === false;

  /**
   * صفحهٔ تنظیماتِ نوبت‌دهی که همین کاربر واقعاً می‌تواند بازش کند.
   *
   * مالک کلینیک و منشیِ محیط کلینیک از تنظیمات کلینیک وارد می‌شوند (با تبِ همان
   * پزشک)؛ پزشک و منشیِ مطب شخصی از صفحهٔ خودِ پزشک که انتخابگر محیط دارد. بدون
   * مجوز `appointment_settings.view` لینک اصلاً نمی‌آید — همان گیتی که `RoleRoute`
   * روی هر دو مسیر می‌گذارد، وگرنه کلیک به داشبورد پرت می‌شد.
   */
  /**
   * محیط‌های کلینیکیِ دیگرِ همین کاربر، وقتی خودش در مطب شخصی ایستاده.
   *
   * پزشکِ مهمانِ یک کلینیک، به‌طور پیش‌فرض در محیط شخصی وارد پنل می‌شود و آنجا نه
   * برنامهٔ کلینیک را می‌بیند نه منابعش (هر دو tenant-scoped‌اند) — بدون این راهنما،
   * «چیزی نیست» با «جای دیگری است» اشتباه گرفته می‌شد.
   */
  const availableContexts = useAuthStore(s => s.availableContexts);
  const otherClinicNames = clinicUuid === null
    ? availableContexts.filter(c => c.type === 'clinic').map(c => c.name)
    : [];

  const apptSettingsHref = !can('appointment_settings', 'view')
    ? null
    : isClinic || isClinicScopedSecretary
      ? `/admin/settings/appointment-settings?scope=doctors&doctor=${selectedDoctorUuid}`
      : isDoctor || isSecretary
        ? '/admin/appointment-settings'
        : null;

  // سرویس‌های موجود در نوبت‌های امروز (برای فیلتر «سرویس مورد نظر...»).
  const serviceOptions = React.useMemo(() => {
    const map = new Map<string, string>();
    appointments.forEach(a => {
      if (a.service_item?.uuid && a.service_item?.name) map.set(a.service_item.uuid, a.service_item.name);
    });
    return Array.from(map.entries()).map(([uuid, name]) => ({ uuid, name }));
  }, [appointments]);

  // نوع پروفایل: کلینیک چندپزشکه (تب دکترها + مدیریت چند پزشک) در برابر پزشک مستقل.
  // نقش clinic/admin = چندپزشکه؛ نقش doctor (حتی مهمانِ کلینیک) = مستقل، فقط برنامهٔ خودش.
  const isMultiDoctorClinic = isClinic || isAdmin || isSecretary;
  const showDoctorTabs = isMultiDoctorClinic && doctors.length >= 1;
  const showDoctorCol = isAdmin && !selectedDoctorUuid;

  // کلینیک و منشی: اولین پزشکِ در دسترس پیش‌فرض انتخاب می‌شود تا تایم‌لاین خالی نماند.
  // برای منشیِ پزشک مستقل این تنها راه انتخاب است (تبِ پزشک هم یک گزینه بیشتر ندارد).
  useEffect(() => {
    if ((isClinic || isSecretary) && !selectedDoctorUuid && doctors.length > 0) {
      setSelectedDoctorUuid(doctors[0].uuid);
    }
  }, [isClinic, isSecretary, selectedDoctorUuid, doctors]);

  // ── محل نوبت‌دهی برای ادمین
  // ادمین context کلینیکی ندارد (useClinicContext → null)؛ بدون clinic_uuid فقط برنامهٔ
  // مطب شخصی خوانده می‌شود. محل از booking-locations همان پزشک انتخاب می‌شود.
  const adminLocationsQuery = useQuery<ApiResponse<any>>({
    queryKey: ['booking-locations', selectedDoctorUuid],
    queryFn: () => api.get(`/api/v1/appointment-booking-locations/${selectedDoctorUuid}?management=1`),
    enabled: isAdmin && !!selectedDoctorUuid,
  });
  const adminLocations: any[] = (adminLocationsQuery.data?.data as any)?.booking_locations ?? EMPTY_ARR;
  const [adminLocKey, setAdminLocKey] = useState<string | null>(null);
  useEffect(() => { setAdminLocKey(null); }, [selectedDoctorUuid]);
  const locKey = (l: any) => l.clinic_uuid ?? 'personal';
  // پیش‌فرض = اولین آیتم؛ backend بر اساس زودترین نوبت آزاد مرتب کرده است.
  const adminLocation = adminLocations.find(l => locKey(l) === adminLocKey) ?? adminLocations[0] ?? null;
  const effectiveClinicUuid: string | null = isAdmin ? (adminLocation?.clinic_uuid ?? null) : clinicUuid;

  // ── Slots query (timeline)
  // effectiveClinicUuid باید در کلید کش باشد، وگرنه برنامهٔ یک محیط برای محیط دیگر نشان داده می‌شود.
  const slotsQueryKey = ['appt-slots', selectedDoctorUuid, selectedDate, effectiveClinicUuid];
  const slotsQuery = useQuery<ApiResponse<any>>({
    queryKey: slotsQueryKey,
    queryFn: () => api.get(
      `/api/v1/appointment-slots?doctor_uuid=${selectedDoctorUuid}&date=${selectedDate}&management=1` +
      (effectiveClinicUuid ? `&clinic_uuid=${encodeURIComponent(effectiveClinicUuid)}` : '')
    ),
    // برنامهٔ هفتگی ندارد ⇒ پاسخ قطعاً خالی است؛ درخواست فرستاده نمی‌شود.
    enabled: viewMode === 'timeline' && !!selectedDoctorUuid && !doctorHasNoSchedule,
  });

  // ── روش نوبت‌دهی پزشکِ انتخاب‌شده (سرویسی/اسلاتی)
  const { bookingMode, services } = useDoctorBookingServices(
    selectedDoctorUuid,
    isAdmin ? (adminLocation?.clinic_uuid ?? null) : undefined,
  );
  const serviceMode = bookingMode === 'service';

  // بازهٔ کاری پزشک در این روز (برای هدرِ تایم‌لاینِ سرویسی).
  const workingRange = React.useMemo(() => {
    const rawSessions: any[] = (slotsQuery.data?.data as any)?.sessions ?? [];
    if (!rawSessions.length) return null;
    const starts = rawSessions.map(s => s.start_time).filter(Boolean).sort();
    const ends = rawSessions.map(s => s.end_time).filter(Boolean).sort();
    if (!starts.length || !ends.length) return null;
    return { start: starts[0], end: ends[ends.length - 1] };
  }, [slotsQuery.data]);

  // ── Merge sessions + appointments → flat timeline slots
  const timelineSlots: TimelineSlot[] = React.useMemo(() => {
    if (viewMode !== 'timeline') return [];

    const activeByStart = new Map<number, Appointment>();
    const cancelledByStart = new Map<number, Appointment>();
    appointments.forEach(a => {
      const key = typeof a.slot_start === 'number' ? a.slot_start : parseInt(String(a.slot_start), 10);
      if (CANCELLED_STATUSES.has(a.status)) {
        const existing = cancelledByStart.get(key);
        if (!existing || a.created_at > existing.created_at) cancelledByStart.set(key, a);
      } else {
        activeByStart.set(key, a);
      }
    });

    // حالت سرویسی: اسلات ثابت وجود ندارد — برای هر شیفتِ کاری، نوبت‌های رزروشده
    // نمایش داده می‌شوند و باقیِ زمان به‌صورت بازه‌(های) خالیِ قابل‌رزرو بین آن‌ها.
    // همان الگوریتمِ تایم‌لاینِ منبع؛ فقط بازهٔ کاری از برنامهٔ هفتگی می‌آید.
    if (serviceMode) {
      const parseHM = (t: string) => { const [h, m] = (t ?? '00:00').split(':').map(Number); return (h * 3600) + (m * 60); };
      const rawSessions: any[] = (slotsQuery.data?.data as any)?.sessions ?? [];

      const windows = rawSessions.flatMap((session: any) => {
        const slots = (session.slots as any[]) ?? [];
        if (!slots.length) return [];
        const dayStart = Number(slots[0].start) - parseHM(session.start_time);
        return [{ start: dayStart + parseHM(session.start_time), end: dayStart + parseHM(session.end_time) }];
      });

      return buildServiceTimeline(windows, appointments);
    }

    const rawSessions: any[] = (slotsQuery.data?.data as any)?.sessions ?? [];
    const out: TimelineSlot[] = [];
    rawSessions.forEach((session: any) => {
      (session.slots as any[]).forEach((s: any) => {
        const slotStart = typeof s.start === 'number' ? s.start : parseInt(String(s.start), 10);
        const slotEnd = typeof s.end === 'number' ? s.end : parseInt(String(s.end), 10);
        out.push({
          start: slotStart,
          end: slotEnd,
          start_time: s.start_time ?? formatTime(slotStart),
          end_time: s.end_time ?? formatTime(slotEnd),
          is_available: s.is_available as boolean,
          appointment: activeByStart.get(slotStart) ?? null,
          cancelled_appointment: cancelledByStart.get(slotStart) ?? null,
        });
      });
    });
    return out;
  }, [viewMode, slotsQuery.data, appointments, serviceMode]);

  // ── Slot click → quick booking modal
  function handleSlotClick(slot: TimelineSlot) {
    if (isRepresentation || !canCreateAppt) return;
    const doctorName = doctors.find(d => d.uuid === selectedDoctorUuid)?.name ?? '';
    setBookingSlot({
      start: slot.start,
      end: slot.end,
      start_time: slot.start_time,
      end_time: slot.end_time,
      doctor_uuid: selectedDoctorUuid,
      doctor_name: doctorName,
    });
  }

  function openDetail(a: Appointment) {
    navigate(`/admin/appointments/${a.uuid}`);
  }

  return (
    <div style={{ padding: '20px 24px' }}>
      <div style={{ maxWidth: 1050, margin: '0 auto' }}>
        {/* عنوان */}
        <h1 style={{ fontSize: 20, fontWeight: 700, color: 'var(--text)', marginBottom: 16 }}>نوبت‌ ها</h1>

        {/* نوار آمار */}
        <TurnsStatInfo stats={stats} />

        {/* نوار ابزار (بیرونِ کارت، مطابق طرح) */}
        <div style={{
          display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 16,
        }}>
          {/* سمت راست: تاریخ + سرویس + سوییچ نما (مطابق طرح) */}
          <DateNavigator date={selectedDate} onChange={setSelectedDate} />

          <ServiceFilterSelect
            value={filters.itemUuid}
            options={serviceOptions}
            onChange={(v) => setFilters(f => ({ ...f, itemUuid: v }))}
          />

          {isAdmin && adminLocations.length > 1 && (
            <div style={{ minWidth: 220 }}>
              <SearchableSelect
                options={adminLocations.map((l: any) => ({
                  value: locKey(l),
                  label: l.type === 'personal' ? `مطب شخصی${l.title ? ` — ${l.title}` : ''}` : l.title,
                }))}
                value={adminLocation ? locKey(adminLocation) : null}
                onChange={v => setAdminLocKey(v ? String(v) : null)}
                placeholder="محل نوبت‌دهی..."
                height={44}
              />
            </div>
          )}

          <TurnsViewToggle viewMode={viewMode} onChange={setViewMode} />

          <div style={{ flex: 1 }} />

          {/* سمت چپ: فیلتر + افزودن نوبت */}
          <button
            aria-label="فیلترها"
            className="btn sm"
            onClick={() => setFiltersOpen(true)}
            style={{
              border: `1px solid ${filtersActive ? 'var(--primary)' : 'var(--border)'}`,
              color: filtersActive ? 'var(--primary)' : 'var(--text-2)',
              background: 'var(--surface)',
            }}
          >
            <AdjustmentsHorizontalIcon style={{ width: 16 }} />
          </button>

          {/* رزرو منبع‌محور مسیر جداست چون جستجویش از تقاطع تقویم منابع می‌آید، نه از
              اسلات‌های یک پزشک؛ ادغامشان در یک فرم، هر دو را گیج می‌کرد. */}
          {!isRepresentation && canCreateAppt && (
            <button
              className="btn secondary sm"
              onClick={() => navigate('/admin/resource-booking')}
              style={{ display: 'flex', alignItems: 'center', gap: 5 }}
            >
              نوبت منبع‌محور
            </button>
          )}

          {!isRepresentation && canCreateAppt && (
            <button
              className="btn primary sm"
              onClick={() => {
                // تب منبع فعال است ⇒ نوبت برای همان منبع، با سرویس‌های خودش.
                if (activeResource) { setBookingResource(activeResource); return; }
                const q = selectedDoctorUuid ? `?doctor=${selectedDoctorUuid}&date=${selectedDate}` : `?date=${selectedDate}`;
                navigate(`/admin/appointments/new${q}`);
              }}
              style={{ display: 'flex', alignItems: 'center', gap: 5 }}
            >
              <PlusIcon style={{ width: 15, height: 15 }} />
              افزودن نوبت
            </button>
          )}
        </div>

        {/* کارت اصلی — تب دکترها (هدر) + محتوا */}
        <div style={{
          background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 'var(--r)', overflow: 'hidden',
        }}>
          {showDoctorTabs && (
            <DoctorTabs
              doctors={doctors.map(d => ({
                uuid: d.uuid,
                name: d.name,
                note: d.hasSchedule ? undefined : 'بدون ساعت کاری',
              }))}
              selected={selectedDoctorUuid}
              onSelect={selectDoctor}
              showAll={isAdmin}
            />
          )}

          {/* منابعِ همین پزشک، نه همهٔ منابع: ارتباط پزشک↔منبع روی خودِ منبع تعریف شده
              (پزشک ناظر)، پس کاربر بین نوبت‌های پزشک و دستگاه‌های تحت نظرش جابه‌جا می‌شود. */}
          {supervisedResources.length > 0 && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '0 16px', borderBottom: '1px solid var(--border)' }}>
              <span style={{ fontSize: 12, color: 'var(--text-3)', flexShrink: 0 }}>نوبت‌دهی</span>
              {/* تب نخست راهِ بازگشت است: بدون آن، برگشتن از منبع به خودِ پزشک فقط با
                  کلیک روی تب بالایی ممکن بود و کاربر دنبالش می‌گشت. */}
              <DoctorTabs
                doctors={[
                  { uuid: '', name: 'نوبت‌های خود پزشک' },
                  ...supervisedResources.map((r) => ({ uuid: r.uuid, name: r.name })),
                ]}
                selected={selectedResourceUuid}
                onSelect={selectResource}
                showAll={false}
              />
            </div>
          )}
          <div style={{ padding: 16 }}>
            {viewMode === 'table' ? (
              <>
                <TurnsTable
                  items={pagedAppointments}
                  loading={apptQuery.isLoading}
                  queryKey={apptQueryKey}
                  showDoctor={showDoctorCol}
                  canManage={canManageAppt}
                  canCancel={canCancelAppt}
                />
                {filteredAppointments.length > TABLE_PAGE_SIZE && (
                  <div style={{ marginTop: 14 }}>
                    <Pagination page={tablePage} total={filteredAppointments.length} limit={TABLE_PAGE_SIZE} onPageChange={setTablePage} />
                  </div>
                )}
              </>
            ) : activeResource ? (
              /* منبع اسلات ندارد: تقویمش سرویسی است، پس تایم‌لاینش هم سرویسی ساخته
                 می‌شود — نوبت‌های همین منبع و بازه‌های خالیِ بینشان. */
              <ResourceDayPanel
                resource={activeResource}
                date={selectedDate}
                appointments={filteredAppointments}
                loading={apptQuery.isLoading}
                canCreate={!isRepresentation && canCreateAppt}
                queryKey={apptQueryKey}
                onBook={() => setBookingResource(activeResource)}
                onView={openDetail}
              />
            ) : (
              <>
                {!selectedDoctorUuid ? (
                  <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-3)' }}>
                    برای نمایش زمانبندی، ابتدا یک پزشک انتخاب کنید
                  </div>
                ) : doctorHasNoSchedule ? (
                  /* بدون برنامهٔ هفتگی، خودِ پزشک اسلاتی ندارد؛ اما منابعِ تحت نظرش
                     تقویم مستقل دارند و همچنان قابل نوبت‌دهی‌اند. */
                  <NoScheduleNotice
                    settingsHref={apptSettingsHref}
                    otherClinicNames={otherClinicNames}
                    resources={supervisedResources}
                    canCreate={!isRepresentation && canCreateAppt}
                    onBookResource={setBookingResource}
                    onOpenResource={selectResource}
                  />
                ) : (
                  <>
                    {serviceMode && (
                      <div style={{
                        display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8,
                        padding: '8px 12px', marginBottom: 12, borderRadius: 'var(--r-sm)',
                        background: 'var(--surface-2)', border: '1px solid var(--border)', fontSize: 12.5, color: 'var(--text-2)',
                      }}>
                        <span>نوبت‌دهی سرویسی — نوبت‌ها بر اساس مدت سرویس چیده می‌شوند.</span>
                        {workingRange && (
                          <span dir="ltr" style={{ color: 'var(--text-3)' }}>ساعت کاری: {workingRange.start} - {workingRange.end}</span>
                        )}
                      </div>
                    )}
                    <TurnsTimeline
                      slots={timelineSlots}
                      loading={apptQuery.isLoading || slotsQuery.isLoading}
                      queryKey={apptQueryKey}
                      onView={openDetail}
                      onBook={handleSlotClick}
                      emptyReason={(slotsQuery.data?.data as any)?.empty_reason ?? null}
                      errorMessage={slotsQuery.isError ? ((slotsQuery.error as Error)?.message || 'خطای نامشخص') : null}
                    />
                  </>
                )}
              </>
            )}
          </div>
        </div>
      </div>

      {/* ثبت نوبتِ منبع — همان مودالِ نوبت‌دهی سرویسی، با تقویمِ خودِ منبع */}
      {bookingResource && (
        <NewAppointmentModal
          slot={{
            start: 0, end: 0, start_time: '', end_time: '',
            // پزشکِ نوبت همان ناظرِ منبع است؛ اینجا فقط برای نمایش و تعرفهٔ ویزیت
            // می‌آید و سرور خودش هم از منبع استنتاجش می‌کند.
            doctor_uuid: bookingResource.supervisor?.uuid ?? '',
            doctor_name: bookingResource.supervisor?.name ?? '',
          }}
          resource={{ uuid: bookingResource.uuid, name: bookingResource.name }}
          services={resourceServices}
          date={selectedDate}
          clinicUuid={effectiveClinicUuid}
          onClose={() => setBookingResource(null)}
          onSuccess={() => {
            qc.invalidateQueries({ queryKey: apptQueryKey });
            qc.invalidateQueries({ queryKey: ['resource-day-slots', bookingResource.uuid, selectedDate] });
            qc.invalidateQueries({ queryKey: ['appt-today-stats', selectedDate] });
          }}
        />
      )}

      {/* مودال ثبت سریع نوبت */}
      {bookingSlot && (
        <NewAppointmentModal
          slot={bookingSlot}
          serviceMode={serviceMode}
          services={services}
          date={selectedDate}
          clinicUuid={effectiveClinicUuid}
          onClose={() => setBookingSlot(null)}
          onSuccess={() => {
            qc.invalidateQueries({ queryKey: apptQueryKey });
            qc.invalidateQueries({ queryKey: slotsQueryKey });
            qc.invalidateQueries({ queryKey: ['appt-today-stats', selectedDate] });
          }}
        />
      )}

      {/* مودال فیلترها */}
      {filtersOpen && (
        <AppointmentFiltersModal value={filters} onApply={setFilters} onClose={() => setFiltersOpen(false)} />
      )}
    </div>
  );
}

/**
 * پزشکِ بدون برنامهٔ هفتگی — به‌جای تایم‌لاینِ همیشه‌خالی.
 *
 * منبع، زیرمجموعهٔ پزشک است ولی تقویمش مستقل؛ پس نبودِ ساعت کاریِ پزشک نوبت‌دهیِ
 * منابعش را متوقف نمی‌کند و همین‌جا میان‌بُرِ رزروشان می‌آید.
 */
function NoScheduleNotice({ settingsHref, otherClinicNames, resources, canCreate, onBookResource, onOpenResource }: {
  settingsHref: string | null;
  /** کلینیک‌هایی که کاربر عضوشان است ولی الان در محیطشان نیست. */
  otherClinicNames: string[];
  resources: ClinicResource[];
  canCreate: boolean;
  onBookResource: (r: ClinicResource) => void;
  onOpenResource: (uuid: string) => void;
}) {
  return (
    <div style={{ padding: '32px 16px', textAlign: 'center' }}>
      <div style={{ fontWeight: 700, fontSize: 15, color: 'var(--text)' }}>ساعت کاری تنظیم نشده است</div>
      <div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 4 }}>
        برای این پزشک برنامهٔ هفتگی تعریف نشده، پس تایم‌لاین نوبت‌هایش خالی است.
      </div>
      {settingsHref && (
        <Link to={settingsHref} className="btn primary sm" style={{ display: 'inline-flex', marginTop: 14 }}>
          تنظیم ساعت کاری
        </Link>
      )}

      {resources.length === 0 && otherClinicNames.length > 0 && (
        <div style={{
          marginTop: 18, paddingTop: 16, borderTop: '1px solid var(--border)',
          fontSize: 12.5, color: 'var(--text-2)', lineHeight: 2,
        }}>
          شما الان در محیط «مطب شخصی» هستید. برنامهٔ کاری و منابعِ
          {' '}{otherClinicNames.map(n => `«${n}»`).join('، ')}{' '}
          در محیط همان کلینیک تعریف می‌شوند.
          <br />
          <Link to="/admin/select-context" className="btn secondary sm" style={{ display: 'inline-flex', marginTop: 8 }}>
            تغییر محیط کاری
          </Link>
        </div>
      )}

      {resources.length > 0 && (
        <div style={{
          marginTop: 22, paddingTop: 18, borderTop: '1px solid var(--border)',
          display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10,
        }}>
          <div style={{ fontSize: 12.5, color: 'var(--text-2)' }}>
            منابع تحت نظر این پزشک تقویم مستقل دارند و همچنان قابل نوبت‌دهی هستند:
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, justifyContent: 'center' }}>
            {resources.map((r) => (
              <div key={r.uuid} style={{
                display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px',
                border: '1px solid var(--border)', borderRadius: 'var(--r-sm)', background: 'var(--surface-2)',
              }}>
                <button
                  type="button"
                  className="btn sm"
                  onClick={() => onOpenResource(r.uuid)}
                  style={{ background: 'none', border: 'none', color: 'var(--text)', fontSize: 13, padding: 0 }}
                >
                  {r.name}
                </button>
                {canCreate && (
                  <button type="button" className="btn primary sm" onClick={() => onBookResource(r)}>
                    ثبت نوبت
                  </button>
                )}
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

/** فیلتر سرویس نوار ابزار — ردیف‌های بارگذاری‌شدهٔ روز را بر اساس سرویس فیلتر می‌کند. */
function ServiceFilterSelect({ value, options, onChange }: {
  value: string; options: { uuid: string; name: string }[]; onChange: (v: string) => void;
}) {
  return (
    <div style={{ minWidth: 280 }}>
      <SearchableSelect
        options={options.map(s => ({ value: s.uuid, label: s.name }))}
        value={value || null}
        onChange={v => onChange(v ? String(v) : '')}
        placeholder="سرویس مورد نظر را انتخاب کنید..."
        isClearable
        height={44}
      />
    </div>
  );
}
