// primitives.jsx — shared UI atoms for the guest portal.
const { useState, useRef, useEffect } = React;

// Optical property name: bold lowercase "master" + light uppercase name at 0.65×.
function PropertyName({ name, size = 18, color = 'var(--fg)' }) {
  const m = name.match(/^(master)\s+(.+)$/i);
  const light = Math.round(size * 0.65);
  if (!m) return <span style={{ fontFamily: 'var(--font-serif)', fontWeight: 700, fontSize: size, color }}>{name}</span>;
  return (
    <span style={{ fontFamily: 'var(--font-serif)', color, lineHeight: 1, display: 'inline-flex', alignItems: 'baseline', gap: '0.18em', whiteSpace: 'nowrap' }}>
      <span style={{ fontWeight: 700, fontSize: size }}>master</span>
      <span style={{ fontWeight: 300, textTransform: 'uppercase', fontSize: light, letterSpacing: '0.02em' }}>{m[2]}</span>
    </span>);

}

// Small uppercase mono eyebrow label.
function Eyebrow({ children, style }) {
  return <div style={{ fontFamily: 'var(--font-mono)', fontWeight: 300, fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--fg-faint)', ...style }}>{children}</div>;
}

// Italic accent label (Lucky Times).
function Accent({ children, style }) {
  return <span style={{ fontFamily: 'var(--font-accent)', fontStyle: 'italic', color: 'var(--fg-faint)', ...style }}>{children}</span>;
}

// Mono button — flat, square corners. variant: solid | outline | ghost
function Btn({ children, variant = 'solid', onClick, full, style }) {
  const [hover, setHover] = useState(false);
  const base = {
    fontFamily: 'var(--font-mono)', fontWeight: 300, fontSize: 12, letterSpacing: '0.12em',
    textTransform: 'uppercase', padding: '14px 22px', cursor: 'pointer', border: 'none',
    borderRadius: 0, transition: 'all .18s ease', width: full ? '100%' : 'auto',
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, lineHeight: 1
  };
  const variants = {
    solid: { background: hover ? 'var(--color-green-dark)' : 'var(--brand)', color: 'var(--on-brand)' },
    outline: { background: hover ? 'var(--bg-2)' : 'transparent', color: 'var(--fg)', border: '1px solid var(--line-strong)' },
    ghost: { background: hover ? 'var(--bg-2)' : 'transparent', color: 'var(--fg)' }
  };
  return (
    <button onClick={onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
    style={{ ...base, ...variants[variant], ...style }}>{children}</button>);

}

// Tiny round icon button (eye toggle, copy).
function IconBtn({ onClick, title, active, children }) {
  const [hover, setHover] = useState(false);
  return (
    <button title={title} onClick={onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
    style={{
      width: 30, height: 30, borderRadius: 999, border: '1px solid var(--line)',
      background: hover || active ? 'var(--bg-2)' : 'transparent', color: 'var(--fg-muted)',
      cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      flexShrink: 0, transition: 'all .15s ease', padding: 0
    }}>{children}</button>);

}

const EyeIcon = ({ off }) =>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round">
    <path d="M1 8s2.6-4.5 7-4.5S15 8 15 8s-2.6 4.5-7 4.5S1 8 1 8Z" />
    <circle cx="8" cy="8" r="1.8" />
    {off && <path d="M2 2l12 12" />}
  </svg>;

const CopyIcon = ({ done }) => done ?
<svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8.5l3.5 3.5L13 4" /></svg> :

<svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"><rect x="5" y="5" width="8" height="8" rx="1.2" /><path d="M3 11V3.5Q3 3 3.5 3H11" /></svg>;


// Fullscreen modal showing a code BIG on a white card, with save-as-image.
function PinModal({ value, label, title, lines, filename, suffix = '#', note, onClose }) {
  const t = window.useT();
  const fullCode = value + (suffix ? ' ' + suffix : '');
  const noteText = note || 'This PIN grants access to your apartment. Only share it with guests included in your reservation.';
  const cardRef = useRef(null);
  useEffect(() => {
    const onKey = (e) => {if (e.key === 'Escape') onClose();};
    window.addEventListener('keydown', onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => {window.removeEventListener('keydown', onKey);document.body.style.overflow = prev;};
  }, []);

  const saveImage = async () => {
    const scale = 2,W = 820,H = 1040;
    const c = document.createElement('canvas');
    c.width = W * scale;c.height = H * scale;
    const ctx = c.getContext('2d');
    ctx.scale(scale, scale);
    ctx.fillStyle = '#ffffff';ctx.fillRect(0, 0, W, H);
    try {await document.fonts.ready;} catch (e) {}
    ctx.textAlign = 'center';
    // eyebrow
    ctx.fillStyle = '#7F9583';
    ctx.font = '400 17px "GT America Mono", "IBM Plex Sans Hebrew", monospace';
    if ('letterSpacing' in ctx) ctx.letterSpacing = '5px';
    ctx.fillText((label || 'Code').toUpperCase(), W / 2, 200);
    if ('letterSpacing' in ctx) ctx.letterSpacing = '0px';
    // apartment title
    ctx.fillStyle = '#044122';
    ctx.font = '300 46px "Schnyder Wide S", "Futurism Display", Georgia, serif';
    ctx.fillText(title || '', W / 2, 280);
    // sub lines
    ctx.fillStyle = '#636260';
    ctx.font = '300 22px "GT America Mono", "IBM Plex Sans Hebrew", monospace';
    ctx.fillText((lines || []).join('   ·   '), W / 2, 330);
    // big code — fit to width so it never breaks
    ctx.fillStyle = '#003319';
    let codeSize = 120;
    if ('letterSpacing' in ctx) ctx.letterSpacing = '8px';
    do {
      ctx.font = '300 ' + codeSize + 'px "GT America Mono", "IBM Plex Sans Hebrew", monospace';
      if (ctx.measureText(fullCode).width <= W - 120) break;
      codeSize -= 4;
    } while (codeSize > 40);
    ctx.fillText(fullCode, W / 2, H / 2 + 70);
    if ('letterSpacing' in ctx) ctx.letterSpacing = '0px';
    // explanatory note — wrapped
    ctx.fillStyle = '#636260';
    ctx.font = '300 20px "GT America Mono", "IBM Plex Sans Hebrew", monospace';
    const words = noteText.split(' ');
    const maxW = W - 180;let line = '';let y = H / 2 + 180;
    for (const w of words) {
      const test = line ? line + ' ' + w : w;
      if (ctx.measureText(test).width > maxW && line) {ctx.fillText(line, W / 2, y);line = w;y += 30;} else
      line = test;
    }
    if (line) ctx.fillText(line, W / 2, y);
    // brand mark
    ctx.fillStyle = '#2D2D2C';
    ctx.font = '700 26px "Schnyder Wide S", "Futurism Display", Georgia, serif';
    ctx.fillText('master', W / 2, H - 80);
    const blob = await new Promise((resolve) => c.toBlob(resolve, 'image/png'));
    if (!blob) return;
    const name = 'master-' + (label || 'code').toLowerCase().replace(/\s+/g, '-') + '-' + (filename || '') + '.png';

    // iOS/Android: the native share sheet has a one-tap "Save Image" that writes
    // straight to the camera roll — far less friction than an <a download>, which
    // on iPhone drops the PNG into Files with a location picker. Fall back to the
    // classic download on desktop or where file sharing isn't supported.
    try {
      const file = new File([blob], name, { type: 'image/png' });
      if (navigator.canShare && navigator.canShare({ files: [file] })) {
        await navigator.share({ files: [file], title: title || 'master' });
        return;
      }
    } catch (err) {
      if (err && err.name === 'AbortError') return; // user dismissed the share sheet
      // otherwise fall through to the download fallback
    }

    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;a.download = name;
    document.body.appendChild(a);a.click();a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };

  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 220, background: 'rgba(18,28,20,0.6)', backdropFilter: 'blur(5px)', WebkitBackdropFilter: 'blur(5px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div onClick={(e) => e.stopPropagation()} style={{ position: 'relative', width: 'min(460px, 100%)', background: 'var(--surface)', border: '1px solid var(--line)', boxShadow: 'var(--shadow-medium)' }}>
        <button onClick={onClose} aria-label="Close" style={{ position: 'absolute', top: 14, right: 14, zIndex: 2, width: 36, height: 36, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(249,244,234,0.9)', border: '1px solid var(--line-strong)', color: 'var(--fg-muted)', cursor: 'pointer' }}>
          <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"><path d="M3 3l10 10M13 3L3 13" /></svg>
        </button>
        {/* the white card */}
        <div ref={cardRef} style={{ background: '#ffffff', padding: '54px 28px 46px', textAlign: 'center' }}>
          <div style={{ fontFamily: 'var(--font-mono)', fontWeight: 300, fontSize: 12, letterSpacing: '0.22em', textTransform: 'uppercase', color: '#7F9583' }}>{label}</div>
          <div style={{ fontFamily: 'var(--font-serif)', fontWeight: 300, fontSize: 26, lineHeight: 1.1, color: '#044122', marginTop: 14 }}>{title}</div>
          {lines && lines.length > 0 &&
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 13, color: '#636260', letterSpacing: '0.04em', marginTop: 8 }}>{lines.join('  ·  ')}</div>
          }
          <div dir="ltr" style={{ fontFamily: 'var(--font-mono)', fontWeight: 300, fontSize: 'clamp(26px, 9vw, 46px)', letterSpacing: '0.04em', color: '#003319', margin: '36px 0 6px', lineHeight: 1, whiteSpace: 'nowrap', maxWidth: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.22em' }}>
            <span>{value}</span>
            {suffix && <span style={{ color: '#7F9583' }}>{suffix}</span>}
          </div>
          <p style={{ fontFamily: 'var(--font-mono)', fontWeight: 300, fontSize: 12.5, lineHeight: 1.6, color: '#636260', maxWidth: 320, margin: '22px auto 0', textWrap: 'pretty' }}>{noteText}</p>
        </div>
        <div style={{ padding: 18, borderTop: '1px solid var(--line)', display: 'flex', justifyContent: 'center' }}>
          <Btn variant="solid" onClick={saveImage}>
            <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"><path d="M8 1.5v8M5 7l3 3 3-3" /><path d="M2.5 11v2.5a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1V11" /></svg>
            {t('saveAsImage')}
          </Btn>
        </div>
      </div>
    </div>);

}

// Field showing a label + secret value with reveal + copy.
function SecretField({ label, value, masked = true, mono = true, layout = 'row', noCopy = false, expand = null }) {
  const [shown, setShown] = useState(!masked);
  const [copied, setCopied] = useState(false);
  const [expanded, setExpanded] = useState(false);
  const copy = () => {
    navigator.clipboard?.writeText(value).catch(() => {});
    setCopied(true);setTimeout(() => setCopied(false), 1400);
  };
  const dotCount = Math.max(4, value.replace(/\s/g, '').length);
  const valueStyle = {
    fontFamily: mono ? 'var(--font-mono)' : 'var(--font-serif)', fontSize: 17,
    whiteSpace: 'nowrap', letterSpacing: '0.02em'
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
      <Eyebrow>{label}</Eyebrow>
      {/* minHeight matches IconBtn (30px) so a field with a copy/reveal button and
          one without still center their value on the same line — keeps the wifi
          network and password rows vertically aligned. */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, minHeight: 30 }}>
        {/* Stack masked + real value so the box always reserves the revealed width */}
        <span style={{ display: 'inline-grid', flex: '0 1 auto', minWidth: 0 }}>
          <span aria-hidden="true" style={{ ...valueStyle, gridArea: '1 / 1', visibility: 'hidden', overflow: 'hidden' }}>{value}</span>
          <span style={{ ...valueStyle, gridArea: '1 / 1', color: 'var(--fg)', overflow: 'hidden', display: 'flex', alignItems: 'center' }}>
            {shown ?
            <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{value}</span> :
            <span aria-hidden="true" style={{ display: 'flex', flex: 1, justifyContent: 'space-between', alignItems: 'center' }}>
                  {Array.from({ length: dotCount }).map((_, i) => <span key={i}>•</span>)}
                </span>}
          </span>
        </span>
        <div style={{ display: 'flex', gap: 4, flexShrink: 0 }}>
          {masked && expand && <IconBtn title="Show full screen" onClick={() => setExpanded(true)}><EyeIcon off={true} /></IconBtn>}
          {masked && !expand && <IconBtn title={shown ? 'Hide' : 'Reveal'} active={shown} onClick={() => setShown((s) => !s)}><EyeIcon off={!shown} /></IconBtn>}
          {!noCopy && <IconBtn title="Copy" active={copied} onClick={copy}><CopyIcon done={copied} /></IconBtn>}
        </div>
      </div>
      {expanded && <PinModal value={value} label={label} title={expand.title} lines={expand.lines || []} filename={expand.filename || 'pin'} suffix={expand.suffix} note={expand.note} onClose={() => setExpanded(false)} />}
    </div>);

}

// Plain copyable value (wifi network, code) — no masking.
function CopyValue({ label, value, big, noCopy }) {
  return <SecretField label={label} value={value} masked={false} noCopy={noCopy} />;
}

// Accordion section — expandable, with optional doodle.
function Accordion({ title, summary, icon, children, defaultOpen = false, dense, last }) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <div style={{ borderBottom: last ? 'none' : '1px solid var(--line)' }}>
      <button onClick={() => setOpen((o) => !o)} style={{
        width: '100%', background: 'none', border: 'none', cursor: 'pointer', textAlign: 'start',
        display: 'flex', alignItems: 'center', gap: 14, padding: dense ? '16px 4px' : '22px 4px', color: 'var(--fg)'
      }}>
        {icon && <span style={{ color: 'var(--illustration)', flexShrink: 0 }}>{icon}</span>}
        <span style={{ flex: 1, minWidth: 0 }}>
          <span style={{ display: 'block', fontFamily: 'var(--font-mono)', fontSize: 14, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--fg)' }}>{title}</span>
          {summary && <span style={{ display: 'block', fontFamily: 'var(--font-accent)', fontStyle: 'italic', fontSize: 13, color: 'var(--fg-faint)', marginTop: 4 }}>{summary}</span>}
        </span>
        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"
        style={{ color: 'var(--fg-muted)', flexShrink: 0, transform: open ? 'rotate(90deg)' : 'none', transition: 'transform .2s ease' }}>
          <path d="M5 2l6 6-6 6" />
        </svg>
      </button>
      <div style={{ display: 'grid', gridTemplateRows: open ? '1fr' : '0fr', transition: 'grid-template-rows .26s ease' }}>
        <div style={{ overflow: 'hidden' }}>
          <div style={{ padding: '0 4px 22px 4px' }}>{children}</div>
        </div>
      </div>
    </div>);

}

// Bulleted list inside accordions.
function BulletList({ items }) {
  return (
    <ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 10 }}>
      {items.map((t, i) =>
      <li key={i} style={{ display: 'flex', gap: 12, fontFamily: 'var(--font-mono)', fontSize: 13.5, color: 'var(--fg-muted)', letterSpacing: '0.02em', lineHeight: 1.5 }}>
          <span style={{ color: 'var(--illustration)', flexShrink: 0, marginTop: 1 }}>—</span>
          <span>{t}</span>
        </li>
      )}
    </ul>);

}

// Map service icon name → doodle component.
function ServiceDoodle({ icon, size = 30 }) {
  const map = { broom: window.DoodleBroom, calendar: window.DoodleCalendar, pin: window.DoodlePin, coffee: window.DoodleCoffee, compass: window.DoodleCompass };
  const C = map[icon] || window.DoodleSparkle;
  return <C size={size} />;
}

Object.assign(window, { PropertyName, Eyebrow, Accent, Btn, IconBtn, EyeIcon, CopyIcon, PinModal, SecretField, CopyValue, Accordion, BulletList, ServiceDoodle });