import React, { useState, useEffect, useMemo, useCallback } from "react";
import { Plus, X, Repeat, CheckSquare, Square, Clock, ListTodo, Trash2, ChevronLeft, ChevronRight, Sparkles, Paperclip, Link2, Download, Wallet, CalendarRange, Cake, Sun, Moon, Star, Settings, Users, Bell, Share2 } from "lucide-react";

// ---------- Utilidades de fecha ----------
const DIAS = ["Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Dom"];
const MESES = ["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"];

function pad(n) { return n < 10 ? "0" + n : "" + n; }
function toKey(d) { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; }
function fromKey(k) { const [y, m, d] = k.split("-").map(Number); return new Date(y, m - 1, d); }
function isSameDay(a, b) { return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); }
function diffDays(a, b) { const A = new Date(a.getFullYear(), a.getMonth(), a.getDate()); const B = new Date(b.getFullYear(), b.getMonth(), b.getDate()); return Math.round((B - A) / 86400000); }
function eventTitle(ev) { return `${ev.title || "(sin título)"}${ev.time ? ` ${ev.time}` : ""}`; }
function wikiUrl(name) { return `https://es.wikipedia.org/wiki/Special:Search?search=${encodeURIComponent(name)}&go=Go`; }
// Abre un enlace externo simulando el clic real de un <a>, más fiable que window.open dentro de un iframe/sandbox.
function openExternalLink(url) {
  const a = document.createElement("a");
  a.href = url;
  a.target = "_blank";
  a.rel = "noopener noreferrer";
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

// El puente de almacenamiento del artifact parece no llevar bien varias llamadas solapadas a la vez
// (da "Unexpected response type" cuando dos peticiones se cruzan). Se sirven todas en fila, una a una,
// desde cualquier parte de la app, para que nunca haya dos en vuelo al mismo tiempo.
let storageQueue = Promise.resolve();
function queueStorageCall(fn) {
  const run = storageQueue.then(fn, fn);
  storageQueue = run.then(() => {}, () => {});
  return run;
}

// Si window.storage no responde en absoluto en esta vista previa (ni compartido ni local), la app no debe
// quedarse bloqueada: se cae a una memoria de la propia sesión como último recurso. Así "Crear calendario"
// (y todo lo demás) siempre funciona, aunque no llegue a guardarse de forma permanente en ese caso.
const memoryStore = {};
let storageDegraded = false;
function withTimeout(promise, ms) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error("Sin respuesta del almacenamiento")), ms);
    promise.then(v => { clearTimeout(timer); resolve(v); }, e => { clearTimeout(timer); reject(e); });
  });
}
function storageGet(key, shared) {
  return queueStorageCall(async () => {
    try {
      const result = await withTimeout(window.storage.get(key, shared), 4000);
      if (result && typeof result.value !== "undefined") memoryStore[key] = result.value;
      return result;
    } catch {
      storageDegraded = true;
      if (Object.prototype.hasOwnProperty.call(memoryStore, key)) return { key, value: memoryStore[key], shared };
      return null;
    }
  });
}
function storageSet(key, value, shared) {
  memoryStore[key] = value; // se guarda ya en memoria, pase lo que pase con el almacenamiento persistente
  return queueStorageCall(async () => {
    try {
      return await withTimeout(window.storage.set(key, value, shared), 4000);
    } catch {
      storageDegraded = true;
      return { key, value, shared };
    }
  });
}
// Sustituye BOOKING_AFFILIATE_ID por tu "aid" real del programa de afiliados de Booking para monetizar estos clics.
const BOOKING_AFFILIATE_ID = "";
function bookingSearchUrl(checkin, checkout) {
  const params = new URLSearchParams();
  if (BOOKING_AFFILIATE_ID) params.set("aid", BOOKING_AFFILIATE_ID);
  if (checkin) params.set("checkin", checkin);
  if (checkout) params.set("checkout", checkout);
  return `https://www.booking.com/searchresults.html?${params.toString()}`;
}
function nextBirthdayInfo(dateStr, todayRaw) {
  const today = new Date(todayRaw.getFullYear(), todayRaw.getMonth(), todayRaw.getDate());
  const birth = fromKey(dateStr);
  let next = new Date(today.getFullYear(), birth.getMonth(), birth.getDate());
  if (next < today) next = new Date(today.getFullYear() + 1, birth.getMonth(), birth.getDate());
  return { diff: diffDays(today, next), age: next.getFullYear() - birth.getFullYear() };
}
// Año usado cuando no se conoce la edad real: solo importa el día/mes, no se muestra la edad.
const UNKNOWN_BIRTH_YEAR = 1904;
function birthYearFromCurrentAge(month, day, age, todayRaw) {
  const today = new Date(todayRaw.getFullYear(), todayRaw.getMonth(), todayRaw.getDate());
  const thisYearBirthday = new Date(today.getFullYear(), month, day);
  // Si el cumpleaños de este año ya pasó (o es hoy), ya ha cumplido "age" años este año.
  // Si no, cumplió "age" el año pasado y este año cumplirá age+1.
  return today >= thisYearBirthday ? today.getFullYear() - age : today.getFullYear() - 1 - age;
}
function startOfGrid(year, month) {
  const first = new Date(year, month, 1);
  const weekday = (first.getDay() + 6) % 7; // lunes=0
  const grid = new Date(first);
  grid.setDate(first.getDate() - weekday);
  return grid;
}

const PARTICIPANT_PALETTE = [
  "#FF7A5C", "#3FBFB0", "#E8C468", "#7A93E0", "#E38FC2",
  "#8BC96B", "#E0A75E", "#6FB8E0", "#C98BE0", "#E06F6F",
];

function hexToRgba(hex, alpha) {
  const h = hex.replace("#", "");
  const r = parseInt(h.substring(0, 2), 16), g = parseInt(h.substring(2, 4), 16), b = parseInt(h.substring(4, 6), 16);
  return `rgba(${r},${g},${b},${alpha})`;
}
// Estilo de color de un participante concreto (bg sólido, soft para fondos suaves, text para texto sobre el bg)
function participantStyle(id, participants) {
  const p = (participants || []).find(x => x.id === id);
  if (!p) return sharedStyle();
  return { bg: p.color, soft: hexToRgba(p.color, 0.18), text: "#20141a" };
}
// Estilo "compartido" para eventos de varios participantes (o de todos)
function sharedStyle() {
  return { bg: "var(--accent)", soft: "var(--accent-soft)", text: "#2A1F0A" };
}
function nextParticipantColor(participants) {
  const used = new Set((participants || []).map(p => p.color));
  return PARTICIPANT_PALETTE.find(c => !used.has(c)) || PARTICIPANT_PALETTE[participants.length % PARTICIPANT_PALETTE.length];
}

const uid = () => Math.random().toString(36).slice(2, 10);

const REMINDER_OPTIONS = [
  { minutes: 0, label: "En el momento" },
  { minutes: 15, label: "15 min antes" },
  { minutes: 60, label: "1 hora antes" },
  { minutes: 1440, label: "1 día antes" },
  { minutes: 10080, label: "1 semana antes" },
];
function formatReminderLabel(minutes) {
  const preset = REMINDER_OPTIONS.find(r => r.minutes === minutes);
  if (preset) return preset.label;
  if (minutes % 43200 === 0) return `${minutes / 43200} mes${minutes / 43200 === 1 ? "" : "es"} antes`;
  if (minutes % 10080 === 0) return `${minutes / 10080} semana${minutes / 10080 === 1 ? "" : "s"} antes`;
  if (minutes % 1440 === 0) return `${minutes / 1440} día${minutes / 1440 === 1 ? "" : "s"} antes`;
  if (minutes % 60 === 0) return `${minutes / 60} hora${minutes / 60 === 1 ? "" : "s"} antes`;
  return `${minutes} min antes`;
}

// Santoral aproximado (tradición católica en España). No es exhaustivo ni infalible:
// el usuario puede editar cualquier día para corregirlo o completarlo.
const SANTORAL_DEFAULT = {
  "01-01": "María Madre de Dios", "01-02": "San Basilio y San Gregorio Nacianceno",
  "01-05": "San Telesforo", "01-06": "Reyes Magos",
  "01-07": "San Raimundo", "01-13": "San Hilario", "01-15": "San Pablo Ermitaño",
  "01-17": "San Antonio Abad", "01-20": "San Sebastián", "01-21": "Santa Inés",
  "01-22": "San Vicente", "01-24": "San Francisco de Sales", "01-25": "Conversión de San Pablo",
  "01-26": "San Timoteo y San Tito", "01-27": "Santa Ángela Merici",
  "01-28": "Santo Tomás de Aquino", "01-31": "San Juan Bosco",
  "02-02": "La Candelaria", "02-03": "San Blas", "02-05": "Santa Águeda",
  "02-08": "San Jerónimo Emiliani", "02-10": "Santa Escolástica",
  "02-11": "Ntra. Sra. de Lourdes", "02-14": "San Valentín", "02-22": "Cátedra de San Pedro",
  "02-23": "San Policarpo",
  "03-07": "Santas Perpetua y Felicidad", "03-08": "San Juan de Dios",
  "03-09": "Santa Francisca Romana", "03-17": "San Patricio", "03-19": "San José",
  "03-25": "La Anunciación",
  "04-02": "San Francisco de Paula", "04-11": "San Estanislao", "04-13": "San Martín I",
  "04-23": "San Jorge", "04-25": "San Marcos",
  "04-29": "Santa Catalina de Siena", "04-30": "San Pío V",
  "05-01": "San José Obrero", "05-02": "San Atanasio", "05-04": "San Florián",
  "05-13": "Ntra. Sra. de Fátima", "05-15": "San Isidro Labrador",
  "05-20": "San Bernardino de Siena", "05-22": "Santa Rita de Casia",
  "05-26": "San Felipe Neri", "05-30": "San Fernando", "05-31": "Visitación de María",
  "06-03": "San Carlos Lwanga", "06-05": "San Bonifacio", "06-09": "San Efrén",
  "06-11": "San Bernabé", "06-13": "San Antonio de Padua", "06-19": "San Romualdo",
  "06-21": "San Luis Gonzaga", "06-22": "Santo Tomás Moro", "06-24": "San Juan Bautista",
  "06-29": "San Pedro y San Pablo",
  "07-03": "Santo Tomás Apóstol", "07-06": "Santa María Goretti", "07-11": "San Benito",
  "07-14": "San Camilo de Lelis", "07-15": "San Buenaventura", "07-16": "Virgen del Carmen",
  "07-21": "San Lorenzo de Brindisi", "07-22": "Santa María Magdalena",
  "07-23": "Santa Brígida", "07-25": "Santiago Apóstol", "07-26": "Santa Ana y San Joaquín",
  "07-29": "Santa Marta", "07-31": "San Ignacio de Loyola",
  "08-01": "San Alfonso María de Ligorio", "08-04": "San Juan María Vianney",
  "08-06": "Transfiguración del Señor", "08-07": "San Cayetano",
  "08-08": "Santo Domingo de Guzmán", "08-10": "San Lorenzo", "08-11": "Santa Clara",
  "08-14": "San Maximiliano Kolbe", "08-15": "Asunción de María", "08-16": "San Roque",
  "08-18": "Santa Elena", "08-20": "San Bernardo", "08-21": "San Pío X",
  "08-23": "Santa Rosa de Lima", "08-24": "San Bartolomé", "08-28": "San Agustín",
  "09-03": "San Gregorio Magno", "09-08": "Natividad de María",
  "09-13": "San Juan Crisóstomo", "09-14": "Exaltación de la Santa Cruz",
  "09-15": "Ntra. Sra. de los Dolores", "09-16": "San Cornelio y San Cipriano",
  "09-19": "San Jenaro", "09-21": "San Mateo", "09-27": "San Vicente de Paúl",
  "09-29": "San Miguel, San Gabriel y San Rafael", "09-30": "San Jerónimo",
  "10-01": "Santa Teresita de Lisieux", "10-02": "Santos Ángeles Custodios",
  "10-04": "San Francisco de Asís", "10-06": "San Bruno", "10-07": "Ntra. Sra. del Rosario",
  "10-09": "San Dionisio", "10-12": "Virgen del Pilar", "10-15": "Santa Teresa de Jesús",
  "10-17": "San Ignacio de Antioquía", "10-18": "San Lucas", "10-19": "Santa Laura",
  "10-28": "San Simón y San Judas",
  "11-01": "Todos los Santos", "11-02": "Fieles Difuntos", "11-03": "San Martín de Porres",
  "11-04": "San Carlos Borromeo", "11-11": "San Martín de Tours", "11-15": "San Alberto Magno",
  "11-17": "Santa Isabel de Hungría", "11-21": "Presentación de María",
  "11-22": "Santa Cecilia", "11-24": "San Andrés Dung-Lac y compañeros mártires",
  "11-25": "Santa Catalina de Alejandría", "11-30": "San Andrés",
  "12-03": "San Francisco Javier", "12-04": "Santa Bárbara", "12-06": "San Nicolás",
  "12-07": "San Ambrosio", "12-08": "Inmaculada Concepción", "12-12": "Ntra. Sra. de Guadalupe",
  "12-13": "Santa Lucía", "12-14": "San Juan de la Cruz", "12-25": "Natividad del Señor",
  "12-26": "San Esteban", "12-27": "San Juan Evangelista", "12-28": "Santos Inocentes",
  "12-31": "San Silvestre",
};
function mmdd(d) { return `${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; }

// ---------- Componente principal ----------
const CALENDAR_EMOJIS = ["💑", "👨‍👩‍👧", "🎉", "🏠", "✈️", "💼", "⚽", "🎓"];

function CalendarSelectorScreen({ calendars, theme, onToggleTheme, onSelect, onCreate, degraded }) {
  const [degradedDismissed, setDegradedDismissed] = useState(false);
  const [view, setView] = useState("list"); // "list" | "create"
  const [name, setName] = useState("");
  const [emoji, setEmoji] = useState(CALENDAR_EMOJIS[0]);
  const [people, setPeople] = useState(["", ""]);
  const [error, setError] = useState("");
  const [creating, setCreating] = useState(false);
  const [justCreated, setJustCreated] = useState(null);

  const updatePerson = (i, v) => setPeople(prev => prev.map((p, idx) => idx === i ? v : p));
  const addPersonField = () => { if (people.length < 10) setPeople(prev => [...prev, ""]); };
  const removePersonField = (i) => { if (i >= 2) setPeople(prev => prev.filter((_, idx) => idx !== i)); };

  const cleanedPeople = people.map(p => p.trim()).filter(Boolean);
  const canSubmit = name.trim().length > 0 && cleanedPeople.length >= 2 && !creating;

  const submit = async () => {
    if (!name.trim()) { setError("Ponle un nombre al calendario."); return; }
    if (cleanedPeople.length < 2) { setError("Necesitáis al menos 2 participantes para crear el calendario."); return; }
    setError("");
    setCreating(true);
    try {
      const meta = await onCreate(name.trim(), emoji, cleanedPeople);
      setJustCreated(meta);
    } catch (err) {
      setError((err && err.message) || "No se pudo crear el calendario. Inténtalo de nuevo.");
    } finally {
      setCreating(false);
    }
  };

  const shareCalendar = (cal) => {
    let link = "";
    try { link = window.location.href; } catch {}
    const text = `Te invito a nuestro calendario "${cal.name}" ${cal.emoji}. Abre la app y selecciónalo en la lista de calendarios${link ? `: ${link}` : "."}`;
    openExternalLink(`https://wa.me/?text=${encodeURIComponent(text)}`);
  };

  return (
    <div data-theme={theme} className="theme-transition" style={{ background: "var(--bg)", minHeight: "100vh", color: "var(--text)", fontFamily: "'Instrument Sans','Segoe UI',sans-serif" }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,500;0,9..144,600;1,9..144,500&family=Instrument+Sans:wght@400;500;600&display=swap');
        * { box-sizing: border-box; }
        .fraunces { font-family: 'Fraunces', serif; }
        .scrollbar-none::-webkit-scrollbar { display: none; }
        button { font-family: inherit; }
        input, textarea, select { font-family: inherit; }
        [data-theme="dark"] {
          --bg: #000000; --field-bg: #000000; --surface: #17171A; --border: #302F34;
          --text: #F5EFE6; --text-muted: #ABA6AE; --text-faint: #86828B;
          --accent: #E8C468; --accent-soft: rgba(232,196,104,0.16); --accent-text: #2A1F0A;
          --danger: #FF7A5C; --overlay: rgba(0,0,0,0.7); --shadow: 0 -10px 40px rgba(0,0,0,0.5);
        }
        [data-theme="light"] {
          --bg: #FFFFFF; --field-bg: #F1EEF6; --surface: #F7F6FA; --border: #E2D9EA;
          --text: #241B30; --text-muted: #6E6180; --text-faint: #978CA6;
          --accent: #B9812A; --accent-soft: rgba(185,129,42,0.14); --accent-text: #FFFFFF;
          --danger: #D9553A; --overlay: rgba(40,30,55,0.35); --shadow: 0 -10px 30px rgba(60,40,80,0.14);
        }
        .theme-transition, .theme-transition * {
          transition: background 0.3s ease, background-color 0.3s ease, color 0.3s ease,
            border-color 0.3s ease, box-shadow 0.3s ease, fill 0.3s ease, stroke 0.3s ease;
        }
      `}</style>

      <div style={{ maxWidth: 480, margin: "0 auto", padding: "28px 18px 60px" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 22 }}>
          <div className="fraunces" style={{ fontSize: 21, fontWeight: 600 }}>
            {view === "list" ? "Vuestros calendarios" : "Nuevo calendario"}
          </div>
          <button onClick={onToggleTheme} aria-label="Cambiar tema" style={iconBtn}>
            {theme === "dark" ? <Sun size={15} /> : <Moon size={15} />}
          </button>
        </div>

        {degraded && !degradedDismissed && (
          <div style={{
            display: "flex", alignItems: "center", gap: 8, background: "var(--accent-soft)",
            border: "1px solid var(--accent)", borderRadius: 10, padding: "8px 10px", marginBottom: 16, fontSize: 11,
          }}>
            <span style={{ flex: 1, color: "var(--text)" }}>
              No se pudo conectar con el guardado permanente. Podéis seguir usando la app, pero los cambios de esta sesión podrían perderse si recargáis.
            </span>
            <button onClick={() => setDegradedDismissed(true)} style={{ background: "transparent", border: "none", cursor: "pointer", padding: 2, flexShrink: 0 }}>
              <X size={13} color="var(--text-muted)" />
            </button>
          </div>
        )}

        {view === "list" && (
          <>
            {calendars.length === 0 && (
              <EmptyState icon={CalendarRange} text="Todavía no tenéis ningún calendario. Cread el primero para empezar." />
            )}
            <div style={{ display: "flex", flexDirection: "column", gap: 10, marginBottom: 18 }}>
              {calendars.map(cal => (
                <div key={cal.id} style={{ background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 14, padding: 14, display: "flex", alignItems: "center", gap: 12 }}>
                  <div onClick={() => onSelect(cal.id)} style={{ display: "flex", alignItems: "center", gap: 12, flex: 1, cursor: "pointer" }}>
                    <div style={{ fontSize: 26 }}>{cal.emoji}</div>
                    <div>
                      <div style={{ fontWeight: 600, fontSize: 15 }}>{cal.name}</div>
                      <div style={{ fontSize: 11.5, color: "var(--text-muted)" }}>{cal.participantCount} participante{cal.participantCount === 1 ? "" : "s"}</div>
                    </div>
                  </div>
                  <button onClick={() => shareCalendar(cal)} aria-label="Compartir calendario" style={iconBtn}>
                    <Share2 size={14} />
                  </button>
                </div>
              ))}
            </div>
            <button onClick={() => { setView("create"); setJustCreated(null); }} style={{ ...primaryBtn, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 8 }}>
              <Plus size={16} /> Crear calendario nuevo
            </button>
          </>
        )}

        {view === "create" && !justCreated && (
          <>
            <Field label="Nombre del calendario (obligatorio)">
              <input style={inputStyle} value={name} onChange={e => setName(e.target.value)} placeholder="Ej. Pareja, Amigos, Familia..." />
            </Field>

            <Field label="Icono">
              <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                {CALENDAR_EMOJIS.map(e => (
                  <button key={e} onClick={() => setEmoji(e)} style={{
                    fontSize: 18, width: 40, height: 40, borderRadius: 9, cursor: "pointer",
                    border: emoji === e ? "1.5px solid var(--accent)" : "1px solid var(--border)",
                    background: emoji === e ? "var(--accent-soft)" : "transparent",
                  }}>{e}</button>
                ))}
              </div>
            </Field>

            <Field label="Participantes (mínimo 2)">
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {people.map((p, i) => (
                  <div key={i} style={{ display: "flex", gap: 6 }}>
                    <input style={{ ...inputStyle, flex: 1 }} value={p} onChange={e => updatePerson(i, e.target.value)}
                      placeholder={`Participante ${i + 1}${i < 2 ? " (obligatorio)" : ""}`} />
                    {i >= 2 && (
                      <button onClick={() => removePersonField(i)} style={{ ...iconBtn, width: 34, height: 34 }}><X size={14} /></button>
                    )}
                  </div>
                ))}
              </div>
              {people.length < 10 && (
                <button onClick={addPersonField} style={{ ...secondaryBtn, width: "100%", marginTop: 8, display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }}>
                  <Plus size={14} /> Añadir participante
                </button>
              )}
            </Field>

            {error && <div style={{ fontSize: 12, color: "var(--danger)", marginBottom: 10 }}>{error}</div>}
            {!error && (name.trim().length === 0 || cleanedPeople.length < 2) && (
              <div style={{ fontSize: 11.5, color: "var(--text-faint)", marginBottom: 10 }}>
                Falta {name.trim().length === 0 ? "el nombre" : ""}{name.trim().length === 0 && cleanedPeople.length < 2 ? " y " : ""}{cleanedPeople.length < 2 ? "al menos 2 participantes" : ""} para poder crear el calendario.
              </div>
            )}

            <div style={{ display: "flex", gap: 10, marginTop: 8 }}>
              <button onClick={() => setView("list")} style={{ ...secondaryBtn, flex: 1 }}>Cancelar</button>
              <button onClick={submit} disabled={!canSubmit} style={{ ...primaryBtn, flex: 2, opacity: canSubmit ? 1 : 0.45, cursor: canSubmit ? "pointer" : "not-allowed" }}>
                {creating ? "Creando..." : "Crear calendario"}
              </button>
            </div>
          </>
        )}

        {view === "create" && justCreated && (
          <div style={{ textAlign: "center", padding: "20px 0" }}>
            <div style={{ fontSize: 36, marginBottom: 10 }}>{justCreated.emoji}</div>
            <div className="fraunces" style={{ fontSize: 18, marginBottom: 6 }}>"{justCreated.name}" creado</div>
            <div style={{ fontSize: 12.5, color: "var(--text-muted)", marginBottom: 18 }}>
              Compártelo con el resto de participantes para que puedan colaborar.
            </div>
            <button onClick={() => shareCalendar(justCreated)} style={{ ...primaryBtn, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 8, marginBottom: 10 }}>
              <Share2 size={16} /> Compartir por WhatsApp
            </button>
            <button onClick={() => onSelect(justCreated.id)} style={{ ...secondaryBtn, width: "100%" }}>
              Entrar al calendario
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

export default function App() {
  const [theme, setTheme] = useState("dark");
  const [calendars, setCalendars] = useState(null); // null = cargando
  const [calendarId, setCalendarId] = useState(null); // null = mostrar selector
  const [selectorDegraded, setSelectorDegraded] = useState(false);

  useEffect(() => {
    (async () => {
      try {
        const th = await storageGet("theme", false);
        if (th && th.value) setTheme(th.value);
      } catch {}

      let idx = [];
      try {
        const r = await storageGet("calendarIndex", true);
        if (r && r.value) idx = JSON.parse(r.value);
      } catch {}

      // Migración: si ya había datos de una versión anterior (sin varios calendarios), se convierten
      // en el primer calendario en vez de perderse.
      if (idx.length === 0) {
        try {
          const oldEvents = await storageGet("events", true);
          const oldParticipants = await storageGet("participants", true);
          const oldNames = await storageGet("names", true);
          if ((oldEvents && oldEvents.value) || (oldParticipants && oldParticipants.value) || (oldNames && oldNames.value)) {
            const id = uid();
            let participants = oldParticipants && oldParticipants.value ? JSON.parse(oldParticipants.value) : [];
            if (participants.length === 0 && oldNames && oldNames.value) {
              const old = JSON.parse(oldNames.value);
              if (old.A) participants.push({ id: uid(), name: old.A, color: PARTICIPANT_PALETTE[0] });
              if (old.B) participants.push({ id: uid(), name: old.B, color: PARTICIPANT_PALETTE[1] });
            }
            const keysToMove = ["events", "plans", "birthdays", "santoralOverrides", "attachmentsByEvent", "participants"];
            for (const k of keysToMove) {
              try {
                const v = await storageGet(k, true);
                if (v && v.value) await storageSet(`cal:${id}:${k}`, v.value, true);
              } catch {}
            }
            if (participants.length > 0) await storageSet(`cal:${id}:participants`, JSON.stringify(participants), true);
            const meta = { id, name: "Nuestro calendario", emoji: "💑", createdAt: Date.now(), participantCount: participants.length };
            idx = [meta];
            await storageSet("calendarIndex", JSON.stringify(idx), true);
          }
        } catch {}
      }

      setCalendars(idx);

      try {
        const last = await storageGet("lastCalendarId", false);
        if (last && last.value && idx.some(c => c.id === last.value)) setCalendarId(last.value);
      } catch {}
      if (storageDegraded) setSelectorDegraded(true);
    })();
  }, []);

  useEffect(() => {
    const interval = setInterval(() => { if (storageDegraded) setSelectorDegraded(true); }, 4000);
    return () => clearInterval(interval);
  }, []);

  const toggleTheme = () => setTheme(t => { const next = t === "dark" ? "light" : "dark"; storageSet("theme", next, false).catch(() => {}); return next; });

  const selectCalendar = (id) => {
    setCalendarId(id);
    storageSet("lastCalendarId", id, false).catch(() => {});
  };

  // Escribe en el almacenamiento y comprueba leyendo de vuelta que se guardó de verdad
  // (antes fallaba en silencio: parecía creado pero los participantes no habían quedado guardados).
  // Reintenta con espera creciente: dos escrituras seguidas al crear un calendario pueden chocar
  // con el límite de peticiones del almacenamiento, que es transitorio y se resuelve esperando.
  const createCalendar = async (name, emoji, participantNames) => {
    const id = uid();
    const newParticipants = participantNames.map((n, i) => ({ id: uid(), name: n, color: PARTICIPANT_PALETTE[i % PARTICIPANT_PALETTE.length] }));
    const meta = { id, name, emoji, createdAt: Date.now(), participantCount: newParticipants.length };
    const newIndex = [...(calendars || []), meta];

    try {
      await storageSet(`cal:${id}:participants`, JSON.stringify(newParticipants), true);
      await storageSet("calendarIndex", JSON.stringify(newIndex), true);
    } catch (err) {
      const detail = (err && (err.message || err.toString())) || "";
      throw new Error(`No se pudo guardar el calendario${detail ? `: ${detail}` : ""}. Espera unos segundos y vuelve a tocar "Crear calendario".`);
    }

    setCalendars(newIndex);
    return meta;
  };

  if (calendars === null) {
    return <div data-theme={theme} style={{ background: "var(--bg)", minHeight: "100vh" }} />;
  }

  if (!calendarId) {
    return (
      <CalendarSelectorScreen
        calendars={calendars} theme={theme} onToggleTheme={toggleTheme}
        onSelect={selectCalendar} onCreate={createCalendar}
        degraded={selectorDegraded}
      />
    );
  }

  return (
    <CalendarApp
      key={calendarId}
      calendarId={calendarId}
      calendarMeta={calendars.find(c => c.id === calendarId)}
      theme={theme} onToggleTheme={toggleTheme}
      onSwitchCalendar={() => setCalendarId(null)}
    />
  );
}

function CalendarApp({ calendarId, calendarMeta, theme, onToggleTheme, onSwitchCalendar }) {
  const [tab, setTab] = useState("calendario");
  const [participants, setParticipants] = useState([]);
  const [events, setEvents] = useState([]);
  const [plans, setPlans] = useState([]);
  const [birthdays, setBirthdays] = useState([]);
  const [santoralOverrides, setSantoralOverrides] = useState({});
  const [showSantos, setShowSantos] = useState(false);
  const [attachmentsByEvent, setAttachmentsByEvent] = useState({});
  const [loaded, setLoaded] = useState(false);
  const [degradedNotice, setDegradedNotice] = useState(false);
  const [degradedDismissed, setDegradedDismissed] = useState(false);
  const [cursor, setCursor] = useState(() => { const d = new Date(); return { y: d.getFullYear(), m: d.getMonth() }; });
  const [selectedDay, setSelectedDay] = useState(null);
  const [modalEvent, setModalEvent] = useState(null); // objeto evento en edición, o null
  const [showSettings, setShowSettings] = useState(false);
  const ck = useCallback((k) => `cal:${calendarId}:${k}`, [calendarId]);

  // Carga inicial desde almacenamiento compartido (todo bajo el prefijo de este calendario)
  useEffect(() => {
    (async () => {
      try {
        const part = await storageGet(ck("participants"), true);
        if (part && part.value) setParticipants(JSON.parse(part.value));
      } catch {}
      try {
        const e = await storageGet(ck("events"), true);
        if (e && e.value) setEvents(JSON.parse(e.value));
      } catch {}
      try {
        const p = await storageGet(ck("plans"), true);
        if (p && p.value) setPlans(JSON.parse(p.value));
      } catch {}
      try {
        const bd = await storageGet(ck("birthdays"), true);
        if (bd && bd.value) setBirthdays(JSON.parse(bd.value));
      } catch {}
      try {
        const so = await storageGet(ck("santoralOverrides"), true);
        if (so && so.value) setSantoralOverrides(JSON.parse(so.value));
      } catch {}
      try {
        const at = await storageGet(ck("attachmentsByEvent"), true);
        if (at && at.value) setAttachmentsByEvent(JSON.parse(at.value));
      } catch {}
      try {
        const ss = await storageGet("showSantos", false);
        if (ss && ss.value) setShowSantos(ss.value === "true");
      } catch {}
      setLoaded(true);
      if (storageDegraded) setDegradedNotice(true);
    })();
  }, [ck]);

  useEffect(() => {
    const interval = setInterval(() => { if (storageDegraded) setDegradedNotice(true); }, 4000);
    return () => clearInterval(interval);
  }, []);

  const persist = useCallback(async (key, value) => {
    try { await storageSet(ck(key), JSON.stringify(value), true); } catch {}
  }, [ck]);

  const [participantsSaveError, setParticipantsSaveError] = useState(false);
  const persistParticipants = useCallback(async (value) => {
    try {
      await storageSet(ck("participants"), JSON.stringify(value), true);
      setParticipantsSaveError(false);
    } catch {
      setParticipantsSaveError(true);
    }
  }, [ck]);

  useEffect(() => { if (loaded) persist("events", events); }, [events, loaded, persist]);
  useEffect(() => { if (loaded) persist("plans", plans); }, [plans, loaded, persist]);
  useEffect(() => { if (loaded) persist("birthdays", birthdays); }, [birthdays, loaded, persist]);
  useEffect(() => { if (loaded) persist("santoralOverrides", santoralOverrides); }, [santoralOverrides, loaded, persist]);
  useEffect(() => { if (loaded) persist("attachmentsByEvent", attachmentsByEvent); }, [attachmentsByEvent, loaded, persist]);
  useEffect(() => { if (loaded && participants.length > 0) persistParticipants(participants); }, [participants, loaded, persistParticipants]);
  useEffect(() => { if (loaded) { storageSet("showSantos", String(showSantos), false).catch(() => {}); } }, [showSantos, loaded]);

  const grid = useMemo(() => {
    const start = startOfGrid(cursor.y, cursor.m);
    const days = [];
    for (let i = 0; i < 42; i++) {
      const d = new Date(start);
      d.setDate(start.getDate() + i);
      days.push(d);
    }
    return days;
  }, [cursor]);

  const eventsByDay = useMemo(() => {
    const map = {};
    for (const ev of events) {
      const list = expandOccurrences(ev, cursor.y, cursor.m);
      for (const k of list) {
        if (!map[k]) map[k] = [];
        map[k].push(ev);
      }
    }
    return map;
  }, [events, cursor]);

  function expandOccurrences(ev, year, month) {
    // Devuelve las claves de fecha (dentro del mes visible, con margen) donde aparece el evento
    const keys = [];
    const base = fromKey(ev.date);
    const rangeStart = new Date(year, month - 1, 20);
    const rangeEnd = new Date(year, month + 1, 10);

    if (ev.endDate && ev.endDate > ev.date) {
      // Evento de varios días: incluir cada día del rango
      const end = fromKey(ev.endDate);
      let d = new Date(Math.max(base, rangeStart));
      const stop = new Date(Math.min(end, rangeEnd));
      while (d <= stop) { keys.push(toKey(d)); d.setDate(d.getDate() + 1); }
      return keys;
    }

    if (!ev.repeat || ev.repeat === "none") {
      if (base >= rangeStart && base <= rangeEnd) keys.push(ev.date);
      return keys;
    }
    let cursorD = new Date(base);
    let guard = 0;
    while (cursorD <= rangeEnd && guard < 400) {
      guard++;
      if (cursorD >= rangeStart) keys.push(toKey(cursorD));
      if (ev.repeat === "weekly") cursorD.setDate(cursorD.getDate() + 7);
      else if (ev.repeat === "monthly") cursorD.setMonth(cursorD.getMonth() + 1);
      else if (ev.repeat === "yearly") cursorD.setFullYear(cursorD.getFullYear() + 1);
      else break;
    }
    return keys;
  }

  function openNewEvent(dateKey) {
    setModalEvent({
      id: uid(), isNew: true, date: dateKey, endDate: "", title: "", time: "",
      participantIds: participants.map(p => p.id), repeat: "none", reminders: [],
    });
  }

  function saveEvent(ev) {
    const { isNew, ...clean } = ev;
    setEvents(prev => {
      const exists = prev.some(e => e.id === clean.id);
      return exists ? prev.map(e => e.id === clean.id ? clean : e) : [...prev, clean];
    });
    // Si el evento es nuevo, no viene ya de un cumpleaños, y su título menciona "cumpleaños"/"cumple",
    // se añade también a la lista de Cumpleaños (asociado al primer participante marcado, si hay alguno).
    if (isNew && !clean.fromBirthday && /cumple/i.test(clean.title || "")) {
      const category = (clean.participantIds && clean.participantIds[0]) || (participants[0] && participants[0].id);
      if (category) {
        setBirthdays(prev => [...prev, {
          id: uid(), name: clean.title.replace(/cumplea[ñn]os\s*(de)?/i, "").trim() || clean.title,
          date: clean.date, category, isOwn: false, eventId: clean.id,
        }]);
      }
    }
    setModalEvent(null);
  }

  function deleteEvent(id) {
    setEvents(prev => prev.filter(e => e.id !== id));
    setBirthdays(prev => prev.filter(b => b.eventId !== id));
    setPlans(prev => prev.map(p => p.eventId === id ? { ...p, date: null, eventId: null } : p));
    setAttachmentsByEvent(prev => { const next = { ...prev }; delete next[id]; return next; });
    setModalEvent(null);
  }

  function setEventAttachments(eventId, list) {
    setAttachmentsByEvent(prev => ({ ...prev, [eventId]: list }));
  }

  const today = new Date();
  const monthLabel = `${MESES[cursor.m]} ${cursor.y}`;
  const isCalendarTab = tab === "calendario";
  const emojiPrefix = calendarMeta ? `${calendarMeta.emoji} ` : "";
  const TAB_TITLES = { calendario: `${emojiPrefix}${monthLabel}`, cartera: "Cartera", cumple: "Cumpleaños", planes: "Planes" };

  return (
    <div data-theme={theme} className="theme-transition" style={{ background: "var(--bg)", minHeight: "100vh", color: "var(--text)", fontFamily: "'Instrument Sans','Segoe UI',sans-serif" }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,500;0,9..144,600;1,9..144,500&family=Instrument+Sans:wght@400;500;600&display=swap');
        * { box-sizing: border-box; }
        .fraunces { font-family: 'Fraunces', serif; }
        .scrollbar-none::-webkit-scrollbar { display: none; }
        button { font-family: inherit; }
        input, textarea, select { font-family: inherit; }

        [data-theme="dark"] {
          --bg: #000000; --field-bg: #000000; --surface: #17171A; --border: #302F34;
          --text: #F5EFE6; --text-muted: #ABA6AE; --text-faint: #86828B;
          --accent: #E8C468; --accent-soft: rgba(232,196,104,0.16); --accent-text: #2A1F0A;
          --danger: #FF7A5C; --overlay: rgba(0,0,0,0.7); --shadow: 0 -10px 40px rgba(0,0,0,0.5);
        }
        [data-theme="light"] {
          --bg: #FFFFFF; --field-bg: #F1EEF6; --surface: #F7F6FA; --border: #E2D9EA;
          --text: #241B30; --text-muted: #6E6180; --text-faint: #978CA6;
          --accent: #B9812A; --accent-soft: rgba(185,129,42,0.14); --accent-text: #FFFFFF;
          --danger: #D9553A; --overlay: rgba(40,30,55,0.35); --shadow: 0 -10px 30px rgba(60,40,80,0.14);
        }
        .theme-transition, .theme-transition * {
          transition: background 0.3s ease, background-color 0.3s ease, color 0.3s ease,
            border-color 0.3s ease, box-shadow 0.3s ease, fill 0.3s ease, stroke 0.3s ease;
        }
      `}</style>

      {showSettings && loaded && (
        <SettingsModal
          participants={participants} setParticipants={setParticipants}
          participantsSaveError={participantsSaveError}
          theme={theme} onToggleTheme={onToggleTheme}
          onSwitchCalendar={onSwitchCalendar}
          onClose={() => setShowSettings(false)}
        />
      )}

      <div style={{ maxWidth: 920, margin: "0 auto", padding: isCalendarTab ? "10px 6px 70px" : "16px 18px 70px" }}>
        <div style={{ padding: isCalendarTab ? "0 12px" : 0 }}>
          <TopBar title={TAB_TITLES[tab]} showSantos={showSantos} onToggleSantos={() => setShowSantos(v => !v)} onOpenSettings={() => setShowSettings(true)} />
          <TabBar tab={tab} setTab={setTab} />
          {degradedNotice && !degradedDismissed && (
            <div style={{
              display: "flex", alignItems: "center", gap: 8, background: "var(--accent-soft)",
              border: "1px solid var(--accent)", borderRadius: 10, padding: "8px 10px", marginBottom: 8, fontSize: 11,
            }}>
              <span style={{ flex: 1, color: "var(--text)" }}>
                No se pudo conectar con el guardado permanente. Podéis seguir usando la app, pero los cambios de esta sesión podrían perderse si recargáis.
              </span>
              <button onClick={() => setDegradedDismissed(true)} style={{ background: "transparent", border: "none", cursor: "pointer", padding: 2, flexShrink: 0 }}>
                <X size={13} color="var(--text-muted)" />
              </button>
            </div>
          )}
        </div>

        {tab === "calendario" && (
          <CalendarView
            grid={grid} cursor={cursor} setCursor={setCursor} today={today}
            events={events} eventsByDay={eventsByDay} participants={participants}
            showSantos={showSantos} santoralOverrides={santoralOverrides}
            onDayClick={(k) => setSelectedDay(k)}
            onEditEvent={(ev) => setModalEvent(ev)}
          />
        )}

        <div style={{ padding: isCalendarTab ? "0 12px" : 0 }}>
          {tab === "planes" && (
            <PlansView plans={plans} setPlans={setPlans} events={events} onEventsChange={setEvents} participants={participants} />
          )}

          {tab === "cumple" && (
            <AnnualDatesView
              items={birthdays} setItems={setBirthdays} participants={participants}
              events={events} onEventsChange={setEvents}
              icon={Cake} groupTitle="Cumpleaños"
              emptyText="Añadid los cumpleaños que no os queráis perder: los de la gente importante para cada uno."
            />
          )}

          {tab === "cartera" && (
            <CarteraView
              events={events} attachmentsByEvent={attachmentsByEvent}
              onAdd={(eventId, item) => {
                setAttachmentsByEvent(prev => ({ ...prev, [eventId]: [...(prev[eventId] || []), item] }));
              }}
              onRemove={(eventId, attId) => {
                setAttachmentsByEvent(prev => ({ ...prev, [eventId]: (prev[eventId] || []).filter(a => a.id !== attId) }));
              }}
              onOpenEvent={(ev) => setModalEvent(ev)}
            />
          )}
        </div>
      </div>

      {tab === "calendario" && (
        <button onClick={() => openNewEvent(toKey(new Date()))} aria-label="Nuevo evento" style={fabStyle}>
          <Plus size={24} />
        </button>
      )}

      {selectedDay && (
        <DayPanel
          dateKey={selectedDay}
          events={(eventsByDay[selectedDay] || [])}
          santo={showSantos ? (santoralOverrides[mmdd(fromKey(selectedDay))] ?? SANTORAL_DEFAULT[mmdd(fromKey(selectedDay))]) : null}
          participants={participants}
          onClose={() => setSelectedDay(null)}
          onAdd={() => { openNewEvent(selectedDay); }}
          onEdit={(ev) => setModalEvent(ev)}
          onDeleteEvent={(id) => deleteEvent(id)}
        />
      )}

      {modalEvent && (
        <EventModal
          ev={modalEvent}
          participants={participants}
          attachments={attachmentsByEvent[modalEvent.id] || []}
          onChangeAttachments={(list) => setEventAttachments(modalEvent.id, list)}
          onClose={() => setModalEvent(null)}
          onSave={saveEvent}
          onDelete={!modalEvent.isNew ? () => deleteEvent(modalEvent.id) : null}
        />
      )}
    </div>
  );
}

// ---------- Cabecera con el motivo firma: dos lunas superpuestas ----------
function TopBar({ title, showSantos, onToggleSantos, onOpenSettings }) {
  return (
    <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
      <button onClick={onToggleSantos} aria-label="Mostrar santos en el calendario"
        style={{
          ...iconBtn, width: 30, height: 30,
          background: showSantos ? "var(--accent-soft)" : "var(--surface)",
          border: showSantos ? "1px solid var(--accent)" : "1px solid var(--border)",
          color: showSantos ? "var(--accent)" : "var(--text)",
        }}>
        <Star size={14} fill={showSantos ? "var(--accent)" : "none"} />
      </button>
      <div className="fraunces" style={{ fontSize: 16, fontWeight: 600, textTransform: "capitalize" }}>{title}</div>
      <button onClick={onOpenSettings} aria-label="Ajustes" style={{ ...iconBtn, width: 30, height: 30 }}>
        <Settings size={14} />
      </button>
    </div>
  );
}

function SettingsModal({ participants, setParticipants, participantsSaveError, theme, onToggleTheme, onSwitchCalendar, onClose }) {
  const [view, setView] = useState("menu"); // "menu" | "participants"
  const [personName, setPersonName] = useState("");

  const addPerson = () => {
    if (!personName.trim() || participants.length >= 10) return;
    setParticipants(prev => [...prev, { id: uid(), name: personName.trim(), color: nextParticipantColor(prev) }]);
    setPersonName("");
  };
  const removePerson = (id) => setParticipants(prev => prev.filter(p => p.id !== id));
  const renamePerson = (id, name) => setParticipants(prev => prev.map(p => p.id === id ? { ...p, name } : p));

  const BackHeader = ({ title }) => (
    <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 16 }}>
      <button onClick={() => setView("menu")} style={iconBtn}><ChevronLeft size={18} /></button>
      <div className="fraunces" style={{ fontSize: 19 }}>{title}</div>
    </div>
  );

  return (
    <Overlay onClick={onClose}>
      <div style={{ ...cardStyle, maxHeight: "85vh", overflowY: "auto" }} className="scrollbar-none" onClick={e => e.stopPropagation()}>

        {view === "menu" && (
          <>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
              <div className="fraunces" style={{ fontSize: 19 }}>Ajustes</div>
              <button onClick={onClose} style={iconBtn}><X size={18} /></button>
            </div>

            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              <button onClick={() => setView("participants")} style={settingsRowStyle}>
                <Users size={16} color="var(--text-muted)" />
                <span style={{ flex: 1, textAlign: "left" }}>Participantes</span>
                <ChevronRight size={15} color="var(--text-faint)" />
              </button>

              <button onClick={onToggleTheme} style={settingsRowStyle}>
                {theme === "dark" ? <Moon size={16} color="var(--text-muted)" /> : <Sun size={16} color="var(--text-muted)" />}
                <span style={{ flex: 1, textAlign: "left" }}>Tema: {theme === "dark" ? "Oscuro" : "Claro"}</span>
                <span style={{ fontSize: 11, color: "var(--text-faint)" }}>Tocar para cambiar</span>
              </button>

              <button onClick={onSwitchCalendar} style={settingsRowStyle}>
                <CalendarRange size={16} color="var(--text-muted)" />
                <span style={{ flex: 1, textAlign: "left" }}>Cambiar de calendario</span>
                <ChevronRight size={15} color="var(--text-faint)" />
              </button>
            </div>
          </>
        )}

        {view === "participants" && (
          <>
            <BackHeader title="Participantes" />
            <div style={{ fontSize: 10.5, color: "var(--text-faint)", marginBottom: 10 }}>
              Cada persona que añadas tiene su propio color en el calendario (hasta 10).
            </div>
            {participantsSaveError && (
              <div style={{ fontSize: 11.5, color: "var(--danger)", marginBottom: 10 }}>
                No se ha podido guardar el último cambio. Revisa tu conexión; si sigue fallando, vuelve a intentarlo en unos segundos.
              </div>
            )}

            <div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 10 }}>
              {participants.length === 0 && (
                <div style={{ fontSize: 12.5, color: "var(--text-faint)" }}>Todavía no has añadido a nadie.</div>
              )}
              {participants.map(p => (
                <div key={p.id} style={{ display: "flex", alignItems: "center", gap: 8, background: "var(--field-bg)", border: "1px solid var(--border)", borderRadius: 8, padding: "7px 10px" }}>
                  <span style={{ width: 10, height: 10, borderRadius: "50%", background: p.color, flexShrink: 0 }} />
                  <input value={p.name} onChange={e => renamePerson(p.id, e.target.value)}
                    style={{ ...inputStyle, flex: 1, border: "none", padding: 0, background: "transparent" }} placeholder="Nombre" />
                  <Trash2 size={14} color="var(--text-faint)" style={{ cursor: "pointer", flexShrink: 0 }} onClick={() => removePerson(p.id)} />
                </div>
              ))}
            </div>

            {participants.length < 10 ? (
              <div style={{ display: "flex", gap: 6 }}>
                <input style={{ ...inputStyle, flex: 1 }} value={personName} onChange={e => setPersonName(e.target.value)}
                  onKeyDown={e => e.key === "Enter" && addPerson()} placeholder="Añadir participante" />
                <button onClick={addPerson} style={iconBtn}><Plus size={16} /></button>
              </div>
            ) : (
              <div style={{ fontSize: 11.5, color: "var(--text-faint)" }}>Máximo de 10 participantes alcanzado.</div>
            )}
          </>
        )}

      </div>
    </Overlay>
  );
}

const settingsRowStyle = {
  display: "flex", alignItems: "center", gap: 10, width: "100%", textAlign: "left",
  background: "var(--field-bg)", border: "1px solid var(--border)", borderRadius: 10,
  padding: "12px 14px", fontSize: 13.5, color: "var(--text)", cursor: "pointer",
};

function TabBar({ tab, setTab }) {
  const tabs = [
    { key: "calendario", label: "Calendario", icon: Clock },
    { key: "cartera", label: "Cartera", icon: Wallet },
    { key: "cumple", label: "Cumpleaños", icon: Cake },
    { key: "planes", label: "Planes", icon: ListTodo },
  ];
  return (
    <div style={{ display: "flex", gap: 3, background: "var(--surface)", padding: 3, borderRadius: 11, marginBottom: 8 }}>
      {tabs.map(t => {
        const active = tab === t.key;
        const Icon = t.icon;
        return (
          <button key={t.key} onClick={() => setTab(t.key)}
            style={{
              flex: 1, minWidth: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 2,
              padding: "6px 2px", borderRadius: 8, border: "none", cursor: "pointer",
              background: active ? "var(--border)" : "transparent",
              color: active ? "var(--text)" : "var(--text-faint)",
              fontSize: 8, fontWeight: 500, transition: "background 0.15s", lineHeight: 1.1, textAlign: "center",
            }}>
            <Icon size={12} /> {t.label}
          </button>
        );
      })}
    </div>
  );
}

function buildWeekSegments(weekKeys, eventsByDay) {
  const segments = [];
  for (let i = 0; i < 7; i++) {
    const dayEvents = eventsByDay[weekKeys[i]] || [];
    for (const ev of dayEvents) {
      const prevKey = i > 0 ? weekKeys[i - 1] : null;
      const prevHas = prevKey && (eventsByDay[prevKey] || []).some(e => e.id === ev.id);
      if (prevHas) continue; // ya forma parte de un segmento que empezó antes en esta semana
      let j = i;
      while (j < 7 && (eventsByDay[weekKeys[j]] || []).some(e => e.id === ev.id)) j++;
      segments.push({ event: ev, startCol: i, endCol: j - 1 });
    }
  }
  // Asignar carriles (lanes) evitando solapes: el santo (si lo hay ese día) tiene prioridad de carril,
  // luego los segmentos más largos primero
  const sorted = segments.slice().sort((a, b) =>
    a.startCol - b.startCol
    || (a.event.isSanto === b.event.isSanto ? 0 : a.event.isSanto ? -1 : 1)
    || (b.endCol - b.startCol) - (a.endCol - a.startCol)
  );
  const laneEnds = [];
  sorted.forEach(seg => {
    let lane = laneEnds.findIndex(end => end < seg.startCol);
    if (lane === -1) { lane = laneEnds.length; laneEnds.push(seg.endCol); }
    else laneEnds[lane] = seg.endCol;
    seg.lane = lane;
  });
  return sorted;
}

const MAX_VISIBLE_LANES = 3;
const BAR_H = 13;
const BAR_GAP = 2;

function CalendarView({ grid, cursor, setCursor, today, events, eventsByDay, participants, showSantos, santoralOverrides, onDayClick, onEditEvent }) {
  const changeMonth = useCallback((delta) => {
    setCursor(prev => {
      let m = prev.m + delta, y = prev.y;
      if (m < 0) { m = 11; y -= 1; } else if (m > 11) { m = 0; y += 1; }
      return { y, m };
    });
  }, [setCursor]);

  // Navegación por rueda del ratón (trackpad / scroll) con protección anti-rebote
  const wheelLock = React.useRef(false);
  const wheelAccum = React.useRef(0);
  const handleWheel = (e) => {
    const delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
    wheelAccum.current += delta;
    if (wheelLock.current) return;
    const THRESHOLD = 60;
    if (wheelAccum.current > THRESHOLD) {
      changeMonth(1); wheelLock.current = true; wheelAccum.current = 0;
      setTimeout(() => { wheelLock.current = false; }, 450);
    } else if (wheelAccum.current < -THRESHOLD) {
      changeMonth(-1); wheelLock.current = true; wheelAccum.current = 0;
      setTimeout(() => { wheelLock.current = false; }, 450);
    }
  };

  // Navegación por swipe táctil (móvil)
  const touchStart = React.useRef(null);
  const handleTouchStart = (e) => { touchStart.current = e.touches[0].clientX; };
  const handleTouchEnd = (e) => {
    if (touchStart.current == null) return;
    const delta = e.changedTouches[0].clientX - touchStart.current;
    if (delta < -50) changeMonth(1);
    else if (delta > 50) changeMonth(-1);
    touchStart.current = null;
  };

  // Animación: al cambiar de mes, la rejilla se desliza suavemente hasta su posición final
  const [slideTransform, setSlideTransform] = useState("translateX(0px)");
  const [slideTransition, setSlideTransition] = useState("none");
  const [slideOpacity, setSlideOpacity] = useState(1);
  const prevIndexRef = React.useRef(cursor.y * 12 + cursor.m);
  useEffect(() => {
    const curIndex = cursor.y * 12 + cursor.m;
    const prevIndex = prevIndexRef.current;
    if (curIndex !== prevIndex) {
      const dir = curIndex > prevIndex ? 1 : -1;
      prevIndexRef.current = curIndex;
      // 1) Salto instantáneo (sin transición) a la posición de partida, con el nuevo mes ya pintado
      setSlideTransition("none");
      setSlideTransform(`translateX(${dir * 36}px)`);
      setSlideOpacity(0.35);
      // 2) Dos frames después, se activa la transición y se anima hasta la posición final
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          setSlideTransition("transform 0.34s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.28s ease-out");
          setSlideTransform("translateX(0px)");
          setSlideOpacity(1);
        });
      });
    }
  }, [cursor]);

  const weeks = useMemo(() => {
    const out = [];
    for (let w = 0; w < 6; w++) out.push(grid.slice(w * 7, w * 7 + 7));
    return out;
  }, [grid]);

  const barsHeight = MAX_VISIBLE_LANES * (BAR_H + BAR_GAP);

  return (
    <div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(7,1fr)", gap: 2, marginBottom: 2 }}>
        {DIAS.map(d => (
          <div key={d} style={{ textAlign: "center", fontSize: 9.5, color: "var(--text-faint)", fontWeight: 600, letterSpacing: 0.3 }}>{d}</div>
        ))}
      </div>

      <div onWheel={handleWheel} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} style={{ touchAction: "pan-y", overflow: "hidden" }}>
        <div style={{ transform: slideTransform, transition: slideTransition, opacity: slideOpacity, willChange: "transform, opacity" }}>
        {weeks.map((weekDays, wi) => {
          const weekKeys = weekDays.map(toKey);
          // Si se muestran los santos, se añaden como un pseudo-evento más de ese día (compitiendo por los mismos huecos)
          const weekEventsByDay = {};
          weekKeys.forEach((key, i) => {
            const dayList = (eventsByDay[key] || []).slice();
            if (showSantos) {
              const santo = santoralOverrides[mmdd(weekDays[i])] ?? SANTORAL_DEFAULT[mmdd(weekDays[i])];
              if (santo) dayList.unshift({ id: "santo-" + key, title: santo, isSanto: true });
            }
            weekEventsByDay[key] = dayList;
          });
          const segments = buildWeekSegments(weekKeys, weekEventsByDay);
          const visible = segments.filter(s => s.lane < MAX_VISIBLE_LANES);
          const overflowByDay = new Array(7).fill(0);
          segments.filter(s => s.lane >= MAX_VISIBLE_LANES).forEach(s => {
            for (let c = s.startCol; c <= s.endCol; c++) overflowByDay[c]++;
          });

          return (
            <div key={wi} style={{ marginBottom: 3, paddingBottom: 3, borderBottom: "1px solid var(--border)" }}>
              <div style={{ display: "grid", gridTemplateColumns: "repeat(7,1fr)", gap: 2, marginBottom: 2 }}>
                {weekDays.map((d, i) => {
                  const key = weekKeys[i];
                  const inMonth = d.getMonth() === cursor.m;
                  const isToday = isSameDay(d, today);
                  return (
                    <div key={i} onClick={() => onDayClick(key)}
                      style={{
                        textAlign: "center", borderRadius: 6, padding: "2px 0", cursor: "pointer",
                        background: isToday ? "var(--accent-soft)" : "transparent",
                        border: isToday ? "1px solid var(--accent)" : "1px solid transparent",
                        opacity: inMonth ? 1 : 0.35,
                      }}>
                      <span style={{ fontSize: 11.5, fontWeight: isToday ? 700 : 500, color: isToday ? "var(--accent)" : "var(--text)" }}>{d.getDate()}</span>
                    </div>
                  );
                })}
              </div>

              <div style={{ position: "relative", minHeight: barsHeight }}>
                {visible.map((seg, si) => {
                  if (seg.event.isSanto) {
                    const leftPct = (seg.startCol / 7) * 100;
                    const widthPct = ((seg.endCol - seg.startCol + 1) / 7) * 100;
                    return (
                      <div key={seg.event.id + "-" + si}
                        onClick={() => onDayClick(weekKeys[seg.startCol])}
                        title={seg.event.title}
                        style={{
                          position: "absolute", left: `calc(${leftPct}% + 1px)`, width: `calc(${widthPct}% - 2px)`,
                          top: seg.lane * (BAR_H + BAR_GAP), height: BAR_H,
                          background: "transparent", borderLeft: "2.5px solid var(--accent)",
                          color: "var(--text-muted)", borderRadius: 2,
                          fontSize: 9, fontWeight: 500, display: "flex", alignItems: "center",
                          padding: "0 4px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                          cursor: "pointer", lineHeight: 1, fontStyle: "italic",
                        }}>
                        {seg.event.title}
                      </div>
                    );
                  }
                  const ids = seg.event.participantIds || [];
                  const c = ids.length === 1 ? participantStyle(ids[0], participants) : sharedStyle();
                  const leftPct = (seg.startCol / 7) * 100;
                  const widthPct = ((seg.endCol - seg.startCol + 1) / 7) * 100;
                  return (
                    <div key={seg.event.id + "-" + si}
                      onClick={(ev) => { ev.stopPropagation(); onEditEvent(seg.event); }}
                      title={eventTitle(seg.event)}
                      style={{
                        position: "absolute", left: `calc(${leftPct}% + 1px)`, width: `calc(${widthPct}% - 2px)`,
                        top: seg.lane * (BAR_H + BAR_GAP), height: BAR_H,
                        background: c.bg, color: c.text, borderRadius: 4,
                        fontSize: 9.5, fontWeight: 600, display: "flex", alignItems: "center",
                        padding: "0 3px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                        cursor: "pointer", lineHeight: 1, letterSpacing: -0.2,
                      }}>
                      {eventTitle(seg.event)}
                    </div>
                  );
                })}
              </div>

              <div style={{ display: "grid", gridTemplateColumns: "repeat(7,1fr)", gap: 2, marginTop: 1, minHeight: 9 }}>
                {overflowByDay.map((n, i) => (
                  <div key={i} style={{ textAlign: "center" }}>
                    {n > 0 && (
                      <span onClick={() => onDayClick(weekKeys[i])} style={{ fontSize: 8.5, color: "var(--text-faint)", cursor: "pointer" }}>+{n}</span>
                    )}
                  </div>
                ))}
              </div>
            </div>
          );
        })}
        </div>
      </div>
    </div>
  );
}

function DayPanel({ dateKey, events, santo, participants, onClose, onAdd, onEdit, onDeleteEvent }) {
  const d = fromKey(dateKey);
  const label = `${d.getDate()} de ${MESES[d.getMonth()].toLowerCase()}`;
  const [pressedId, setPressedId] = useState(null);
  const pressTimer = React.useRef(null);
  const longPressFired = React.useRef(false);

  const startPress = (id) => {
    setPressedId(null);
    longPressFired.current = false;
    pressTimer.current = setTimeout(() => {
      longPressFired.current = true;
      setPressedId(id);
    }, 500);
  };
  const cancelPress = () => {
    if (pressTimer.current) { clearTimeout(pressTimer.current); pressTimer.current = null; }
  };
  const handleRowClick = (ev) => {
    if (longPressFired.current) { longPressFired.current = false; return; }
    onEdit(ev);
  };

  return (
    <Overlay onClick={onClose}>
      <div style={{ ...cardStyle, alignSelf: "flex-end", width: "100%", maxWidth: 760, borderRadius: "18px 18px 0 0", margin: 0 }} onClick={e => e.stopPropagation()}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 14 }}>
          <div className="fraunces" style={{ fontSize: 19 }}>{label}</div>
          <button onClick={onClose} style={iconBtn}><X size={18} /></button>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8, maxHeight: 300, overflowY: "auto" }} className="scrollbar-none">
          {events.length === 0 && !santo && <div style={{ color: "var(--text-faint)", fontSize: 13.5 }}>Sin eventos este día.</div>}
          {santo && (
            <button onClick={() => openExternalLink(wikiUrl(santo))}
              style={{ display: "block", width: "100%", textAlign: "left", background: "transparent", border: "none", borderLeft: "3px solid var(--accent)", borderRadius: 8, padding: "9px 12px", cursor: "pointer" }}>
              <div style={{ fontWeight: 600, fontSize: 14, color: "var(--text-muted)", fontStyle: "italic" }}>
                <Star size={12} style={{ verticalAlign: -1, marginRight: 5 }} color="var(--accent)" />{santo}
              </div>
            </button>
          )}
          {events.map(ev => {
            const ids = ev.participantIds || [];
            const c = ids.length === 1 ? participantStyle(ids[0], participants) : sharedStyle();
            const owner = ids.length === 0 || ids.length === participants.length ? "Todos"
              : ids.map(id => (participants.find(p => p.id === id) || {}).name).filter(Boolean).join(", ");
            const showDelete = pressedId === ev.id;
            return (
              <div key={ev.id}
                onClick={() => handleRowClick(ev)}
                onMouseDown={() => startPress(ev.id)} onMouseUp={cancelPress} onMouseLeave={cancelPress}
                onTouchStart={() => startPress(ev.id)} onTouchEnd={cancelPress}
                style={{ position: "relative", background: c.soft, borderLeft: `3px solid ${c.bg}`, borderRadius: 8, padding: "9px 12px", cursor: "pointer", userSelect: "none" }}>
                <div style={{ fontWeight: 600, fontSize: 14, paddingRight: showDelete ? 30 : 0 }}>{eventTitle(ev)}</div>
                <div style={{ fontSize: 11.5, color: "var(--text-muted)", marginTop: 2, display: "flex", gap: 10, flexWrap: "wrap" }}>
                  <span>{owner}</span>
                  {ev.reminders && ev.reminders.length > 0 && <span style={{ display: "flex", alignItems: "center", gap: 3 }}><Bell size={11} />{ev.reminders.length === 1 ? "1 recordatorio" : `${ev.reminders.length} recordatorios`}</span>}
                  {ev.endDate && <span style={{ display: "flex", alignItems: "center", gap: 3 }}><CalendarRange size={11} />hasta {fromKey(ev.endDate).getDate()} de {MESES[fromKey(ev.endDate).getMonth()].toLowerCase()}</span>}
                  {!ev.endDate && ev.repeat !== "none" && <span style={{ display: "flex", alignItems: "center", gap: 3 }}><Repeat size={11} />se repite</span>}
                </div>
                {showDelete && (
                  <button onClick={(e) => { e.stopPropagation(); onDeleteEvent(ev.id); setPressedId(null); }}
                    aria-label="Eliminar evento"
                    style={{
                      position: "absolute", top: 8, right: 8, width: 24, height: 24, borderRadius: "50%",
                      background: "var(--danger)", border: "none", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer",
                    }}>
                    <Trash2 size={13} color="#fff" />
                  </button>
                )}
              </div>
            );
          })}
        </div>
        <button onClick={onAdd} style={{ ...primaryBtn, marginTop: 14, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 8 }}>
          <Plus size={16} /> Añadir evento
        </button>
      </div>
    </Overlay>
  );
}

function EventModal({ ev, participants, attachments, onChangeAttachments, onClose, onSave, onDelete }) {
  const [form, setForm] = useState(ev);
  const [linkText, setLinkText] = useState("");
  const [fileError, setFileError] = useState("");
  const [customAmount, setCustomAmount] = useState("");
  const [customUnit, setCustomUnit] = useState("minutos");

  const handleFiles = (fileList) => {
    setFileError("");
    const files = Array.from(fileList || []);
    const valid = [];
    const errors = [];
    files.forEach(file => {
      if (file.size > 4 * 1024 * 1024) errors.push(`"${file.name}" pesa demasiado (máx. ~4 MB).`);
      else valid.push(file);
    });
    if (errors.length) setFileError(errors.join(" "));
    if (valid.length === 0) return;
    Promise.all(valid.map(file => new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve({
        id: uid(), kind: "file", name: file.name,
        mime: file.type, dataUrl: reader.result, addedAt: Date.now(),
      });
      reader.onerror = reject;
      reader.readAsDataURL(file);
    }))).then(newItems => {
      onChangeAttachments([...(attachments || []), ...newItems]);
    }).catch(() => setFileError("No se pudo leer alguno de los archivos."));
  };

  const addLink = () => {
    if (!linkText.trim()) return;
    const item = { id: uid(), kind: "link", name: linkText.trim(), addedAt: Date.now() };
    onChangeAttachments([...(attachments || []), item]);
    setLinkText("");
  };

  const removeAttachment = (id) => {
    onChangeAttachments((attachments || []).filter(a => a.id !== id));
  };

  const update = (k, v) => setForm(prev => ({ ...prev, [k]: v }));
  const toggleReminder = (minutes) => {
    const current = form.reminders || [];
    update("reminders", current.includes(minutes) ? current.filter(m => m !== minutes) : [...current, minutes]);
  };
  const addCustomReminder = () => {
    const amount = parseInt(customAmount, 10);
    if (!amount || amount <= 0) return;
    const factors = { minutos: 1, horas: 60, dias: 1440, semanas: 10080, meses: 43200 };
    const minutes = amount * (factors[customUnit] || 1);
    const current = form.reminders || [];
    if (!current.includes(minutes)) update("reminders", [...current, minutes]);
    setCustomAmount("");
  };
  const toggleParticipant = (id) => {
    const current = form.participantIds || [];
    update("participantIds", current.includes(id) ? current.filter(x => x !== id) : [...current, id]);
  };

  const shareEvent = async () => {
    const lines = [`📅 ${form.title || "(sin título)"}`];
    const dateStr = form.date ? `${fromKey(form.date).getDate()} de ${MESES[fromKey(form.date).getMonth()].toLowerCase()}` : "";
    const endStr = form.endDate ? ` al ${fromKey(form.endDate).getDate()} de ${MESES[fromKey(form.endDate).getMonth()].toLowerCase()}` : "";
    lines.push(`${dateStr}${endStr}${form.time ? ` · ${form.time}` : ""}`);
    if ((attachments || []).length > 0) {
      lines.push("", "Adjuntos:");
      attachments.forEach(a => lines.push(`- ${a.name}`));
    }
    const text = lines.join("\n");

    // Intenta compartir con el selector nativo (incluye WhatsApp si está instalado)
    if (navigator.share) {
      try {
        await navigator.share({ title: form.title || "Evento", text });
        return;
      } catch {} // el usuario canceló o no se pudo: seguimos con la alternativa
    }
    // Alternativa universal: abre WhatsApp con el texto ya escrito
    openExternalLink(`https://wa.me/?text=${encodeURIComponent(text)}`);
  };

  return (
    <Overlay onClick={onClose}>
      <div style={{ ...cardStyle, maxHeight: "85vh", overflowY: "auto" }} className="scrollbar-none" onClick={e => e.stopPropagation()}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
          <div className="fraunces" style={{ fontSize: 19 }}>{ev.isNew ? "Nuevo evento" : "Editar evento"}</div>
          <div style={{ display: "flex", gap: 6 }}>
            {!ev.isNew && (
              <button onClick={shareEvent} aria-label="Compartir evento" style={iconBtn}><Share2 size={16} /></button>
            )}
            <button onClick={onClose} style={iconBtn}><X size={18} /></button>
          </div>
        </div>

        <Field label="Título">
          <input style={inputStyle} value={form.title} onChange={e => update("title", e.target.value)} placeholder="Cena, cita, viaje..." />
        </Field>

        <div style={{ display: "flex", gap: 8 }}>
          <Field label="Fecha" style={{ flex: 1.2, minWidth: 0 }}>
            <input type="date" style={{ ...inputStyle, width: "100%", minWidth: 0, padding: "8px 2px", fontSize: 12, textAlign: "center" }} value={form.date} onChange={e => update("date", e.target.value)} />
          </Field>
          <Field label="Fecha fin" style={{ flex: 1.2, minWidth: 0 }}>
            <input type="date" style={{ ...inputStyle, width: "100%", minWidth: 0, padding: "8px 2px", fontSize: 12, textAlign: "center" }} min={form.date} value={form.endDate || ""} onChange={e => update("endDate", e.target.value)} />
          </Field>
          <Field label="Hora" style={{ flex: 0.8, minWidth: 0 }}>
            <input type="time" style={{ ...inputStyle, width: "100%", minWidth: 0, padding: "8px 2px", fontSize: 12, textAlign: "center" }} value={form.time} onChange={e => update("time", e.target.value)} />
          </Field>
        </div>

        <Field label="¿Quién participa?">
          {participants.length === 0 ? (
            <div style={{ fontSize: 12, color: "var(--danger)" }}>
              No hay participantes en este calendario. Ve a Ajustes → Participantes para añadirlos.
            </div>
          ) : (
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            {participants.map(p => {
              const active = (form.participantIds || []).includes(p.id);
              return (
                <button key={p.id} onClick={() => toggleParticipant(p.id)} style={{
                  display: "flex", alignItems: "center", gap: 6, padding: "7px 10px", borderRadius: 9,
                  border: active ? `1.5px solid ${p.color}` : "1px solid var(--border)",
                  background: active ? hexToRgba(p.color, 0.18) : "transparent", color: "var(--text)", fontSize: 12.5, cursor: "pointer",
                }}>
                  <span style={{ width: 8, height: 8, borderRadius: "50%", background: p.color, flexShrink: 0 }} />
                  {p.name}
                </button>
              );
            })}
          </div>
          )}
        </Field>

        <Field label="Repetir">
          <select style={inputStyle} value={form.repeat} onChange={e => update("repeat", e.target.value)} disabled={!!form.endDate}>
            <option value="none">No se repite</option>
            <option value="weekly">Cada semana</option>
            <option value="monthly">Cada mes</option>
            <option value="yearly">Cada año</option>
          </select>
        </Field>

        <Field label="Recordatorios">
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 8 }}>
            {REMINDER_OPTIONS.map(r => {
              const active = (form.reminders || []).includes(r.minutes);
              return (
                <button key={r.minutes} onClick={() => toggleReminder(r.minutes)} style={{
                  padding: "6px 10px", borderRadius: 8, fontSize: 12, cursor: "pointer",
                  border: active ? "1.5px solid var(--accent)" : "1px solid var(--border)",
                  background: active ? "var(--accent-soft)" : "transparent",
                  color: active ? "var(--accent)" : "var(--text-muted)",
                }}>{r.label}</button>
              );
            })}
            {(form.reminders || []).filter(m => !REMINDER_OPTIONS.some(r => r.minutes === m)).map(m => (
              <button key={m} onClick={() => toggleReminder(m)} style={{
                padding: "6px 10px", borderRadius: 8, fontSize: 12, cursor: "pointer",
                border: "1.5px solid var(--accent)", background: "var(--accent-soft)", color: "var(--accent)",
                display: "flex", alignItems: "center", gap: 5,
              }}>{formatReminderLabel(m)} <X size={11} /></button>
            ))}
          </div>
          <div style={{ display: "flex", gap: 6 }}>
            <input type="number" min="1" style={{ ...inputStyle, width: 64 }} value={customAmount} onChange={e => setCustomAmount(e.target.value)} placeholder="Nº" />
            <select style={{ ...inputStyle, flex: 1 }} value={customUnit} onChange={e => setCustomUnit(e.target.value)}>
              <option value="minutos">Minutos antes</option>
              <option value="horas">Horas antes</option>
              <option value="dias">Días antes</option>
              <option value="semanas">Semanas antes</option>
              <option value="meses">Meses antes</option>
            </select>
            <button onClick={addCustomReminder} style={iconBtn}><Plus size={16} /></button>
          </div>
        </Field>

        {form.endDate && (
          <button type="button"
            onClick={() => openExternalLink(bookingSearchUrl(form.date, form.endDate))}
            style={{ ...secondaryBtn, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 8, marginBottom: 14, cursor: "pointer" }}>
            🏨 Buscar alojamiento para estas fechas
          </button>
        )}

        <Field label="Adjuntos">
          <div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 10 }}>
            {(attachments || []).length === 0 && (
              <div style={{ fontSize: 12, color: "var(--text-faint)" }}>Sin adjuntos todavía.</div>
            )}
            {(attachments || []).map(a => (
              <div key={a.id} style={{ display: "flex", alignItems: "center", gap: 8, background: "var(--field-bg)", border: "1px solid var(--border)", borderRadius: 8, padding: "7px 10px" }}>
                {a.kind === "link" ? <Link2 size={14} color="var(--text-faint)" style={{ flexShrink: 0 }} /> : <Paperclip size={14} color="var(--text-faint)" style={{ flexShrink: 0 }} />}
                {a.kind === "link" ? (
                  <a href={/^https?:\/\//i.test(a.name) ? a.name : `https://${a.name}`} target="_blank" rel="noopener noreferrer"
                    style={{ flex: 1, fontSize: 12.5, color: "var(--text)", textDecoration: "underline", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                    {a.name}
                  </a>
                ) : (
                  <a href={a.dataUrl} download={a.name} style={{ flex: 1, fontSize: 12.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: 6 }}>
                    {a.name} <Download size={12} color="var(--text-faint)" />
                  </a>
                )}
                <Trash2 size={14} color="var(--text-faint)" style={{ cursor: "pointer", flexShrink: 0 }} onClick={() => removeAttachment(a.id)} />
              </div>
            ))}
          </div>

          <div style={{ position: "relative", width: "100%", marginBottom: 8 }}>
            <button type="button" tabIndex={-1} style={{ ...secondaryBtn, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 8, cursor: "pointer", pointerEvents: "none" }}>
              <Paperclip size={14} /> Adjuntar archivo
            </button>
            <input type="file" accept="image/*,application/pdf" multiple
              onChange={e => { handleFiles(e.target.files); e.target.value = ""; }}
              style={{ position: "absolute", inset: 0, width: "100%", height: "100%", opacity: 0, cursor: "pointer" }} />
          </div>
          {fileError && <div style={{ fontSize: 11.5, color: "var(--danger)", marginBottom: 8 }}>{fileError}</div>}

          <div style={{ display: "flex", gap: 6 }}>
            <input style={{ ...inputStyle, flex: 1 }} value={linkText} onChange={e => setLinkText(e.target.value)}
              onKeyDown={e => e.key === "Enter" && addLink()} placeholder="O pega un enlace" />
            <button onClick={addLink} style={iconBtn}><Plus size={16} /></button>
          </div>
        </Field>

        <div style={{ display: "flex", gap: 10, marginTop: 10 }}>
          {onDelete && (
            <button onClick={onDelete} style={{ ...secondaryBtn, color: "var(--danger)", flex: 1 }}>Eliminar</button>
          )}
          <button onClick={() => onSave(form)} style={{ ...primaryBtn, flex: 2 }}>Guardar</button>
        </div>
      </div>
    </Overlay>
  );
}

// ---------- Planes pendientes (backlog) ----------
function PlansView({ plans, setPlans, events, onEventsChange, participants }) {
  const [text, setText] = useState("");
  const [date, setDate] = useState("");
  const [pendingDelete, setPendingDelete] = useState(null);

  const add = () => {
    if (!text.trim()) return;
    const planId = uid();
    let eventId = null;
    if (date) {
      eventId = uid();
      onEventsChange(prev => [...prev, {
        id: eventId, date, endDate: "", title: text.trim(), time: "",
        participantIds: participants.map(p => p.id), repeat: "none", reminders: [], fromPlan: true,
      }]);
    }
    setPlans(prev => [{ id: planId, text: text.trim(), done: false, createdAt: Date.now(), date: date || null, eventId }, ...prev]);
    setText(""); setDate("");
  };
  const toggle = (id) => setPlans(prev => prev.map(p => p.id === id ? { ...p, done: !p.done } : p));
  const confirmRemove = () => {
    const plan = pendingDelete;
    setPendingDelete(null);
    if (!plan) return;
    setPlans(prev => prev.filter(p => p.id !== plan.id));
    if (plan.eventId) onEventsChange(prev => prev.filter(e => e.id !== plan.eventId));
  };
  const assignDate = (plan, newDate) => {
    if (!newDate) return;
    if (plan.eventId) {
      // Ya tenía fecha: solo se actualiza el evento existente
      onEventsChange(prev => prev.map(e => e.id === plan.eventId ? { ...e, date: newDate } : e));
      setPlans(prev => prev.map(p => p.id === plan.id ? { ...p, date: newDate } : p));
    } else {
      const eventId = uid();
      onEventsChange(prev => [...prev, {
        id: eventId, date: newDate, endDate: "", title: plan.text, time: "",
        participantIds: participants.map(p => p.id), repeat: "none", reminders: [], fromPlan: true,
      }]);
      setPlans(prev => prev.map(p => p.id === plan.id ? { ...p, date: newDate, eventId } : p));
    }
  };
  const unassignDate = (plan) => {
    if (plan.eventId) onEventsChange(prev => prev.filter(e => e.id !== plan.eventId));
    setPlans(prev => prev.map(p => p.id === plan.id ? { ...p, date: null, eventId: null } : p));
  };

  const pending = plans.filter(p => !p.done);
  const done = plans.filter(p => p.done);

  const PlanRow = ({ p, faded }) => {
    const [editingDate, setEditingDate] = useState(false);
    return (
      <div style={{ background: "var(--surface)", padding: "10px 12px", borderRadius: 10, opacity: faded ? 0.55 : 1 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          {p.done
            ? <CheckSquare size={16} color="var(--accent)" style={{ cursor: "pointer", flexShrink: 0 }} onClick={() => toggle(p.id)} />
            : <Square size={17} color="var(--text-faint)" style={{ cursor: "pointer", flexShrink: 0 }} onClick={() => toggle(p.id)} />}
          <span style={{ flex: 1, fontSize: 14, textDecoration: p.done ? "line-through" : "none" }}>{p.text}</span>
          <CalendarRange size={15} color={p.date ? "var(--accent)" : "var(--text-faint)"} style={{ cursor: "pointer" }} onClick={() => setEditingDate(v => !v)} />
          <Trash2 size={15} color="var(--text-faint)" style={{ cursor: "pointer" }} onClick={() => setPendingDelete(p)} />
        </div>
        {p.date && !editingDate && (
          <div style={{ fontSize: 11, color: "var(--text-muted)", marginTop: 4, marginLeft: 27 }}>
            📅 {fromKey(p.date).getDate()} de {MESES[fromKey(p.date).getMonth()].toLowerCase()}
          </div>
        )}
        {editingDate && (
          <div style={{ display: "flex", gap: 6, marginTop: 8, marginLeft: 27 }}>
            <input type="date" style={{ ...inputStyle, flex: 1 }} value={p.date || ""} onChange={e => assignDate(p, e.target.value)} />
            {p.date && (
              <button onClick={() => { unassignDate(p); setEditingDate(false); }} style={{ ...iconBtn, width: 34, height: 34 }}><X size={14} /></button>
            )}
          </div>
        )}
      </div>
    );
  };

  return (
    <div>
      <div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 18 }}>
        <div style={{ display: "flex", gap: 8 }}>
          <input style={{ ...inputStyle, flex: 1 }} value={text} onChange={e => setText(e.target.value)}
            onKeyDown={e => e.key === "Enter" && add()} placeholder="Una idea para hacer juntos..." />
          <button onClick={add} style={iconBtn}><Plus size={16} /></button>
        </div>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <input type="date" style={{ ...inputStyle, flex: 1 }} value={date} onChange={e => setDate(e.target.value)} />
          <span style={{ fontSize: 10.5, color: "var(--text-faint)", flexShrink: 0, maxWidth: 130 }}>Opcional: si le pones fecha, se añade al calendario</span>
        </div>
      </div>

      {pending.length === 0 && done.length === 0 && (
        <EmptyState icon={Sparkles} text="Aún no hay planes guardados. Añadid la primera idea de algo que queráis hacer juntos, sin necesidad de ponerle fecha todavía." />
      )}

      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
        {pending.map(p => <PlanRow key={p.id} p={p} />)}
      </div>

      {done.length > 0 && (
        <div style={{ marginTop: 20 }}>
          <div style={{ fontSize: 11.5, color: "var(--text-faint)", marginBottom: 8, letterSpacing: 0.5, textTransform: "uppercase" }}>Ya hechos</div>
          <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
            {done.map(p => <PlanRow key={p.id} p={p} faded />)}
          </div>
        </div>
      )}

      {pendingDelete && (
        <ConfirmDialog
          message={`¿Eliminar el plan "${pendingDelete.text}"?`}
          onConfirm={confirmRemove}
          onCancel={() => setPendingDelete(null)}
        />
      )}
    </div>
  );
}

// ---------- Cumpleaños y Onomástica (misma estructura, con o sin edad) ----------
function AnnualDatesView({ items, setItems, participants, events, onEventsChange, icon, groupTitle, emptyText }) {
  const [view, setView] = useState(items.length === 0 ? "anadir" : "lista"); // "lista" | "anadir"
  const [isOwn, setIsOwn] = useState(true);
  const participantsWithoutOwn = participants.filter(p => !items.some(b => b.isOwn && b.category === p.id));
  const [ownParticipantId, setOwnParticipantId] = useState(participantsWithoutOwn[0]?.id || "");
  const [name, setName] = useState("");
  const [category, setCategory] = useState(participants[0]?.id || "");
  const [birthMonth, setBirthMonth] = useState(0);
  const [birthDay, setBirthDay] = useState(1);
  const [ageInput, setAgeInput] = useState("");
  const [pendingDelete, setPendingDelete] = useState(null);
  const [formError, setFormError] = useState("");

  const daysInSelectedMonth = new Date(2000, birthMonth + 1, 0).getDate(); // 2000 es bisiesto: permite el 29 de febrero
  const dayOptions = Array.from({ length: daysInSelectedMonth }, (_, i) => i + 1);

  const add = () => {
    setFormError("");
    if (isOwn) {
      // Cada participante solo puede tener un único cumpleaños propio
      if (items.some(b => b.isOwn && b.category === ownParticipantId)) {
        setFormError("Ese participante ya tiene su cumpleaños registrado.");
        return;
      }
    }
    const finalName = isOwn ? (participants.find(p => p.id === ownParticipantId)?.name || "").trim() : name.trim();
    const finalCategory = isOwn ? ownParticipantId : category;
    if (!finalName || !finalCategory) return;
    const ageNum = parseInt(ageInput, 10);
    const hasAge = ageInput.trim() !== "" && !isNaN(ageNum);
    // Sin edad no se conoce el año real de nacimiento: se usa un año "desconocido" (no se muestra la edad)
    const birthYear = hasAge ? birthYearFromCurrentAge(birthMonth, birthDay, ageNum, new Date()) : UNKNOWN_BIRTH_YEAR;
    const finalDate = `${birthYear}-${pad(birthMonth + 1)}-${pad(birthDay)}`;
    const birthdayId = uid();
    const eventId = uid();
    const eventParticipantIds = finalCategory === "ambos" ? participants.map(p => p.id) : [finalCategory];
    // Se añade también al calendario como evento anual recurrente, con recordatorios por defecto:
    // una semana antes, un día antes, y el mismo día a las 9:00 (la hora se fija a las 9am para que
    // el recordatorio "en el momento" caiga esa hora).
    onEventsChange(prev => [...prev, {
      id: eventId, date: finalDate, endDate: "", title: `Cumpleaños de ${finalName}`, time: "09:00",
      participantIds: eventParticipantIds, repeat: "yearly", reminders: [10080, 1440, 0], fromBirthday: true,
    }]);
    setItems(prev => [...prev, { id: birthdayId, name: finalName, date: finalDate, category: finalCategory, isOwn, eventId }]);
    setName(""); setAgeInput("");
  };
  const confirmRemove = () => {
    const item = pendingDelete;
    setPendingDelete(null);
    if (!item) return;
    setItems(prev => prev.filter(b => b.id !== item.id));
    if (item.eventId) onEventsChange(prev => prev.filter(e => e.id !== item.eventId));
  };

  const today = new Date();
  const enrich = (list) => list
    .map(b => ({ ...b, ...nextBirthdayInfo(b.date, today), knownAge: fromKey(b.date).getFullYear() !== UNKNOWN_BIRTH_YEAR }))
    .sort((a, b) => a.diff - b.diff);

  // Dos listas totalmente independientes: cumpleaños propios de los participantes, y de sus amigos/familia
  const ownItems = enrich(items.filter(b => b.isOwn));
  const friendGroups = [
    ...participants.map(p => ({ key: p.id, label: `Amigos y familia de ${p.name}`, items: enrich(items.filter(b => !b.isOwn && b.category === p.id)) })),
    { key: "ambos", label: "Amigos y familia compartidos", items: enrich(items.filter(b => !b.isOwn && b.category === "ambos")) },
  ];

  const BirthdayRow = ({ b }) => (
    <div style={{ background: "var(--surface)", borderRadius: 12, padding: "12px 14px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
      <div>
        <div style={{ fontSize: 14, fontWeight: 600 }}>{b.isOwn && "🎂 "}{b.name}</div>
        <div style={{ fontSize: 11.5, color: "var(--text-muted)", marginTop: 2 }}>
          {fromKey(b.date).getDate()} de {MESES[fromKey(b.date).getMonth()].toLowerCase()}{b.knownAge ? ` · cumple ${b.age}` : ""}
        </div>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        <div className="fraunces" style={{ fontSize: 20, fontWeight: 600, color: b.diff === 0 ? "var(--accent)" : "var(--text)" }}>
          {b.diff === 0 ? "¡Hoy!" : b.diff}
        </div>
        {b.diff !== 0 && <div style={{ fontSize: 10, color: "var(--text-faint)" }}>días</div>}
        <Trash2 size={14} color="var(--text-faint)" style={{ cursor: "pointer" }} onClick={() => setPendingDelete(b)} />
      </div>
    </div>
  );

  return (
    <div>
      <div style={{ display: "flex", gap: 6, marginBottom: 18, background: "var(--surface)", padding: 3, borderRadius: 10 }}>
        <button onClick={() => setView("lista")} style={{
          flex: 1, padding: "8px 6px", borderRadius: 8, fontSize: 12.5, fontWeight: 500, cursor: "pointer", border: "none",
          background: view === "lista" ? "var(--border)" : "transparent", color: view === "lista" ? "var(--text)" : "var(--text-faint)",
        }}>Lista</button>
        <button onClick={() => setView("anadir")} style={{
          flex: 1, padding: "8px 6px", borderRadius: 8, fontSize: 12.5, fontWeight: 500, cursor: "pointer", border: "none",
          background: view === "anadir" ? "var(--border)" : "transparent", color: view === "anadir" ? "var(--text)" : "var(--text-faint)",
        }}>Añadir nuevos</button>
      </div>

      {view === "anadir" && (
      <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 18 }}>
        <div style={{ display: "flex", gap: 6 }}>
          <button onClick={() => { setIsOwn(true); setFormError(""); }} style={{
            flex: 1, padding: "8px 6px", borderRadius: 9, fontSize: 12.5, cursor: "pointer",
            border: isOwn ? "1.5px solid var(--accent)" : "1px solid var(--border)",
            background: isOwn ? "var(--accent-soft)" : "transparent", color: isOwn ? "var(--accent)" : "var(--text-muted)",
          }}>Un participante</button>
          <button onClick={() => { setIsOwn(false); setFormError(""); }} style={{
            flex: 1, padding: "8px 6px", borderRadius: 9, fontSize: 12.5, cursor: "pointer",
            border: !isOwn ? "1.5px solid var(--accent)" : "1px solid var(--border)",
            background: !isOwn ? "var(--accent-soft)" : "transparent", color: !isOwn ? "var(--accent)" : "var(--text-muted)",
          }}>Amigo o familiar</button>
        </div>

        {participants.length === 0 && (
          <div style={{ fontSize: 12, color: "var(--danger)" }}>
            No hay participantes en este calendario. Ve a Ajustes → Participantes para añadirlos.
          </div>
        )}

        {isOwn ? (
          participantsWithoutOwn.length === 0 ? (
            <div style={{ fontSize: 12, color: "var(--text-faint)" }}>
              Todos los participantes ya tienen su cumpleaños registrado.
            </div>
          ) : (
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
              {participantsWithoutOwn.map(p => (
                <button key={p.id} onClick={() => { setOwnParticipantId(p.id); setFormError(""); }} style={{
                  display: "flex", alignItems: "center", gap: 6, padding: "7px 10px", borderRadius: 9, fontSize: 12.5, cursor: "pointer",
                  border: ownParticipantId === p.id ? `1.5px solid ${p.color}` : "1px solid var(--border)",
                  background: ownParticipantId === p.id ? hexToRgba(p.color, 0.18) : "transparent", color: "var(--text)",
                }}>
                  <span style={{ width: 8, height: 8, borderRadius: "50%", background: p.color, flexShrink: 0 }} />
                  {p.name}
                </button>
              ))}
            </div>
          )
        ) : (
          <>
            <input style={inputStyle} value={name} onChange={e => setName(e.target.value)} placeholder="Nombre del amigo o familiar" />
            <div style={{ fontSize: 10.5, color: "var(--text-faint)" }}>¿De quién es amigo/familiar?</div>
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
              {participants.map(p => (
                <button key={p.id} onClick={() => setCategory(p.id)} style={{
                  display: "flex", alignItems: "center", gap: 6, padding: "7px 10px", borderRadius: 9, fontSize: 12.5, cursor: "pointer",
                  border: category === p.id ? `1.5px solid ${p.color}` : "1px solid var(--border)",
                  background: category === p.id ? hexToRgba(p.color, 0.18) : "transparent", color: "var(--text)",
                }}>
                  <span style={{ width: 8, height: 8, borderRadius: "50%", background: p.color, flexShrink: 0 }} />
                  {p.name}
                </button>
              ))}
              <button onClick={() => setCategory("ambos")} style={{
                padding: "7px 10px", borderRadius: 9, fontSize: 12.5, cursor: "pointer",
                border: category === "ambos" ? "1.5px solid var(--accent)" : "1px solid var(--border)",
                background: category === "ambos" ? "var(--accent-soft)" : "transparent",
                color: category === "ambos" ? "var(--accent)" : "var(--text)",
              }}>Ambos</button>
            </div>
          </>
        )}

        {(!isOwn || participantsWithoutOwn.length > 0) && (
          <>
            <div style={{ fontSize: 10.5, color: "var(--text-faint)", marginTop: 4 }}>Día y mes de nacimiento (se repite cada año)</div>
            <div style={{ display: "flex", gap: 8 }}>
              <select style={{ ...inputStyle, flex: 1.3 }} value={birthMonth} onChange={e => { const m = Number(e.target.value); setBirthMonth(m); const max = new Date(2000, m + 1, 0).getDate(); if (birthDay > max) setBirthDay(max); }}>
                {MESES.map((m, i) => <option key={m} value={i}>{m}</option>)}
              </select>
              <select style={{ ...inputStyle, flex: 0.7 }} value={birthDay} onChange={e => setBirthDay(Number(e.target.value))}>
                {dayOptions.map(d => <option key={d} value={d}>{d}</option>)}
              </select>
              <input type="number" min="0" max="130" style={{ ...inputStyle, width: 80 }} value={ageInput} onChange={e => setAgeInput(e.target.value)} placeholder="Edad" />
              <button onClick={add} style={iconBtn}><Plus size={16} /></button>
            </div>
            <div style={{ fontSize: 10.5, color: "var(--text-faint)", marginTop: -2 }}>
              La edad es opcional: si la pones, calculamos el año de nacimiento y podremos mostrar cuántos cumple.
            </div>
          </>
        )}
        {formError && <div style={{ fontSize: 11.5, color: "var(--danger)" }}>{formError}</div>}
      </div>
      )}

      {view === "lista" && (
      <>
      {items.length === 0 && (
        <EmptyState icon={icon} text={emptyText} />
      )}

      {/* Lista independiente: cumpleaños propios de los participantes */}
      {ownItems.length > 0 && (
        <div style={{ marginBottom: 22 }}>
          <div style={{ fontSize: 11.5, color: "var(--text-faint)", marginBottom: 8, letterSpacing: 0.5, textTransform: "uppercase" }}>Cumpleaños de los participantes</div>
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            {ownItems.map(b => <BirthdayRow key={b.id} b={b} />)}
          </div>
        </div>
      )}

      {/* Lista independiente: cumpleaños de amigos y familiares */}
      {friendGroups.some(g => g.items.length > 0) && (
        <div>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12 }}>Amigos y familia</div>
          <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
            {friendGroups.map(g => g.items.length > 0 && (
              <div key={g.key}>
                <div style={{ fontSize: 11.5, color: "var(--text-faint)", marginBottom: 8, letterSpacing: 0.5, textTransform: "uppercase" }}>{g.label}</div>
                <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                  {g.items.map(b => <BirthdayRow key={b.id} b={b} />)}
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
      </>
      )}

      {pendingDelete && (
        <ConfirmDialog
          message={`¿Eliminar el cumpleaños de ${pendingDelete.name}?`}
          onConfirm={confirmRemove}
          onCancel={() => setPendingDelete(null)}
        />
      )}
    </div>
  );
}

// ---------- Onomástica: calendario alternativo con el santo de cada día ----------

// window.confirm() necesita permiso "allow-modals" del iframe para funcionar; dentro del sandbox del
// artifact ese permiso no está, así que el navegador cancela el diálogo automáticamente (siempre da
// por hecho que el usuario ha pulsado "Cancelar"). Por eso se usa un diálogo propio en su lugar.
function ConfirmDialog({ message, confirmLabel, onConfirm, onCancel }) {
  return (
    <Overlay onClick={onCancel}>
      <div style={cardStyle} onClick={e => e.stopPropagation()}>
        <div style={{ fontSize: 14, marginBottom: 18 }}>{message}</div>
        <div style={{ display: "flex", gap: 10 }}>
          <button onClick={onCancel} style={{ ...secondaryBtn, flex: 1 }}>Cancelar</button>
          <button onClick={onConfirm} style={{ ...primaryBtn, flex: 1, background: "var(--danger)", color: "#fff" }}>{confirmLabel || "Eliminar"}</button>
        </div>
      </div>
    </Overlay>
  );
}

function EmptyState({ icon: Icon, text }) {
  return (
    <div style={{ textAlign: "center", padding: "36px 20px", color: "var(--text-faint)" }}>
      <Icon size={26} style={{ marginBottom: 10, opacity: 0.7 }} />
      <div style={{ fontSize: 13.5, lineHeight: 1.5 }}>{text}</div>
    </div>
  );
}

// ---------- Cartera: todos los adjuntos, agrupados por evento ----------
const CARTERA_SIN_EVENTO = "_sin_evento";

function CarteraView({ events, attachmentsByEvent, onAdd, onRemove, onOpenEvent }) {
  const [showForm, setShowForm] = useState(false);
  const [linkText, setLinkText] = useState("");
  const [eventId, setEventId] = useState(CARTERA_SIN_EVENTO);
  const [fileError, setFileError] = useState("");

  const sortedEvents = useMemo(() =>
    events.slice().sort((a, b) => fromKey(b.date) - fromKey(a.date)), [events]);

  const handleFiles = (fileList) => {
    setFileError("");
    const files = Array.from(fileList || []);
    files.forEach(file => {
      if (file.size > 4 * 1024 * 1024) {
        setFileError(`"${file.name}" pesa demasiado (máx. ~4 MB).`);
        return;
      }
      const reader = new FileReader();
      reader.onload = () => {
        onAdd(eventId, { id: uid(), kind: "file", name: file.name, mime: file.type, dataUrl: reader.result, addedAt: Date.now() });
      };
      reader.readAsDataURL(file);
    });
  };

  const addLink = () => {
    if (!linkText.trim()) return;
    onAdd(eventId, { id: uid(), kind: "link", name: linkText.trim(), addedAt: Date.now() });
    setLinkText("");
  };

  const groups = useMemo(() => {
    const evById = Object.fromEntries(events.map(e => [e.id, e]));
    return Object.entries(attachmentsByEvent)
      .map(([evId, list]) => ({ eventId: evId, event: evById[evId], items: (list || []).slice().sort((a, b) => b.addedAt - a.addedAt) }))
      .filter(g => g.items.length > 0)
      .sort((a, b) => {
        if (a.eventId === CARTERA_SIN_EVENTO) return -1;
        if (b.eventId === CARTERA_SIN_EVENTO) return 1;
        const da = a.event ? fromKey(a.event.date) : new Date(0);
        const db = b.event ? fromKey(b.event.date) : new Date(0);
        return db - da;
      });
  }, [events, attachmentsByEvent]);

  const totalItems = groups.reduce((sum, g) => sum + g.items.length, 0);

  return (
    <div>
      <button onClick={() => setShowForm(v => !v)} style={{ ...secondaryBtn, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 8, marginBottom: 14 }}>
        <Plus size={16} /> {showForm ? "Cancelar" : "Añadir a la cartera"}
      </button>

      {showForm && (
        <div style={{ background: "var(--surface)", borderRadius: 12, padding: 14, marginBottom: 18 }}>
          <Field label="Asociar a un evento (opcional)">
            <select style={inputStyle} value={eventId} onChange={e => setEventId(e.target.value)}>
              <option value={CARTERA_SIN_EVENTO}>Sin evento asociado</option>
              {sortedEvents.map(ev => (
                <option key={ev.id} value={ev.id}>
                  {(ev.title || "(sin título)")} — {fromKey(ev.date).getDate()} de {MESES[fromKey(ev.date).getMonth()].toLowerCase()}
                </option>
              ))}
            </select>
          </Field>

          <div style={{ position: "relative", width: "100%", marginBottom: 8 }}>
            <button type="button" tabIndex={-1} style={{ ...secondaryBtn, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 8, cursor: "pointer", pointerEvents: "none" }}>
              <Paperclip size={14} /> Adjuntar archivo
            </button>
            <input type="file" accept="image/*,application/pdf" multiple
              onChange={e => { handleFiles(e.target.files); e.target.value = ""; }}
              style={{ position: "absolute", inset: 0, width: "100%", height: "100%", opacity: 0, cursor: "pointer" }} />
          </div>
          {fileError && <div style={{ fontSize: 11.5, color: "var(--danger)", marginBottom: 8 }}>{fileError}</div>}

          <div style={{ display: "flex", gap: 6 }}>
            <input style={{ ...inputStyle, flex: 1 }} value={linkText} onChange={e => setLinkText(e.target.value)}
              onKeyDown={e => e.key === "Enter" && addLink()} placeholder="O pega un enlace" />
            <button onClick={addLink} style={iconBtn}><Plus size={16} /></button>
          </div>
        </div>
      )}

      {totalItems === 0 ? (
        <EmptyState icon={Wallet} text="Aquí se guardan los archivos y enlaces, asociados o no a un evento del calendario." />
      ) : (
      <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
        {groups.map(g => {
          const isUnassigned = g.eventId === CARTERA_SIN_EVENTO;
          return (
          <div key={g.eventId} style={{ background: "var(--surface)", borderRadius: 12, padding: 14 }}>
            <div
              onClick={() => g.event && onOpenEvent(g.event)}
              style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 10, cursor: g.event ? "pointer" : "default" }}>
              <div>
                <div style={{ fontSize: 14, fontWeight: 600 }}>
                  {isUnassigned ? "Sin evento asociado" : g.event ? (g.event.title || "(sin título)") : "Evento eliminado"}
                </div>
                {g.event && (
                  <div style={{ fontSize: 11.5, color: "var(--text-muted)", marginTop: 1 }}>
                    {fromKey(g.event.date).getDate()} de {MESES[fromKey(g.event.date).getMonth()].toLowerCase()}
                    {g.event.endDate ? ` – ${fromKey(g.event.endDate).getDate()} de ${MESES[fromKey(g.event.endDate).getMonth()].toLowerCase()}` : ""}
                  </div>
                )}
              </div>
              {g.event && <CalendarRange size={15} color="var(--text-faint)" />}
            </div>

            <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
              {g.items.map(a => (
                <div key={a.id} style={{ display: "flex", alignItems: "center", gap: 8, background: "var(--field-bg)", border: "1px solid var(--border)", borderRadius: 8, padding: "8px 10px" }}>
                  {a.kind === "link" ? <Link2 size={14} color="var(--text-faint)" style={{ flexShrink: 0 }} /> : <Paperclip size={14} color="var(--text-faint)" style={{ flexShrink: 0 }} />}
                  {a.kind === "link" ? (
                    <a href={/^https?:\/\//i.test(a.name) ? a.name : `https://${a.name}`} target="_blank" rel="noopener noreferrer"
                      style={{ flex: 1, fontSize: 12.5, color: "var(--text)", textDecoration: "underline", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {a.name}
                    </a>
                  ) : (
                    <a href={a.dataUrl} download={a.name} style={{ flex: 1, fontSize: 12.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: 6 }}>
                      {a.name} <Download size={12} color="var(--text-faint)" />
                    </a>
                  )}
                  <Trash2 size={14} color="var(--text-faint)" style={{ cursor: "pointer", flexShrink: 0 }} onClick={() => onRemove(g.eventId, a.id)} />
                </div>
              ))}
            </div>
          </div>
        );})}
      </div>
      )}
    </div>
  );
}

function Field({ label, children, style }) {
  return (
    <div style={{ marginBottom: 12, ...style }}>
      <div style={{ fontSize: 11.5, color: "var(--text-muted)", marginBottom: 5, fontWeight: 500 }}>{label}</div>
      {children}
    </div>
  );
}

function Overlay({ children, onClick }) {
  return (
    <div onClick={onClick} style={{
      position: "fixed", inset: 0, background: "var(--overlay)", backdropFilter: "blur(2px)",
      display: "flex", alignItems: "flex-end", justifyContent: "center", zIndex: 50, padding: 0,
    }}>
      {children}
    </div>
  );
}

const cardStyle = {
  background: "var(--surface)", borderRadius: 18, padding: 22, width: "100%", maxWidth: 420,
  margin: "0 auto 0", boxShadow: "var(--shadow)",
};

const inputStyle = {
  width: "100%", padding: "9px 11px", borderRadius: 9, border: "1px solid var(--border)",
  background: "var(--field-bg)", color: "var(--text)", fontSize: 13.5, outline: "none",
};

const primaryBtn = {
  background: "var(--accent)", color: "var(--accent-text)", border: "none", borderRadius: 10,
  padding: "11px 16px", fontSize: 13.5, fontWeight: 600, cursor: "pointer",
};

const secondaryBtn = {
  background: "transparent", border: "1px solid var(--border)", borderRadius: 10,
  padding: "11px 16px", fontSize: 13.5, fontWeight: 600, cursor: "pointer",
};

const iconBtn = {
  background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 9,
  width: 36, height: 36, display: "flex", alignItems: "center", justifyContent: "center",
  cursor: "pointer", color: "var(--text)", flexShrink: 0,
};

const fabStyle = {
  position: "fixed", right: 20, bottom: 24, width: 54, height: 54, borderRadius: "50%",
  background: "var(--accent)", color: "var(--accent-text)", border: "none",
  display: "flex", alignItems: "center", justifyContent: "center",
  boxShadow: "0 6px 18px rgba(0,0,0,0.3)", cursor: "pointer", zIndex: 30,
};
// ---------- Arranque (añadido para ejecutar la app en el navegador) ----------
import { createRoot } from "react-dom/client";
const _rootEl = document.getElementById("root");
createRoot(_rootEl).render(<App />);
