/* ============================================================
   AGENDA — Calendário (dia / semana / mês)
   ============================================================ */
function CalendarScreen(ctx) {
  const { terms, appts, services, team, clients, openNewAppt, openAppt, statusMap, settings, addAppt, toast } = ctx;
  const DB = window.DB;
  const [view, setView] = React.useState(() => {
    const saved = localStorage.getItem('ag-calendar-view');
    return ['dia', 'semana', 'mes'].includes(saved) ? saved : (window.innerWidth <= 820 ? 'dia' : 'semana');
  });
  const [anchor, setAnchor] = React.useState(new Date(DB.today)); // data de referência
  const user = ctx.user || {};
  const isPlainUser = user.role === 'user';
  const isAdmin = user.role === 'admin';
  const isPro = user.role === 'pro';
  const userProId = user.proId ? Number(user.proId) : 0;
  const resolveCalendarFilter = () => {
    if (isPlainUser) return 'mine';
    const requested = ctx.calPro && ctx.calPro !== 'default'
      ? ctx.calPro
      : (settings.calendar_default_scope || 'mine');
    if (requested === 'mine' || requested === 'all') return requested;
    const specificMatch = String(requested).match(/^pro:(\d+)$/);
    const requestedProId = specificMatch ? Number(specificMatch[1]) : Number(requested);
    return requestedProId > 0 && team.some((p) => Number(p.id) === requestedProId && !p.archived)
      ? requestedProId
      : 'mine';
  };
  const [proFilter, setProFilter] = React.useState(resolveCalendarFilter); // pré-filtro via configuração ou "Ver agenda"
  const [clientFilter, setClientFilter] = React.useState('all');
  const [openGroup, setOpenGroup] = React.useState(null);
  const [freeSlotsOpen, setFreeSlotsOpen] = React.useState(false);
  const [printOpen, setPrintOpen] = React.useState(false);
  const [draggingId, setDraggingId] = React.useState(null);
  const dragMovedRef = React.useRef(false);
  const dragOffsetYRef = React.useRef(0);
  // relógio vivo: a linha de "agora" anda sozinha (atualiza a cada 30s, sem recarregar)
  const [now, setNow] = React.useState(() => new Date());
  React.useEffect(() => { localStorage.setItem('ag-calendar-view', view); }, [view]);
  React.useEffect(() => {
    const t = setInterval(() => setNow(new Date()), 30000);
    return () => clearInterval(t);
  }, []);
  React.useEffect(() => {
    setProFilter(resolveCalendarFilter());
  }, [ctx.calPro, settings.calendar_default_scope, isPlainUser, team]);
  React.useEffect(() => {
    if (clientFilter !== 'all' && !clients.some((c) => Number(c.id) === Number(clientFilter))) setClientFilter('all');
  }, [clients, clientFilter]);

  const readHour = (value, fallback) => {
    const m = String(value || '').match(/^(\d{1,2})(?::\d{2})?$/);
    const h = m ? Number(m[1]) : fallback;
    return Number.isFinite(h) ? Math.max(0, Math.min(23, h)) : fallback;
  };
  const H_START = readHour(settings.calendar_start, 7);
  const rawEnd = readHour(settings.calendar_end, 21);
  const H_END = Math.max(H_START + 1, Math.min(24, rawEnd));
  const DEFAULT_APPT_START = `${String(Math.min(Math.max(9, H_START), H_END - 1)).padStart(2, '0')}:00`;
  const PX = 64; // px/h
  const svc = (id) => services.find(s => s.id === id);
  const cli = (id) => clients.find(c => c.id === id);
  const pro = (id) => team.find(p => p.id === id);
  const personColor = (proId) => DB.colorValue((pro(proId) || {}).color);
  const personColorBg = (proId) => DB.colorBg((pro(proId) || {}).color);
  const eventColorStyle = (a, done = false) => ({
    '--c': done ? 'var(--text-3)' : personColor(a._displayProId || a.proId),
    '--cbg': done ? 'var(--surface-2)' : personColorBg(a._displayProId || a.proId),
  });
  const iso = DB.iso; // ISO local (sem deslocamento de fuso)
  const sameMonth = (d) => d.getMonth() === anchor.getMonth();

  const apptHasPro = (a, proId) => Number(a.proId) === Number(proId) || (a.participants || []).some((p) => Number(p.proId) === Number(proId));
  const proAvailabilityForDate = (p, date) => {
    if (!p || !date || !p.availability) return null;
    const d = new Date(date + 'T00:00');
    return p.availability[String(d.getDay())] || null;
  };
  const proWorksAt = (p, date, start, end) => {
    const row = proAvailabilityForDate(p, date);
    if (row) {
      if (!row.enabled) return false;
      const ps = window.timeToMin(row.start || p.start || '00:00');
      const pe = window.timeToMin(row.end || p.end || '23:59');
      const ls = row.lunchStart ? window.timeToMin(row.lunchStart) : -1;
      const le = row.lunchEnd ? window.timeToMin(row.lunchEnd) : -1;
      return start >= ps && end <= pe && !(le > ls && start < le && end > ls);
    }
    const ps = window.timeToMin(p.start || '00:00');
    const pe = window.timeToMin(p.end || '23:59');
    const hasLunch = !!(p.lunchStart && p.lunchEnd);
    const ls = hasLunch ? window.timeToMin(p.lunchStart) : -1;
    const le = hasLunch ? window.timeToMin(p.lunchEnd) : -1;
    return start >= ps && end <= pe && !(hasLunch && le > ls && start < le && end > ls);
  };
  const apptIsMine = (a) => Number(a.createdByUserId) === Number(user.id) || (userProId > 0 && apptHasPro(a, userProId));
  const filteredByPro = (list) => (isPlainUser || proFilter === 'mine')
    ? list.filter(apptIsMine)
    : (proFilter === 'all' ? list : list.filter(a => apptHasPro(a, proFilter)));
  const filtered = (list) => {
    const byPro = filteredByPro(list);
    return clientFilter === 'all' ? byPro : byPro.filter((a) => Number(a.clientId) === Number(clientFilter));
  };
  const newApptProId = () => {
    if (isPlainUser) return userProId || undefined;
    if (proFilter === 'mine') return userProId || undefined;
    return proFilter !== 'all' ? proFilter : undefined;
  };
  const filterLabel = () => {
    if (isPlainUser || proFilter === 'mine') return 'Minha Agenda';
    if (proFilter === 'all') return terms.pros;
    const selected = pro(proFilter);
    return selected ? selected.name.replace('Dra. ', '') : terms.pros;
  };
  const clientFilterLabel = () => {
    if (clientFilter === 'all') return `${terms.clients} (todos)`;
    const selected = cli(Number(clientFilter));
    return selected ? selected.name : `${terms.clients} (todos)`;
  };

  const computeOverlaps = (list) => {
    const sorted = [...list].sort((a, b) => window.timeToMin(a.start) - window.timeToMin(b.start) || window.apptDur(b) - window.apptDur(a));
    const cols = [];
    sorted.forEach(a => {
      const aStart = window.timeToMin(a.start);
      const aDur = a._groupDur || window.apptDur(a);
      const aEnd = aStart + aDur;
      let placed = false;
      for (let i = 0; i < cols.length; i++) {
        const last = cols[i][cols[i].length - 1];
        const lastDur = last._groupDur || window.apptDur(last);
        const lastEnd = window.timeToMin(last.start) + lastDur;
        if (aStart >= lastEnd) {
          cols[i].push(a);
          a._col = i;
          placed = true;
          break;
        }
      }
      if (!placed) {
        a._col = cols.length;
        cols.push([a]);
      }
    });
    // Agrupa por grupos de sobreposição
    let currentGroup = [];
    let groupEnd = 0;
    let maxColsInGroup = 0;
    
    sorted.forEach((a, index) => {
      const aStart = window.timeToMin(a.start);
      const aDur = a._groupDur || window.apptDur(a);
      const aEnd = aStart + aDur;
      if (aStart >= groupEnd && currentGroup.length > 0) {
        currentGroup.forEach(ev => ev._maxCol = maxColsInGroup);
        currentGroup = [];
        groupEnd = 0;
        maxColsInGroup = 0;
      }
      currentGroup.push(a);
      groupEnd = Math.max(groupEnd, aEnd);
      maxColsInGroup = Math.max(maxColsInGroup, a._col + 1);
      
      if (index === sorted.length - 1) {
        currentGroup.forEach(ev => ev._maxCol = maxColsInGroup);
      }
    });
    return sorted;
  };
  const buildWeekItems = (list) => {
    const makeGroupItem = (items) => {
      const group = [...items];
      const sortedGroup = [...group].sort((a, b) => String(a.clientLabel || a.clientName || '').localeCompare(String(b.clientLabel || b.clientName || ''), 'pt-BR'));
      const minS = Math.min(...group.map(x => window.timeToMin(x.start)));
      const maxE = Math.max(...group.map(x => window.timeToMin(x.start) + window.apptDur(x)));
      return {
        ...sortedGroup[0],
        id: `group-${sortedGroup.map((item) => item.id).join('-')}`,
        _group: sortedGroup,
        start: window.minToTime(minS),
        _groupEnd: window.minToTime(maxE),
        _groupDur: maxE - minS
      };
    };
    const teamKey = (a) => {
      const ids = [a.proId, ...(a.participants || []).map((part) => part.proId)]
        .map(Number)
        .filter((id) => id > 0);
      return [...new Set(ids)].sort((x, y) => x - y).join(',') || `none-${a.id}`;
    };
    const byTeam = new Map();
    list.forEach((a) => {
      const key = teamKey(a);
      byTeam.set(key, [...(byTeam.get(key) || []), a]);
    });
    const condensed = [];
    byTeam.forEach((teamItems) => {
      const sorted = [...teamItems].sort((a, b) => window.timeToMin(a.start) - window.timeToMin(b.start) || window.apptDur(b) - window.apptDur(a));
      let cluster = [];
      let clusterEnd = -1;
      const flush = () => {
        if (!cluster.length) return;
        condensed.push(cluster.length >= 2 ? makeGroupItem(cluster) : cluster[0]);
        cluster = [];
        clusterEnd = -1;
      };
      sorted.forEach((a) => {
        const start = window.timeToMin(a.start);
        const end = start + window.apptDur(a);
        if (cluster.length && start >= clusterEnd) flush();
        cluster.push(a);
        clusterEnd = Math.max(clusterEnd, end);
      });
      flush();
    });
    return computeOverlaps(condensed);
  };

  // week days (domingo a sábado)
  const weekStart = (() => { const d = new Date(anchor); d.setDate(d.getDate() - d.getDay()); d.setHours(0, 0, 0, 0); return d; })();
  const weekDays = Array.from({ length: 7 }, (_, i) => { const d = new Date(weekStart); d.setDate(d.getDate() + i); return d; });

  const move = (dir) => {
    const d = new Date(anchor);
    if (view === 'dia') d.setDate(d.getDate() + dir);
    else if (view === 'semana') d.setDate(d.getDate() + dir * 7);
    else d.setMonth(d.getMonth() + dir);
    setAnchor(d);
  };

  const nowMin = now.getHours() * 60 + now.getMinutes();
  const isToday = (d) => iso(d) === iso(now);

  const title = view === 'mes'
    ? anchor.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })
    : view === 'semana'
      ? `${weekDays[0].toLocaleDateString('pt-BR', { day: 'numeric', month: 'short' })} – ${weekDays[6].toLocaleDateString('pt-BR', { day: 'numeric', month: 'short' })}`
      : anchor.toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' });

  const hours = Array.from({ length: H_END - H_START }, (_, i) => H_START + i);
  const apptById = (id) => appts.find(a => a.id === Number(id));
  const canMoveAppt = (a) => !!(ctx.user && Number(a.createdByUserId) === Number(ctx.user.id)
    && !['cancelado', 'concluido', 'faltou'].includes(a.status));
  const markDragHandled = () => {
    dragMovedRef.current = true;
    window.setTimeout(() => { dragMovedRef.current = false; }, 250);
  };
  const onDragStart = (e, a) => {
    if (!canMoveAppt(a)) {
      e.preventDefault();
      return;
    }
    e.stopPropagation();
    setDraggingId(a.id);
    dragMovedRef.current = false;
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', String(a.id));
    const rect = e.currentTarget.getBoundingClientRect();
    dragOffsetYRef.current = e.clientY - rect.top;
  };
  const onDragEnd = () => setDraggingId(null);
  const moveAppt = async (a, nextDate, nextStart, nextProId) => {
    if (!a || !nextDate || !nextStart) return;
    if (!canMoveAppt(a)) {
      toast(`Somente quem criou este ${terms.appt.toLowerCase()} pode movê-lo.`, 'x');
      return;
    }
    const dur = window.apptDur(a);
    const nextEnd = window.addMinutes(nextStart, dur);
    const nextPrimaryProId = nextProId || a.proId;
    if (a.date === nextDate && a.start === nextStart && window.apptEnd(a) === nextEnd && Number(a.proId) === Number(nextPrimaryProId)) return;
    try {
      await addAppt({
        editId: a.id,
        clientId: a.clientId,
        serviceId: a.serviceId,
        proId: nextPrimaryProId,
        date: nextDate,
        start: nextStart,
        end: nextEnd,
        status: a.status,
        notes: a.notes,
        showNotification: a.showNotification,
        launchFinance: a.launchFinance,
      });
      toast(`${terms.appt} movido!`, 'calendar');
    } catch (e) {
      toast(e.message || `Não foi possível mover o ${terms.appt.toLowerCase()}.`, 'x');
    }
  };
  const startFromDrop = (e, a) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const dur = Math.max(15, window.apptDur(a));
    const minStart = H_START * 60;
    const maxStart = Math.max(minStart, H_END * 60 - dur);
    const raw = minStart + Math.round(((e.clientY - dragOffsetYRef.current - rect.top) / PX) * 4) * 15;
    return window.minToTime(Math.max(minStart, Math.min(maxStart, raw)));
  };
  const dropOnTimedGrid = async (e, d, targetProId) => {
    e.preventDefault();
    e.stopPropagation();
    const a = apptById(e.dataTransfer.getData('text/plain'));
    if (!a) return;
    markDragHandled();
    setDraggingId(null);
    await moveAppt(a, iso(d), startFromDrop(e, a), targetProId);
  };
  const dropOnMonthDay = async (e, d) => {
    e.preventDefault();
    e.stopPropagation();
    const a = apptById(e.dataTransfer.getData('text/plain'));
    if (!a) return;
    markDragHandled();
    setDraggingId(null);
    await moveAppt(a, iso(d), a.start || DEFAULT_APPT_START);
  };

  function EventBlock({ a, compact }) {
    const displayProId = a._displayProId || a.proId;
    const s = a.manualServiceName ? { name: a.manualServiceName, price: a.manualServicePrice || 0, color: 'violet' } : svc(a.serviceId), c = cli(a.clientId), p = pro(displayProId);
    const partCount = (a.participants || []).length;
    const partConfirmed = (a.participants || []).filter((x) => x.response === 'confirmado').length;
    if (!s) return null;
    const startMin = window.timeToMin(a.start);
    if (startMin < H_START * 60 || startMin >= H_END * 60) return null;
    const top = (startMin - H_START * 60) / 60 * PX;
    const durMin = a._groupDur || window.apptDur(a);
    const height = Math.max(26, durMin / 60 * PX - 4);
    const done = a.status === 'concluido' || a.status === 'faltou';
    const group = Array.isArray(a._group) ? a._group : null;
    
    const maxCol = a._maxCol || 1;
    const col = a._col || 0;
    const width = `calc(${100 / maxCol}% - 2px)`;
    const left = `${(100 / maxCol) * col}%`;
    const shortName = (name) => String(name || '').replace('Dra. ', '').trim();
    const teamNamesFor = (item) => {
      const ids = [item.proId, item._displayProId, ...(item.participants || []).map((part) => part.proId)]
        .map(Number)
        .filter((id) => id > 0);
      return [...new Set(ids)]
        .map((id) => pro(id))
        .filter(Boolean)
        .map((itemPro) => shortName(itemPro.name))
        .filter(Boolean)
        .join(', ');
    };
    const clientNameFor = (item) => {
      const itemClient = cli(item.clientId);
      return shortName(item.clientLabel || item.clientName || (itemClient ? itemClient.name : ''));
    };
    const proNameFor = (item, fallbackProId) => {
      const itemPro = pro(fallbackProId || item._displayProId || item.proId);
      return shortName(itemPro ? itemPro.name : '');
    };
    const peopleLabelFor = (item, fallbackProId) => [proNameFor(item, fallbackProId), clientNameFor(item)].filter(Boolean).join(' · ');
    const peopleLabel = peopleLabelFor(a, displayProId);
    const teamLabel = teamNamesFor(a) || proNameFor(a, displayProId) || terms.pro;
    const apptStatus = statusMap[a.status] || { label: a.status || 'Pendente', cls: 'amber' };
    const eventTitle = [s.name, peopleLabel].filter(Boolean).join(' - ');

    if (group) {
      const sameService = group.every((item) => Number(item.serviceId) === Number(group[0].serviceId));
      const groupTitle = `${group.length} ${terms.appt.toLowerCase()}s${sameService ? ` · ${s.name}` : ''}`;
      const groupPros = [];
      const addGroupPro = (proId) => {
        const pid = Number(proId);
        if (!pid || groupPros.some((item) => Number(item.id) === pid)) return;
        const found = pro(pid);
        if (found) groupPros.push(found);
      };
      group.forEach((item) => {
        addGroupPro(item.proId);
        (item.participants || []).forEach((part) => addGroupPro(part.proId));
      });
      const groupColors = groupPros.map((item) => personColor(item.id));
      const groupColorBar = groupColors.length
        ? `linear-gradient(90deg, ${groupColors.map((color, i) => `${color} ${Math.round(i / groupColors.length * 100)}% ${Math.round((i + 1) / groupColors.length * 100)}%`).join(', ')})`
        : personColor(displayProId);
      const groupProNames = groupPros.map((item) => String(item.name || '').replace('Dra. ', '')).filter(Boolean);
      const visibleProNames = groupProNames.slice(0, 3).join(', ') + (groupProNames.length > 3 ? ` +${groupProNames.length - 3}` : '');
      const mixedPros = groupPros.length > 1;
      const sameStatus = group.every((item) => item.status === group[0].status);
      const statusCounts = group.reduce((counts, item) => {
        const status = item.status || 'pendente';
        counts[status] = (counts[status] || 0) + 1;
        return counts;
      }, {});
      const statusOrder = ['confirmado', 'pendente', 'concluido', 'faltou', 'cancelado'];
      const countStatusLabel = (status, count) => {
        const labels = {
          confirmado: ['confirmado', 'confirmados'],
          pendente: ['pendente', 'pendentes'],
          concluido: ['concluído', 'concluídos'],
          faltou: ['falta', 'faltas'],
          cancelado: ['cancelado', 'cancelados'],
        };
        const pair = labels[status];
        if (pair) return `${count} ${pair[count === 1 ? 0 : 1]}`;
        const fallback = (statusMap[status] || {}).label || status;
        return `${count} ${String(fallback).toLowerCase()}`;
      };
      const mixedStatusLabel = [...statusOrder, ...Object.keys(statusCounts).filter((status) => !statusOrder.includes(status))]
        .filter((status) => statusCounts[status])
        .map((status) => countStatusLabel(status, statusCounts[status]))
        .join(' · ');
      const mixedStatusClass = statusCounts.cancelado || statusCounts.faltou
        ? 'red'
        : (statusCounts.pendente ? 'amber' : (statusCounts.concluido ? 'blue' : 'green'));
      const groupedStatus = sameStatus
        ? (statusMap[group[0].status] || { label: group[0].status || 'Pendente', cls: 'amber' })
        : { label: mixedStatusLabel, cls: mixedStatusClass };
      const groupDone = group.every((item) => item.status === 'concluido' || item.status === 'faltou');
      const groupHeight = Math.max(58, height);
      const groupStyle = groupDone
        ? { ...eventColorStyle(a, groupDone), '--group-colors': groupColorBar }
        : (mixedPros
          ? { '--c': 'var(--text-2)', '--cbg': 'var(--surface-2)', '--group-colors': groupColorBar }
          : { ...eventColorStyle(a, groupDone), '--group-colors': groupColorBar });
      const addAtSameTime = (e) => {
        e.stopPropagation();
        openNewAppt({ date: a.date, start: a.start, end: window.apptEnd(a), proId: newApptProId() || displayProId, serviceId: sameService ? a.serviceId : undefined });
      };
      const names = group.map((item) => {
        return peopleLabelFor(item) || 'Sem participante informado';
      }).filter(Boolean);
      const visibleNames = names.slice(0, 4).join(', ') + (names.length > 4 ? ` +${names.length - 4}` : '');
      return (
        <div className={'ev ev-group' + (groupDone ? ' ev-done' : '')}
          data-appt-group={a.id}
          title={`Clique para ver os ${terms.appt.toLowerCase()}s deste horário${visibleProNames ? `\n${terms.pros}: ${visibleProNames}` : ''}\nStatus: ${groupedStatus.label}`}
          style={{ top, height: groupHeight, left, width, zIndex: col, ...groupStyle }}
          onClick={(e) => { e.stopPropagation(); setOpenGroup(group); }}>
          <div className="ev-group-colors" />
          <button type="button" className="ev-add" title={`${['Aula', 'Consulta', 'Sessão'].includes(terms.appt) ? 'Nova' : 'Novo'} ${terms.appt.toLowerCase()} neste horário`} onMouseDown={(e) => e.stopPropagation()} onClick={addAtSameTime}>
            <window.Icon name="plus" size={14} />
          </button>
          <div className={'ev-main' + (compact ? ' ev-main-compact' : '')}>
            <b>{compact ? `${visibleProNames || terms.pros} (${group.length})` : groupTitle}</b>
            <span className={`ev-confirm ev-confirm-group ${groupedStatus.cls || 'plain'}`}>{groupedStatus.label}</span>
          </div>
          {!compact && <div className="who">{visibleNames}</div>}
          {!compact && mixedPros && groupHeight > 72 && <div className="ev-group-pros">{visibleProNames}</div>}
          {!compact && <div className="row" style={{ gap: 5, marginTop: 3 }}>
            <span className="t">{a.start}–{a._groupEnd || window.apptEnd(a)}</span>
          </div>}
        </div>
      );
    }

    return (
      <div className={'ev' + (draggingId === a.id ? ' dragging' : '') + (done ? ' ev-done' : '')} draggable={canMoveAppt(a)}
        data-appt-id={a.id}
        title={canMoveAppt(a) ? `${eventTitle}\nArraste para alterar o dia ou horário` : `${eventTitle}\nAgendado por ${a.createdBy || 'outra pessoa'} — somente o criador pode mover`}
        style={{ top, height, left, width, zIndex: col, ...eventColorStyle(a, done), opacity: a.status === 'cancelado' ? .5 : 1, textDecoration: a.status === 'cancelado' ? 'line-through' : 'none' }}
        onDragStart={(e) => onDragStart(e, a)}
        onDragEnd={onDragEnd}
        onClick={(e) => { e.stopPropagation(); if (dragMovedRef.current) return; openAppt(a); }}>
        <button type="button" className="ev-add" title={`${['Aula', 'Consulta', 'Sessão'].includes(terms.appt) ? 'Nova' : 'Novo'} ${terms.appt.toLowerCase()} neste horário`} onMouseDown={(e) => e.stopPropagation()} onClick={(e) => {
          e.stopPropagation();
          openNewAppt({ date: a.date, start: a.start, end: window.apptEnd(a), proId: newApptProId() || displayProId, serviceId: a.serviceId });
        }}>
          <window.Icon name="plus" size={14} />
        </button>
        <div className={'ev-main' + (compact ? ' ev-main-compact' : '')}>
          <b>{compact ? teamLabel : s.name}</b>
          <span className={`ev-confirm ${apptStatus.cls || 'plain'}`}>{apptStatus.label}</span>
        </div>
        {!compact && height > 40 && peopleLabel && <div className="who">
          {peopleLabel}
          {partCount > 1 ? ` · ${partConfirmed}/${partCount} confirmados` : ''}
        </div>}
        {!compact && height > 58 && <div className="row" style={{ gap: 5, marginTop: 3 }}><span className="t">{a.start}–{window.apptEnd(a)}</span></div>}
      </div>
    );
  }

  // === WEEK / DAY grid ===
  const gridDays = view === 'dia' ? [anchor] : weekDays;
  const visibleDayPros = (() => {
    const activeTeam = team.filter((p) => !p.archived);
    if (isPlainUser || proFilter === 'mine') return activeTeam.filter((p) => Number(p.id) === Number(userProId));
    if (proFilter === 'all') return activeTeam;
    const selected = pro(proFilter);
    return selected && !selected.archived ? [selected] : [];
  })();
  const dayPros = visibleDayPros.length
    ? visibleDayPros
    : [{ id: newApptProId() || 0, name: filterLabel(), initials: '?', color: 'gray' }];
  const dayISO = iso(anchor);
  const baseDayAppts = filtered(appts.filter((a) => a.date === dayISO));
  const weekDayItems = view === 'semana'
    ? weekDays.map((d) => buildWeekItems(filtered(appts.filter((a) => a.date === iso(d)))))
    : [];
  const weekColumnWidths = weekDayItems.map((items) => {
    const concurrentColumns = Math.max(1, ...items.map((item) => Number(item._maxCol) || 1));
    return Math.max(170, concurrentColumns * 150);
  });
  const weekColumnTracks = weekColumnWidths.map((width) => {
    const proportionalShare = Math.max(1, width / 170).toFixed(3);
    return `minmax(${width}px, ${proportionalShare}fr)`;
  });

  const calendarToolbar = (
      <div className="calendar-topbar-controls">
        <div className="calendar-topbar-date">
          <div className="seg">
            <button onClick={() => move(-1)} style={{ padding: '0 9px' }} title="Período anterior" aria-label="Período anterior"><window.Icon name="chevLeft" size={16} /></button>
            <button onClick={() => setAnchor(new Date(DB.today))}>Hoje</button>
            <button onClick={() => move(1)} style={{ padding: '0 9px' }} title="Próximo período" aria-label="Próximo período"><window.Icon name="chevRight" size={16} /></button>
          </div>
          <h2 className="calendar-topbar-title">{title}</h2>
        </div>
        <div className="calendar-topbar-actions">
          {/* agendar para / filtro de pessoa */}
          {isPlainUser ? (
            <div className="chip" style={{ background: 'var(--surface-2)', border: '1px solid var(--border)', cursor: 'default' }}>
              <window.Icon name="user" size={15} />
              Minha Agenda
            </div>
          ) : (
            <window.Menu width={220} trigger={
              <button className="chip">
                <window.Icon name="users" size={15} />
                {filterLabel()}
                <window.Icon name="chevDown" size={14} />
              </button>
            }>
              <div className="mlabel">{terms.pros}</div>
              {(isAdmin || isPro) && <button onClick={() => setProFilter('mine')}><window.Icon name="user" size={15} /> Minha Agenda {proFilter === 'mine' && <window.Icon name="check" size={15} style={{ marginLeft: 'auto' }} />}</button>}
              <button onClick={() => setProFilter('all')}><window.Icon name="users" size={15} /> {terms.pros} (todos) {proFilter === 'all' && <window.Icon name="check" size={15} style={{ marginLeft: 'auto' }} />}</button>
              {team.filter(p => !p.archived).map(p => (
                <button key={p.id} onClick={() => setProFilter(p.id)}>
                  <window.Avatar initials={p.initials} color={p.color} size={20} />
                  {p.name.replace('Dra. ', '')}
                  {proFilter === p.id && <window.Icon name="check" size={15} style={{ marginLeft: 'auto' }} />}
                </button>
              ))}
            </window.Menu>
          )}
          <window.Menu width={260} trigger={
            <button className="chip" title={`Filtrar por ${terms.client.toLowerCase()}`}>
              <window.Icon name="user" size={15} />
              <span style={{ maxWidth: 170, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{clientFilterLabel()}</span>
              <window.Icon name="chevDown" size={14} />
            </button>
          }>
            <div className="mlabel">{terms.clients}</div>
            <div style={{ maxHeight: 320, overflowY: 'auto' }}>
              <button onClick={() => setClientFilter('all')}>
                <window.Icon name="users" size={15} />
                {terms.clients} (todos)
                {clientFilter === 'all' && <window.Icon name="check" size={15} style={{ marginLeft: 'auto' }} />}
              </button>
              {[...clients].sort((a, b) => String(a.name).localeCompare(String(b.name), 'pt-BR')).map((c) => (
                <button key={c.id} onClick={() => setClientFilter(Number(c.id))}>
                  <window.Icon name="user" size={15} />
                  <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.name}</span>
                  {Number(clientFilter) === Number(c.id) && <window.Icon name="check" size={15} style={{ marginLeft: 'auto' }} />}
                </button>
              ))}
            </div>
          </window.Menu>
          <window.Seg value={view} onChange={setView} options={[{ value: 'dia', label: 'Dia' }, { value: 'semana', label: 'Semana' }, { value: 'mes', label: 'Mês' }]} />
          <window.Button variant="outline" icon="print" onClick={() => setPrintOpen(true)}>Imprimir</window.Button>
          {ctx.businessType === 'pilates' && <window.Button variant="outline" icon="clock" onClick={() => setFreeSlotsOpen(true)}>Horários livres</window.Button>}
        </div>
      </div>
  );

  return (
    <div className="content-inner calendar-content-inner fadein" style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
      {ctx.calendarToolbarHost && ReactDOM.createPortal(calendarToolbar, ctx.calendarToolbarHost)}
      {ctx.calendarNewActionHost && ReactDOM.createPortal(
        <window.Button variant="primary" icon="plus" onClick={() => openNewAppt({ proId: newApptProId() })}>Novo</window.Button>,
        ctx.calendarNewActionHost
      )}
      {view === 'mes' ? (
        <MonthGrid {...{ anchor, appts: filtered(appts), svc, cli, pro, openNewAppt, openAppt, statusMap, DB, sameMonth, isToday, iso, DEFAULT_APPT_START, draggingId, dragMovedRef, onDragStart, onDragEnd, dropOnMonthDay, canMoveAppt, newApptProId, goToDay: (d) => { setAnchor(d); setView('dia'); } }} />
      ) : (
        <div className="cal-grid">
          <div className="cal-times">
            <div className="cal-times-head" />
            {hours.map(h => (
              <div className="cal-time-cell" key={h}><span>{String(h).padStart(2, '0')}:00</span></div>
            ))}
          </div>
          <div className="cal-cols" style={{
            gridTemplateColumns: view === 'dia'
              ? `repeat(${dayPros.length}, minmax(220px, 1fr))`
              : weekColumnTracks.join(' '),
            minWidth: view === 'dia' ? Math.max(0, dayPros.length * 220) : weekColumnWidths.reduce((sum, width) => sum + width, 0),
          }}>
            {view === 'dia' ? dayPros.map((p) => {
              const dayAppts = computeOverlaps(baseDayAppts
                .filter((a) => p.id ? apptHasPro(a, p.id) : true)
                .map((a) => ({ ...a, _displayProId: p.id || a.proId })));
              return (
                <div key={p.id || 'day'} style={{ minWidth: 0, position: 'relative' }}>
                  <div className={'cal-day-head' + (isToday(anchor) ? ' today' : '')} style={{ position: 'sticky', top: 0 }}>
                    <span className="dow">{terms.pro}</span>
                    <div className="row" style={{ gap: 7, minWidth: 0, maxWidth: '100%', justifyContent: 'center' }}>
                      <window.Avatar initials={p.initials || '?'} color={p.color || 'gray'} size={24} />
                      <span style={{ fontSize: 13, fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{String(p.name || filterLabel()).replace('Dra. ', '')}</span>
                    </div>
                  </div>
                  <div className="cal-col" style={{ position: 'relative' }}
                    onDragOver={(e) => e.preventDefault()}
                    onDrop={(e) => dropOnTimedGrid(e, anchor, p.id || undefined)}>
                    {hours.map(h => (
                      <div className="cal-hour" key={h} onClick={() => openNewAppt({ date: dayISO, start: `${String(h).padStart(2, '0')}:00`, proId: p.id || newApptProId() })} />
                    ))}
                    {isToday(anchor) && nowMin >= H_START * 60 && nowMin <= H_END * 60 && (
                      <div className="cal-now" style={{ top: (nowMin - H_START * 60) / 60 * PX }} />
                    )}
                    {dayAppts.map(a => <EventBlock key={`${p.id || 'day'}-${a.id}`} a={a} />)}
                  </div>
                </div>
              );
            }) : gridDays.map((d, di) => {
              const dayAppts = weekDayItems[di] || [];
              return (
                <div key={di} className="cal-week-day">
                  <div className={'cal-day-head' + (isToday(d) ? ' today' : '')} style={{ position: 'sticky', top: 0 }}>
                    <span className="dow">{d.toLocaleDateString('pt-BR', { weekday: 'short' }).replace('.', '')}</span>
                    <span className="dnum">{d.getDate()}</span>
                  </div>
                  <div className="cal-col" style={{ position: 'relative' }}
                    onDragOver={(e) => e.preventDefault()}
                    onDrop={(e) => dropOnTimedGrid(e, d)}>
                    {hours.map(h => (
                      <div className="cal-hour" key={h} onClick={() => openNewAppt({ date: iso(d), start: `${String(h).padStart(2, '0')}:00`, proId: newApptProId() })} />
                    ))}
                    {isToday(d) && nowMin >= H_START * 60 && nowMin <= H_END * 60 && (
                      <div className="cal-now" style={{ top: (nowMin - H_START * 60) / 60 * PX }} />
                    )}
                    {dayAppts.map(a => <EventBlock key={a.id} a={a} compact />)}
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}
      {openGroup && (
        <window.Modal wide
          title={`${openGroup.length} ${terms.appt.toLowerCase()}s no mesmo horário`}
          sub={`${openGroup[0].start}–${window.apptEnd(openGroup[0])} · ${
            openGroup.every((item) => Number(item.serviceId) === Number(openGroup[0].serviceId))
              ? (openGroup[0].manualServiceName || svc(openGroup[0].serviceId)?.name || terms.appt)
              : `${terms.services.toLowerCase()} diferentes`
          }`}
          onClose={() => setOpenGroup(null)}>
          <div className="calendar-group-list">
            {openGroup.map((a) => {
              const s = a.manualServiceName ? { name: a.manualServiceName, price: a.manualServicePrice || 0, color: 'violet' } : svc(a.serviceId), c = cli(a.clientId), p = pro(a.proId), st = statusMap[a.status];
              const clientLabel = a.clientLabel || a.clientName || (c ? c.name : '');
              const proLabel = p ? String(p.name || '').replace('Dra. ', '') : '';
              const name = [proLabel, clientLabel].filter(Boolean).join(' · ') || 'Sem participante informado';
              return (
                <button type="button" key={a.id} className="calendar-group-row" onClick={() => { openAppt(a); }}>
                  <div className="ic" style={{ background: p ? DB.colorBg(p.color) : 'var(--surface-2)', color: p ? DB.colorValue(p.color) : 'var(--text-2)' }}>
                    <window.Icon name="user" size={16} />
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontWeight: 700, fontSize: 13.5 }}>{name}</div>
                    <div className="muted" style={{ fontSize: 12 }}>{s ? s.name : terms.service} · {a.start}–{window.apptEnd(a)}</div>
                  </div>
                  <window.Badge cls={st?.cls || 'plain'} dot>{st?.label || a.status}</window.Badge>
                </button>
              );
            })}
          </div>
        </window.Modal>
      )}
      {freeSlotsOpen && (
        <FreeSlotsModal
          initialDate={iso(anchor)}
          team={visibleDayPros}
          services={services}
          appts={appts}
          settings={settings}
          terms={terms}
          slotCapacity={Math.max(1, Math.min(99, Number(settings.appointment_slot_capacity) || 1))}
          proWorksAt={proWorksAt}
          apptHasPro={apptHasPro}
          openNewAppt={(prefill) => { setFreeSlotsOpen(false); openNewAppt(prefill); }}
          onClose={() => setFreeSlotsOpen(false)}
        />
      )}
      {printOpen && <CalendarPrintModal
        anchor={anchor}
        initialPeriod={view}
        appts={appts}
        clients={clients}
        services={services}
        team={team}
        terms={terms}
        statusMap={statusMap}
        user={user}
        initialClientId={clientFilter === 'all' ? 0 : Number(clientFilter)}
        initialProId={proFilter === 'all' ? 0 : (proFilter === 'mine' ? userProId : Number(proFilter))}
        apptHasPro={apptHasPro}
        onClose={() => setPrintOpen(false)}
      />}
    </div>
  );
}

function CalendarPrintModal({ anchor, initialPeriod, appts, clients, services, team, terms, statusMap, user, initialClientId, initialProId, apptHasPro, onClose }) {
  const DB = window.DB;
  const [period, setPeriod] = React.useState(['dia', 'semana', 'mes'].includes(initialPeriod) ? initialPeriod : 'semana');
  const [referenceDate, setReferenceDate] = React.useState(DB.iso(anchor));
  const [scope, setScope] = React.useState(initialClientId ? 'client' : (initialProId ? 'pro' : 'all'));
  const [clientId, setClientId] = React.useState(initialClientId || (clients[0] ? clients[0].id : 0));
  const [proId, setProId] = React.useState(initialProId || (team.find((p) => !p.archived) || {}).id || 0);
  const [includeCancelled, setIncludeCancelled] = React.useState(false);
  const activeTeam = team.filter((p) => !p.archived);
  const dateRange = () => {
    const target = new Date(referenceDate + 'T12:00');
    let start = new Date(target), end = new Date(target);
    if (period === 'semana') {
      start.setDate(start.getDate() - start.getDay());
      end = new Date(start); end.setDate(end.getDate() + 6);
    } else if (period === 'mes') {
      start = new Date(target.getFullYear(), target.getMonth(), 1, 12);
      end = new Date(target.getFullYear(), target.getMonth() + 1, 0, 12);
    }
    return { startISO: DB.iso(start), endISO: DB.iso(end) };
  };
  const range = dateRange();
  const rows = (appts || []).filter((a) => {
    if (a.date < range.startISO || a.date > range.endISO || (!includeCancelled && a.status === 'cancelado')) return false;
    if (scope === 'client') return Number(a.clientId) === Number(clientId);
    if (scope === 'pro') return apptHasPro(a, proId);
    return true;
  }).sort((a, b) => String(a.date).localeCompare(String(b.date)) || window.timeToMin(a.start) - window.timeToMin(b.start));
  const findClient = (id) => clients.find((c) => Number(c.id) === Number(id));
  const findService = (id) => services.find((s) => Number(s.id) === Number(id));
  const findPro = (id) => team.find((p) => Number(p.id) === Number(id));
  const proNames = (a) => [...new Set([a.proId, ...(a.participants || []).map((p) => p.proId)].map((id) => {
    const p = findPro(id); return p ? p.name : '';
  }).filter(Boolean))].join(', ');
  const clientName = (a) => (findClient(a.clientId) || {}).name || a.clientLabel || a.clientName || '—';
  const serviceName = (a) => a.manualServiceName || (findService(a.serviceId) || {}).name || terms.service;
  const periodLabel = `${new Date(range.startISO + 'T12:00').toLocaleDateString('pt-BR')} a ${new Date(range.endISO + 'T12:00').toLocaleDateString('pt-BR')}`;
  const scopeLabel = scope === 'client'
    ? `${terms.client}: ${(findClient(clientId) || {}).name || 'não selecionado'}`
    : scope === 'pro' ? `${terms.pro}: ${(findPro(proId) || {}).name || 'não selecionado'}` : 'Todos os clientes e profissionais';
  const printReport = () => {
    const esc = (value) => String(value == null ? '' : value).replace(/[&<>"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }[char]));
    const uniqueClients = new Set(rows.map((a) => Number(a.clientId) || clientName(a))).size;
    const bodyRows = rows.map((a) => {
      const status = statusMap[a.status] ? statusMap[a.status].label : a.status;
      return `<tr><td class="check">□</td><td>${esc(new Date(a.date + 'T12:00').toLocaleDateString('pt-BR'))}</td><td>${esc(a.start)}–${esc(window.apptEnd(a))}</td><td>${esc(clientName(a))}</td><td>${esc(proNames(a) || '—')}</td><td>${esc(serviceName(a))}</td><td>${esc(status)}</td></tr>`;
    }).join('');
    const popup = window.open('', '_blank', 'width=1100,height=800');
    if (!popup) return;
    popup.document.write(`<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><title>Agenda - ${esc(periodLabel)}</title><style>
      @page{size:A4 landscape;margin:12mm}*{box-sizing:border-box}body{font-family:Arial,sans-serif;color:#1f2937;margin:0;font-size:11px}h1{font-size:20px;margin:0 0 5px}.meta{color:#5b6472;margin-bottom:14px}.summary{display:flex;gap:10px;margin:10px 0 14px}.summary span{border:1px solid #d8dde5;border-radius:6px;padding:6px 10px;font-weight:700}table{width:100%;border-collapse:collapse}th{background:#f1f4f8;text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:.03em}th,td{border:1px solid #d8dde5;padding:6px 7px;vertical-align:top}.check{width:24px;text-align:center;font-size:15px}tbody tr:nth-child(even){background:#fafbfc}.empty{text-align:center;padding:28px;color:#6b7280}.footer{margin-top:10px;color:#6b7280;font-size:9px;text-align:right}@media print{body{print-color-adjust:exact;-webkit-print-color-adjust:exact}}
    </style></head><body><h1>${esc(user.tenantName || 'Agenda')}</h1><div class="meta"><b>${esc(periodLabel)}</b> · ${esc(scopeLabel)}${includeCancelled ? ' · incluindo cancelados' : ''}</div><div class="summary"><span>${rows.length} agendamento${rows.length === 1 ? '' : 's'}</span><span>${uniqueClients} ${esc(terms.clients.toLowerCase())}</span></div><table><thead><tr><th></th><th>Data</th><th>Horário</th><th>${esc(terms.client)}</th><th>${esc(terms.pro)}</th><th>${esc(terms.service)}</th><th>Status</th></tr></thead><tbody>${bodyRows || `<tr><td colspan="7" class="empty">Nenhum agendamento encontrado neste filtro.</td></tr>`}</tbody></table><div class="footer">Impresso em ${esc(new Date().toLocaleString('pt-BR'))}</div><script>window.addEventListener('load',()=>setTimeout(()=>window.print(),150));<\/script></body></html>`);
    popup.document.close();
  };
  const invalidScope = (scope === 'client' && !clientId) || (scope === 'pro' && !proId);
  return <window.Modal wide title="Imprimir agenda" sub="Monte a lista por período, cliente ou profissional" onClose={onClose}
    footer={<><div style={{ flex: 1 }} /><window.Button variant="ghost" onClick={onClose}>Cancelar</window.Button><window.Button variant="primary" icon="print" disabled={invalidScope} onClick={printReport}>Imprimir lista</window.Button></>}>
    <div className="form-grid">
      <window.Field label="Período"><window.Select value={period} onChange={(e) => setPeriod(e.target.value)}><option value="dia">Dia</option><option value="semana">Semana</option><option value="mes">Mês</option></window.Select></window.Field>
      <window.Field label="Data de referência"><window.Input type="date" value={referenceDate} onChange={(e) => setReferenceDate(e.target.value)} /></window.Field>
      <window.Field label="Imprimir por" full><window.Seg value={scope} onChange={setScope} options={[{ value: 'all', label: 'Tudo' }, { value: 'client', label: terms.client }, { value: 'pro', label: terms.pro }]} /></window.Field>
      {scope === 'client' && <window.Field label={terms.client} full><window.Select value={clientId} onChange={(e) => setClientId(Number(e.target.value))}>{[...clients].sort((a, b) => String(a.name).localeCompare(String(b.name), 'pt-BR')).map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}</window.Select></window.Field>}
      {scope === 'pro' && <window.Field label={terms.pro} full><window.Select value={proId} onChange={(e) => setProId(Number(e.target.value))}>{activeTeam.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}</window.Select></window.Field>}
    </div>
    <label className="card card-pad row between" style={{ padding: 12, marginTop: 14, cursor: 'pointer' }}><span><b>Incluir cancelados</b><span className="muted" style={{ display: 'block', fontSize: 12, marginTop: 2 }}>Por padrão a lista mostra apenas agendamentos que permaneceram ativos.</span></span><window.Switch on={includeCancelled} onClick={() => setIncludeCancelled((value) => !value)} /></label>
    <div className="card card-pad" style={{ padding: 12, marginTop: 14 }}><div className="row between"><b>Prévia</b><span className="badge plain">{rows.length} itens</span></div><div className="muted" style={{ fontSize: 12, marginTop: 5 }}>{periodLabel} · {scopeLabel}</div>{rows.slice(0, 6).map((a) => <div key={a.id} className="row between" style={{ gap: 10, paddingTop: 8, marginTop: 8, borderTop: '1px solid var(--border)', fontSize: 12 }}><span>{new Date(a.date + 'T12:00').toLocaleDateString('pt-BR')} · {a.start} · {clientName(a)}</span><span className="muted">{proNames(a)}</span></div>)}{rows.length === 0 && <div className="muted" style={{ padding: '14px 0 2px', textAlign: 'center' }}>Nenhum agendamento neste filtro.</div>}</div>
  </window.Modal>;
}

function FreeSlotsModal({ initialDate, team, services, appts, settings, terms, slotCapacity, proWorksAt, apptHasPro, openNewAppt, onClose }) {
  const DB = window.DB;
  const [date, setDate] = React.useState(initialDate);
  const [serviceId, setServiceId] = React.useState(services[0] ? services[0].id : '');
  const selectedService = services.find((s) => String(s.id) === String(serviceId));
  const duration = Math.max(15, Number(selectedService && selectedService.dur) || 60);
  const slotInterval = Math.max(5, Math.min(240, Number(settings.appointment_slot_interval) || 15));
  const slotGap = Math.max(0, Math.min(240, Number(settings.appointment_slot_gap) || 0));
  const slotStep = Math.max(slotInterval, duration + slotGap);
  const readHour = (value, fallback) => {
    const m = String(value || '').match(/^(\d{1,2})(?::\d{2})?$/);
    const h = m ? Number(m[1]) : fallback;
    return Number.isFinite(h) ? Math.max(0, Math.min(23, h)) : fallback;
  };
  const hStart = readHour(settings.calendar_start, 7);
  const hEnd = Math.max(hStart + 1, Math.min(24, readHour(settings.calendar_end, 21)));
  const workPeriodsFor = (p) => {
    const row = p && p.availability ? p.availability[String(new Date(date + 'T00:00').getDay())] : null;
    if (row && !row.enabled) return [];
    const rawStart = window.timeToMin((row && row.start) || p.start || '00:00');
    const rawEnd = window.timeToMin((row && row.end) || p.end || '23:59');
    const start = Math.max(hStart * 60, rawStart);
    const end = Math.min(hEnd * 60, rawEnd);
    if (end <= start) return [];
    const lunchStart = (row && row.lunchStart) || p.lunchStart || '';
    const lunchEnd = (row && row.lunchEnd) || p.lunchEnd || '';
    const ls = lunchStart ? window.timeToMin(lunchStart) : -1;
    const le = lunchEnd ? window.timeToMin(lunchEnd) : -1;
    if (!(le > ls && start < le && end > ls)) return [{ start, end }];
    return [
      { start, end: Math.min(end, ls) },
      { start: Math.max(start, le), end },
    ].filter((period) => period.end > period.start);
  };
  const busyCount = (proId, start, end) => (appts || []).filter((a) => {
    if (a.date !== date || a.status === 'cancelado' || !apptHasPro(a, proId)) return false;
    const aStart = window.timeToMin(a.start);
    const aEnd = window.timeToMin(window.apptEnd(a));
    return aStart < end && aEnd > start;
  }).length;
  const slotsFor = (p) => {
    const slots = [];
    workPeriodsFor(p).forEach((period) => {
      for (let m = period.start; m + duration <= period.end; m += slotStep) {
        const end = m + duration;
        if (proWorksAt(p, date, m, end) && busyCount(p.id, m, end) < slotCapacity) {
          slots.push({ start: window.minToTime(m), end: window.minToTime(end) });
        }
      }
    });
    return slots;
  };

  return (
    <window.Modal wide title={`Horários livres por ${terms.pro.toLowerCase()}`} sub={new Date(date + 'T00:00').toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' })} onClose={onClose}
      footer={<><div style={{ flex: 1 }} /><window.Button variant="ghost" onClick={onClose}>Fechar</window.Button></>}>
      <div className="form-grid" style={{ marginBottom: 16 }}>
        <window.Field label="Data">
          <window.Input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
        </window.Field>
        <window.Field label={terms.service || 'Aula'}>
          <window.Select value={serviceId} onChange={(e) => setServiceId(e.target.value)}>
            {services.map((s) => <option key={s.id} value={s.id}>{s.name} · {s.dur} min</option>)}
          </window.Select>
        </window.Field>
      </div>
      <div className="free-slots-list">
        {team.length === 0 ? (
          <window.Empty
            icon="users"
            title={`Nenhum ${terms.pro.toLowerCase()} selecionado`}
            desc={`Selecione um ${terms.pro.toLowerCase()} específico no filtro do calendário para consultar os horários livres.`}
          />
        ) : team.map((p) => {
          const slots = slotsFor(p);
          return (
            <div key={p.id} className="card card-pad free-slots-pro">
              <div className="row between" style={{ gap: 10 }}>
                <div className="row" style={{ gap: 10, minWidth: 0 }}>
                  <window.Avatar initials={p.initials} color={p.color} size={34} />
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontWeight: 700 }}>{p.name}</div>
                    <div className="muted" style={{ fontSize: 12 }}>{slots.length ? `${slots.length} horários livres` : 'Sem horário livre neste dia'}</div>
                  </div>
                </div>
              </div>
              <div className="free-slots-grid">
                {slots.length === 0 ? <span className="muted" style={{ fontSize: 12 }}>Nenhum encaixe disponível.</span> : slots.map((slot) => (
                  <button type="button" key={slot.start} className="chip" onClick={() => openNewAppt({ date, start: slot.start, end: slot.end, proId: p.id, serviceId })}>
                    {slot.start}–{slot.end}
                  </button>
                ))}
              </div>
            </div>
          );
        })}
      </div>
    </window.Modal>
  );
}

function MonthGrid({ anchor, appts, svc, cli, pro, openNewAppt, openAppt, statusMap, DB, sameMonth, isToday, iso, DEFAULT_APPT_START, draggingId, dragMovedRef, onDragStart, onDragEnd, dropOnMonthDay, canMoveAppt, newApptProId, goToDay }) {
  const first = new Date(anchor.getFullYear(), anchor.getMonth(), 1);
  const startOffset = first.getDay();
  const gridStart = new Date(first); gridStart.setDate(first.getDate() - startOffset);
  const cells = Array.from({ length: 42 }, (_, i) => { const d = new Date(gridStart); d.setDate(gridStart.getDate() + i); return d; });
  const dows = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];

  return (
    <div className="month">
      <div className="month-dow">{dows.map(d => <div key={d}>{d}</div>)}</div>
      <div className="month-body">
        {cells.map((d, i) => {
          const list = appts.filter(a => a.date === iso(d)).sort((a, b) => window.timeToMin(a.start) - window.timeToMin(b.start));
          return (
            <div key={i} className={'mcell' + (!sameMonth(d) ? ' out' : '') + (isToday(d) ? ' today' : '')}
              onDragOver={(e) => e.preventDefault()}
              onDrop={(e) => dropOnMonthDay(e, d)}
              onClick={() => openNewAppt({ date: iso(d), start: DEFAULT_APPT_START, proId: newApptProId ? newApptProId() : undefined })}>
              <div className="mnum">{d.getDate()}</div>
              {list.slice(0, 3).map(a => {
                const s = a.manualServiceName ? { name: a.manualServiceName, price: a.manualServicePrice || 0, color: 'violet' } : svc(a.serviceId), c = cli(a.clientId), p = pro(a.proId);
                if (!s) return null;
                const done = a.status === 'concluido' || a.status === 'faltou';
                const clientLabel = a.clientLabel || a.clientName || (c ? c.name : '');
                const proLabel = p ? String(p.name || '').replace('Dra. ', '') : '';
                const peopleLabel = [proLabel, clientLabel].filter(Boolean).join(' · ');
                const st = statusMap[a.status] || { label: a.status || 'Pendente', cls: 'amber' };
                const title = [s.name, peopleLabel, st.label].filter(Boolean).join(' - ');
                return (
                  <div key={a.id} className={'mpill' + (draggingId === a.id ? ' dragging' : '') + (done ? ' mpill-done' : '')} style={{ '--c': done ? 'var(--text-3)' : DB.colorValue(p && p.color), '--cbg': done ? 'var(--surface-2)' : DB.colorBg(p && p.color), opacity: a.status === 'cancelado' ? .55 : 1 }}
                    data-appt-id={a.id}
                    draggable={canMoveAppt(a)}
                    title={canMoveAppt(a) ? `${title}\nArraste para alterar o dia` : `${title}\nAgendado por ${a.createdBy || 'outra pessoa'} — somente o criador pode mover`}
                    onDragStart={(e) => onDragStart(e, a)}
                    onDragEnd={onDragEnd}
                    onClick={(e) => { e.stopPropagation(); if (dragMovedRef.current) return; openAppt(a); }}>
                    <span className={`mpill-state ${st.cls || 'plain'}`}>{st.label}</span>
                    <span className="mpill-copy"><span className="mono" style={{ fontSize: 10, opacity: .8 }}>{a.start}</span> {s.name.split(' ')[0]} {proLabel ? `· ${proLabel.split(' ')[0]}` : ''}{clientLabel ? ` — ${clientLabel.split(' ')[0]}` : ''}</span>
                  </div>
                );
              })}
              {list.length > 3 && <div className="mmore" onClick={(e) => { e.stopPropagation(); goToDay(d); }}>+{list.length - 3} mais</div>}
            </div>
          );
        })}
      </div>
    </div>
  );
}

window.CalendarScreen = CalendarScreen;
