/* ============================================================
   AGENDA — App shell + modais compartilhados
   ============================================================ */
const { useState: uS, useEffect: uE } = React;

/* ---------- NOVO / EDITAR AGENDAMENTO ---------- */
function NewApptModal({ ctx, prefill, onClose }) {
  const { terms, clients, services, team, appts, transactions, toast, user } = ctx;
  const DB = window.DB;
  const editing = !!prefill.editId;
  const isPlainUser = !!(user && user.role === 'user');
  const canManage = !!(user && (user.role === 'admin' || user.role === 'pro'));
  const forcedProId = isPlainUser && user.proId ? Number(user.proId) : 0;

  // pessoa vinculada ao cliente, se houver vínculo e ela ainda estiver cadastrada na área do segmento
  const linkedProId = (clientId) => {
    const c = clients.find(x => x.id === clientId);
    return c && c.proId && team.some(p => p.id === c.proId && !p.archived) ? c.proId : null;
  };
  const hasPrefill = (key) => prefill[key] !== undefined && prefill[key] !== null && prefill[key] !== '';
  const proStart = (proId) => {
    const p = team.find(x => x.id === proId);
    return (p && p.start) || '09:00';
  };
  const initClientId = hasPrefill('clientId') ? Number(prefill.clientId) : '';
  const initClientName = hasPrefill('clientName') ? String(prefill.clientName) : '';
  const linkedPlanFor = (clientId) => {
    if (editing || terms.id !== 'pilates' || !clientId) return null;
    const client = clients.find((item) => Number(item.id) === Number(clientId));
    if (!client || !client.planId) return null;
    return services.find((service) => Number(service.id) === Number(client.planId)) || null;
  };
  const initLinkedPlan = !hasPrefill('serviceId') ? linkedPlanFor(initClientId) : null;
  const initServiceId = hasPrefill('serviceId') ? Number(prefill.serviceId) : (initLinkedPlan ? Number(initLinkedPlan.id) : '');
  const initService = services.find(x => x.id === initServiceId);
  const shouldLaunchServiceFinance = (service, clientId) => {
    if (!service || Number(service.price) <= 0) return false;
    const client = clients.find((item) => Number(item.id) === Number(clientId));
    const coveredByMonthlyBilling = terms.id === 'pilates' && client && client.monthlyBillingEnabled
      && Number(client.planId) === Number(service.id);
    return !coveredByMonthlyBilling;
  };
  const weeklyPlanLimitFor = (service, clientId) => {
    if (!service) return 0;
    const client = clients.find((item) => Number(item.id) === Number(clientId));
    const usesClientOverride = !!(client && Number(client.planId) === Number(service.id));
    const period = usesClientOverride && client.planPeriod ? client.planPeriod : service.planPeriod;
    const frequency = usesClientOverride && Number(client.planFrequency) > 0 ? Number(client.planFrequency) : Number(service.planFrequency);
    return period === 'semana' && frequency > 0 ? Math.min(7, Math.max(1, frequency)) : 0;
  };
  const existingFinanceTx = editing ? (transactions || []).find((t) => Number(t.apptId) === Number(prefill.editId)) : null;
  const hasFinanceTx = !!existingFinanceTx;
  const initLaunchFinance = hasFinanceTx || (hasPrefill('launchFinance')
    ? prefill.launchFinance === true
    : (editing ? hasFinanceTx : shouldLaunchServiceFinance(initService, initClientId)));
  const initProId = forcedProId || (hasPrefill('proId') ? Number(prefill.proId) : (initClientId ? (linkedProId(initClientId) || '') : ''));
  const initStart = hasPrefill('start') ? prefill.start : (initProId ? proStart(initProId) : '');
  const initDate = prefill.date || DB.iso(DB.today);
  const serviceDur = (serviceId) => {
    const s = services.find(x => x.id === Number(serviceId));
    return s ? Math.max(5, Number(s.dur) || 60) : 60;
  };
  const defaultEnd = (start, serviceId = initServiceId) => start ? window.addMinutes(start, serviceDur(serviceId)) : '';
  const initialWeeklyPlanLimit = !editing ? weeklyPlanLimitFor(initService, initClientId) : 0;
  const initialWeekday = new Date(initDate + 'T12:00').getDay();
  const initialRepeatDays = initialWeeklyPlanLimit > 0
    ? { [initialWeekday]: { start: initStart || '09:00', end: (hasPrefill('end') ? prefill.end : defaultEnd(initStart || '09:00', initServiceId)) } }
    : {};
  const [f, setF] = uS({
    clientId: initClientId,
    clientName: initClientName,
    proId: initProId,
    isGroup: false,
    clientIds: [],
    proIds: initProId ? [initProId] : [],
    serviceId: initServiceId,
    manualServiceName: hasPrefill('manualServiceName') ? String(prefill.manualServiceName) : '',
    manualServicePrice: hasPrefill('manualServicePrice') ? prefill.manualServicePrice : '',
    date: initDate,
    start: initStart,
    end: hasPrefill('end') ? prefill.end : defaultEnd(initStart, initServiceId),
    repeat: initialWeeklyPlanLimit > 0 ? 'sem' : 'nao',
    repeatUntil: '',
    repeatDays: initialRepeatDays,
    monthlyWeekendPolicy: hasPrefill('monthlyWeekendPolicy') ? String(prefill.monthlyWeekendPolicy) : 'keep',
    notes: prefill.notes || '',
    notify: false,
    sendEmail: false,
    emailReminderMinutes: '1440',
    showNotification: hasPrefill('showNotification') ? prefill.showNotification !== false : true,
    launchFinance: initLaunchFinance,
    financeDueDate: (existingFinanceTx && (existingFinanceTx.dueDate || existingFinanceTx.date)) || DB.iso(DB.today),
    status: editing ? (prefill.status === 'pendente' ? 'pendente' : 'confirmado') : 'pendente',
    quotaAction: hasPrefill('quotaAction') ? String(prefill.quotaAction) : 'included',
  });
  React.useEffect(() => {
    if (!forcedProId) return;
    setF((prev) => (
      prev.proId === forcedProId && prev.proIds.length === 1 && prev.proIds[0] === forcedProId && !prev.isGroup
        ? prev
        : { ...prev, proId: forcedProId, proIds: [forcedProId], isGroup: false }
    ));
  }, [forcedProId]);
  const [saving, setSaving] = uS(false);
  const [quickAdd, setQuickAdd] = uS(null);
  const [quotaPrompt, setQuotaPrompt] = uS(null);
  const set = (k, num) => (e) => setF({ ...f, [k]: num ? (e.target.value ? Number(e.target.value) : '') : e.target.value });
  // Trocar o cliente leva junto a pessoa e, no Pilates, o plano vinculados.
  const withSelectedClientDefaults = (prev, clientId) => {
    const previousClient = clients.find((item) => Number(item.id) === Number(prev.clientId));
    const previousPlanWasSelected = !!(terms.id === 'pilates' && previousClient && Number(previousClient.planId) > 0
      && Number(previousClient.planId) === Number(prev.serviceId));
    const clientName = clientId ? '' : prev.clientName;
    const lp = clientId ? linkedProId(clientId) : null;
    const plan = linkedPlanFor(clientId);
    let next = { ...prev, clientId, clientName };
    if (plan) {
      const serviceId = Number(plan.id);
      const end = prev.start ? defaultEnd(prev.start, serviceId) : prev.end;
      const weeklyPlanLimit = weeklyPlanLimitFor(plan, clientId);
      const weekday = prev.date ? new Date(prev.date + 'T12:00').getDay() : new Date().getDay();
      next = {
        ...next,
        serviceId,
        manualServiceName: '',
        manualServicePrice: '',
        end,
        launchFinance: shouldLaunchServiceFinance(plan, clientId),
        ...(weeklyPlanLimit > 0 ? {
          repeat: 'sem',
          repeatDays: { [weekday]: { start: prev.start || '09:00', end: end || defaultEnd(prev.start || '09:00', serviceId) } },
        } : {}),
      };
    } else if (clientId && previousPlanWasSelected) {
      next = {
        ...next,
        serviceId: '',
        end: prev.start ? window.addMinutes(prev.start, 60) : prev.end,
        launchFinance: false,
        repeat: 'nao',
        repeatDays: {},
      };
    }
    if (forcedProId) return { ...next, proId: forcedProId, proIds: [forcedProId] };
    if (!lp || (prev.proId && prev.proId !== lp)) return next;
    return { ...next, proId: lp };
  };
  const setClient = (e) => {
    const clientId = e.target.value ? Number(e.target.value) : '';
    setF((prev) => withSelectedClientDefaults(prev, clientId));
  };
  const setClientName = (e) => {
    setF((prev) => ({ ...prev, clientName: e.target.value, clientId: '', proId: forcedProId || prev.proId, proIds: forcedProId ? [forcedProId] : prev.proIds }));
  };

  const addGroupClient = (e) => {
    const cid = e.target.value ? Number(e.target.value) : '';
    if (cid && !f.clientIds.includes(cid)) {
      setF({ ...f, clientIds: [...f.clientIds, cid] });
    }
  };
  const removeGroupClient = (cid) => {
    setF({ ...f, clientIds: f.clientIds.filter(id => id !== cid) });
  };
  const setPro = (e) => {
    if (forcedProId) return;
    const proId = e.target.value ? Number(e.target.value) : '';
    // A escolha do instrutor não deve alterar o horário já informado.
    setF((prev) => ({ ...prev, proId }));
  };
  const setService = (e) => {
    const serviceId = e.target.value ? Number(e.target.value) : '';
    const selected = services.find(x => x.id === serviceId);
    const hasPrice = !!(selected && Number(selected.price) > 0);
    setF((prev) => {
      const end = prev.start ? defaultEnd(prev.start, serviceId) : prev.end;
      const weeklyPlanLimit = !editing ? weeklyPlanLimitFor(selected, prev.clientId) : 0;
      const weekday = prev.date ? new Date(prev.date + 'T12:00').getDay() : new Date().getDay();
      return {
        ...prev,
        serviceId,
        manualServiceName: serviceId ? '' : prev.manualServiceName,
        manualServicePrice: serviceId ? '' : prev.manualServicePrice,
        end,
        launchFinance: hasPrice ? (editing ? prev.launchFinance : shouldLaunchServiceFinance(selected, prev.clientId)) : false,
        ...(weeklyPlanLimit > 0 ? {
          repeat: 'sem',
          repeatDays: { [weekday]: { start: prev.start || '09:00', end: end || defaultEnd(prev.start || '09:00', serviceId) } },
        } : {}),
      };
    });
  };
  const setStart = (e) => {
    const start = e.target.value;
    setF((prev) => ({ ...prev, start, end: start ? defaultEnd(start, prev.serviceId) : '' }));
  };
  const setEnd = (e) => {
    setF((prev) => ({ ...prev, end: e.target.value }));
  };
  const svc = services.find(s => s.id === f.serviceId) || null;
  const pro = team.find(p => p.id === f.proId) || null;
  const cli = clients.find(c => c.id === f.clientId) || null;
  const weeklyPlanLimit = !editing ? weeklyPlanLimitFor(svc, f.clientId) : 0;
  const apptIsFeminine = ['Aula', 'Consulta', 'Sessão'].includes(terms.appt);
  React.useEffect(() => {
    if (weeklyPlanLimit <= 0 || f.repeat !== 'sem') return;
    const selectedDays = Object.keys(f.repeatDays || {});
    if (selectedDays.length <= weeklyPlanLimit) return;
    const limitedDays = selectedDays.slice(0, weeklyPlanLimit).reduce((result, key) => {
      result[key] = f.repeatDays[key];
      return result;
    }, {});
    setF((prev) => ({ ...prev, repeatDays: limitedDays }));
  }, [weeklyPlanLimit, f.repeat]);
  const responsibleTeam = forcedProId ? team.filter((p) => Number(p.id) === forcedProId && !p.archived) : team.filter((p) => !p.archived);
  const duration = f.start && f.end ? window.timeToMin(f.end) - window.timeToMin(f.start) : 0;
  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(ctx.settings.calendar_start, 7);
  const hEnd = Math.max(hStart + 1, Math.min(24, readHour(ctx.settings.calendar_end, 21)));
  const slotCapacity = ctx.businessType === 'pilates' ? Math.max(1, Math.min(99, Number(ctx.settings.appointment_slot_capacity) || 1)) : 1;
  const slotInterval = Math.max(5, Math.min(240, Number(ctx.settings.appointment_slot_interval) || 15));
  const slotGap = ctx.businessType === 'pilates' ? Math.max(0, Math.min(240, Number(ctx.settings.appointment_slot_gap) || 0)) : 0;
  const slotStepFor = (dur) => ctx.businessType === 'pilates' ? Math.max(slotInterval, dur + slotGap) : slotInterval;
  const selectedProIds = f.isGroup ? f.proIds : (f.proId ? [f.proId] : []);
  const emailClients = (f.isGroup ? clients.filter((c) => f.clientIds.includes(c.id)) : (cli ? [cli] : [])).filter((c) => c && String(c.email || '').trim());
  const emailPros = selectedProIds
    .map((pid) => team.find((p) => Number(p.id) === Number(pid)))
    .filter((p) => p && String(p.email || '').trim());
  const hasEmailRecipients = emailClients.length > 0 || emailPros.length > 0;
  uE(() => {
    if (editing || !canManage || !(user && user.hasAppointmentEmail)) return;
    setF((current) => {
      if (current.sendEmail === hasEmailRecipients) return current;
      return { ...current, sendEmail: hasEmailRecipients };
    });
  }, [editing, canManage, !!(user && user.hasAppointmentEmail), hasEmailRecipients]);
  const emailRecipientLabel = hasEmailRecipients
    ? 'Enviar link de confirmação por E-mail'
    : 'Confirmação por E-mail indisponível sem e-mail cadastrado';
  const reminderOptions = [
    { value: '0', label: 'Somente envio inicial' },
    { value: '120', label: 'Lembrar 2h antes' },
    { value: '720', label: 'Lembrar 12h antes' },
    { value: '1440', label: 'Lembrar 24h antes' },
    { value: '2880', label: 'Lembrar 2 dias antes' },
    { value: '4320', label: 'Lembrar 3 dias antes' },
  ];
  const monthlyWeekendOptions = [
    { value: 'keep', label: 'Manter no dia mesmo se cair no fim de semana' },
    { value: 'previous_business_day', label: 'Antecipar para o dia util anterior' },
    { value: 'next_business_day', label: 'Adiar para o proximo dia util' },
  ];
  const hasManualService = String(f.manualServiceName || '').trim() !== '';
  const effectiveServiceId = f.serviceId || (isPlainUser && services[0] ? services[0].id : '');
  const availabilityForDate = (p, date) => {
    if (!p || !date || !p.availability) return null;
    const d = new Date(date + 'T00:00');
    const row = p.availability[String(d.getDay())];
    return row || null;
  };
  const worksAt = (p, date, start, end) => {
    const row = availabilityForDate(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 workBoundsFor = (p, date) => {
    const row = availabilityForDate(p, date);
    if (row && !row.enabled) return null;
    const start = window.timeToMin((row && row.start) || p.start || '00:00');
    const end = window.timeToMin((row && row.end) || p.end || '23:59');
    return { start, end };
  };
  const apptHasPro = (a, proId) => a.proId === proId || (a.participants || []).some((p) => p.proId === proId);
  const hasBusy = (proId, start, end) => (appts || []).filter((a) => {
    if (a.date !== f.date || a.status === 'cancelado' || !apptHasPro(a, proId)) return false;
    if (editing && a.id === prefill.editId) return false;
    const aStart = window.timeToMin(a.start);
    const aEnd = window.timeToMin(window.apptEnd(a));
    return aStart < end && aEnd > start;
  }).length >= slotCapacity;
  const commonFreeSlots = (() => {
    if (!f.date || !effectiveServiceId || selectedProIds.length === 0) return [];
    const dur = Math.max(15, duration > 0 ? duration : serviceDur(effectiveServiceId));
    const step = slotStepFor(dur);
    const bounds = selectedProIds.map((pid) => {
      const p = team.find((x) => x.id === pid);
      return p ? workBoundsFor(p, f.date) : null;
    });
    if (bounds.some((b) => !b)) return [];
    const startMinute = Math.max(hStart * 60, ...bounds.map((b) => b.start));
    const endMinute = Math.min(hEnd * 60, ...bounds.map((b) => b.end));
    const slots = [];
    for (let m = startMinute; m + dur <= endMinute; m += step) {
      const end = m + dur;
      const ok = selectedProIds.every((pid) => {
        const p = team.find((x) => x.id === pid);
        if (!p) return false;
        return worksAt(p, f.date, m, end) && !hasBusy(pid, m, end);
      });
      if (ok) slots.push({ start: window.minToTime(m), end: window.minToTime(end) });
    }
    return slots;
  })();
  const hasClientInfo = f.isGroup || !!f.clientId || String(f.clientName || '').trim() !== '';
  const valid = !!((effectiveServiceId || hasManualService) && hasClientInfo && (!f.isGroup ? !!f.proId : f.proIds.length > 0) && f.date && f.start && f.end && duration > 0);
  const generatedTimes = [];
  const timeStep = effectiveServiceId ? slotStepFor(Math.max(15, serviceDur(effectiveServiceId))) : slotInterval;
  for (let m = hStart * 60; m < hEnd * 60; m += timeStep) generatedTimes.push(window.minToTime(m));
  const repeatDayValues = Object.values(f.repeatDays || {});
  const sortTimes = (values) => [...new Set(values.filter((value) => /^\d{2}:\d{2}$/.test(String(value || ''))))]
    .sort((a, b) => window.timeToMin(a) - window.timeToMin(b));
  const times = sortTimes([...generatedTimes, f.start, ...repeatDayValues.map((day) => day.start)]);
  const endTimes = sortTimes([
    ...times,
    ...(hEnd < 24 ? [window.minToTime(hEnd * 60)] : []),
    f.end,
    ...repeatDayValues.map((day) => day.end),
  ]);
  const ini = (n) => n.split(' ').map(x => x[0]).slice(0, 2).join('').toUpperCase();
  const visibleTeam = team.filter((p) => !p.systemIdentity && !p.archived);
  const maxPros = (ctx.limits && ctx.limits.maxPros) || 0;
  const maxUsers = (ctx.limits && ctx.limits.maxUsers) || 0;
  const proLimitReached = maxPros > 0 && visibleTeam.length >= maxPros;
  const canAddPro = !!(user && user.hasTeam) && !proLimitReached && !isPlainUser;
  const canQuickAddClient = !isPlainUser;
  const accessLimitReached = maxUsers > 0 && ((user && user.loginUserCount) || 1) >= maxUsers;
  const manualPrice = Number(f.manualServicePrice) || 0;
  const linkedPlanSelected = !!(terms.id === 'pilates' && cli && Number(cli.planId) > 0 && Number(cli.planId) === Number(f.serviceId));
  const serviceCoveredByMonthlyBilling = linkedPlanSelected && !!cli.monthlyBillingEnabled;
  const canLaunchFinance = !!(((svc && Number(svc.price) > 0) || (hasManualService && manualPrice > 0)) && user && user.hasFinance);
  const launchFinanceOn = hasFinanceTx || !!f.launchFinance;

  const saveQuickClient = async (data) => {
    try {
      const created = await ctx.addClient(data);
      if (!created) return;
      setF((prev) => prev.isGroup
        ? { ...prev, clientIds: prev.clientIds.includes(created.id) ? prev.clientIds : [...prev.clientIds, created.id] }
        : withSelectedClientDefaults(prev, created.id));
      toast(`${terms.client} cadastrado e selecionado!`);
      setQuickAdd(null);
    } catch (e) {
      toast(e.message, 'x');
    }
  };

  const saveQuickService = async (data) => {
    try {
      const created = await ctx.addService(data);
      if (!created) return;
      setF((prev) => ({ ...prev, serviceId: created.id, manualServiceName: '', manualServicePrice: '', end: prev.start ? window.addMinutes(prev.start, Math.max(5, Number(created.dur) || 30)) : prev.end, launchFinance: Number(created.price) > 0 }));
      toast(`${terms.service} cadastrado e selecionado!`);
      setQuickAdd(null);
    } catch (e) { toast(e.message, 'x'); }
  };

  const saveQuickPro = async (data) => {
    try {
      const created = await ctx.addPro(data);
      if (!created) return;
      setF((prev) => prev.isGroup
        ? { ...prev, proIds: prev.proIds.includes(created.id) ? prev.proIds : [...prev.proIds, created.id] }
        : { ...prev, proId: created.id });
      toast(`${terms.pro} cadastrado e selecionado!`);
      setQuickAdd(null);
    } catch (e) {
      toast(e.message, 'x');
    }
  };

  const handleRepeatChange = (e) => {
    const repeat = e.target.value;
    if (repeat === 'sem' && f.date) {
      // pré-seleciona automaticamente o dia da semana atual com os horários preenchidos
      const w = new Date(f.date + 'T12:00').getDay();
      setF({ ...f, repeat, repeatDays: { ...f.repeatDays, [w]: { start: f.start || '', end: f.end || '' } } });
    } else {
      setF({ ...f, repeat });
    }
  };
  const monthEndIso = (monthValue) => {
    const [year, month] = String(monthValue || '').split('-').map(Number);
    if (!year || !month) return '';
    return new Date(year, month, 0).toISOString().slice(0, 10);
  };
  const planQuotaInfo = () => {
    if (ctx.businessType !== 'pilates') return null;
    if (!cli || !cli.planId || !svc || !f.date) return null;
    const plan = services.find((service) => Number(service.id) === Number(cli.planId));
    const limit = Math.max(0, Number(cli.planFrequency || (plan && plan.planFrequency)) || 0);
    if (!plan || limit <= 0 || (!svc.planFrequency && Number(svc.id) !== Number(cli.planId))) return null;
    const period = cli.planPeriod || plan.planPeriod || 'semana';
    const target = new Date(f.date + 'T12:00');
    let start;
    let end;
    if (period === 'mes') {
      const dueDay = Math.max(1, Math.min(31, Number(cli.planDueDay) || 1));
      const anchor = (year, month) => new Date(year, month, Math.min(dueDay, new Date(year, month + 1, 0).getDate()), 12, 0, 0);
      start = anchor(target.getFullYear(), target.getMonth());
      if (target < start) start = anchor(target.getFullYear(), target.getMonth() - 1);
      const next = anchor(start.getFullYear(), start.getMonth() + 1);
      end = new Date(next); end.setDate(end.getDate() - 1);
    } else {
      start = new Date(target);
      start.setDate(target.getDate() - ((target.getDay() + 6) % 7));
      end = new Date(start); end.setDate(start.getDate() + 6);
    }
    const startISO = DB.iso(start);
    const endISO = DB.iso(end);
    const count = (appts || []).filter((a) => {
      if (Number(a.clientId) !== Number(cli.id) || a.status === 'cancelado' || a.quotaAction === 'extra' || a.quotaAction === 'replacement') return false;
      if (editing && Number(a.id) === Number(prefill.editId)) return false;
      const appointmentService = services.find((service) => Number(service.id) === Number(a.serviceId));
      return appointmentService && (Number(appointmentService.planFrequency) > 0 || Number(appointmentService.id) === Number(cli.planId)) && a.date >= startISO && a.date <= endISO;
    }).length;
    const extraPrice = Number(cli.extraClassPrice) > 0 ? Number(cli.extraClassPrice) : (Number(plan.price) > 0 ? Number(plan.price) : Number(svc.price) || 0);
    return { count, limit, period, startISO, endISO, extraPrice, credits: Math.max(0, Number(cli.replacementCredits) || 0), planName: plan.name };
  };

  const save = async (quotaChoice = '') => {
    const selectedQuotaAction = typeof quotaChoice === 'string' && ['extra', 'replacement'].includes(quotaChoice) ? quotaChoice : '';
    if (saving) return;
    if (launchFinanceOn && !f.financeDueDate) {
      toast('Informe a data para receber.', 'info');
      return;
    }
    if (!valid) {
      toast(!hasClientInfo
        ? `Informe ${terms.client.toLowerCase()} para este ${terms.appt.toLowerCase()}.`
        : (duration <= 0 && f.start && f.end ? 'O horário final deve ser maior que o início.' : `Selecione ${terms.pro.toLowerCase()} e horários.`), 'info');
      return;
    }
    const quota = !f.isGroup && f.quotaAction === 'included' ? planQuotaInfo() : null;
    if (quota && quota.count >= quota.limit && !selectedQuotaAction) {
      if (f.repeat !== 'nao') {
        toast('O limite do plano foi atingido. Reposições e aulas extras devem ser agendadas individualmente.', 'info');
        return;
      }
      setQuotaPrompt(quota);
      return;
    }
    setQuotaPrompt(null);
    setSaving(true);

    const finalRepeatUntil = (f.repeat === 'mes' && f.repeatUntil && f.repeatUntil.length === 7)
      ? monthEndIso(f.repeatUntil)
      : f.repeatUntil;
    try {
      let chosenService = svc;
      let serviceIdToSave = effectiveServiceId;
      if (!serviceIdToSave && hasManualService) {
        chosenService = {
          name: String(f.manualServiceName).trim(),
          dur: Math.max(5, duration || 30),
          price: manualPrice,
        };
      }
      await ctx.addAppt({ ...f, quotaAction: selectedQuotaAction || f.quotaAction || 'included', sendEmail: canManage && user.hasAppointmentEmail && hasEmailRecipients && f.sendEmail, serviceId: serviceIdToSave, repeatUntil: finalRepeatUntil, editId: prefill.editId, editScope: prefill.editScope || 'single' });
        const fem = ['Aula', 'Consulta', 'Sessão'].includes(terms.appt);
        const art = fem ? 'a' : 'o';
        toast(editing ? `${terms.appt} atualizad${art}!` : `${terms.appt} criad${art}!`, 'calendar');
      if (f.notify && !editing && cli && cli.phone) {
        const when = new Date(f.date + 'T00:00').toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' });
        const article = ctx.businessType === 'pilates' ? 'Sua' : 'Seu';
        window.waOpen(cli.phone, `Olá, ${cli.name.split(' ')[0]}! ${article} ${terms.appt.toLowerCase()} de ${chosenService.name} está marcado para ${when}, das ${f.start} às ${f.end}, com ${pro.name}. Podemos confirmar?`);
      }
      onClose();
    } catch (e) {
      toast(e.message, 'x'); // ex.: conflito de horário — mantém o modal aberto
    }
    setSaving(false);
  };

  return (
    <>
    <window.Modal wide title={editing ? `Remarcar ${terms.appt.toLowerCase()}` : `${apptIsFeminine ? 'Nova' : 'Novo'} ${terms.appt.toLowerCase()}`}
      sub={`Selecione ${terms.client.toLowerCase()}, ${terms.service.toLowerCase()} e ${terms.pro.toLowerCase()}`} onClose={() => quickAdd ? setQuickAdd(null) : onClose()}
      footer={<>
        <div className="row" style={{ gap: 8, flex: 1, color: 'var(--text-3)', fontSize: 12 }}>
          <window.Icon name="info" size={14} /> Agendado por <b style={{ color: 'var(--text-2)' }}>Você ({(user && (user.name || user.login)) || 'você'})</b>
        </div>
        <window.Button variant="ghost" onClick={onClose}>Cancelar</window.Button>
        <window.Button variant="primary" icon="check" disabled={saving || !valid} onClick={save}>
          {saving ? <><span className="btn-spinner" /> Salvando...</> : (editing ? 'Salvar alterações' : 'Confirmar ' + terms.appt.toLowerCase())}
        </window.Button>
      </>}>
      {/* destaque: para quem / com quem */}
      <div className="card" style={{ background: 'var(--surface-2)', border: '1px solid var(--border)', padding: 14, marginBottom: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
        {!editing && !isPlainUser && user.hasGroupMeetings && (
          <label className="row" style={{ gap: 6, alignSelf: 'flex-start', cursor: 'pointer', padding: '2px 8px', background: 'var(--surface-3)', borderRadius: 4 }}>
            <input type="checkbox" checked={f.isGroup} onChange={(e) => setF({ ...f, isGroup: e.target.checked, clientIds: f.clientId ? [f.clientId] : [], proIds: f.proId ? [f.proId] : [] })} />
            <span style={{ fontSize: 13, fontWeight: 600 }}>{terms.appt} em grupo / reunião de {terms.pros.toLowerCase()}</span>
          </label>
        )}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
          <window.Field label={f.isGroup ? `Adicionar ${terms.clients}` : terms.client}>
            {f.isGroup ? (
              <div className="stack" style={{ gap: 8 }}>
                <div className="row" style={{ gap: 8 }}>
                <window.Select value="" onChange={addGroupClient} style={{ flex: 1 }}>
                  <option value="">Selecione para adicionar...</option>
                  {clients.filter(c => !f.clientIds.includes(c.id)).map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
                </window.Select>
                {canQuickAddClient && <button type="button" className="iconbtn quick-add-btn" title={`Cadastrar ${terms.client.toLowerCase()}`} aria-label={`Cadastrar ${terms.client.toLowerCase()}`} onClick={() => setQuickAdd('client')}><window.Icon name="plus" size={17} /></button>}
                </div>
                {f.clientIds.length > 0 && (
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 4 }}>
                    {f.clientIds.map(cid => {
                      const c = clients.find(x => x.id === cid);
                      return (
                        <div key={cid} className="row" style={{ gap: 6, padding: '4px 8px', background: 'var(--surface-3)', border: '1px solid var(--border)', borderRadius: 12, fontSize: 13 }}>
                          {c ? c.name.split(' ')[0] : '...'}
                          <div style={{ cursor: 'pointer', opacity: 0.6 }} onClick={() => removeGroupClient(cid)}>
                            <window.Icon name="x" size={14} />
                          </div>
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            ) : isPlainUser ? (
              <div className="row" style={{ gap: 10 }}>
                <window.Avatar initials={f.clientName ? ini(f.clientName) : '--'} color={pro ? pro.color : 'blue'} size={36} />
                <window.Input value={f.clientName} onChange={setClientName} placeholder={`Nome do ${terms.client.toLowerCase()} ou observação rápida`} style={{ flex: 1 }} />
              </div>
            ) : (
              <div className="stack" style={{ gap: 8 }}>
                <div className="row" style={{ gap: 10 }}>
                  <window.Avatar initials={cli ? ini(cli.name) : (f.clientName ? ini(f.clientName) : '--')} color={pro ? pro.color : 'blue'} size={36} />
                  <window.Select value={f.clientId} onChange={setClient} style={{ flex: 1 }}>
                    <option value="">Selecione {terms.client.toLowerCase()}</option>
                    {clients.map(c => <option key={c.id} value={c.id}>{c.name}{c.companyName ? ' · ' + c.companyName + (c.companyRole ? ' / ' + c.companyRole : '') : ''}</option>)}
                  </window.Select>
                  {canQuickAddClient && <button type="button" className="iconbtn quick-add-btn" title={`Cadastrar ${terms.client.toLowerCase()}`} aria-label={`Cadastrar ${terms.client.toLowerCase()}`} onClick={() => setQuickAdd('client')}><window.Icon name="plus" size={17} /></button>}
                </div>
                {!f.clientId && <window.Input value={f.clientName} onChange={setClientName} placeholder={`Nome do ${terms.client.toLowerCase()} sem cadastro`} />}
              </div>
            )}
          </window.Field>
          <window.Field label={f.isGroup ? `Adicionar Equipe` : `${terms.pro} (${terms.pros})`}>
            {f.isGroup ? (
              <div className="stack" style={{ gap: 8 }}>
                <div className="row" style={{ gap: 8 }}>
                <window.Select value="" onChange={(e) => {
                  const pid = Number(e.target.value);
                  if (pid && !f.proIds.includes(pid)) setF({ ...f, proIds: [...f.proIds, pid] });
                }} style={{ flex: 1 }}>
                  <option value="">Selecione para adicionar...</option>
                  {responsibleTeam.filter(p => !f.proIds.includes(p.id)).map(p => <option key={p.id} value={p.id}>{p.name}{p.systemIdentity ? ' · ' + (p.role || 'Usuário') : ''}</option>)}
                </window.Select>
                {canAddPro && <button type="button" className="iconbtn quick-add-btn" title={`Cadastrar ${terms.pro.toLowerCase()}`} aria-label={`Cadastrar ${terms.pro.toLowerCase()}`} onClick={() => setQuickAdd('pro')}><window.Icon name="plus" size={17} /></button>}
                </div>
                {f.proIds.length > 0 && (
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 4 }}>
                    {f.proIds.map(pid => {
                      const p = team.find(x => x.id === pid);
                      return (
                        <div key={pid} className="row" style={{ gap: 6, padding: '4px 8px', background: 'var(--surface-3)', border: '1px solid var(--border)', borderRadius: 12, fontSize: 13 }}>
                          {p ? p.name.split(' ')[0] : '...'}
                          <div style={{ cursor: 'pointer', opacity: 0.6 }} onClick={() => setF({ ...f, proIds: f.proIds.filter(x => x !== pid) })}>
                            <window.Icon name="x" size={14} />
                          </div>
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            ) : (
              <div className="row" style={{ gap: 10 }}>
                <window.Avatar initials={pro ? pro.initials : '--'} color={pro ? pro.color : 'blue'} size={36} />
                <window.Select value={f.proId} onChange={setPro} disabled={!!forcedProId} style={{ flex: 1 }}>
                  <option value="">Selecione {terms.pro.toLowerCase()}</option>
                  {responsibleTeam.map(p => <option key={p.id} value={p.id}>{p.name}{p.systemIdentity ? ' · ' + (p.role || 'Usuário') : ` · ${p.role}`}</option>)}
                </window.Select>
                {canAddPro && <button type="button" className="iconbtn quick-add-btn" title={`Cadastrar ${terms.pro.toLowerCase()}`} aria-label={`Cadastrar ${terms.pro.toLowerCase()}`} onClick={() => setQuickAdd('pro')}><window.Icon name="plus" size={17} /></button>}
              </div>
            )}
            {!f.isGroup && (() => {
              const lp = linkedProId(f.clientId);
              if (!lp || !cli) return null;
              const lpro = team.find(p => p.id === lp);
              if (!lpro) return null;
              return lp === f.proId
                ? <div className="row" style={{ gap: 6, marginTop: 7, fontSize: 12, color: 'var(--success)' }}><window.Icon name="check" size={13} /> {terms.pro} vinculado a {cli.name.split(' ')[0]}</div>
                : <div className="row" style={{ gap: 6, marginTop: 7, fontSize: 12, color: 'var(--warning)' }}><window.Icon name="info" size={13} /> {cli.name.split(' ')[0]} tem vínculo com {lpro.name}</div>;
            })()}
          </window.Field>
        </div>
      </div>

      <div className="form-grid">
        <window.Field label={terms.service} optional={isPlainUser || hasManualService} full>
          <div className="row" style={{ gap: 8 }}>
            <window.Select value={f.serviceId} onChange={setService} style={{ flex: 1 }}>
              <option value="">{isPlainUser ? `Sem ${terms.service.toLowerCase()} específico` : `Selecione ${terms.service.toLowerCase()}`}</option>
              {services.map(s => <option key={s.id} value={s.id}>{s.name} · {s.dur} min{s.price > 0 ? ' · ' + DB.money0(s.price) : ''}</option>)}
            </window.Select>
            {!isPlainUser && <button type="button" className="iconbtn quick-add-btn" title={`Cadastrar ${terms.service.toLowerCase()}`} aria-label={`Cadastrar ${terms.service.toLowerCase()}`} onClick={() => setQuickAdd('service')}><window.Icon name="plus" size={17} /></button>}
          </div>
          {linkedPlanSelected && <div className="field-hint ok"><window.Icon name="check" size={12} /> Plano vinculado a {cli.name} selecionado.</div>}
        </window.Field>
        {!f.serviceId && !isPlainUser && <>
          <window.Field label={`${terms.service} manual`} optional>
            <window.Input value={f.manualServiceName} onChange={set('manualServiceName')} placeholder="Digite um compromisso avulso" />
          </window.Field>
          <window.Field label="Valor manual" optional>
            <window.MoneyInput value={f.manualServicePrice} onChange={(value) => setF((prev) => ({ ...prev, manualServicePrice: value, launchFinance: Number(value) > 0 && !editing }))} />
          </window.Field>
          <div className="field-hint" style={{ gridColumn: '1 / -1', marginTop: -8 }}>Uso somente neste agendamento; não será incluído no cadastro de compromissos.</div>
        </>}
        {canLaunchFinance && (
          <div style={{ gridColumn: '1 / -1', marginTop: -8, marginBottom: 8, padding: '8px 12px', background: 'var(--surface-2)', borderRadius: 8, border: '1px solid var(--border)' }}>
            <label className="row between" style={{ cursor: hasFinanceTx ? 'default' : 'pointer', opacity: hasFinanceTx ? .78 : 1, padding: '4px 0' }}>
              <span className="row" style={{ gap: 9 }}>
                <window.Icon name="card" size={16} style={{ color: 'var(--text-2)' }} />
                <span style={{ fontSize: 13, fontWeight: 500 }}>
                  {hasFinanceTx ? 'Já lançado no financeiro' : `Lançar no financeiro (${DB.money0(svc ? svc.price : manualPrice)})`}
                </span>
              </span>
              <window.Switch on={launchFinanceOn} onClick={() => !hasFinanceTx && setF((prev) => ({ ...prev, launchFinance: !prev.launchFinance, financeDueDate: prev.financeDueDate || DB.iso(DB.today) }))} />
            </label>
            {launchFinanceOn && <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 12, alignItems: 'end', padding: '6px 0 2px' }}>
              <window.Field label="Data para receber">
                <window.Input type="date" value={f.financeDueDate} disabled={hasFinanceTx} onChange={set('financeDueDate')} />
              </window.Field>
              <div className="field-hint" style={{ paddingBottom: 9 }}>{hasFinanceTx ? 'O lançamento já foi criado no financeiro.' : 'Será lançado como A receber nesta data.'}</div>
            </div>}
            {serviceCoveredByMonthlyBilling && !launchFinanceOn && <div className="field-hint" style={{ paddingTop: 5 }}>Esta aula já faz parte da mensalidade automática; o lançamento individual ficou desativado por padrão.</div>}
          </div>
        )}
        <div className="appt-time-grid">
          <window.Field label="Data"><window.Input type="date" value={f.date} onChange={set('date')} /></window.Field>
          <window.Field label="Início">
            <window.Select value={f.start} onChange={setStart}>
              <option value="">Selecione</option>
              {times.map(t => <option key={t} value={t}>{t}</option>)}
            </window.Select>
          </window.Field>
          <window.Field label="Fim">
            <window.Select value={f.end} onChange={setEnd}>
              <option value="">Selecione</option>
              {endTimes.map(t => <option key={t} value={t}>{t}</option>)}
            </window.Select>
          </window.Field>
        </div>
        {selectedProIds.length > 1 && f.serviceId && (
          <div className="card card-pad" style={{ gridColumn: '1 / -1', padding: 12, background: 'var(--surface-2)', border: '1px solid var(--border)' }}>
            <div className="row between" style={{ marginBottom: 10 }}>
              <div style={{ fontSize: 13, fontWeight: 600 }}>{'Hor\u00e1rios livres em comum'}</div>
              <span className="muted" style={{ fontSize: 12 }}>{selectedProIds.length} participante{selectedProIds.length > 1 ? 's' : ''}</span>
            </div>
            {commonFreeSlots.length ? (
              <div className="row wrap" style={{ gap: 7 }}>
                {commonFreeSlots.map((slot) => (
                  <button key={slot.start} type="button" className={'chip' + (f.start === slot.start ? ' active' : '')}
                    onClick={() => setF((prev) => ({ ...prev, start: slot.start, end: slot.end }))}>
                    {slot.start} - {slot.end}
                  </button>
                ))}
              </div>
            ) : (
              <div className="muted" style={{ fontSize: 12.5 }}>{'Nenhum hor\u00e1rio livre encontrado neste dia para todos os selecionados.'}</div>
            )}
          </div>
        )}
        {!editing && (
          <div className={'appt-repeat-grid' + (f.repeat === 'nao' ? ' two' : '')}>
            <window.Field label="Repetir">
              <window.Select value={f.repeat} onChange={handleRepeatChange}>
                <option value="nao">Não se repete</option><option value="sem">Toda semana</option>
                <option value="quin">A cada 15 dias</option><option value="mes">Todo mês</option>
              </window.Select>
            </window.Field>
            {f.repeat !== 'nao' &&
              <window.Field label={f.repeat === 'mes' ? "Até qual mês" : "Até a data"} optional>
                <window.Input
                  type={f.repeat === 'mes' ? 'month' : 'date'}
                  value={f.repeatUntil || ''}
                  onChange={set('repeatUntil')}
                  min={f.repeat === 'mes' ? f.date.substring(0, 7) : f.date}
                />
                <div className="field-hint">Em branco, os {terms.appt.toLowerCase()}s serão criados por 1 ano.</div>
              </window.Field>
            }
            {f.repeat === 'mes' && (
              <window.Field label="Se cair no sábado ou domingo">
                <window.Select value={f.monthlyWeekendPolicy || 'keep'} onChange={set('monthlyWeekendPolicy')}>
                  {monthlyWeekendOptions.map((opt) => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
                </window.Select>
                <div className="field-hint">Quando o mês não tiver esse dia, o sistema usa o último dia válido e depois aplica esta regra.</div>
              </window.Field>
            )}
            <window.Field label="Status">
              <window.Select value={f.status} onChange={set('status')}>
                <option value="confirmado">Confirmado</option><option value="pendente">Pendente</option>
              </window.Select>
            </window.Field>
          </div>
        )}
        
        {f.repeat === 'sem' && (
          <div className="card card-pad" style={{ gridColumn: '1 / -1', padding: 14, background: 'var(--surface-2)', border: '1px solid var(--border)' }}>
            <div style={{ fontSize: 13, fontWeight: 600, marginBottom: weeklyPlanLimit > 0 ? 4 : 10 }}>Dias e horários {apptIsFeminine ? 'das próximas' : 'dos próximos'} {terms.appt.toLowerCase()}s</div>
            {weeklyPlanLimit > 0 && <div className="muted" style={{ fontSize: 12, marginBottom: 10 }}>Este plano permite {weeklyPlanLimit} {weeklyPlanLimit === 1 ? 'dia' : 'dias'} por semana.</div>}
            
            <div className="row" style={{ gap: 8, flexWrap: 'wrap', marginBottom: 14 }}>
              {['Domingo','Segunda','Terça','Quarta','Quinta','Sexta','Sábado'].map((dayName, w) => {
                const repeatDays = f.repeatDays || {};
                const isSelected = !!repeatDays[w];
                const limitReached = weeklyPlanLimit > 0 && Object.keys(repeatDays).length >= weeklyPlanLimit;
                const isDisabled = !isSelected && limitReached;
                return (
                  <div key={w} onClick={() => {
                    if (!isSelected) {
                      if (isDisabled) {
                        toast(`Este plano permite somente ${weeklyPlanLimit} ${weeklyPlanLimit === 1 ? 'dia' : 'dias'} por semana.`, 'info');
                        return;
                      }
                      setF({ ...f, repeatDays: { ...repeatDays, [w]: { start: f.start || '09:00', end: f.end || '10:00' } } });
                    } else {
                      const copy = { ...repeatDays };
                      delete copy[w];
                      setF({ ...f, repeatDays: copy });
                    }
                  }} style={{
                    padding: '6px 14px', borderRadius: 20, cursor: isDisabled ? 'not-allowed' : 'pointer', fontSize: 13, fontWeight: 600,
                    background: isSelected ? 'var(--primary)' : 'var(--surface-3)',
                    color: isSelected ? '#fff' : 'var(--text-1)',
                    border: isSelected ? '1px solid var(--primary)' : '1px solid var(--border)',
                    transition: 'all 0.2s', userSelect: 'none', opacity: isDisabled ? .5 : 1
                  }}>
                    {dayName.substring(0, 3)}
                  </div>
                );
              })}
            </div>

            {Object.keys(f.repeatDays || {}).length > 0 && (
              <div className="stack" style={{ gap: 8 }}>
                {Object.keys(f.repeatDays || {}).map((wStr) => {
                  const w = Number(wStr);
                  const rd = (f.repeatDays || {})[w];
                  return (
                    <div className="row" key={w} style={{ gap: 10, background: 'var(--surface-1)', padding: '8px 12px', borderRadius: 8, border: '1px solid var(--surface-3)' }}>
                      <span style={{ fontSize: 13, fontWeight: 600, width: 70 }}>
                        {['Domingo','Segunda','Terça','Quarta','Quinta','Sexta','Sábado'][w]}
                      </span>
                      <window.Select style={{ width: 85, padding: '4px 8px' }} value={rd.start} onChange={(e) => {
                        const st = e.target.value;
                        const ed = st ? defaultEnd(st, effectiveServiceId) : rd.end;
                        setF({ ...f, repeatDays: { ...f.repeatDays, [w]: { start: st, end: ed } } });
                      }}>
                        <option value="">Início</option>
                        {times.map(t => <option key={t} value={t}>{t}</option>)}
                      </window.Select>
                      <span className="muted" style={{ fontSize: 12 }}>até</span>
                      <window.Select style={{ width: 85, padding: '4px 8px' }} value={rd.end} onChange={(e) => {
                        setF({ ...f, repeatDays: { ...f.repeatDays, [w]: { ...rd, end: e.target.value } } });
                      }}>
                        <option value="">Fim</option>
                        {endTimes.map(t => <option key={t} value={t}>{t}</option>)}
                      </window.Select>
                    </div>
                  );
                })}
              </div>
            )}
            
            <div className="muted" style={{ fontSize: 12, marginTop: 12, lineHeight: 1.4 }}>
              <window.Icon name="info" size={13} style={{ marginBottom: -2, marginRight: 4 }} />
              A partir do dia seguinte {apptIsFeminine ? 'à primeira' : 'ao primeiro'} {terms.appt.toLowerCase()}, os dias selecionados gerarão repetições semanais nos horários definidos, até a data final ou por 1 ano se ela ficar em branco.
            </div>
          </div>
        )}

        {editing && <window.Field label="Status">
          <window.Select value={f.status} onChange={set('status')}>
            <option value="confirmado">Confirmado</option><option value="pendente">Pendente</option>
          </window.Select>
        </window.Field>}
        <window.Field label="Observações" optional full>
          <window.Textarea placeholder={`Detalhes do ${terms.appt.toLowerCase()}, preferências…`} value={f.notes} onChange={set('notes')} />
        </window.Field>
      </div>

      {/* resumo + lembrete */}
      <div className="row between" style={{ marginTop: 16, padding: '12px 14px', borderRadius: 'var(--r-md)', background: 'var(--primary-soft)', color: 'var(--primary)' }}>
        <div className="row" style={{ gap: 9 }}>
          <window.Icon name="clock" size={17} />
          <span style={{ fontSize: 13, fontWeight: 600 }}>{valid ? `${new Date(f.date + 'T00:00').toLocaleDateString('pt-BR', { weekday: 'short', day: 'numeric', month: 'short' })} · ${f.start}–${f.end} (${duration} min)` : (!hasClientInfo ? `Informe ${terms.client.toLowerCase()}` : `Selecione ${terms.pro.toLowerCase()} e horários`)}</span>
        </div>
      </div>
      {(
        <label className="row between" style={{ marginTop: 12, cursor: 'pointer' }}>
          <span className="row" style={{ gap: 9 }}><window.Icon name="bell" size={16} /><span style={{ fontSize: 13, fontWeight: 500 }}>Mostrar notificação</span></span>
          <window.Switch on={f.showNotification} onClick={() => setF({ ...f, showNotification: !f.showNotification })} />
        </label>
      )}

      {!editing && !isPlainUser && (
        <label className="row between" style={{ marginTop: 12, cursor: 'pointer' }}>
          <span className="row" style={{ gap: 9 }}><window.Icon name="whatsapp" size={16} style={{ color: 'var(--success)' }} /><span style={{ fontSize: 13, fontWeight: 500 }}>{cli && cli.phone ? 'Enviar confirmação por WhatsApp para ' + cli.name.split(' ')[0] : `Confirmação por WhatsApp indisponível sem ${terms.client.toLowerCase()}`}</span></span>
          <window.Switch on={f.notify} onClick={() => setF({ ...f, notify: !f.notify })} />
        </label>
      )}

      {!editing && canManage && user.hasAppointmentEmail && (
        <div className="row between" style={{ marginTop: 12, cursor: 'default', opacity: hasEmailRecipients ? 1 : .78 }}>
          <span className="row" style={{ gap: 9 }}>
            <span style={{ fontSize: 16 }}>✉️</span>
            <span style={{ fontSize: 13, fontWeight: 500 }}>{emailRecipientLabel}</span>
          </span>
          <window.Switch
            on={hasEmailRecipients && f.sendEmail}
            onClick={() => hasEmailRecipients && setF({ ...f, sendEmail: !f.sendEmail, emailReminderMinutes: f.emailReminderMinutes || '1440' })}
            disabled={!hasEmailRecipients}
          />
        </div>
      )}
      {!editing && canManage && user.hasAppointmentEmail && hasEmailRecipients && f.sendEmail && (
        <window.Field label="Novo lembrete por e-mail" optional full>
          <window.Select value={String(f.emailReminderMinutes || '1440')} onChange={set('emailReminderMinutes')}>
            {reminderOptions.map((opt) => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
          </window.Select>
        </window.Field>
      )}
    </window.Modal>
    {quotaPrompt && <window.Modal title="Limite do plano atingido" sub={`${cli ? cli.name : terms.client} já utilizou ${quotaPrompt.count}/${quotaPrompt.limit} aulas ${quotaPrompt.period === 'mes' ? 'neste ciclo mensal' : 'nesta semana'}.`} onClose={() => !saving && setQuotaPrompt(null)}
      footer={<>
        <window.Button variant="ghost" disabled={saving} onClick={() => setQuotaPrompt(null)}>Voltar</window.Button>
        {quotaPrompt.credits > 0 && <window.Button variant="outline" icon="refresh" disabled={saving} onClick={() => save('replacement')}>Usar 1 crédito</window.Button>}
        <window.Button variant="primary" icon="plus" disabled={saving} onClick={() => save('extra')}>
          {saving ? <><span className="btn-spinner" /> Salvando...</> : `Aula extra · ${DB.money(quotaPrompt.extraPrice)}`}
        </window.Button>
      </>}>
      <div className="stack" style={{ gap: 12 }}>
        <div className="card card-pad" style={{ padding: 14, background: 'var(--warning-soft)' }}>
          <div style={{ fontWeight: 700 }}>{quotaPrompt.planName}</div>
          <div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>
            Período: {new Date(quotaPrompt.startISO + 'T12:00').toLocaleDateString('pt-BR')} até {new Date(quotaPrompt.endISO + 'T12:00').toLocaleDateString('pt-BR')}.
          </div>
        </div>
        {quotaPrompt.credits > 0
          ? <div className="row" style={{ gap: 8, color: 'var(--primary)', fontSize: 13 }}><window.Icon name="refresh" size={15} /> Há {quotaPrompt.credits} crédito{quotaPrompt.credits === 1 ? '' : 's'} de reposição disponível{quotaPrompt.credits === 1 ? '' : 'is'}.</div>
          : <div className="muted" style={{ fontSize: 13 }}>Não há crédito de reposição. A nova aula será registrada como extra pelo valor mostrado.</div>}
      </div>
    </window.Modal>}
    {quickAdd === 'client' && <window.ClientModal terms={terms} team={team} companies={ctx.companies || []} services={services}
      onClose={() => setQuickAdd(null)} onSave={saveQuickClient} />}
    {quickAdd === 'pro' && <window.TeamModal terms={terms} team={team} accessLimitReached={accessLimitReached} maxUsers={maxUsers}
      onClose={() => setQuickAdd(null)} onSave={saveQuickPro} />}
    {quickAdd === 'service' && <window.ServiceModal terms={terms} initial={null} onClose={() => setQuickAdd(null)} onSave={saveQuickService} />}
    </>
  );
}

/* ---------- DETALHE DO AGENDAMENTO ---------- */
function ApptDrawer({ ctx, appt, onClose }) {
  const { terms, clients, services, team, transactions, statusMap, toast, user } = ctx;
  const DB = window.DB;
  const [rescheduleScopeOpen, setRescheduleScopeOpen] = React.useState(false);
  const [seriesActionOpen, setSeriesActionOpen] = React.useState(false);
  const [cancelConfirmOpen, setCancelConfirmOpen] = React.useState(false);
  const [addReplacementCredit, setAddReplacementCredit] = React.useState(true);
  const [absenceConfirmOpen, setAbsenceConfirmOpen] = React.useState(false);
  const [statusBusy, setStatusBusy] = React.useState('');
  const c = clients.find(x => x.id === appt.clientId);
  const s = appt.manualServiceName ? { name: appt.manualServiceName, price: appt.manualServicePrice || 0, color: 'violet' } : services.find(x => x.id === appt.serviceId);
  const p = team.find(x => x.id === appt.proId);
  const clientPlan = c && services.find(x => Number(x.id) === Number(c.planId));
  const canGrantReplacementCredit = !!(ctx.businessType === 'pilates' && c && Number(c.planId) > 0
    && Number(c.planFrequency || (clientPlan && clientPlan.planFrequency)) > 0
    && appt.quotaAction !== 'extra'
    && (Number(appt.serviceId) === Number(c.planId) || Number(s && s.planFrequency) > 0));
  const clientColor = c ? (DB.colors[Number(c.id) % DB.colors.length] || DB.personColorFallback) : DB.personColorFallback;
  const typedClientName = String(appt.clientLabel || appt.clientName || (c ? c.name : '')).trim();
  const st = statusMap[appt.status];
  if (!s || !p) { return null; }
  const ini = (n) => n.split(' ').map(x => x[0]).slice(0, 2).join('').toUpperCase();
  const cancellationEmailRecipients = Math.max(0, Number(appt.emailNotifiedRecipientCount) || 0);
  const setStatus = async (status, msg, opts = {}) => {
    if (statusBusy) return;
    if (status === 'cancelado' && !opts.confirmed) {
      setCancelConfirmOpen(true);
      return;
    }
    if (status === 'faltou' && !opts.confirmed) {
      setAbsenceConfirmOpen(true);
      return;
    }
    if (status === 'cancelado') setCancelConfirmOpen(false);
    if (status === 'faltou') setAbsenceConfirmOpen(false);
    setStatusBusy(status);
    try {
      await ctx.setApptStatus(appt.id, status, status === 'cancelado' ? { addReplacementCredit: !!(canGrantReplacementCredit && opts.addReplacementCredit) } : {});
      toast(status === 'cancelado' && canGrantReplacementCredit && opts.addReplacementCredit ? `${msg} · 1 crédito de reposição adicionado` : msg);
      onClose();
    } catch (e) {
      toast(e.message || 'Erro ao atualizar.', 'x');
      setStatusBusy('');
    }
  };
  const setStatusInline = async (status, msg) => {
    if (statusBusy) return;
    setStatusBusy(status);
    try {
      await ctx.setApptStatus(appt.id, status);
      toast(msg);
    } catch (e) {
      toast(e.message || 'Erro ao atualizar.', 'x');
    }
    setStatusBusy('');
  };
  const myParticipant = user && user.proId ? (appt.participants || []).find((part) => Number(part.proId) === Number(user.proId)) : null;
  const isCreator = !!(user && Number(appt.createdByUserId) === Number(user.id));
  const isPlainUser = !!(user && user.role === 'user');
  const canManage = !!(user && (user.role === 'admin' || (user.role === 'pro' && isCreator)));
  const canConfirmClient = !!(canManage || (user && user.role === 'pro' && user.proId && Number(appt.proId) === Number(user.proId)));
  const canDelete = !!(user && (canManage || (isPlainUser && isCreator)));
  const canManageSeries = !!(appt.seriesId && canDelete);
  const canManageOrphanFuture = !!(!appt.seriesId && appt.createdAt && canDelete);
  const participantCanRespond = myParticipant && !['cancelado', 'concluido', 'faltou'].includes(appt.status);
  const isFutureAppointment = appt.date > DB.iso(DB.today);
  const responseLabel = { pendente: ['Pendente', 'amber'], confirmado: ['Confirmou', 'green'], recusado: ['Recusou', 'red'] };
  const headBadge = myParticipant && !isCreator && myParticipant.response === 'confirmado'
    ? { label: 'Presen\u00e7a confirmada', cls: 'green' }
    : myParticipant && !isCreator && myParticipant.response === 'recusado'
      ? { label: 'Presen\u00e7a recusada', cls: 'red' }
      : st;
  const respond = async (response) => {
    await ctx.respondAppt(appt.id, response, user && user.proId);
    toast(response === 'confirmado' ? 'Presen\u00e7a confirmada' : 'Presen\u00e7a recusada');
    onClose();
  };
  const respondFor = async (part, response) => {
    await ctx.respondAppt(appt.id, response, part.proId);
    toast(response === 'confirmado' ? `${part.name} confirmado` : `${part.name} recusou`);
  };
  const remove = async () => {
    if (!window.confirm('Excluir este registro? Essa ação não pode ser desfeita.')) return;
    const hasLinkedFinance = !!appt.hasFinanceTransaction || (transactions || []).some((tx) => Number(tx.apptId) === Number(appt.id));
    const deleteFinance = hasLinkedFinance && window.confirm('Este agendamento possui um lançamento no financeiro.\n\nClique em OK para excluir também o lançamento financeiro.\nClique em Cancelar para manter o lançamento financeiro.\n\nO agendamento será excluído em ambos os casos.');
    try {
      const result = await ctx.deleteAppt(appt.id, deleteFinance);
      const fem = ['Aula', 'Consulta', 'Sessão'].includes(terms.appt);
      const financeMessage = deleteFinance
        ? ' · lançamento financeiro excluído'
        : (result && result.financeTransactionKept ? ' · lançamento financeiro mantido' : '');
      toast(terms.appt + (fem ? ' excluída' : ' excluído') + financeMessage);
      onClose();
    } catch (e) {
      toast(e.message, 'x');
    }
  };
  const manageSeries = async (mode) => {
    if (statusBusy) return;
    setStatusBusy(mode === 'delete' ? 'delete_future' : 'end_series');
    try {
      const res = await ctx.manageApptSeries(appt.id, mode);
      const count = Number(res && res.updated ? res.updated : 0);
      if (mode === 'delete') {
        toast(count > 0
          ? `${count} ${count === 1 ? 'ocorrência excluída da série' : 'ocorrências excluídas da série'}.`
          : 'Recorrência encerrada. Não havia ocorrências para excluir.');
      } else {
        toast(count > 0
          ? `${count} ${count === 1 ? 'ocorrência futura cancelada' : 'ocorrências futuras canceladas'}.`
          : 'Recorrência encerrada. Não havia futuras para cancelar.');
      }
      setSeriesActionOpen(false);
      onClose();
    } catch (e) {
      toast(e.message || 'Erro ao atualizar a recorrência.', 'x');
      setStatusBusy('');
    }
  };
  const manageOrphanFuture = async () => {
    if (statusBusy) return;
    setStatusBusy('orphan_future');
    try {
      const res = await ctx.manageApptOrphanFuture(appt.id);
      const count = Number(res && res.updated ? res.updated : 0);
      toast(count > 0
        ? `${count} ${count === 1 ? terms.appt.toLowerCase() + ' excluido do lote' : terms.appt.toLowerCase() + 's excluidos do lote'}.`
        : `Nenhum ${terms.appt.toLowerCase()} futuro encontrado neste lote.`);
      setSeriesActionOpen(false);
      onClose();
    } catch (e) {
      toast(e.message || 'Erro ao excluir o lote.', 'x');
      setStatusBusy('');
    }
  };
  const reschedule = (scope = 'single') => {
    setRescheduleScopeOpen(false);
    onClose();
    ctx.openNewAppt({ editId: appt.id, editScope: scope, clientId: appt.clientId, clientName: appt.clientLabel || appt.clientName, serviceId: appt.serviceId, manualServiceName: appt.manualServiceName, manualServicePrice: appt.manualServicePrice, proId: appt.proId, date: appt.date, start: appt.start, end: window.apptEnd(appt), status: appt.status, notes: appt.notes, showNotification: appt.showNotification, launchFinance: appt.launchFinance, quotaAction: appt.quotaAction });
  };

  return (
    <>
    <window.Drawer className="appt-drawer" onClose={() => rescheduleScopeOpen ? setRescheduleScopeOpen(false) : (seriesActionOpen ? setSeriesActionOpen(false) : (absenceConfirmOpen ? setAbsenceConfirmOpen(false) : onClose()))}
      head={<div className="row between">
        <div className="row" style={{ gap: 10 }}><window.Badge cls={headBadge.cls} dot>{headBadge.label}</window.Badge><span className="muted" style={{ fontSize: 12.5 }}>#{appt.id}</span></div>
        <button className="iconbtn" onClick={onClose}><window.Icon name="x" size={18} /></button>
      </div>}
      footer={!canManage ? null : appt.status === 'concluido' || appt.status === 'cancelado' || appt.status === 'faltou' ? (
        <window.Button variant="outline" block icon="history" disabled={!!statusBusy} onClick={() => setStatus('confirmado', 'Reagendado')}>{statusBusy === 'confirmado' ? <><span className="btn-spinner" /> Salvando...</> : 'Reabrir'}</window.Button>
      ) : (<>
        {appt.status === 'confirmado' && isCreator && !isFutureAppointment && <window.Button variant="primary" icon="check2" block onClick={() => setStatus('concluido', 'Marcado como concluído')}>Concluir</window.Button>}
        {!isFutureAppointment && <window.Button variant="outline" icon="userX" disabled={!!statusBusy} onClick={() => setAbsenceConfirmOpen(true)}>{statusBusy === 'faltou' ? <><span className="btn-spinner" /> Salvando...</> : 'Registrar falta'}</window.Button>}
        <window.Button variant="danger" icon="trash" disabled={!!statusBusy} onClick={() => setCancelConfirmOpen(true)}>{statusBusy === 'cancelado' ? <><span className="btn-spinner" /> Cancelando...</> : 'Cancelar'}</window.Button>
      </>)}>
      <div style={{ textAlign: 'center', padding: '8px 0 18px', borderBottom: '1px solid var(--border)' }}>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 26, fontWeight: 600, marginBottom: 4 }}>{appt.start} – {window.apptEnd(appt)}</div>
        <div className="muted" style={{ fontSize: 13, textTransform: 'capitalize' }}>{new Date(appt.date + 'T00:00').toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' })}</div>
      </div>

      <window.SectionLabel>{terms.service}</window.SectionLabel>
      <div className="card card-pad" style={{ padding: 13, display: 'flex', alignItems: 'center', gap: 12, borderLeft: `3px solid var(--ev-${s.color})` }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontWeight: 600 }}>{s.name}</div><div className="muted" style={{ fontSize: 12 }}>{s.dur} min</div>
          {appt.quotaAction === 'extra' && <window.Badge cls="amber">Aula extra · {DB.money(appt.extraClassPrice || s.price || 0)}</window.Badge>}
          {appt.quotaAction === 'replacement' && <window.Badge cls="blue">Reposição com crédito</window.Badge>}
          {appt.replacementCreditGranted && <window.Badge cls="green">Gerou crédito de reposição</window.Badge>}
        </div>
        <span className="mono" style={{ fontWeight: 700 }}>{s.price > 0 ? DB.money0(s.price) : 'Interno'}</span>
      </div>

      <window.SectionLabel>{terms.client}</window.SectionLabel>
      <div className="card card-pad" style={{ padding: 13, display: 'flex', alignItems: 'center', gap: 12 }}>
        <window.Avatar initials={c ? ini(c.name) : (typedClientName ? ini(typedClientName) : '--')} color={clientColor} size={42} />
        <div style={{ flex: 1 }}><div style={{ fontWeight: 600 }}>{c ? c.name : (typedClientName || `Sem ${terms.client.toLowerCase()} informado`)}</div><div className="muted" style={{ fontSize: 12 }}>{c ? ([c.companyName, c.companyRole || c.phone || 'Sem telefone'].filter(Boolean).join(' · ')) : (typedClientName ? 'Informado manualmente' : 'Compromisso interno ou avulso')}</div></div>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 6 }}>
          {c && <window.Badge cls={appt.status === 'confirmado' ? 'green' : (appt.status === 'pendente' ? 'amber' : 'gray')} dot>{appt.status === 'confirmado' ? 'Confirmado' : (appt.status === 'pendente' ? 'Pendente' : st.label)}</window.Badge>}
          {c && canConfirmClient && appt.status === 'pendente' && <window.Button size="sm" variant="primary" icon="check" onClick={() => setStatusInline('confirmado', `${terms.client} confirmado`)}>Confirmar</window.Button>}
          {c && c.phone && <button className="iconbtn bordered" title="Abrir WhatsApp" onClick={() => window.waOpen(c.phone)}><window.Icon name="whatsapp" size={16} /></button>}
        </div>
      </div>

      <window.SectionLabel>{terms.pro}</window.SectionLabel>
      <window.InfoRow icon="user" label={p.role} value={p.name} />

      {(appt.participants || []).length > 0 && <>
        <window.SectionLabel>{'Participantes \u00b7 '}{(appt.participants || []).filter((x) => x.response === 'confirmado').length}/{(appt.participants || []).length} confirmaram</window.SectionLabel>
        <div className="stack" style={{ gap: 8 }}>
          {(appt.participants || []).map((part) => {
            const meta = responseLabel[part.response] || responseLabel.pendente;
            const responseDate = part.responseAt ? new Date(String(part.responseAt).replace(' ', 'T')) : null;
            const responseWhen = responseDate && !Number.isNaN(responseDate.getTime())
              ? responseDate.toLocaleDateString('pt-BR') + ' às ' + responseDate.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
              : '';
            return (
              <div key={part.proId} className="card card-pad" style={{ padding: 11, display: 'flex', alignItems: 'center', gap: 11 }}>
                <window.Avatar initials={part.initials || ini(part.name)} color={part.color || 'blue'} size={34} />
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 600 }}>{part.name}</div>
                  <div className="muted" style={{ fontSize: 12 }}>{part.role || terms.pro}</div>
                  {responseWhen && part.response !== 'pendente' && <div className="muted" style={{ fontSize: 11.5, marginTop: 2 }}>{meta[0]} em {responseWhen}</div>}
                </div>
                <div className="row" style={{ gap: 7, justifyContent: 'flex-end' }}>
                  <window.Badge cls={meta[1]} dot>{meta[0]}</window.Badge>
                  {participantCanRespond && Number(part.proId) === Number(user && user.proId) && part.response === 'pendente' && (
                    <>
                      <window.Button size="sm" variant="primary" icon="check" onClick={() => respond('confirmado')}>Confirmar</window.Button>
                      <window.Button size="sm" variant="outline" icon="x" onClick={() => respond('recusado')}>Recusar</window.Button>
                    </>
                  )}
                  {participantCanRespond && Number(part.proId) === Number(user && user.proId) && part.response === 'recusado' && (
                    <window.Button size="sm" variant="primary" icon="check" onClick={() => respond('confirmado')}>Confirmar</window.Button>
                  )}
                  {participantCanRespond && Number(part.proId) === Number(user && user.proId) && part.response === 'confirmado' && !isCreator && (
                    <window.Button size="sm" variant="outline" icon="x" onClick={() => respond('recusado')}>Recusar</window.Button>
                  )}
                  {canManage && !(participantCanRespond && Number(part.proId) === Number(user && user.proId)) && !['cancelado', 'concluido', 'faltou'].includes(appt.status) && part.response !== 'confirmado' && (
                    <window.Button size="sm" variant="primary" icon="check" onClick={() => respondFor(part, 'confirmado')}>Confirmar</window.Button>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      </>}

      {appt.notes && <><window.SectionLabel>Observações</window.SectionLabel>
        <div className="card card-pad muted" style={{ padding: 12, fontSize: 12.5 }}>{appt.notes}</div></>}

      <window.SectionLabel>Registro</window.SectionLabel>
      <div className="muted" style={{ fontSize: 12.5, lineHeight: 1.7 }}>
        <div className="row" style={{ gap: 7 }}><window.Icon name="user" size={13} /> Agendado por <b style={{ color: 'var(--text-2)' }}>{appt.createdBy || 'Secretaria'}</b></div>
        {appt.createdAt && <div className="row" style={{ gap: 7 }}><window.Icon name="clock" size={13} /> Criado há {DB.timeAgo(appt.createdAt)}</div>}
      </div>

      {(canManage || canDelete) && <div className="row" style={{ gap: 8, marginTop: 18, flexWrap: 'wrap' }}>
        {canManage && <window.Button variant="outline" size="sm" icon="edit" onClick={() => appt.seriesId ? setRescheduleScopeOpen(true) : reschedule('single')}>Remarcar</window.Button>}
        {(canManageSeries || canManageOrphanFuture) && <window.Button variant="outline" size="sm" icon="refresh" onClick={() => setSeriesActionOpen(true)}>Recorrência</window.Button>}
        {canDelete && <window.Button variant="outline" size="sm" icon="trash" onClick={remove}>Excluir</window.Button>}
      </div>}
    </window.Drawer>
    {cancelConfirmOpen && <window.Modal title={`Cancelar ${terms.appt.toLowerCase()}`} sub={`Essa ação altera o status do ${terms.appt.toLowerCase()} para cancelado.`} onClose={() => !statusBusy && setCancelConfirmOpen(false)}
      footer={<>
        <window.Button variant="ghost" disabled={!!statusBusy} onClick={() => setCancelConfirmOpen(false)}>Manter {terms.appt.toLowerCase()}</window.Button>
        <window.Button variant="danger" icon="x" disabled={!!statusBusy} onClick={() => setStatus('cancelado', `${terms.appt} cancelado`, { confirmed: true, addReplacementCredit })}>
          {statusBusy === 'cancelado' ? <><span className="btn-spinner" /> Cancelando...</> : `Cancelar ${terms.appt.toLowerCase()}`}
        </window.Button>
      </>}>
      <div className="stack" style={{ gap: 14 }}>
        <div className="card card-pad" style={{ padding: 14, display: 'flex', gap: 12, alignItems: 'center', background: 'var(--danger-soft)', borderColor: 'color-mix(in srgb, var(--danger) 36%, var(--border))' }}>
          <div style={{ width: 40, height: 40, borderRadius: 10, display: 'grid', placeItems: 'center', background: 'var(--surface)', color: 'var(--danger)', flex: 'none' }}>
            <window.Icon name="x" size={20} />
          </div>
          <div>
            <div style={{ fontWeight: 700, marginBottom: 3 }}>Tem certeza que deseja cancelar este {terms.appt.toLowerCase()}?</div>
            <div className="muted" style={{ fontSize: 12.5 }}>O horário será marcado como cancelado e deixará de contar como {terms.appt.toLowerCase()} ativo.</div>
          </div>
        </div>
        <div className="card card-pad" style={{ padding: 13 }}>
          <div style={{ fontWeight: 700 }}>{s.name}</div>
          <div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>
            {new Date(appt.date + 'T00:00').toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' })} · {appt.start} - {window.apptEnd(appt)}
          </div>
          {(c || typedClientName) && <div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{c ? c.name : typedClientName}</div>}
        </div>
        {cancellationEmailRecipients > 0 && (
          <div className="row" style={{ gap: 8, color: 'var(--warning)', fontSize: 12.5, fontWeight: 600 }}>
            <window.Icon name="info" size={15} />
            O cancelamento será enviado para {cancellationEmailRecipients} destinatário{cancellationEmailRecipients === 1 ? '' : 's'} que já recebeu confirmação ou lembrete por e-mail.
          </div>
        )}
        {canGrantReplacementCredit && <label className="card card-pad row between" style={{ padding: 13, cursor: 'pointer', gap: 12 }}>
          <span>
            <span style={{ display: 'block', fontWeight: 700 }}>Adicionar crédito de reposição</span>
            <span className="muted" style={{ display: 'block', fontSize: 12.5, marginTop: 3 }}>Saldo atual: {Number(c.replacementCredits) || 0}. O crédito poderá ser usado quando o limite do plano for atingido.</span>
          </span>
          <window.Switch on={addReplacementCredit} onClick={() => setAddReplacementCredit((value) => !value)} />
        </label>}
      </div>
    </window.Modal>}
    {absenceConfirmOpen && <window.Modal title="Registrar falta" sub={`Marca este ${terms.appt.toLowerCase()} como não compareceu.`} onClose={() => !statusBusy && setAbsenceConfirmOpen(false)}
      footer={<>
        <window.Button variant="ghost" disabled={!!statusBusy} onClick={() => setAbsenceConfirmOpen(false)}>Voltar</window.Button>
        <window.Button variant="danger" icon="userX" disabled={!!statusBusy} onClick={() => setStatus('faltou', 'Falta registrada', { confirmed: true })}>
          {statusBusy === 'faltou' ? <><span className="btn-spinner" /> Salvando...</> : 'Marcar como faltou'}
        </window.Button>
      </>}>
      <div className="stack" style={{ gap: 14 }}>
        <div className="card card-pad" style={{ padding: 14, display: 'flex', gap: 12, alignItems: 'center', background: 'var(--danger-soft)', borderColor: 'color-mix(in srgb, var(--danger) 28%, var(--border))' }}>
          <div style={{ width: 40, height: 40, borderRadius: 10, display: 'grid', placeItems: 'center', background: 'var(--surface)', color: 'var(--danger)', flex: 'none' }}>
            <window.Icon name="userX" size={20} />
          </div>
          <div>
            <div style={{ fontWeight: 700, marginBottom: 3 }}>A pessoa não compareceu?</div>
            <div className="muted" style={{ fontSize: 12.5 }}>O status ficará como faltou, sem contar como {terms.appt.toLowerCase()} concluído. Se precisar, você pode reabrir depois.</div>
          </div>
        </div>
        <div className="card card-pad" style={{ padding: 13 }}>
          <div style={{ fontWeight: 700 }}>{s.name}</div>
          <div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>
            {new Date(appt.date + 'T00:00').toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' })} · {appt.start} - {window.apptEnd(appt)}
          </div>
          {(c || typedClientName) && <div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{c ? c.name : typedClientName}</div>}
        </div>
      </div>
    </window.Modal>}
    {rescheduleScopeOpen && <window.Modal title={`Remarcar ${terms.appt.toLowerCase()}`} sub="Escolha o alcance da alteração" onClose={() => setRescheduleScopeOpen(false)}
      footer={<><div style={{ flex: 1 }} /><window.Button variant="ghost" onClick={() => setRescheduleScopeOpen(false)}>Voltar</window.Button></>}>
      <div className="stack" style={{ gap: 10 }}>
        <button type="button" className="card" onClick={() => reschedule('single')}
          style={{ padding: 15, textAlign: 'left', cursor: 'pointer', display: 'flex', gap: 12, alignItems: 'center', background: 'var(--surface)' }}>
          <div className="ic" style={{ width: 40, height: 40, borderRadius: 10, background: 'var(--surface-2)', display: 'grid', placeItems: 'center' }}><window.Icon name="calendar" size={18} /></div>
          <div><div style={{ fontWeight: 600 }}>Somente este {terms.appt.toLowerCase()}</div><div className="muted" style={{ fontSize: 12 }}>Os outros {terms.appt.toLowerCase()}s da série não serão alterados.</div></div>
        </button>
        {(appt.participants || []).length <= 1 && <button type="button" className="card" onClick={() => reschedule('future')}
          style={{ padding: 15, textAlign: 'left', cursor: 'pointer', display: 'flex', gap: 12, alignItems: 'center', background: 'var(--primary-soft)', borderColor: 'color-mix(in srgb, var(--primary) 28%, var(--border))' }}>
          <div className="ic" style={{ width: 40, height: 40, borderRadius: 10, color: 'var(--primary)', display: 'grid', placeItems: 'center' }}><window.Icon name="refresh" size={18} /></div>
          <div><div style={{ fontWeight: 600 }}>Este e os próximos</div><div className="muted" style={{ fontSize: 12 }}>Altera o dia e horário deste {terms.appt.toLowerCase()} em diante.</div></div>
        </button>}
      </div>
    </window.Modal>}
    {seriesActionOpen && <window.Modal title="Gerenciar recorrencia" sub={canManageSeries ? "Escolha como tratar esta serie a partir desta ocorrencia" : `Este ${terms.appt.toLowerCase()} veio de um lote sem vinculo de serie`} onClose={() => !statusBusy && setSeriesActionOpen(false)}
      footer={<><div style={{ flex: 1 }} /><window.Button variant="ghost" disabled={!!statusBusy} onClick={() => setSeriesActionOpen(false)}>Voltar</window.Button></>}>
      <div className="stack" style={{ gap: 10 }}>
        {canManageSeries && <button type="button" className="card" disabled={!!statusBusy} onClick={() => manageSeries('end')}
          style={{ padding: 15, textAlign: 'left', cursor: 'pointer', display: 'flex', gap: 12, alignItems: 'center', background: 'var(--surface)' }}>
          <div className="ic" style={{ width: 40, height: 40, borderRadius: 10, background: 'var(--surface-2)', display: 'grid', placeItems: 'center', color: 'var(--text-2)' }}><window.Icon name="calendar" size={18} /></div>
          <div>
            <div style={{ fontWeight: 600 }}>Manter este e encerrar os próximos</div>
            <div className="muted" style={{ fontSize: 12 }}>O {terms.appt.toLowerCase()} atual fica como está, e a série deixa de gerar ocorrências dali para frente.</div>
          </div>
        </button>}
        {canManageSeries && <button type="button" className="card" disabled={!!statusBusy} onClick={() => manageSeries('delete')}
          style={{ padding: 15, textAlign: 'left', cursor: 'pointer', display: 'flex', gap: 12, alignItems: 'center', background: 'var(--danger-soft)', borderColor: 'color-mix(in srgb, var(--danger) 30%, var(--border))' }}>
          <div className="ic" style={{ width: 40, height: 40, borderRadius: 10, display: 'grid', placeItems: 'center', color: 'var(--danger)' }}><window.Icon name="trash" size={18} /></div>
          <div>
            <div style={{ fontWeight: 600 }}>Excluir este e todos os futuros</div>
            <div className="muted" style={{ fontSize: 12 }}>Remove a ocorrência atual e as próximas, e também encerra a série.</div>
          </div>
        </button>}
        {canManageOrphanFuture && <button type="button" className="card" disabled={!!statusBusy} onClick={manageOrphanFuture}
          style={{ padding: 15, textAlign: 'left', cursor: 'pointer', display: 'flex', gap: 12, alignItems: 'center', background: 'var(--danger-soft)', borderColor: 'color-mix(in srgb, var(--danger) 30%, var(--border))' }}>
          <div className="ic" style={{ width: 40, height: 40, borderRadius: 10, display: 'grid', placeItems: 'center', color: 'var(--danger)' }}><window.Icon name="trash" size={18} /></div>
          <div>
            <div style={{ fontWeight: 600 }}>Excluir este lote futuro</div>
            <div className="muted" style={{ fontSize: 12 }}>Remove este registro e os proximos criados no mesmo lote, com mesmo {terms.client.toLowerCase()}, {terms.service.toLowerCase()}, {terms.pro.toLowerCase()} e horario.</div>
          </div>
        </button>}
      </div>
    </window.Modal>}
    </>
  );
}

/* ---------- PAINEL DE NOTIFICAÇÕES ---------- */
function NotifPanel({ notifications, appts = [], pendingInvites = [], pendingMeetings = [], onRead, onMarkRead, onReadAll, onRespond, onDismissAppt, onClose }) {
  const DB = window.DB;
  const [showOlder, setShowOlder] = React.useState(false);
  const iconMap = { check: 'check', calendar: 'calendar', x: 'x', card: 'card', bell: 'bell' };
  const colorMap = { confirm: 'var(--success)', new: 'var(--info)', cancel: 'var(--danger)', pay: 'var(--ev-cyan)', remind: 'var(--warning)', invite: 'var(--primary)', pending_read: 'var(--warning)' };
  const hasPending = pendingInvites.length > 0 || pendingMeetings.length > 0;
  const todayISO = window.DB.iso(window.DB.today);
  const limitISO = window.DB.iso(window.DB.addDays(5));
  const cutoff = Date.now() - 5 * 24 * 60 * 60 * 1000;
  const apptById = new Map((appts || []).map((a) => [a.id, a]));
  const isRecent = (n) => {
    if (n.type === 'pending_read') return false;
    if (n.apptId) {
      const appt = apptById.get(n.apptId);
      if (!appt) return false;
      if (appt.showNotification === false) return false;
      if (appt.date < todayISO || appt.date > limitISO) return false;
      if (appt.date === todayISO && appt.start) {
        const now = new Date();
        const nowStr = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
        if (appt.start < nowStr) return false;
      }
      return true;
    }
    const timestamp = new Date(String(n.createdAt || '').replace(' ', 'T')).getTime();
    return Number.isFinite(timestamp) && timestamp >= cutoff;
  };
  const recentNotifications = notifications.filter((n) => !n.read && isRecent(n));
  const olderCount = notifications.length - recentNotifications.length;
  const visibleNotifications = showOlder ? notifications : recentNotifications;
  return (
    <div className="menu" style={{ top: 'calc(100% + 8px)', right: 0, width: 340, padding: 0, overflow: 'hidden' }} onClick={e => e.stopPropagation()}>
      <div className="row between" style={{ padding: '13px 15px', borderBottom: '1px solid var(--border)' }}>
        <b style={{ fontSize: 14 }}>Notificações</b>
        <button className="btn ghost sm" onClick={onReadAll}>Marcar lidas</button>
      </div>
      <div style={{ maxHeight: 420, overflowY: 'auto' }}>
        {/* Pendentes fixos no topo — derivados da agenda, não somem ao marcar lido */}
        {hasPending && (
          <div style={{ borderBottom: '1px solid var(--border)', background: 'var(--surface-2)' }}>
            <div style={{ padding: '11px 15px 4px', fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.04em', color: 'var(--text-3)' }}>
              <window.Icon name="clock" size={12} /> Confirmações pendentes
            </div>
            {/* convites que EU preciso confirmar (com ação) */}
            {pendingInvites.map((it) => (
              <div key={'inv' + it.apptId} style={{ padding: '9px 15px 12px', position: 'relative' }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{it.title}</div>
                <div className="muted" style={{ fontSize: 12 }}>{it.sub}</div>
                <div className="row" style={{ gap: 8, marginTop: 8 }}>
                  <window.Button variant="primary" size="sm" icon="check" onClick={() => onRespond(it.apptId, 'confirmado')}>Confirmar</window.Button>
                  <window.Button variant="outline" size="sm" icon="x" onClick={() => onRespond(it.apptId, 'recusado')}>Recusar</window.Button>
                </div>
                <button type="button" className="btn ghost sm" style={{ position: 'absolute', top: 8, right: 10, padding: 4, height: 24, width: 24, color: 'var(--text-3)' }} title="Ignorar este aviso" onClick={() => onDismissAppt && onDismissAppt(it.apptId)}>
                  <window.Icon name="x" size={14} />
                </button>
              </div>
            ))}
            {/* quem ainda não confirmou (visão de quem marcou) */}
            {pendingMeetings.map((it) => (
              <div key={'mt' + it.apptId} style={{ padding: '9px 15px 12px', position: 'relative' }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{it.title}</div>
                <div className="muted" style={{ fontSize: 12, marginBottom: 6 }}>{it.sub} · aguardando</div>
                <div className="row wrap" style={{ gap: 5 }}>
                  {it.names.map((nm, i) => <span key={i} className="badge amber" style={{ height: 21 }}>{nm}</span>)}
                </div>
                <button type="button" className="btn ghost sm" style={{ position: 'absolute', top: 8, right: 10, padding: 4, height: 24, width: 24, color: 'var(--text-3)' }} title="Ignorar este aviso" onClick={() => onDismissAppt && onDismissAppt(it.apptId)}>
                  <window.Icon name="x" size={14} />
                </button>
              </div>
            ))}
          </div>
        )}
        {visibleNotifications.length === 0 && !hasPending && <div className="muted" style={{ padding: 18, fontSize: 12.5, textAlign: 'center' }}>{olderCount > 0 ? 'Nenhuma notificação nos próximos 5 dias.' : 'Nenhuma notificação.'}</div>}
        {visibleNotifications.map(n => (
          <div key={n.id} className="lrow" style={{ padding: '12px 15px', background: n.read ? 'transparent' : 'var(--primary-soft)', borderBottom: '1px solid var(--border)', cursor: 'pointer', alignItems: 'flex-start' }}
            onClick={() => onRead(n)}>
            <div className="ic" style={{ width: 34, height: 34, borderRadius: 9, background: 'var(--surface-2)', color: colorMap[n.type], display: 'grid', placeItems: 'center', flex: 'none' }}><window.Icon name={iconMap[n.icon] || 'bell'} size={16} /></div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13, fontWeight: 600 }}>{n.title}</div>
              <div className="muted" style={{ fontSize: 12 }}>{n.desc}</div>
            </div>
            <span className="muted" style={{ fontSize: 11, whiteSpace: 'nowrap', paddingTop: 2 }}>{DB.timeAgo(n.createdAt)}</span>
            {!n.read && onMarkRead && (
              <button 
                type="button" 
                className="btn ghost sm" 
                onClick={(e) => { e.stopPropagation(); onMarkRead(n.id); }} 
                style={{ padding: '4px', marginLeft: 8, height: 24, width: 24, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-3)' }} 
                title="Marcar como lida"
              >
                <window.Icon name="x" size={14} />
              </button>
            )}
          </div>
        ))}
        {olderCount > 0 && (
          <button type="button" className="btn ghost sm" onClick={() => setShowOlder((value) => !value)}
            style={{ width: '100%', justifyContent: 'center', borderRadius: 0, padding: '11px 15px' }}>
            {showOlder ? 'Ocultar lidas/anteriores' : `Ver lidas/anteriores (${olderCount})`}
          </button>
        )}
      </div>
    </div>
  );
}

/* ---------- TROCAR TIPO DE NEGÓCIO ---------- */
function BusinessModal({ current, onSelect, onClose }) {
  const types = Object.values(window.DB.businessTypes);
  return (
    <window.Modal title="Tipo de negócio" sub="Ajusta os termos e campos da agenda ao seu segmento" onClose={onClose}>
      <div className="stack" style={{ gap: 10 }}>
        {types.map(t => (
          <button key={t.id} className="card" onClick={() => onSelect(t.id)}
            style={{ padding: 14, display: 'flex', alignItems: 'center', gap: 13, textAlign: 'left', border: current === t.id ? '1.5px solid var(--primary)' : '1px solid var(--border)', background: current === t.id ? 'var(--primary-soft)' : 'var(--surface)', cursor: 'pointer' }}>
            <div className="ic" style={{ width: 44, height: 44, borderRadius: 12, background: current === t.id ? 'var(--primary)' : 'var(--surface-2)', color: current === t.id ? '#fff' : 'var(--text-2)', display: 'grid', placeItems: 'center', flex: 'none' }}>
              <window.Icon name={t.icon} size={20} />
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontWeight: 600, fontSize: 14.5 }}>{t.name}</div>
              <div className="muted" style={{ fontSize: 12 }}>{t.client} · {t.service} · {t.appt}{t.extra.length ? ' · +' + t.extra.join(', ') : ''}</div>
            </div>
            {current === t.id && <window.Icon name="check" size={18} style={{ color: 'var(--primary)' }} />}
          </button>
        ))}
      </div>
    </window.Modal>
  );
}

/* ---------- SETTINGS ---------- */
function SettingsScreen(ctx) {
  const { theme, setTheme, accent, setAccent, businessType, settings, saveSetting, team, user } = ctx;
  const calendarStart = settings.calendar_start || '07:00';
  const calendarEnd = settings.calendar_end || '21:00';
  const slotCapacity = settings.appointment_slot_capacity || '1';
  const slotInterval = settings.appointment_slot_interval || '15';
  const slotGap = settings.appointment_slot_gap || '0';
  const activeTeam = (team || []).filter((p) => !p.archived);
  const savedCalendarScope = String(settings.calendar_default_scope || 'mine');
  const calendarDefaultScope = savedCalendarScope === 'mine' || savedCalendarScope === 'all'
    || activeTeam.some((p) => savedCalendarScope === `pro:${p.id}`)
    ? savedCalendarScope
    : 'mine';
  const themes = [
    { id: 'indigo',    name: 'Índigo',    color: '#3a5bd9' },
    { id: 'esmeralda', name: 'Esmeralda', color: '#0f9d77' },
    { id: 'vinho',     name: 'Vinho',     color: '#c23a5e' },
    { id: 'ambar',     name: 'Âmbar',     color: '#c97a16' },
  ];
  const notifPrefs = [
    ['notif_whatsapp', 'Confirmações automáticas por WhatsApp'],
    ['notif_remind24', 'Lembrete 24h antes'],
    ['notif_daily_email', 'Resumo diário por e-mail'],
  ];
  return (
    <div className="content-inner fadein" style={{ maxWidth: 760 }}>
      <window.Header title="Configurações" sub="Preferências da conta e do espaço de trabalho" />
      <div className="stack">
        <div className="card">
          <div className="card-head"><h3>Aparência</h3></div>
          <div className="card-pad stack" style={{ gap: 4 }}>
            <div className="row between" style={{ padding: '8px 0' }}>
              <div><div style={{ fontWeight: 600, fontSize: 13.5 }}>Tema escuro</div><div className="muted" style={{ fontSize: 12.5 }}>Reduz o brilho em ambientes com pouca luz</div></div>
              <window.Switch on={theme === 'dark'} onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} />
            </div>
          </div>
        </div>
        <div className="card">
          <div className="card-head"><h3>Temas</h3></div>
          <div className="card-pad">
            <div className="muted" style={{ fontSize: 12.5, marginBottom: 12 }}>Cor de destaque do sistema — vale para botões, menus, calendário e gráficos</div>
            <div className="row wrap" style={{ gap: 10 }}>
              {themes.map(t => (
                <button key={t.id} className="card" onClick={() => setAccent(t.id)}
                  style={{ flex: '1 1 130px', padding: '12px 14px', display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
                    border: accent === t.id ? '1.5px solid var(--primary)' : '1px solid var(--border)',
                    background: accent === t.id ? 'var(--primary-soft)' : 'var(--surface)' }}>
                  <span style={{ width: 22, height: 22, borderRadius: 99, background: t.color, flex: 'none', border: '2px solid var(--surface)', boxShadow: 'var(--shadow-xs)' }} />
                  <span style={{ fontWeight: 600, fontSize: 13.5, flex: 1, textAlign: 'left' }}>{t.name}</span>
                  {accent === t.id && <window.Icon name="check" size={16} style={{ color: 'var(--primary)' }} />}
                </button>
              ))}
            </div>
          </div>
        </div>
        <div className="card">
          <div className="card-head"><h3>Negócio</h3></div>
          <div className="card-pad">
            <div className="row between" style={{ padding: '8px 0' }}>
              <div><div style={{ fontWeight: 600, fontSize: 13.5 }}>Tipo de negócio</div><div className="muted" style={{ fontSize: 12.5 }}>{window.DB.businessTypes[businessType].name}</div></div>
              <span className="badge plain" title="O segmento é definido pelo provedor do sistema">Definido pelo provedor</span>
            </div>
          </div>
        </div>
        <div className="card">
          <div className="card-head"><h3>Calendário</h3></div>
          <div className="card-pad">
            <div className={'form-grid' + (businessType === 'pilates' ? ' settings-calendar-grid' : '')}>
              <window.Field label="Agenda exibida ao abrir" full>
                <window.Select value={user && user.role === 'user' ? 'mine' : calendarDefaultScope} onChange={(e) => saveSetting('calendar_default_scope', e.target.value)} disabled={user && user.role === 'user'}>
                  <option value="mine">Minha agenda</option>
                  {(!user || user.role !== 'user') && <option value="all">Todas as agendas</option>}
                  {(!user || user.role !== 'user') && activeTeam.map((p) => <option key={p.id} value={`pro:${p.id}`}>{p.name}</option>)}
                </window.Select>
                <div className="field-hint">Usuários comuns continuam vendo somente a própria agenda, independentemente desta preferência.</div>
              </window.Field>
              <window.Field label="Mostrar de">
                <window.Input type="time" value={calendarStart} onChange={(e) => saveSetting('calendar_start', e.target.value)} />
              </window.Field>
              <window.Field label="Mostrar ate">
                <window.Input type="time" value={calendarEnd} onChange={(e) => saveSetting('calendar_end', e.target.value)} />
              </window.Field>
              <window.Field label="Intervalo dos horarios">
                <window.Input
                  type="number"
                  min="5"
                  max="240"
                  step="5"
                  value={slotInterval}
                  onChange={(e) => saveSetting('appointment_slot_interval', String(Math.max(5, Math.min(240, Number(e.target.value) || 15))))}
                />
                {businessType !== 'pilates' && <div className="field-hint">Define de quantos em quantos minutos aparecem as opcoes e horarios livres.</div>}
              </window.Field>
              {businessType === 'pilates' && (
                <window.Field label="Pausa entre horarios">
                  <window.Input
                    type="number"
                    min="0"
                    max="240"
                    step="5"
                    value={slotGap}
                    onChange={(e) => saveSetting('appointment_slot_gap', String(Math.max(0, Math.min(240, Number(e.target.value) || 0))))}
                  />
                </window.Field>
              )}
              {businessType === 'pilates' && (
                <window.Field label={`Agend. por ${window.DB.businessTypes[businessType].pro.toLowerCase()}`}>
                  <window.Input
                    type="number"
                    min="1"
                    max="99"
                    value={slotCapacity}
                    onChange={(e) => saveSetting('appointment_slot_capacity', String(Math.max(1, Math.min(99, Number(e.target.value) || 1))))}
                  />
                </window.Field>
              )}
            </div>
          </div>
        </div>
        <div className="card">
          <div className="card-head"><h3>Notificações</h3></div>
          <div className="card-pad stack" style={{ gap: 4 }}>
            {notifPrefs.map(([key, label]) => (
              <div className="row between" key={key} style={{ padding: '8px 0' }}>
                <div style={{ fontWeight: 500, fontSize: 13.5 }}>{label}</div>
                <span className="badge">Em breve</span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { NewApptModal, ApptDrawer, NotifPanel, BusinessModal, SettingsScreen });
