// pantry-screens.jsx — Dashboard + sheets

const { useState, useEffect, useRef, useMemo } = React;

// ─────────────────────────────────────────────────────────────
// Reusable UI primitives, theme-aware
// ─────────────────────────────────────────────────────────────
function StatusPill({ status, label, palette, big = false }) {
  const map = {
    urgent: { bg: palette.redSoft, ink: palette.redSoftInk, dot: palette.red },
    expired: { bg: palette.redSoft, ink: palette.redSoftInk, dot: palette.red },
    soon: { bg: palette.amberSoft, ink: palette.amberSoftInk, dot: palette.amber },
    safe: { bg: palette.primarySoft, ink: palette.primarySoftInk, dot: palette.primary },
  };
  const c = map[status] || map.safe;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      padding: big ? '5px 10px' : '3px 8px',
      borderRadius: 999, background: c.bg, color: c.ink,
      fontSize: big ? 12 : 11, fontWeight: 500, fontFamily: 'Geist Mono, monospace',
      letterSpacing: 0.02,
    }}>
      <span style={{ width: 5, height: 5, borderRadius: 3, background: c.dot }} />
      {label}
    </span>
  );
}

// ─────────────────────────────────────────────────────────────
// Bottom tab nav (mobile) / Side rail (web)
// ─────────────────────────────────────────────────────────────
function TabNav({ active, onChange, palette, lang, iconStyle, variant = 'bottom' }) {
  // Bottom: 7 tabs, + centered, symmetric. Direct access to everything.
  const tabs = [
    { id: 'home', icon: 'home', label: t(lang, 'home') },
    { id: 'inv', icon: 'box', label: t(lang, 'inventory') },
    { id: 'rec', icon: 'chef', label: t(lang, 'recipes') },
    { id: 'add', icon: 'plus', label: t(lang, 'add'), fab: true },
    { id: 'list', icon: 'cart', label: t(lang, 'grocery') },
    { id: 'plan', icon: 'calendar', label: t(lang, 'plan') },
    { id: 'family', icon: 'users', label: t(lang, 'familyHub') },
  ];
  if (variant === 'side') {
    const sideTabs = [
      { id: 'home', icon: 'home', label: t(lang, 'home') },
      { id: 'inv', icon: 'box', label: t(lang, 'inventory') },
      { id: 'rec', icon: 'chef', label: t(lang, 'recipes') },
      { id: 'list', icon: 'cart', label: t(lang, 'grocery') },
      { id: 'family', icon: 'users', label: t(lang, 'familyHub') },
      { id: 'prof', icon: 'user', label: t(lang, 'profile') },
    ];
    return (
      <div style={{
        width: 240, height: '100%', boxSizing: 'border-box',
        background: palette.card, borderRight: `1px solid ${palette.line}`,
        padding: '24px 14px', display: 'flex', flexDirection: 'column', gap: 4,
      }}>
        <div style={{ padding: '0 8px 24px', display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{
            width: 30, height: 30, borderRadius: 9, background: palette.primary, color: palette.primaryInk,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <Icon name="leaf" size={18} style="filled" />
          </div>
          <div style={{ fontSize: 17, fontWeight: 600, letterSpacing: -0.2, color: palette.ink }}>{t(lang, 'brand')}</div>
        </div>
        {sideTabs.map(tab => {
          const isActive = active === tab.id;
          return (
            <div key={tab.id} onClick={() => onChange(tab.id)} style={{
              display: 'flex', alignItems: 'center', gap: 12,
              padding: '10px 12px', borderRadius: 10, cursor: 'pointer',
              background: isActive ? palette.primarySoft : 'transparent',
              color: isActive ? palette.primarySoftInk : palette.ink2,
              fontSize: 14, fontWeight: isActive ? 500 : 400,
            }}>
              <Icon name={tab.icon} size={19} style={isActive ? 'filled' : iconStyle === 'emoji' ? 'line' : iconStyle} />
              {tab.label}
            </div>
          );
        })}
        <div style={{ flex: 1 }} />
        <div onClick={() => onChange('add')} style={{
          display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px',
          borderRadius: 10, cursor: 'pointer', background: palette.primary, color: palette.primaryInk,
          fontSize: 14, fontWeight: 500,
        }}>
          <Icon name="plus" size={18} />
          {t(lang, 'quickAdd')}
        </div>
      </div>
    );
  }
  // bottom
  return (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: '6px 6px 4px',
      borderTop: `1px solid ${palette.line}`,
      background: palette.card,
      flexShrink: 0,
    }}>
      {tabs.map(tab => {
        const isActive = active === tab.id;
        if (tab.fab) {
          return (
            <div key={tab.id} onClick={() => onChange(tab.id)} style={{
              width: 44, height: 44, borderRadius: 14,
              background: palette.primary, color: palette.primaryInk,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              cursor: 'pointer', flexShrink: 0, margin: '0 2px',
              boxShadow: `0 4px 12px ${palette.primary}40, 0 1px 0 ${palette.primaryInk}26 inset`,
              transform: 'translateY(-4px)',
            }}>
              <Icon name="plus" size={21} />
            </div>
          );
        }
        return (
          <div key={tab.id} onClick={() => onChange(tab.id)} style={{
            flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2,
            padding: '4px 0', cursor: 'pointer', minWidth: 0,
            color: isActive ? palette.primary : palette.ink3,
          }}>
            <Icon name={tab.icon} size={19} style={isActive ? 'filled' : 'line'} />
            <span style={{ fontSize: 8.5, fontWeight: 500, letterSpacing: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '100%' }}>{tab.label}</span>
          </div>
        );
      })}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Header (greeting / date / bell)
// ─────────────────────────────────────────────────────────────
function HomeHeader({ palette, lang, onToggleLang, onOpenProfile, authUser }) {
  return (
    <div style={{ padding: '20px 20px 8px', display: 'flex', alignItems: 'flex-start', gap: 10 }}>
      <div style={{ flex: 1 }}>
        <div style={{
          fontSize: 11.5, color: palette.ink3, fontFamily: 'Geist Mono, monospace',
          letterSpacing: 0.04, textTransform: 'uppercase', marginBottom: 4,
        }}>{t(lang, 'todayLabel')}</div>
        <div style={{ fontSize: 26, fontWeight: 600, letterSpacing: -0.6, color: palette.ink, lineHeight: 1.1 }}>
          {t(lang, 'greeting')}
        </div>
      </div>
      <div onClick={onToggleLang} style={{
        height: 30, padding: '0 10px', borderRadius: 10,
        background: palette.cardSub, color: palette.ink2,
        display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer',
        fontSize: 11, fontFamily: 'Geist Mono, monospace', fontWeight: 500, letterSpacing: 0.06,
        border: `1px solid ${palette.line}`,
      }}>{lang.toUpperCase()}</div>
      <div style={{
        position: 'relative',
        width: 38, height: 38, borderRadius: 12,
        background: palette.cardSub, color: palette.ink,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        cursor: 'pointer', border: `1px solid ${palette.line}`,
      }}>
        <Icon name="bell" size={18} />
        <div style={{
          position: 'absolute', top: 7, right: 8,
          width: 8, height: 8, borderRadius: 4, background: palette.red,
          border: `2px solid ${palette.cardSub}`,
        }} />
      </div>
      <UserAvatar palette={palette} size={38} gradient onClick={onOpenProfile}
        name={authUser?.name} userId={authUser?.id} />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// HERO STAT (one big number) — dashStyle === 'hero'
// ─────────────────────────────────────────────────────────────
function HeroStat({ palette, lang, inventory, onTapAtRisk }) {
  const atRisk = inventory.filter(i => i.daysLeft <= 3 && i.daysLeft >= 0).length;
  const expired = inventory.filter(i => i.daysLeft < 0).length;
  // count recipes that use any at-risk item (dynamic — fixes "3 recipes" hardcode)
  const atRiskIds = inventory.filter(i => i.daysLeft <= 3 && i.daysLeft >= 0).map(i => i.id);
  const matchingRecipes = RECIPES.filter(r => r.uses.some(id => atRiskIds.includes(id))).length;
  return (
    <div style={{ padding: '8px 20px 16px' }}>
      <div onClick={onTapAtRisk} style={{
        position: 'relative', overflow: 'hidden',
        background: palette.isDark ? 'linear-gradient(135deg,#1F2A24,#243329)' : 'linear-gradient(135deg, #F2E8D0, #F7EBC9)',
        border: `1px solid ${palette.line}`,
        borderRadius: 22, padding: '22px 20px 20px',
        cursor: 'pointer',
      }}>
        <div style={{ position: 'absolute', top: -20, right: -20, opacity: 0.15, fontSize: 140, lineHeight: 1, filter: 'blur(0.3px)' }}>🥑</div>
        <div style={{ position: 'relative', display: 'flex', alignItems: 'baseline', gap: 10 }}>
          <div style={{ fontSize: 64, fontWeight: 600, letterSpacing: -2.5, color: palette.ink, lineHeight: 1, fontFamily: 'Geist, sans-serif' }}>{atRisk}</div>
          <div style={{ fontSize: 13, color: palette.ink2, lineHeight: 1.35, flex: 1 }}>
            <div style={{ fontWeight: 500, color: palette.ink }}>{t(lang, 'expSoon')}</div>
            <div>{expired > 0 ? `${expired} ${t(lang, 'expiredCount')} · ` : ''}{inventory.length} {t(lang, 'totalItems')}</div>
          </div>
        </div>
        <div style={{ position: 'relative', display: 'flex', alignItems: 'center', gap: 8, marginTop: 16, color: palette.primarySoftInk, fontWeight: 500, fontSize: 13 }}>
          <Icon name="sparkle" size={16} color={palette.primarySoftInk} />
          {matchingRecipes} {t(lang, 'nMatching')}
          <Icon name="chevR" size={14} color={palette.primarySoftInk} />
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// STAT CARDS (3-up) — dashStyle === 'cards'
// ─────────────────────────────────────────────────────────────
function StatCards({ palette, lang, inventory }) {
  const atRisk = inventory.filter(i => i.daysLeft <= 3 && i.daysLeft >= 0).length;
  const expired = inventory.filter(i => i.daysLeft < 0).length;
  const safe = inventory.length - atRisk - expired;
  const cards = [
    { v: atRisk, label: t(lang, 'expSoon'), bg: palette.amberSoft, ink: palette.amberSoftInk, dot: palette.amber },
    { v: expired, label: t(lang, 'expiredCount'), bg: palette.redSoft, ink: palette.redSoftInk, dot: palette.red },
    { v: safe, label: t(lang, 'inStock'), bg: palette.primarySoft, ink: palette.primarySoftInk, dot: palette.primary },
  ];
  return (
    <div style={{ padding: '8px 20px 16px', display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 10 }}>
      {cards.map((c, i) => (
        <div key={i} style={{
          background: c.bg, borderRadius: 16, padding: '14px 12px 12px',
          position: 'relative', overflow: 'hidden',
        }}>
          <div style={{ width: 6, height: 6, borderRadius: 3, background: c.dot, marginBottom: 6 }} />
          <div style={{ fontSize: 28, fontWeight: 600, color: c.ink, letterSpacing: -1, lineHeight: 1, fontFamily: 'Geist, sans-serif' }}>{c.v}</div>
          <div style={{ fontSize: 11, color: c.ink, marginTop: 4, lineHeight: 1.25 }}>{c.label}</div>
        </div>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// SECTION HEADER
// ─────────────────────────────────────────────────────────────
function SectionHeader({ title, action, palette }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', padding: '12px 20px 6px', flexShrink: 0 }}>
      <div style={{ fontSize: 16, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>{title}</div>
      {action && (
        <div onClick={action.onClick} style={{ fontSize: 12, color: palette.ink3, cursor: 'pointer', fontFamily: 'Geist Mono, monospace', letterSpacing: 0.04 }}>{action.label}</div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Expiring strip — horizontal scroll
// ─────────────────────────────────────────────────────────────
function ExpiringStrip({ items, palette, lang, iconStyle, onPick }) {
  return (
    <div style={{
      display: 'flex', gap: 10, padding: '4px 20px 10px',
      overflowX: 'auto', scrollbarWidth: 'none',
      flexShrink: 0,
    }}>
      {items.map(item => {
        const status = statusOf(item);
        const stColor = status === 'urgent' || status === 'expired' ? palette.red : palette.amber;
        const stBg = status === 'urgent' || status === 'expired' ? palette.redSoft : palette.amberSoft;
        const stInk = status === 'urgent' || status === 'expired' ? palette.redSoftInk : palette.amberSoftInk;
        return (
          <div key={item.id} onClick={() => onPick(item)} style={{
            flexShrink: 0, width: 132, padding: 12,
            borderRadius: 16, background: palette.card, border: `1px solid ${palette.line}`,
            cursor: 'pointer', position: 'relative',
          }}>
            <FoodChip item={item} size={40} style={iconStyle} palette={palette} />
            <div style={{ marginTop: 10, fontSize: 13, fontWeight: 500, color: palette.ink, letterSpacing: -0.1, lineHeight: 1.2 }}>
              {localized(lang, item.name)}
            </div>
            <div style={{
              marginTop: 6, fontSize: 10.5, color: stInk, fontWeight: 500,
              fontFamily: 'Geist Mono, monospace', letterSpacing: 0.02,
              display: 'inline-flex', alignItems: 'center', gap: 5,
              padding: '3px 7px', borderRadius: 6, background: stBg,
            }}>
              <span style={{ width: 4, height: 4, borderRadius: 2, background: stColor }} />
              {item.daysLeft < 0 ? (lang === 'fr' ? `−${-item.daysLeft}j` : `${item.daysLeft}d`)
                : item.daysLeft === 0 ? t(lang, 'today')
                : item.daysLeft === 1 ? t(lang, 'tomorrow')
                : `${item.daysLeft}${t(lang, 'days')}`}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Recipe card — style 'standard' | 'minimal'
// ─────────────────────────────────────────────────────────────
function RecipeCard({ recipe, inventory, palette, lang, style = 'standard', onOpen }) {
  const matched = recipe.uses.filter(id => inventory.some(i => i.id === id)).length;
  const total = recipe.ingredients.length;
  const missing = recipe.ingredients.filter(ing => ing.missing).length;

  if (style === 'minimal') {
    return (
      <div onClick={onOpen} style={{
        background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 18,
        padding: 14, display: 'flex', gap: 14, alignItems: 'center', cursor: 'pointer',
      }}>
        <div style={{
          width: 64, height: 64, borderRadius: 14,
          background: `linear-gradient(135deg, ${recipe.bg1}, ${recipe.bg2})`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: 32, flexShrink: 0,
          boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.3)',
        }}>{recipe.hero}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 15, fontWeight: 600, color: palette.ink, letterSpacing: -0.2, lineHeight: 1.2 }}>
            {localized(lang, recipe.name)}
          </div>
          <div style={{ fontSize: 12, color: palette.ink3, marginTop: 3 }}>{localized(lang, recipe.tagline)}</div>
          <div style={{ display: 'flex', gap: 10, marginTop: 8, fontSize: 11, color: palette.ink2, fontFamily: 'Geist Mono, monospace' }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
              <Icon name="clock" size={12} color={palette.ink3} /> {recipe.minutes}m
            </span>
            <span style={{ color: palette.primary, fontWeight: 500 }}>
              {matched}/{total} {t(lang, 'haveIt')}
            </span>
          </div>
        </div>
        <Icon name="chevR" size={16} color={palette.ink3} />
      </div>
    );
  }
  // standard — hero image card
  return (
    <div onClick={onOpen} style={{
      borderRadius: 22, overflow: 'hidden', cursor: 'pointer',
      background: palette.card, border: `1px solid ${palette.line}`,
    }}>
      <div style={{
        height: 124, background: `linear-gradient(135deg, ${recipe.bg1}, ${recipe.bg2})`,
        position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <div style={{ fontSize: 64, filter: 'drop-shadow(0 4px 12px rgba(0,0,0,0.18))' }}>{recipe.hero}</div>
        <div style={{
          position: 'absolute', top: 12, left: 12,
          padding: '4px 9px', borderRadius: 999,
          background: 'rgba(255,255,255,0.85)', backdropFilter: 'blur(8px)',
          fontSize: 10.5, fontFamily: 'Geist Mono, monospace', fontWeight: 500,
          color: '#2A3531', display: 'flex', alignItems: 'center', gap: 5,
        }}>
          <Icon name="sparkle" size={11} color="#2A3531" />
          {t(lang, 'suggestedToday')}
        </div>
        <div style={{
          position: 'absolute', top: 12, right: 12,
          padding: '4px 9px', borderRadius: 999,
          background: 'rgba(255,255,255,0.85)', backdropFilter: 'blur(8px)',
          fontSize: 10.5, fontFamily: 'Geist Mono, monospace', fontWeight: 500, color: '#2A3531',
        }}>{recipe.minutes} {t(lang, 'minutes')}</div>
      </div>
      <div style={{ padding: '14px 16px 16px' }}>
        <div style={{ fontSize: 17, fontWeight: 600, color: palette.ink, letterSpacing: -0.3, lineHeight: 1.2 }}>
          {localized(lang, recipe.name)}
        </div>
        <div style={{ fontSize: 13, color: palette.ink3, marginTop: 4 }}>{localized(lang, recipe.tagline)}</div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 12 }}>
          <div style={{ flex: 1, height: 5, borderRadius: 4, background: palette.line2, overflow: 'hidden' }}>
            <div style={{ width: `${(matched/total)*100}%`, height: '100%', background: palette.primary, borderRadius: 4 }} />
          </div>
          <span style={{ fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.primary, fontWeight: 500 }}>
            {matched}/{total}
          </span>
        </div>
        <div style={{ fontSize: 11.5, color: palette.ink2, marginTop: 8, lineHeight: 1.45 }}>
          {missing === 0
            ? t(lang, 'haveAll')
            : (lang === 'fr' ? `${missing} ${missing > 1 ? t(lang, 'missingPlural') : t(lang, 'missing')} · ` : `${missing} ${missing > 1 ? t(lang, 'missingPlural') : t(lang, 'missing')} · `) + (lang === 'fr' ? 'utilise les aliments à risque' : 'uses at-risk items')}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Inventory list — density 'cards' | 'compact'
// ─────────────────────────────────────────────────────────────
function InventoryList({ items, palette, lang, iconStyle, density, onPick, web = false }) {
  if (density === 'compact') {
    return (
      <div style={{ padding: '0 20px 16px' }}>
        <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 16, overflow: 'hidden' }}>
          {items.map((item, i) => {
            const st = statusOf(item);
            const stColor = st === 'urgent' || st === 'expired' ? palette.red : st === 'soon' ? palette.amber : palette.primary;
            return (
              <div key={item.id} onClick={() => onPick(item)} style={{
                display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px',
                borderBottom: i < items.length - 1 ? `1px solid ${palette.line2}` : 'none',
                cursor: 'pointer', minWidth: 0,
              }}>
                <FoodChip item={item} size={34} style={iconStyle} palette={palette} />
                <div style={{ flex: 1, minWidth: 0, overflow: 'hidden' }}>
                  <div style={{ fontSize: 14, fontWeight: 500, color: palette.ink, letterSpacing: -0.1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {localized(lang, item.name)}
                  </div>
                  <div style={{ fontSize: 11.5, color: palette.ink3, marginTop: 2, fontFamily: 'Geist Mono, monospace', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {item.qty} {localized(lang, item.unit)} · {t(lang, item.location)}
                  </div>
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, color: stColor, fontSize: 11, fontWeight: 500, fontFamily: 'Geist Mono, monospace', flexShrink: 0 }}>
                  <span style={{ width: 5, height: 5, borderRadius: 3, background: stColor }} />
                  {item.daysLeft < 0 ? `−${-item.daysLeft}${t(lang, 'days')}` : item.daysLeft === 0 ? t(lang, 'today') : `${item.daysLeft}${t(lang, 'days')}`}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    );
  }
  // cards
  return (
    <div style={{
      padding: '0 20px 16px',
      display: 'grid',
      gridTemplateColumns: web ? 'repeat(3, 1fr)' : 'repeat(2, 1fr)',
      gap: 10,
    }}>
      {items.map(item => {
        const st = statusOf(item);
        const stColor = st === 'urgent' || st === 'expired' ? palette.red : st === 'soon' ? palette.amber : palette.primary;
        const stBg = st === 'urgent' || st === 'expired' ? palette.redSoft : st === 'soon' ? palette.amberSoft : palette.primarySoft;
        const stInk = st === 'urgent' || st === 'expired' ? palette.redSoftInk : st === 'soon' ? palette.amberSoftInk : palette.primarySoftInk;
        return (
          <div key={item.id} onClick={() => onPick(item)} style={{
            background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 16,
            padding: 12, cursor: 'pointer', position: 'relative',
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
              <FoodChip item={item} size={36} style={iconStyle} palette={palette} />
              <div style={{
                padding: '2px 7px', borderRadius: 6,
                background: stBg, color: stInk,
                fontSize: 10, fontFamily: 'Geist Mono, monospace', fontWeight: 500,
                display: 'inline-flex', alignItems: 'center', gap: 4,
              }}>
                <span style={{ width: 4, height: 4, borderRadius: 2, background: stColor }} />
                {item.daysLeft < 0 ? `−${-item.daysLeft}${t(lang, 'days')}` : item.daysLeft === 0 ? t(lang, 'today') : item.daysLeft === 1 ? t(lang, 'tomorrow') : `${item.daysLeft}${t(lang, 'days')}`}
              </div>
            </div>
            <div style={{ fontSize: 13.5, fontWeight: 500, color: palette.ink, letterSpacing: -0.15, marginTop: 12, lineHeight: 1.2 }}>
              {localized(lang, item.name)}
            </div>
            <div style={{ fontSize: 11, color: palette.ink3, marginTop: 3, fontFamily: 'Geist Mono, monospace' }}>
              {item.qty} {localized(lang, item.unit)} · {t(lang, item.location)}
            </div>
          </div>
        );
      })}
    </div>
  );
}

Object.assign(window, {
  StatusPill, TabNav, HomeHeader, HeroStat, StatCards, SectionHeader,
  ExpiringStrip, RecipeCard, InventoryList, WeekPlanStrip, LeftoversCard, RemindersCard, WasteScoreCard,
});

// ─────────────────────────────────────────────────────────────
// WEEK PLAN STRIP — compact week view for the Home page
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// REMINDERS CARD — dinner tonight, expiring food, grocery run, deals ending.
// In-app reminders + optional local browser notification (when the app is
// open / installed). True background push would need a push server.
// ─────────────────────────────────────────────────────────────
function RemindersCard({ palette, lang, mealPlan, inventory, grocery, onOpenPlan, onOpenGrocery, onOpenInv }) {
  const today = todayISO();
  const catalog = RECIPES.concat((typeof window !== 'undefined' && window.EXTRA_RECIPES) || []);
  const reminders = [];
  const dinner = (mealPlan || []).find(m => m.date === today && m.slot === 'dinner');
  if (dinner) {
    const snap = dinner.recipeName ? { name: dinner.recipeName } : catalog.find(x => x.id === dinner.recipeId);
    const nm = snap ? localized(lang, snap.name) : (lang === 'fr' ? 'Repas planifié' : 'Planned meal');
    const full = catalog.find(x => x.id === dinner.recipeId);
    const missing = (full && window.matchRecipe) ? window.matchRecipe(full, inventory).missing.length : 0;
    reminders.push({ icon: '🍽️', title: lang === 'fr' ? `Souper ce soir : ${nm}` : `Dinner tonight: ${nm}`,
      sub: missing > 0 ? (lang === 'fr' ? `${missing} ingrédient${missing > 1 ? 's' : ''} manquant${missing > 1 ? 's' : ''}` : `${missing} missing ingredient${missing > 1 ? 's' : ''}`) : (dinner.time || ''),
      urgent: missing > 0, onTap: onOpenPlan });
  } else {
    reminders.push({ icon: '🍽️', title: lang === 'fr' ? 'Aucun souper planifié ce soir' : 'No dinner planned tonight',
      sub: lang === 'fr' ? 'Planifie un repas' : 'Plan a meal', onTap: onOpenPlan });
  }
  const exp = (inventory || []).filter(i => i.daysLeft != null && i.daysLeft <= 2);
  if (exp.length) reminders.push({ icon: '⏰', urgent: true,
    title: lang === 'fr' ? `${exp.length} aliment${exp.length > 1 ? 's' : ''} à utiliser vite` : `${exp.length} item${exp.length > 1 ? 's' : ''} to use soon`,
    sub: exp.slice(0, 3).map(i => localized(lang, i.name)).join(', '), onTap: onOpenInv });
  const togo = (grocery || []).filter(g => !g.checked).length;
  if (togo) reminders.push({ icon: '🛒',
    title: lang === 'fr' ? `${togo} article${togo > 1 ? 's' : ''} sur ta liste` : `${togo} item${togo > 1 ? 's' : ''} on your list`,
    sub: lang === 'fr' ? 'Pense à l\'épicerie' : 'Don\'t forget groceries', onTap: onOpenGrocery });
  const deals = (typeof FLYER_SPECIALS !== 'undefined' ? FLYER_SPECIALS : []).filter(s => s.ends <= 2);
  if (deals.length) reminders.push({ icon: '🏷️',
    title: lang === 'fr' ? `${deals.length} rabais finissent bientôt` : `${deals.length} deals ending soon`,
    sub: deals.map(d => d.retailer).join(', '), onTap: onOpenGrocery });

  const [notifState, setNotifState] = useState(typeof Notification !== 'undefined' ? Notification.permission : 'unsupported');
  // Fire one local notification for the top reminder, once per day, if allowed.
  useEffect(() => {
    if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return;
    if (!reminders.length) return;
    try {
      const key = 'pw_notif_' + today;
      if (localStorage.getItem(key)) return;
      const top = reminders.find(r => r.urgent) || reminders[0];
      new Notification('PantryWise', { body: `${top.icon} ${top.title}`, tag: 'pw-daily' });
      localStorage.setItem(key, '1');
    } catch (e) {}
  }, []);
  const enableNotif = () => {
    if (typeof Notification === 'undefined') return;
    Notification.requestPermission().then(async (p) => {
      setNotifState(p);
      if (p !== 'granted') return;
      // Immediate local confirmation.
      if (reminders.length) {
        const top = reminders.find(r => r.urgent) || reminders[0];
        try { new Notification('PantryWise', { body: `${top.icon} ${top.title}` }); } catch (e) {}
      }
      // Subscribe to Web Push for background expiry alerts (needs a VAPID public
      // key — set window.PW_VAPID_PUBLIC_KEY. No key → local-only, no error).
      try {
        const key = typeof window !== 'undefined' && window.PW_VAPID_PUBLIC_KEY;
        if (key && navigator.serviceWorker && window.PWAuth && window.PWAuth.api && window.PWAuth.api.pushSubscribe) {
          const reg = await navigator.serviceWorker.ready;
          const b64 = (key + '='.repeat((4 - key.length % 4) % 4)).replace(/-/g, '+').replace(/_/g, '/');
          const raw = atob(b64); const arr = new Uint8Array(raw.length);
          for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
          const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: arr });
          await window.PWAuth.api.pushSubscribe(sub.toJSON());
        }
      } catch (e) { /* push optional — local reminders still work */ }
    });
  };

  if (!reminders.length) return null;
  return (
    <div style={{ padding: '4px 20px 16px' }}>
      <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 18, padding: 14 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
          <Icon name="bell" size={16} color={palette.ink2} />
          <div style={{ flex: 1, fontSize: 14, fontWeight: 600, color: palette.ink, letterSpacing: -0.2 }}>
            {lang === 'fr' ? 'Rappels' : 'Reminders'}
          </div>
          {notifState !== 'granted' && notifState !== 'unsupported' && (
            <div onClick={enableNotif} style={{
              padding: '5px 10px', borderRadius: 9, background: palette.primarySoft, color: palette.primarySoftInk,
              fontSize: 11, fontWeight: 600, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 5,
            }}>
              <Icon name="bell" size={12} color={palette.primarySoftInk} />
              {lang === 'fr' ? 'Activer' : 'Enable'}
            </div>
          )}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {reminders.slice(0, 4).map((r, i) => (
            <div key={i} onClick={r.onTap} style={{
              display: 'flex', alignItems: 'center', gap: 11, padding: '9px 11px', borderRadius: 12,
              background: r.urgent ? palette.amberSoft : palette.cardSub,
              border: `1px solid ${r.urgent ? palette.amber + '40' : palette.line2}`, cursor: 'pointer',
            }}>
              <div style={{ fontSize: 19 }}>{r.icon}</div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: r.urgent ? palette.amberSoftInk : palette.ink, letterSpacing: -0.1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.title}</div>
                {r.sub && <div style={{ fontSize: 11, color: r.urgent ? palette.amberSoftInk : palette.ink3, opacity: 0.85, marginTop: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.sub}</div>}
              </div>
              <Icon name="chevR" size={14} color={r.urgent ? palette.amberSoftInk : palette.ink3} />
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// WASTE SCORE — small motivating card: meals cooked, $ saved, week streak.
// Real data from the cook log (empty for new users → hidden).
// ─────────────────────────────────────────────────────────────
function WasteScoreCard({ palette, lang, cookLog }) {
  const log = cookLog || [];
  if (!log.length) return null;
  const now = Date.now();
  const week = log.filter(e => now - e.ts < 7 * 86400000);
  const cooked = week.length;
  const saved = Math.round(week.reduce((s, e) => s + (e.savedCents || 0), 0) / 100);
  // Streak = consecutive ISO-weeks (ending this week) with ≥1 cook event.
  const weekKey = (ts) => startOfWeekISO(isoDate(new Date(ts)));
  const weeks = new Set(log.map(e => weekKey(e.ts)));
  let streak = 0; let cur = startOfWeekISO(todayISO());
  while (weeks.has(cur)) { streak++; cur = addDays(cur, -7); }
  return (
    <div style={{ padding: '4px 20px 16px' }}>
      <div style={{
        background: `linear-gradient(135deg, ${palette.primary}, ${palette.primary}cc)`,
        color: palette.primaryInk, borderRadius: 18, padding: '14px 16px',
        display: 'flex', alignItems: 'center', gap: 14,
      }}>
        <div style={{ fontSize: 30 }}>🌱</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 14, fontWeight: 700, letterSpacing: -0.2 }}>
            {streak > 1
              ? (lang === 'fr' ? `${streak} semaines anti-gaspillage 🔥` : `${streak}-week waste-saving streak 🔥`)
              : (lang === 'fr' ? 'Ta semaine anti-gaspillage' : 'Your waste-saving week')}
          </div>
          <div style={{ fontSize: 12, opacity: 0.9, marginTop: 2, fontFamily: 'Geist Mono, monospace' }}>
            {lang === 'fr'
              ? `${cooked} repas cuisinés · ~${saved}$ sauvés`
              : `${cooked} meals cooked · ~$${saved} saved`}
          </div>
        </div>
      </div>
    </div>
  );
}

function WeekPlanStrip({ palette, lang, mealPlan, onOpenPlan }) {
  const dayLabels = ['planMon', 'planTue', 'planWed', 'planThu', 'planFri', 'planSat', 'planSun'];
  const todayIso = todayISO();
  const weekStart = startOfWeekISO(todayIso);
  const weekDates = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
  // Full catalog lookup so planned QC/extra recipes resolve, not just seeds.
  const catalog = RECIPES.concat((typeof window !== 'undefined' && window.EXTRA_RECIPES) || []);
  const findRecipe = (id) => catalog.find(r => r.id === id);
  const memberById = (id) => HOUSEHOLD.find(m => m.id === id);
  // Who's eating that day = cooks + people who voted yes across the day's meals.
  const attendeesFor = (meals) => {
    const ids = new Set();
    meals.forEach(m => {
      if (m.cook) ids.add(m.cook);
      if (m.votes) Object.keys(m.votes).forEach(uid => { if (m.votes[uid] === true) ids.add(uid); });
      if (m.status === 'confirmed' && !m.votes) HOUSEHOLD.forEach(h => ids.add(h.id));
    });
    return [...ids].map(memberById).filter(Boolean);
  };
  return (
    <div style={{ padding: '4px 20px 16px' }}>
      <div onClick={onOpenPlan} style={{
        background: palette.card, border: `1px solid ${palette.line}`,
        borderRadius: 18, padding: 14, cursor: 'pointer',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
          <Icon name="calendar" size={16} color={palette.ink2} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 14, fontWeight: 600, color: palette.ink, letterSpacing: -0.2 }}>
              {t(lang, 'planThisWeek')}
            </div>
            <div style={{ fontSize: 11, color: palette.ink3, fontFamily: 'Geist Mono, monospace', textTransform: 'capitalize', marginTop: 1 }}>
              {monthYearLabel(weekStart, lang)}
            </div>
          </div>
          <div style={{ display: 'flex' }}>
            {HOUSEHOLD.slice(0, 3).map((m, i) => (
              <div key={m.id} style={{
                width: 20, height: 20, borderRadius: 10,
                background: m.tone, color: '#fff',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 10, fontWeight: 600, fontFamily: 'Geist Mono, monospace',
                marginLeft: i === 0 ? 0 : -5,
                border: `1.5px solid ${palette.card}`,
              }}>{m.initial}</div>
            ))}
          </div>
          <Icon name="chevR" size={14} color={palette.ink3} />
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 5 }}>
          {dayLabels.map((k, i) => {
            const iso = weekDates[i];
            const meals = mealPlan.filter(m => m.date === iso);
            const dinner = meals.find(m => m.slot === 'dinner');
            // Prefer the meal's own snapshot (covers online recipes), else catalog.
            const dinnerRecipe = dinner && findRecipe(dinner.recipeId);
            const recipe = dinner && (dinner.recipeHero || dinner.recipeImage
              ? { hero: dinner.recipeHero, image: dinner.recipeImage, name: dinner.recipeName }
              : dinnerRecipe);
            const isToday = iso === todayIso;
            const who = attendeesFor(meals);
            return (
              <div key={i} style={{
                padding: '6px 3px 7px', borderRadius: 10, textAlign: 'center',
                background: isToday ? palette.primarySoft : palette.cardSub,
                border: isToday ? `1px solid ${palette.primary}` : `1px solid ${palette.line2}`,
                position: 'relative', minHeight: 70,
              }}>
                <div style={{
                  fontSize: 9, fontFamily: 'Geist Mono, monospace',
                  color: isToday ? palette.primarySoftInk : palette.ink3,
                  letterSpacing: 0.04, fontWeight: 500,
                }}>{t(lang, k)}</div>
                <div style={{
                  fontSize: 12, fontWeight: 600, lineHeight: 1,
                  color: isToday ? palette.primarySoftInk : palette.ink2,
                }}>{dateNumber(iso)}</div>
                {recipe ? (
                  recipe.image ? (
                    <img src={recipe.image} alt="" style={{ width: 24, height: 24, borderRadius: 6, objectFit: 'cover', marginTop: 3 }} />
                  ) : (
                  <div style={{
                    fontSize: 19, marginTop: 3, lineHeight: 1,
                    filter: dinner.status === 'voting' ? 'grayscale(0.4)' : 'none',
                  }}>{recipe.hero || '🍽'}</div>
                  )
                ) : (
                  <div style={{ fontSize: 13, marginTop: 4, color: palette.ink3, opacity: 0.45 }}>—</div>
                )}
                {/* who's eating */}
                <div style={{ display: 'flex', justifyContent: 'center', marginTop: 4, minHeight: 12 }}>
                  {who.slice(0, 3).map((m, j) => (
                    <div key={m.id} title={m.name} style={{
                      width: 12, height: 12, borderRadius: 6, background: m.tone, color: '#fff',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      fontSize: 7, fontWeight: 700, fontFamily: 'Geist Mono, monospace',
                      marginLeft: j === 0 ? 0 : -3, border: `1px solid ${isToday ? palette.primarySoft : palette.cardSub}`,
                    }}>{m.initial}</div>
                  ))}
                </div>
                {dinner && dinner.status === 'voting' && (
                  <div style={{
                    position: 'absolute', top: 3, right: 3,
                    width: 5, height: 5, borderRadius: 3, background: palette.amber,
                  }} />
                )}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// LEFTOVERS CARD — surfaces what's available for lunch tomorrow
// ─────────────────────────────────────────────────────────────
function LeftoversCard({ palette, lang, leftovers, onPlan }) {
  if (!leftovers || leftovers.length === 0) return null;
  const lf = leftovers[0];
  return (
    <div style={{ padding: '0 20px 16px' }}>
      <div style={{
        background: palette.amberSoft,
        borderRadius: 18, padding: 14,
        display: 'flex', alignItems: 'center', gap: 12,
      }}>
        <div style={{
          width: 52, height: 52, borderRadius: 14,
          background: palette.amber, color: '#fff',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: 26, flexShrink: 0,
        }}>{lf.emoji}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 10.5, fontFamily: 'Geist Mono, monospace', color: palette.amberSoftInk, letterSpacing: 0.06, textTransform: 'uppercase', marginBottom: 2 }}>
            {t(lang, 'lunchSuggest')} · {t(lang, 'leftoversSub')}
          </div>
          <div style={{ fontSize: 14.5, fontWeight: 600, color: palette.amberSoftInk, letterSpacing: -0.2, lineHeight: 1.2 }}>
            {localized(lang, lf.name)}
          </div>
          <div style={{ fontSize: 11.5, color: palette.amberSoftInk, opacity: 0.85, marginTop: 2, fontFamily: 'Geist Mono, monospace' }}>
            {lf.portions} {t(lang, 'portionsLeft')}
          </div>
        </div>
        <div onClick={onPlan} style={{
          padding: '8px 12px', borderRadius: 10,
          background: palette.amberSoftInk, color: palette.amberSoft,
          fontSize: 12, fontWeight: 500, cursor: 'pointer',
          flexShrink: 0,
        }}>{lang === 'fr' ? 'Planifier' : 'Plan'}</div>
      </div>
    </div>
  );
}
