// Reusable UI atoms for the Zocker prototype

// Viewport hook — true when the window is at least `bp` px wide (desktop/tablet)
function useWide(bp = 900) {
  const read = () => (typeof window !== 'undefined' ? window.innerWidth >= bp : false);
  const [wide, setWide] = React.useState(read);
  React.useEffect(() => {
    const onResize = () => setWide(read());
    onResize();
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, [bp]);
  return wide;
}

// Full-bleed shell hook — true on actual phone-sized screens or when launched
// as an installed PWA, where the app should fill the device edge-to-edge for a
// native feel instead of sitting inside the desktop "device frame" mockup.
function useNativeShell() {
  const read = () => {
    if (typeof window === 'undefined') return false;
    const mm = window.matchMedia ? window.matchMedia.bind(window) : null;
    const standalone = (mm && mm('(display-mode: standalone)').matches)
      || window.navigator.standalone === true;
    const phone = mm ? mm('(max-width: 440px)').matches : window.innerWidth <= 440;
    return Boolean(standalone || phone);
  };
  const [val, setVal] = React.useState(read);
  React.useEffect(() => {
    const on = () => setVal(read());
    on();
    window.addEventListener('resize', on);
    return () => window.removeEventListener('resize', on);
  }, []);
  return val;
}

// True once the initial Firestore load has settled (used to show a loading
// spinner instead of an empty list while data is still coming from the DB).
function useDataReady() {
  const [ready, setReady] = React.useState(!!window.__DATA_READY);
  React.useEffect(() => {
    if (ready) return;
    const on = () => setReady(!!window.__DATA_READY);
    window.addEventListener('zocker-data-ready', on);
    return () => window.removeEventListener('zocker-data-ready', on);
  }, [ready]);
  return ready;
}

// Centered confirmation modal in app style (replaces window.confirm).
function ConfirmDialog({ open, title, message, confirmLabel, cancelLabel, danger = false, onConfirm, onCancel }) {
  const t = window.t;
  const [render, setRender] = React.useState(open);
  const [visible, setVisible] = React.useState(open);
  React.useEffect(() => {
    if (open) {
      setRender(true);
      const id = setTimeout(() => setVisible(true), 20);
      return () => clearTimeout(id);
    }
    setVisible(false);
    const id = setTimeout(() => setRender(false), 240);
    return () => clearTimeout(id);
  }, [open]);
  if (!render) return null;
  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 200,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
    }}>
      <div onClick={onCancel} style={{
        position: 'absolute', inset: 0,
        background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(2px)',
        opacity: visible ? 1 : 0, transition: 'opacity 200ms ease',
      }} />
      <div style={{
        position: 'relative', width: 'min(92vw, 360px)',
        background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 22,
        boxShadow: '0 40px 90px rgba(0,0,0,0.55)', padding: 22,
        transform: visible ? 'scale(1)' : 'scale(0.96)', opacity: visible ? 1 : 0,
        transition: 'transform 200ms cubic-bezier(.2,.8,.2,1), opacity 160ms ease',
      }}>
        <div style={{
          fontFamily: 'Space Grotesk, system-ui', fontSize: 18, fontWeight: 700,
          color: 'var(--text)', marginBottom: 8,
        }}>{title}</div>
        {message && (
          <div style={{
            fontFamily: 'Space Grotesk, system-ui', fontSize: 14, color: 'var(--muted)',
            lineHeight: 1.5, marginBottom: 20,
          }}>{message}</div>
        )}
        <div style={{ display: 'flex', gap: 10 }}>
          <Button variant="ghost" full onClick={onCancel}>{cancelLabel || (t ? t('cancel') : 'Cancel')}</Button>
          <Button variant={danger ? 'danger' : 'primary'} full onClick={onConfirm}>{confirmLabel || 'OK'}</Button>
        </div>
      </div>
    </div>
  );
}

// Centered loading spinner
function Spinner({ label }) {
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      gap: 12, padding: '48px 16px', color: 'var(--muted)',
    }}>
      <div className="spinner" />
      {label && (
        <div style={{ fontFamily: 'Space Grotesk, system-ui', fontSize: 13 }}>{label}</div>
      )}
    </div>
  );
}

// Avatar — initial in a tinted disc with platform indicator
function Avatar({ id, size = 36, ring = false, showStatus = false, stack = false, ringColor = null }) {
  const f = window.FRIENDS_BY_ID[id];
  if (!f) return null;
  const cls = 'avatar' + (ring ? ' avatar--ring' : '') + (stack ? ' avatar--stack' : '');
  // A status ring (e.g. readiness) draws a colored halo around the disc, with a
  // bg-deep gap so it reads as a ring even when avatars overlap in a stack.
  const discStyle = ringColor
    ? { boxShadow: `0 0 0 2px var(--bg-deep), 0 0 0 4px ${ringColor}` }
    : undefined;
  return (
    <div className={cls} style={{ '--av-size': size + 'px', '--av-hue': f.hue }}>
      <div className="avatar__disc" style={discStyle}>
        {f.photo
          ? <img className="avatar__img" src={f.photo} alt={f.name} />
          : f.name[0]}
      </div>
      {showStatus && f.online && <div className="avatar__status" />}
    </div>
  );
}

// Readiness status → label + color. Used by the ready-check UI (avatar rings,
// roster, toast). status is 'yes' | 'coming' | null (no reply yet).
function readyStatusMeta(status) {
  const t = window.t;
  if (status === 'yes')    return { color: 'var(--accent)',      label: t('ready_status_yes') };
  if (status === 'coming') return { color: 'oklch(0.82 0.16 75)', label: t('ready_status_coming') };
  return { color: 'var(--muted)', label: t('ready_status_pending') };
}

// Stack of avatars (overlapping)
function AvatarStack({ ids, size = 28, max = 5 }) {
  const shown = ids.slice(0, max);
  const extra = ids.length - shown.length;
  return (
    <div className="avatar-stack">
      {shown.map((id, i) => (
        <Avatar key={id} id={id} size={size} stack={i > 0} />
      ))}
      {extra > 0 && (
        <div className="avatar-stack__more" style={{ '--av-size': size + 'px' }}>+{extra}</div>
      )}
    </div>
  );
}

// Chip — small filled pill
function Chip({ children, color = 'neutral', active = false, onClick, style = {} }) {
  const cls = `chip chip--${color}`
    + (active ? ' chip--active' : '')
    + (onClick ? '' : ' chip--static');
  return (
    <button onClick={onClick} className={cls} style={style}>{children}</button>
  );
}

// Card surface
function Card({ children, style = {}, onClick, padded = true }) {
  const cls = 'card'
    + (padded ? '' : ' card--flush')
    + (onClick ? ' card--clickable' : '');
  return (
    <div onClick={onClick} className={cls} style={style}>{children}</div>
  );
}

// Section header
function SectionHead({ title, action, actionLabel }) {
  return (
    <div className="section-head">
      <div className="section-head__title">{title}</div>
      {action && (
        <button onClick={action} className="section-head__action">
          {actionLabel || 'mehr →'}
        </button>
      )}
    </div>
  );
}

// Game cover — uses a real cover image when available, else gradient + monogram
function GameCover({ game, size = 64, rounded = 14 }) {
  if (!game) return <div style={{ width: size, height: size, borderRadius: rounded, flexShrink: 0, background: 'var(--surface-2)' }} />;
  const initials = game.title.split(' ').slice(0, 2).map(w => w[0]).join('');
  const cover = game.img || game.capsule;
  if (cover) {
    return (
      <div style={{
        width: size, height: size, borderRadius: rounded, flexShrink: 0,
        position: 'relative', overflow: 'hidden',
        background: 'var(--surface-2)',
        boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)',
      }}>
        <img src={cover} alt={game.title} loading="lazy" style={{
          width: '100%', height: '100%', objectFit: 'cover', display: 'block',
        }} onError={(e) => { e.target.style.display = 'none'; }} />
      </div>
    );
  }
  return (
    <div style={{
      width: size, height: size, borderRadius: rounded, flexShrink: 0,
      background: `linear-gradient(135deg, ${game.bg1 || '#2a2440'} 0%, ${game.bg2 || '#4a3a6a'} 100%)`,
      position: 'relative', overflow: 'hidden',
      boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)',
    }}>
      {/* diagonal stripe pattern */}
      <div style={{
        position: 'absolute', inset: 0,
        background: 'repeating-linear-gradient(135deg, transparent 0 8px, rgba(255,255,255,0.04) 8px 9px)',
      }} />
      <div style={{
        position: 'absolute', inset: 0,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        fontFamily: 'Space Grotesk, system-ui', fontWeight: 800,
        fontSize: Math.round(size * 0.36), color: 'rgba(255,255,255,0.95)',
        letterSpacing: -1, textShadow: '0 2px 8px rgba(0,0,0,0.5)',
      }}>{initials}</div>
    </div>
  );
}

// Mood badge
function MoodBadge({ mood }) {
  const m = window.MOODS[mood];
  if (!m) return null;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 4,
      fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 600,
      letterSpacing: 0.4, textTransform: 'uppercase',
      color: `oklch(0.85 0.16 ${m.hue})`,
      padding: '3px 8px', borderRadius: 999,
      background: `oklch(0.85 0.16 ${m.hue} / 0.12)`,
      border: `1px solid oklch(0.85 0.16 ${m.hue} / 0.3)`,
    }}>
      <span style={{ fontSize: 10 }}>{m.glyph}</span>
      {m.label}
    </span>
  );
}

// Play-mode badge — single-player vs multiplayer/co-op, derived from a game's
// `modes` array (['single','multi','coop','pvp']). Multiplayer-capable games get
// a mint badge so they stand out for group nights; solo-only games a muted one.
function ModeBadge({ modes }) {
  const t = window.t;
  if (!modes || !modes.length) return null;
  const coop = modes.includes('coop');
  const multi = coop || modes.includes('multi') || modes.includes('pvp');
  const label = coop ? t('mode_coop') : multi ? t('mode_multi') : t('mode_single');
  const hue = multi ? 155 : 270;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center',
      fontFamily: 'Space Grotesk, system-ui', fontSize: 10, fontWeight: 700,
      letterSpacing: 0.3,
      color: `oklch(0.85 0.13 ${hue})`,
      padding: '2px 7px', borderRadius: 999,
      background: `oklch(0.85 0.13 ${hue} / 0.12)`,
      border: `1px solid oklch(0.85 0.13 ${hue} / 0.32)`,
      whiteSpace: 'nowrap',
    }}>{label}</span>
  );
}

// True when a game supports more than one player (used for filtering).
function gameIsMultiplayer(g) {
  const m = g && g.modes;
  return !!(m && (m.includes('multi') || m.includes('coop') || m.includes('pvp')));
}

// Human genre label for a game, ignoring the internal source tags.
function gameGenreLabel(g) {
  if (g.genre) return g.genre;
  const tag = g.tag;
  if (tag && !['steam', 'manual', 'game'].includes(tag)) {
    return tag.charAt(0).toUpperCase() + tag.slice(1);
  }
  return '';
}

// Platform pill (Steam / Epic)
function PlatformPill({ platform }) {
  const labels = { steam: 'STEAM', epic: 'EPIC' };
  return (
    <span style={{
      fontFamily: 'JetBrains Mono, monospace', fontSize: 9, fontWeight: 600,
      letterSpacing: 1, color: 'var(--muted)',
      padding: '2px 5px', borderRadius: 3,
      background: 'var(--surface-2)',
      border: '1px solid var(--border)',
    }}>{labels[platform] || platform.toUpperCase()}</span>
  );
}

// Primary button
function Button({ children, onClick, variant = 'primary', full, style = {} }) {
  const cls = `btn btn--${variant}` + (full ? ' btn--full' : '');
  return (
    <button onClick={onClick} className={cls} style={style}>{children}</button>
  );
}

// Numeric/time label using mono
function Mono({ children, size = 13, color, style = {} }) {
  return (
    <span style={{
      fontFamily: 'JetBrains Mono, monospace',
      fontSize: size, fontWeight: 500,
      letterSpacing: 0.2, color: color || 'inherit',
      ...style,
    }}>{children}</span>
  );
}

// Toggle switch
function Toggle({ on, onChange, size = 'md' }) {
  const w = size === 'sm' ? 36 : 46;
  const h = size === 'sm' ? 22 : 28;
  const knob = h - 6;
  return (
    <button onClick={() => onChange(!on)}
      className={'toggle' + (on ? ' toggle--on' : '')}
      style={{ '--tg-w': w + 'px', '--tg-h': h + 'px', '--tg-knob': knob + 'px' }}>
      <div className="toggle__knob" />
    </button>
  );
}

// Compress an uploaded image file to a square JPEG data-URL, suitable for
// storing inline (≈ a few KB). Center-crops to a square, downsamples to
// `maxSize` px and encodes at `quality`.
function compressImageFile(file, maxSize = 256, quality = 0.82) {
  return new Promise((resolve, reject) => {
    if (!file || !file.type || !file.type.startsWith('image/')) {
      reject(new Error('not-an-image'));
      return;
    }
    const reader = new FileReader();
    reader.onerror = () => reject(reader.error || new Error('read-failed'));
    reader.onload = () => {
      const img = new Image();
      img.onerror = () => reject(new Error('decode-failed'));
      img.onload = () => {
        const side = Math.min(img.width, img.height);
        const sx = (img.width - side) / 2;
        const sy = (img.height - side) / 2;
        const out = Math.min(maxSize, side);
        const canvas = document.createElement('canvas');
        canvas.width = out;
        canvas.height = out;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, sx, sy, side, side, 0, 0, out, out);
        resolve(canvas.toDataURL('image/jpeg', quality));
      };
      img.src = reader.result;
    };
    reader.readAsDataURL(file);
  });
}

Object.assign(window, {
  compressImageFile,
  Avatar, AvatarStack, readyStatusMeta, Chip, Card, SectionHead, GameCover,
  MoodBadge, ModeBadge, PlatformPill, Button, Mono, Toggle, useWide, useNativeShell,
  useDataReady, Spinner, ConfirmDialog, gameIsMultiplayer, gameGenreLabel,
});
