// lib.jsx — Arco Rooms shared atoms, EstateFlow-styled
// Lucide-style icons, EF status badges, EF stat tile.

// ───────────────────────── ICONS ─────────────────────────
// All Lucide-style: 1.8 stroke, round caps & joins, 24px viewBox.
// Same approach as EstateFlow design system's icons.jsx.
const ICON_PATHS = {
  // brand / nav
  house:     '<path d="M3 11.5 12 4l9 7.5"/><path d="M5 10v9a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1v-9"/>',
  building:  '<path d="M3 21h18"/><path d="M5 21V7l8-4v18"/><path d="M19 21V11l-6-4"/>',
  users:     '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
  dashboard: '<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>',
  euro:      '<path d="M18 7a6 6 0 0 0-9 0c-2 2.4-2 7.6 0 10a6 6 0 0 0 9 0"/><path d="M5 11h9M5 14h9"/>',
  wrench:    '<path d="M14.7 6.3a4 4 0 0 1 5.5 5.5l-1.5-1.5-2 .5-.5 2 1.5 1.5a4 4 0 0 1-5.5-5.5"/><path d="M12 13l-7 7-2-2 7-7"/>',
  chat:      '<path d="M21 12a8 8 0 1 1-3.3-6.5L21 5l-1 3.3A8 8 0 0 1 21 12Z"/>',
  sun:       '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>',
  moon:      '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>',
  calendar:  '<rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4"/><path d="M8 2v4"/><path d="M3 10h18"/>',
  bed:       '<path d="M3 18v-7a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v7"/><path d="M3 14h18M3 18v3M21 18v3"/><circle cx="8" cy="11" r="1.8"/>',
  door:      '<path d="M5 21V4a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v17"/><path d="M3 21h18"/><circle cx="15.5" cy="12.5" r=".8" fill="currentColor"/>',
  receipt:   '<path d="M5 3v18l3-2 2 2 2-2 2 2 2-2 3 2V3z"/><path d="M9 8h6M9 12h6M9 16h4"/>',
  file:      '<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5z"/><path d="M14 2v6h6"/><path d="M8 13h8"/><path d="M8 17h8"/>',
  // search / nav glyphs
  search:    '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
  bell:      '<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/>',
  settings:  '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3h.1a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8v.1a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/>',
  // arrows / chevrons
  chevR:     '<path d="m9 18 6-6-6-6"/>',
  chevL:     '<path d="m15 18-6-6 6-6"/>',
  chevD:     '<path d="m6 9 6 6 6-6"/>',
  chevU:     '<path d="m18 15-6-6-6 6"/>',
  arrowUp:   '<path d="m5 12 7-7 7 7"/><path d="M12 19V5"/>',
  arrowDown: '<path d="M12 5v14"/><path d="m19 12-7 7-7-7"/>',
  arrowR:    '<path d="M5 12h14m-6-7 7 7-7 7"/>',
  arrowUR:   '<path d="M7 17 17 7"/><path d="M7 7h10v10"/>',
  // misc actions
  check:     '<path d="m5 12 4 4 10-10"/>',
  checkCircle: '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>',
  x:         '<path d="M18 6 6 18M6 6l12 12"/>',
  plus:      '<path d="M5 12h14"/><path d="M12 5v14"/>',
  filter:    '<path d="M3 5h18l-7 9v6l-4-2v-4z"/>',
  sort:      '<path d="M7 4v16M3 8l4-4 4 4M17 20V4M21 16l-4 4-4-4"/>',
  grid:      '<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>',
  list:      '<path d="M8 6h13M8 12h13M8 18h13"/><circle cx="4" cy="6" r="1" fill="currentColor"/><circle cx="4" cy="12" r="1" fill="currentColor"/><circle cx="4" cy="18" r="1" fill="currentColor"/>',
  more:      '<circle cx="12" cy="12" r="1" fill="currentColor"/><circle cx="19" cy="12" r="1" fill="currentColor"/><circle cx="5" cy="12" r="1" fill="currentColor"/>',
  clock:     '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
  send:      '<path d="m22 2-7 20-4-9-9-4z"/><path d="M22 2 11 13"/>',
  paperclip: '<path d="M21 11 12 20a5 5 0 0 1-7-7l9-9a3.5 3.5 0 0 1 5 5L10 17a2 2 0 1 1-3-3l8-8"/>',
  download:  '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/>',
  eye:       '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/>',
  // status
  trendUp:   '<path d="m3 17 6-6 4 4 8-8"/><path d="M14 7h7v7"/>',
  trendDown: '<path d="m3 7 6 6 4-4 8 8"/><path d="M14 17h7v-7"/>',
  spark:     '<path d="m12 2 2.4 6.6L21 11l-6.6 2.4L12 20l-2.4-6.6L3 11l6.6-2.4Z"/>',
  panelClose:'<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/><path d="m16 15-3-3 3-3"/>',
  panelOpen: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/><path d="m14 9 3 3-3 3"/>',
};

function Icon({ name, size = 16, color, strokeWidth = 1.8, style = {}, className = "", ...rest }) {
  const p = ICON_PATHS[name] || "";
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
         stroke={color || "currentColor"} strokeWidth={strokeWidth}
         strokeLinecap="round" strokeLinejoin="round"
         className={className} style={style}
         dangerouslySetInnerHTML={{ __html: p }} {...rest} />
  );
}

// ───────────────────────── BRAND MARK ─────────────────────────
// EstateFlow uses an outline house icon for the brand. We adapt — same
// shape and stroke language, paired with the "Arco Rooms" wordmark.
function BrandMark({ size = 26 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
         stroke="currentColor" strokeWidth="1.8"
         strokeLinecap="round" strokeLinejoin="round"
         style={{ color: "var(--ef-ink)", flexShrink: 0 }}>
      <path d="M3 11.5 12 4l9 7.5" />
      <path d="M5 10v9a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1v-9" />
    </svg>
  );
}

// ───────────────────────── FLAGS ─────────────────────────
const FLAGS = { FR: "🇫🇷", DE: "🇩🇪", IT: "🇮🇹", PL: "🇵🇱", DK: "🇩🇰", NL: "🇳🇱", ES: "🇪🇸", GB: "🇬🇧", PT: "🇵🇹", US: "🇺🇸", BR: "🇧🇷", AT: "🇦🇹", JP: "🇯🇵", NO: "🇳🇴" };
const Flag = ({ code }) => <span style={{ fontSize: "13px", lineHeight: 1 }}>{FLAGS[code] || "🌍"}</span>;

// ───────────────────────── FORMATTING ─────────────────────────
const fmtEUR = (n, { sign = false } = {}) => {
  if (n == null) return "—";
  const abs = Math.abs(Math.round(n));
  const grouped = abs.toLocaleString("en-GB").replace(/,/g, "\u202f");
  const prefix = n < 0 ? "−" : (sign ? "+" : "");
  return `${prefix}${grouped} €`;
};
const fmtEURc = (n) => {
  if (n == null) return "—";
  const grouped = n.toLocaleString("en-GB", { minimumFractionDigits: 0, maximumFractionDigits: 0 });
  return `${grouped} €`;
};
const fmtDate = (iso, opts = {}) => {
  if (!iso) return "—";
  const d = new Date(iso);
  const { short = false, withYear = false, withTime = false } = opts;
  const m = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][d.getMonth()];
  const ml = ["January","February","March","April","May","June","July","August","September","October","November","December"][d.getMonth()];
  const day = d.getDate();
  const yr = d.getFullYear();
  if (short) return `${day} ${m}` + (withYear ? ` ${yr}` : "");
  if (withTime) {
    const h = String(d.getHours()).padStart(2, "0");
    const mm = String(d.getMinutes()).padStart(2, "0");
    return `${day} ${m}, ${h}:${mm}`;
  }
  return `${day} ${ml}` + (withYear ? ` ${yr}` : "");
};
const relTime = (iso) => {
  const now = ArcoData.today.getTime();
  const t = new Date(iso).getTime();
  const diff = Math.round((now - t) / 60000);
  if (diff < 1) return "just now";
  if (diff < 60) return `${diff}m ago`;
  if (diff < 60 * 24) return `${Math.floor(diff / 60)}h ago`;
  const days = Math.floor(diff / (60 * 24));
  if (days < 7) return `${days}d ago`;
  return fmtDate(iso, { short: true });
};
const initials = (name) => name.split(/\s+/).slice(0, 2).map((s) => s[0]).join("").toUpperCase();
// "Camille Bertrand" → "Camille B." (first name + last surname's initial).
// Caller can decide how to truncate visually; this returns the canonical form.
const shortName = (name) => {
  if (!name) return "—";
  const parts = name.split(/\s+/);
  if (parts.length === 1) return parts[0];
  const last = parts[parts.length - 1];
  return `${parts[0]} ${last[0]}.`;
};

// ───────────────────────── COMPONENTS ─────────────────────────

const Card = ({ children, className = "", lift = false, tinted = false, lg = false, ...rest }) => (
  <div className={`card${lg ? " lg" : ""}${lift ? " lift" : ""}${tinted ? " tinted" : ""} ${className}`} {...rest}>{children}</div>
);

// Delta chip — EstateFlow's signature "↑5%" pill.
function Delta({ dir = "up", value }) {
  const isUp = dir === "up";
  const isFlat = dir === "flat";
  return (
    <span className={`delta ${isFlat ? "flat" : isUp ? "up" : "down"}`}>
      {!isFlat && (isUp ? "↑" : "↓")} {value}
    </span>
  );
}

// Stat tile — label, big value, optional delta chip and sub line.
function Stat({ label, value, icon, delta, deltaDir = "up", sub, prefix, onClick, href }) {
  const interactive = !!(onClick || href);
  const Tag = href ? "a" : interactive ? "button" : "div";
  const extra = href
    ? { href }
    : interactive
      ? { onClick, type: "button" }
      : {};
  return (
    <Tag className={`stat${interactive ? " stat-interactive" : ""}`} {...extra}>
      <div className="lbl">
        {icon && <Icon name={icon} size={13} />}
        {label}
      </div>
      <div className="row">
        <span className="val">{prefix}{value}</span>
        {delta && <Delta dir={deltaDir} value={delta} />}
      </div>
      {sub && <div className="sub">{sub}</div>}
    </Tag>
  );
}

// Status badge — ALL CAPS, soft tint. Style per status via CSS class.
// Labels use the i18n dictionary (st.*) so they read in EN or ES.
const StatusBadge = ({ status, label }) => {
  if (!status) return null;
  const tt = (typeof window.t === "function") ? window.t : (k) => k;
  const keyMap = {
    paid: "st.paid", pending: "st.pending", overdue: "st.overdue",
    open: "st.open", "in-progress": "st.inProgress", resolved: "st.resolved",
    confirmed: "st.confirmed", tentative: "st.tentative", blocked: "st.blocked",
    resident: "st.resident", active: "st.active", past: "st.past",
    occupied: "st.occupied", vacant: "st.vacant", maintenance: "st.maintenance",
    high: "st.high", medium: "st.medium", low: "st.low", med: "st.medium",
  };
  const k = keyMap[status];
  const resolved = label || (k ? tt(k) : status.toUpperCase());
  return <span className={`badge ${status}`}>{resolved}</span>;
};

// Pill — non-status flag with hairline border. Optional tone for emphasis.
const Pill = ({ children, className = "", tone, ...rest }) => (
  <span className={`pill${tone ? ` pill-${tone}` : ""} ${className}`} {...rest}>{children}</span>
);

// Avatar — neutral monochrome circle with initials. When `apt` is set,
// the avatar takes the apartment's identity color (clay / moss / denim / sand).
const Avatar = ({ name, size = "", accent = false, primary = false, apt = null }) => {
  const cls = ["avatar"];
  if (size) cls.push(size);
  if (accent) cls.push("accent");
  if (primary) cls.push("primary");
  return <span className={cls.join(" ")} data-apt={apt || undefined} title={name}>{initials(name || "?")}</span>;
};

// Tabs — segmented (default) or underline.
const Tabs = ({ tabs, value, onChange, underline = false }) => (
  <div className={`tabs${underline ? " underline" : ""}`}>
    {tabs.map((t) => (
      <button key={t.value} type="button" aria-selected={t.value === value} onClick={() => onChange(t.value)}>
        {t.icon && <Icon name={t.icon} size={14} />}
        <span>{t.label}</span>
        {t.count != null && (
          <span style={{
            fontSize: 10.5, fontWeight: 600,
            color: t.value === value ? "var(--ef-ink-2)" : "var(--ef-muted)",
            background: t.value === value ? "var(--ef-bg-soft)" : "transparent",
            padding: "1px 6px", borderRadius: 999, marginLeft: 2,
          }}>{t.count}</span>
        )}
      </button>
    ))}
  </div>
);

// Donut (occupancy)
const Donut = ({ value, size = 120, stroke = 14, label, sub }) => {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const dash = c * (value / 100);
  return (
    <div style={{ position: "relative", width: size, height: size, flexShrink: 0 }}>
      <svg className="donut" width={size} height={size}>
        <circle className="bg" cx={size / 2} cy={size / 2} r={r} strokeWidth={stroke} />
        <circle className="fg" cx={size / 2} cy={size / 2} r={r} strokeWidth={stroke}
                strokeDasharray={`${dash} ${c - dash}`} />
      </svg>
      <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column",
                    alignItems: "center", justifyContent: "center", textAlign: "center" }}>
        <div className="display num" style={{ fontSize: 26, fontWeight: 600, letterSpacing: "-0.02em", color: "var(--ef-ink)" }}>{label}</div>
        {sub && <div className="muted txt-xs" style={{ marginTop: 2 }}>{sub}</div>}
      </div>
    </div>
  );
};

// Link — quiet inline link.
const Link = ({ to, onClick, children, className = "", ...rest }) => (
  <a href={`#${to || ""}`}
     onClick={(e) => { e.preventDefault(); onClick && onClick(); }}
     className={`arco-link ${className}`}
     style={{ color: "var(--ef-ink-2)", borderBottom: "1px dotted var(--ef-muted)", paddingBottom: 1, cursor: "pointer" }}
     {...rest}>{children}</a>
);

const Empty = ({ icon, title, body, action }) => (
  <div className="empty">
    {icon && <div className="ic"><Icon name={icon} size={20} /></div>}
    <div className="title">{title}</div>
    {body && <div className="body">{body}</div>}
    {action && <div style={{ marginTop: 14 }}>{action}</div>}
  </div>
);

const SectionTitle = ({ children, hint, right, idx }) => (
  <div className="section-title">
    {idx && <span className="section-index">{idx}</span>}
    <h2>{children}</h2>
    {hint && <span className="hint">{hint}</span>}
    {right && <div className="head-right">{right}</div>}
  </div>
);

// Bar chart — income green, expenses dark ink.
const BarChart = ({ data, max, labels, activeMonth }) => {
  const incLabel = labels?.income || "Income";
  const expLabel = labels?.expenses || "Expenses";
  const netLabel = labels?.net || "Net";
  const avgLabel = labels?.avg || "Avg net";
  const [hover, setHover] = React.useState(null);

  const maxRaw = max || Math.max(...data.flatMap((d) => [d.income, d.expenses]));
  const niceMax = (() => {
    const target = maxRaw * 1.1;
    const mag = Math.pow(10, Math.floor(Math.log10(target)));
    const step = mag / 2;
    return Math.ceil(target / step) * step;
  })();
  const ticks = [0, 0.25, 0.5, 0.75, 1].map((p) => niceMax * p);

  const totals = {
    income:   data.reduce((s, d) => s + d.income, 0),
    expenses: data.reduce((s, d) => s + d.expenses, 0),
    net:      data.reduce((s, d) => s + (d.income - d.expenses), 0),
  };
  const avgNet = totals.net / Math.max(data.length, 1);
  const margin = totals.income ? Math.round((totals.net / totals.income) * 100) : 0;

  const W = 720, H = 150;
  const padL = 40, padR = 8, padT = 10, padB = 28;
  const innerW = W - padL - padR;
  const innerH = H - padT - padB;
  const groupW = innerW / data.length;
  const barW = Math.min(14, groupW * 0.28);
  const barGap = 3;

  const yFor    = (v) => padT + innerH - (v / niceMax) * innerH;
  const xCenter = (i) => padL + groupW * (i + 0.5);

  const netPath = data
    .map((d, i) => `${i === 0 ? "M" : "L"} ${xCenter(i)} ${yFor(d.income - d.expenses)}`)
    .join(" ");

  const fmtK = (n) => {
    if (n >= 1000) return `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k €`;
    return `${Math.round(n)} €`;
  };

  return (
    <div className="cashflow-chart">
      <div className="cashflow-summary">
        <div className="flex center gap-3 txt-xs muted">
          <span className="flex center gap-1">
            <i style={{ width: 10, height: 10, background: "var(--ef-accent)", borderRadius: 2, display: "inline-block" }} />
            {incLabel} <b className="muted-2 num" style={{ marginLeft: 2 }}>{fmtEUR(totals.income)}</b>
          </span>
          <span className="flex center gap-1">
            <i style={{ width: 10, height: 10, background: "var(--ef-ink)", borderRadius: 2, display: "inline-block" }} />
            {expLabel} <b className="muted-2 num" style={{ marginLeft: 2 }}>{fmtEUR(totals.expenses)}</b>
          </span>
          <span className="flex center gap-1">
            <i style={{ width: 14, height: 2, background: "var(--ef-accent-deep)", display: "inline-block" }} />
            {netLabel} <b className="num" style={{ marginLeft: 2, color: totals.net >= 0 ? "var(--ef-accent-deep)" : "var(--ef-danger)" }}>{fmtEUR(totals.net, { sign: totals.net > 0 })}</b>
          </span>
          <span className="muted">·</span>
          <span>{avgLabel} <b className="muted-2 num">{fmtEUR(avgNet)}/mo</b></span>
          <span className="muted">·</span>
          <span className="muted">{margin}% margin</span>
        </div>
      </div>

      <div style={{ position: "relative", width: "100%" }}>
        <svg viewBox={`0 0 ${W} ${H}`} width="100%" preserveAspectRatio="xMidYMid meet"
             style={{ display: "block", overflow: "visible" }}>
          {ticks.map((t, i) => (
            <g key={i}>
              <line x1={padL} y1={yFor(t)} x2={W - padR} y2={yFor(t)}
                    stroke="var(--ef-line)" strokeWidth="0.5" strokeDasharray={i === 0 ? "0" : "2 3"} />
              <text x={padL - 8} y={yFor(t)} dy="3.5"
                    fill="var(--ef-muted)" fontSize="9.5" textAnchor="end"
                    fontFamily="var(--ef-font-display)">
                {fmtK(t)}
              </text>
            </g>
          ))}

          {avgNet > 0 && (
            <line x1={padL} y1={yFor(avgNet)} x2={W - padR} y2={yFor(avgNet)}
                  stroke="var(--ef-accent-deep)" strokeWidth="1" strokeDasharray="3 3" opacity="0.4" />
          )}

          {data.map((d, i) => {
            const isHover  = hover === i;
            const isActive = activeMonth && d.month === activeMonth;
            const incH = (d.income   / niceMax) * innerH;
            const expH = (d.expenses / niceMax) * innerH;
            const cx   = xCenter(i);
            const incX = cx - barW - barGap / 2;
            const expX = cx + barGap / 2;
            return (
              <g key={i}
                 onMouseEnter={() => setHover(i)}
                 onMouseLeave={() => setHover(null)}
                 style={{ cursor: "pointer" }}>
                <rect x={padL + groupW * i} y={padT} width={groupW} height={innerH} fill="transparent" />
                <rect x={incX} y={yFor(d.income)} width={barW} height={incH}
                      rx="2" fill="var(--ef-accent)"
                      opacity={hover !== null && !isHover ? 0.55 : 1} />
                <rect x={expX} y={yFor(d.expenses)} width={barW} height={expH}
                      rx="2" fill="var(--ef-ink)"
                      opacity={hover !== null && !isHover ? 0.55 : 1} />
                {(isHover || isActive) && (
                  <text x={incX + barW / 2} y={yFor(d.income) - 5}
                        fill="var(--ef-ink)" fontSize="10" fontWeight="600"
                        textAnchor="middle" fontFamily="var(--ef-font-display)">
                    {fmtK(d.income)}
                  </text>
                )}
                <text x={cx} y={H - padB + 14}
                      fill={isActive ? "var(--ef-ink)" : "var(--ef-ink-2)"}
                      fontSize="11" fontWeight={isActive ? 600 : 500}
                      textAnchor="middle">
                  {d.month}
                </text>
                <text x={cx} y={H - padB + 27}
                      fill={(d.income - d.expenses) >= 0 ? "var(--ef-accent-deep)" : "var(--ef-danger)"}
                      fontSize="9.5" textAnchor="middle"
                      fontFamily="var(--ef-font-display)" fontWeight="500">
                  {(d.income - d.expenses) >= 0 ? "+" : ""}{fmtK(d.income - d.expenses)}
                </text>
              </g>
            );
          })}

          <path d={netPath} fill="none" stroke="var(--ef-accent-deep)" strokeWidth="1.5"
                strokeLinecap="round" strokeLinejoin="round" opacity="0.85" />
          {data.map((d, i) => (
            <circle key={i} cx={xCenter(i)} cy={yFor(d.income - d.expenses)} r="2.5"
                    fill="var(--ef-bg)" stroke="var(--ef-accent-deep)" strokeWidth="1.5" />
          ))}
        </svg>

        {hover !== null && (() => {
          const d  = data[hover];
          const cx = (xCenter(hover) / W) * 100;
          const net = d.income - d.expenses;
          return (
            <div className="cashflow-tip" style={{ left: `${cx}%` }}>
              <div className="cashflow-tip-month">{d.month}</div>
              <div className="cashflow-tip-row">
                <span><i style={{ background: "var(--ef-accent)" }} />{incLabel}</span>
                <b className="num">{fmtEUR(d.income)}</b>
              </div>
              <div className="cashflow-tip-row">
                <span><i style={{ background: "var(--ef-ink)" }} />{expLabel}</span>
                <b className="num">−{fmtEUR(d.expenses)}</b>
              </div>
              <div className="cashflow-tip-row total">
                <span>{netLabel}</span>
                <b className="num" style={{ color: net >= 0 ? "var(--ef-accent-deep)" : "var(--ef-danger)" }}>
                  {fmtEUR(net, { sign: net > 0 })}
                </b>
              </div>
            </div>
          );
        })()}
      </div>
    </div>
  );
};

// Apartment hero — a placeholder with per-apt identity color.
// Each flat carries a muted brand hue (--apt-{id}-bg/ink) defined in
// styles.css so they stay tonally aligned with the EstateFlow neutrals.
const APT_PALETTE = {
  "ap-pez":  { bg: "var(--apt-pez-bg)",  ink: "var(--apt-pez-ink)",  soft: "var(--apt-pez-soft)",  name: "Clay" },
  "ap-hort": { bg: "var(--apt-hort-bg)", ink: "var(--apt-hort-ink)", soft: "var(--apt-hort-soft)", name: "Moss" },
  "ap-atoc": { bg: "var(--apt-atoc-bg)", ink: "var(--apt-atoc-ink)", soft: "var(--apt-atoc-soft)", name: "Denim" },
  "ap-pmay": { bg: "var(--apt-pmay-bg)", ink: "var(--apt-pmay-ink)", soft: "var(--apt-pmay-soft)", name: "Sand" },
};
const aptPalette = (id) => APT_PALETTE[id] || { bg: "#EDEAE0", ink: "#3A3F4A", soft: "#F6F6F3", name: "—" };

// Small inline dot — sits next to apartment names in lists/tables.
const AptDot = ({ aptId, size = 8 }) => {
  const p = aptPalette(aptId);
  return (
    <span style={{
      display: "inline-block",
      width: size, height: size,
      borderRadius: 999,
      background: p.ink,
      flexShrink: 0,
    }} aria-hidden="true" />
  );
};

const AptArchHero = ({ aptId }) => {
  const p = aptPalette(aptId);
  return (
    <svg width="100%" height="100%" viewBox="0 0 320 160" preserveAspectRatio="xMidYMid slice"
         xmlns="http://www.w3.org/2000/svg" style={{ display: "block" }}>
      <rect width="320" height="160" fill={p.bg} />
      {/* Dotted blueprint grid on top — the EF signature texture, tinted */}
      <defs>
        <pattern id={`gd-${aptId}`} width="22" height="22" patternUnits="userSpaceOnUse">
          <circle cx="1" cy="1" r="1" fill="rgba(40,44,58,0.10)" />
        </pattern>
        <linearGradient id={`gr-${aptId}`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor={p.bg} stopOpacity="0" />
          <stop offset="1" stopColor="#000" stopOpacity="0.08" />
        </linearGradient>
      </defs>
      <rect width="320" height="160" fill={`url(#gd-${aptId})`} />
      <rect width="320" height="160" fill={`url(#gr-${aptId})`} />
      {/* Stylized building silhouette — single-stroke, monochrome ink */}
      <g stroke={p.ink} strokeWidth="1.6" fill="none" opacity="0.5">
        <path d="M40 130 L40 80 L100 50 L160 80 L160 130 Z" />
        <path d="M70 130 L70 100 L130 100 L130 130" />
        <line x1="100" y1="50" x2="100" y2="80" />
      </g>
      <g stroke={p.ink} strokeWidth="1.6" fill="none" opacity="0.5">
        <path d="M180 130 L180 70 L220 50 L260 70 L260 130 Z" />
        <rect x="200" y="90" width="20" height="40" />
        <rect x="225" y="90" width="20" height="40" />
      </g>
      <g stroke={p.ink} strokeWidth="1.6" fill="none" opacity="0.5">
        <rect x="270" y="60" width="40" height="70" />
        <line x1="280" y1="75" x2="300" y2="75" />
        <line x1="280" y1="95" x2="300" y2="95" />
        <line x1="280" y1="115" x2="300" y2="115" />
      </g>
      {/* Solid accent corner — the apartment's identity color, prominent */}
      <circle cx="296" cy="20" r="6" fill={p.ink} opacity="0.9" />
    </svg>
  );
};

// Confirm-style toast
const Toast = (props) => {
  // Backwards-compatible: callers can pass `message` directly OR pass the
  // whole `{ message, undo, ttl }` shape via `toast` (from app.jsx state).
  const { message, undo, ttl = 2400, onDone } = props;
  const ttt = (typeof window.t === "function") ? window.t : (k) => k;
  React.useEffect(() => {
    const t = setTimeout(onDone, undo ? Math.max(ttl, 5000) : ttl);
    return () => clearTimeout(t);
  }, [onDone, ttl, undo]);
  return (
    <div style={{ position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)",
                  background: "var(--ef-primary)", color: "var(--ef-primary-ink)", padding: "10px 18px",
                  borderRadius: 999, fontSize: 13, fontWeight: 500, boxShadow: "var(--ef-shadow-pop)", zIndex: 100,
                  display: "flex", alignItems: "center", gap: 10 }}>
      <Icon name="check" size={14} />
      <span>{message}</span>
      {undo && (
        <button onClick={() => { undo(); onDone && onDone(); }}
                style={{ background: "transparent", color: "var(--ef-primary-ink)",
                         border: "0.5px solid rgba(255,255,255,0.25)", borderRadius: 6,
                         padding: "3px 10px", fontSize: 12.5, fontWeight: 500,
                         cursor: "pointer", marginLeft: 4 }}>
          {ttt("toast.undo") || "Undo"}
        </button>
      )}
    </div>
  );
};

// Modal shell
const Modal = ({ title, onClose, children, footer, maxWidth = 580, ariaLabel }) => {
  const bodyRef = React.useRef(null);
  React.useEffect(() => {
    const onKey = (e) => e.key === "Escape" && onClose && onClose();
    window.addEventListener("keydown", onKey);
    // Focus first input on open for accessibility
    const timer = setTimeout(() => {
      const el = bodyRef.current && bodyRef.current.querySelector("input, select, textarea, button");
      if (el) el.focus();
    }, 50);
    return () => { window.removeEventListener("keydown", onKey); clearTimeout(timer); };
  }, [onClose]);
  return (
    <div className="scrim" onClick={(e) => e.target === e.currentTarget && onClose && onClose()}
         role="dialog" aria-modal="true" aria-label={ariaLabel || title || "Diálogo"}>
      <div className="modal" style={{ maxWidth }}>
        {title && (
          <div className="modal-head">
            <h2>{title}</h2>
            <button className="iconbtn close" onClick={onClose}
                    aria-label={(window.t && window.t("ui.close")) || "Cerrar"}>
              <Icon name="x" size={16} />
            </button>
          </div>
        )}
        <div className="modal-body" ref={bodyRef}>{children}</div>
        {footer && <div className="modal-foot">{footer}</div>}
      </div>
    </div>
  );
};

// ConfirmDialog — small modal for destructive/important confirmations.
// Replaces native window.confirm() and keeps the visual language consistent.
const ConfirmDialog = ({ title, body, confirmLabel, cancelLabel, danger, onConfirm, onCancel }) => {
  const tt = (window.t || ((k) => k));
  return (
    <Modal title={title} onClose={onCancel} maxWidth={440}
           ariaLabel={title}
           footer={<>
             <button className="btn ghost" onClick={onCancel}>{cancelLabel || tt("conf.cancel")}</button>
             <button className={danger ? "btn" : "btn accent"}
                     style={danger ? { borderColor: "var(--ef-danger)", color: "var(--ef-danger)" } : undefined}
                     onClick={onConfirm}>
               {danger && <Icon name="x" size={12} />}
               {!danger && <Icon name="check" size={13} />}
               {confirmLabel || tt("conf.confirm")}
             </button>
           </>}>
      {body && <div style={{ color: "var(--ef-ink-2)", fontSize: 13.5, lineHeight: 1.5 }}>{body}</div>}
    </Modal>
  );
};
window.ConfirmDialog = ConfirmDialog;

// Sparkline
const Sparkline = ({ values, width = 90, height = 28, stroke = "var(--ef-accent)" }) => {
  const max = Math.max(...values), min = Math.min(...values);
  const range = max - min || 1;
  const step = width / (values.length - 1);
  const pts = values.map((v, i) => `${i * step},${height - ((v - min) / range) * (height - 4) - 2}`).join(" ");
  return (
    <svg width={width} height={height}>
      <polyline points={pts} fill="none" stroke={stroke} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
};

// Filter pills row
const FilterPills = ({ value, onChange, options }) => (
  <div className="flex center" style={{ gap: 6, flexWrap: "wrap" }}>
    {options.map((o) => (
      <button key={o.value} className={`pill`}
              style={{
                cursor: "pointer",
                background: o.value === value ? "var(--ef-primary)" : "var(--ef-bg)",
                color: o.value === value ? "var(--ef-primary-ink)" : "var(--ef-ink-2)",
                borderColor: o.value === value ? "var(--ef-primary)" : "var(--ef-line)",
              }}
              onClick={() => onChange(o.value)}>
        {o.label}
      </button>
    ))}
  </div>
);

// Detail row for tenant profile
const DetailRow = ({ icon, label, value }) => (
  <div className="flex center gap-3" style={{ minHeight: 24 }}>
    {icon && <Icon name={icon} size={14} className="muted" />}
    <span className="muted" style={{ minWidth: 90 }}>{label}</span>
    <span className="grow ta-r ink b500">{value}</span>
  </div>
);

// Compact KPI tile — used inside detail headers
const KPI = ({ label, value, color }) => (
  <div style={{
    padding: "12px 16px",
    background: "var(--ef-bg-soft)",
    borderRadius: 10,
    border: "1px solid var(--ef-line)",
  }}>
    <div className="muted txt-xs">{label}</div>
    <div className="num" style={{ fontSize: 20, fontWeight: 600, marginTop: 4, color: color || "var(--ef-ink)" }}>{value}</div>
  </div>
);

// Expose globally
// ───────────────────────── DROPDOWN / MENU ─────────────────────────
// Ported from preview-d. Portal-rendered so it escapes overflow:hidden,
// keyboard escape, click-outside close, smart drop-up positioning.
// Uses React Context to provide close() to nested MenuItems without
// polluting unrelated children (divs, profile rows) via cloneElement.
const DropdownContext = React.createContext({ close: () => {} });

function Dropdown({ trigger, children, align = "right", width, menuClassName = "" }) {
  const [open, setOpen] = React.useState(false);
  const [pos, setPos] = React.useState(null);
  const triggerRef = React.useRef();
  const menuRef = React.useRef();

  const measure = React.useCallback(() => {
    const tr = triggerRef.current?.firstChild;
    if (!tr) return;
    const r = tr.getBoundingClientRect();
    const spaceBelow = window.innerHeight - r.bottom;
    const spaceAbove = r.top;
    const dropUp = spaceAbove > spaceBelow && spaceBelow < 240;
    setPos({
      top: dropUp ? undefined : r.bottom + 6,
      bottom: dropUp ? window.innerHeight - r.top + 6 : undefined,
      left: r.left,
      right: window.innerWidth - r.right,
      triggerW: r.width,
      maxHeight: Math.max(180, (dropUp ? spaceAbove : spaceBelow) - 16),
    });
  }, []);

  React.useEffect(() => {
    if (!open) return;
    measure();
    const onClick = (e) => {
      if (triggerRef.current?.contains(e.target) || menuRef.current?.contains(e.target)) return;
      setOpen(false);
    };
    const onKey = (e) => e.key === "Escape" && setOpen(false);
    const onScroll = () => measure();
    document.addEventListener("mousedown", onClick);
    document.addEventListener("keydown", onKey);
    window.addEventListener("resize", onScroll);
    window.addEventListener("scroll", onScroll, true);
    return () => {
      document.removeEventListener("mousedown", onClick);
      document.removeEventListener("keydown", onKey);
      window.removeEventListener("resize", onScroll);
      window.removeEventListener("scroll", onScroll, true);
    };
  }, [open, measure]);

  const toggle = (e) => { e.stopPropagation(); setOpen((o) => !o); trigger.props.onClick?.(e); };

  const menuStyle = pos ? {
    position: "fixed",
    top: pos.top != null ? pos.top : "auto",
    bottom: pos.bottom != null ? pos.bottom : "auto",
    ...(align === "right" ? { right: pos.right } : align === "center" ? { left: pos.left + pos.triggerW / 2, transform: "translateX(-50%)" } : { left: pos.left }),
    ...(width ? { width } : {}),
    maxHeight: pos.maxHeight,
  } : { display: "none" };

  return (
    <>
      <span ref={triggerRef} style={{ display: "inline-flex" }}>
        {React.cloneElement(trigger, { onClick: toggle, "aria-expanded": open })}
      </span>
      {open && pos && ReactDOM.createPortal(
        <DropdownContext.Provider value={{ close: () => setOpen(false) }}>
          <div ref={menuRef} className={`dd-menu align-${align} ${menuClassName}`} style={menuStyle}>
            {children}
          </div>
        </DropdownContext.Provider>,
        document.body
      )}
    </>
  );
}

function MenuItem({ icon, label, hint, onClick, danger, disabled }) {
  const { close } = React.useContext(DropdownContext);
  return (
    <button type="button" className="dd-item" data-danger={!!danger} disabled={disabled}
            onClick={(e) => { if (disabled) return; e.stopPropagation(); onClick?.(); close(); }}>
      {icon && <span className="dd-item-icon"><Icon name={icon} size={14} /></span>}
      <span className="dd-item-label">{label}</span>
      {hint && <span className="dd-item-hint">{hint}</span>}
    </button>
  );
}
function MenuDivider() { return <div className="dd-divider" />; }
function MenuHeader({ children }) { return <div className="dd-header">{children}</div>; }
function MenuFooter({ children }) { return <div className="dd-footer">{children}</div>; }

Object.assign(window, {
  Icon, BrandMark, Flag, FLAGS,
  fmtEUR, fmtDate, relTime, initials,
  Card, Stat, Delta, StatusBadge, Pill, Avatar, Tabs, Donut,
  Link, Empty, SectionTitle, BarChart, AptArchHero, AptDot, aptPalette, APT_PALETTE, Toast, Modal,
  Sparkline, FilterPills, DetailRow, KPI,
  Dropdown, MenuItem, MenuDivider, MenuHeader, MenuFooter,
  shortName,
});
