// Shared visual primitives for all three directions
// PaperBg, ChopSeal, Stamp, useEnvelopeOpen, LetterScan, MiniMap

// ─── Paper background ─────────────────────────────────────────────────
// SVG turbulence + slight vignette for warm aged paper.
function PaperBg({ tone = 'cream', children, style }) {
  const tones = {
    cream:  { base: '#efe4ca', deeper: '#d9c79c', shadow: 'rgba(70,40,15,0.18)' },
    sepia:  { base: '#e8d6b0', deeper: '#c9a76b', shadow: 'rgba(70,30,10,0.22)' },
    rice:   { base: '#f5ecd6', deeper: '#e3d2a4', shadow: 'rgba(80,50,15,0.14)' },
    ink:    { base: '#1f1812', deeper: '#100a06', shadow: 'rgba(0,0,0,0.4)' },
  };
  const t = tones[tone] || tones.cream;
  return (
    <div style={{ position: 'relative', background: t.base, overflow: 'hidden', ...style }}>
      <svg width="0" height="0" style={{ position: 'absolute' }} aria-hidden>
        <defs>
          <filter id="paper-grain">
            <feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" seed="7" />
            <feColorMatrix values="0 0 0 0 0.42  0 0 0 0 0.30  0 0 0 0 0.16  0 0 0 0.13 0" />
            <feComposite in2="SourceGraphic" operator="in" />
          </filter>
          <filter id="paper-fiber">
            <feTurbulence type="fractalNoise" baseFrequency="0.012 0.6" numOctaves="2" seed="3" />
            <feColorMatrix values="0 0 0 0 0.55  0 0 0 0 0.42  0 0 0 0 0.22  0 0 0 0.08 0" />
            <feComposite in2="SourceGraphic" operator="in" />
          </filter>
        </defs>
      </svg>
      {/* paper grain */}
      <div style={{ position: 'absolute', inset: 0, filter: 'url(#paper-grain)', background: '#fff', mixBlendMode: 'multiply', pointerEvents: 'none' }} />
      {/* horizontal fibers */}
      <div style={{ position: 'absolute', inset: 0, filter: 'url(#paper-fiber)', background: '#fff', mixBlendMode: 'multiply', opacity: 0.6, pointerEvents: 'none' }} />
      {/* vignette */}
      <div style={{ position: 'absolute', inset: 0, background: `radial-gradient(ellipse at 50% 40%, transparent 50%, ${t.shadow} 110%)`, pointerEvents: 'none' }} />
      <div style={{ position: 'relative', height: '100%' }}>{children}</div>
    </div>
  );
}

// ─── Red square chop ──────────────────────────────────────────────────
function ChopSeal({ text = '僑批', size = 64, rotate = -4, style }) {
  const cells = text.split('').slice(0, 4);
  const grid = cells.length <= 2 ? 1 : 2;
  return (
    <div style={{
      width: size, height: size,
      background: 'linear-gradient(135deg, #9b3a2a 0%, #7a2818 100%)',
      color: '#f5ecd6',
      display: 'grid',
      gridTemplateColumns: `repeat(${grid}, 1fr)`,
      gridTemplateRows: `repeat(${cells.length <= 2 ? 1 : 2}, 1fr)`,
      fontFamily: '"Noto Serif SC", serif',
      fontWeight: 900,
      fontSize: size / (cells.length <= 2 ? 2.2 : 2.6),
      lineHeight: 1,
      placeItems: 'center',
      borderRadius: 2,
      boxShadow: 'inset 0 0 0 2px #5a1810, 0 1px 0 rgba(0,0,0,0.2)',
      transform: `rotate(${rotate}deg)`,
      filter: 'url(#paper-grain)',
      opacity: 0.92,
      ...style,
    }}>
      {cells.map((c, i) => <div key={i}>{c}</div>)}
    </div>
  );
}

// ─── Postage stamp ────────────────────────────────────────────────────
function Stamp({ year, country, w = 56, h = 70, rotate = 3, style }) {
  return (
    <div style={{
      width: w, height: h,
      background: 'repeating-linear-gradient(90deg, transparent 0 4px, #efe4ca 4px 6px), repeating-linear-gradient(0deg, transparent 0 4px, #efe4ca 4px 6px), #c8a45a',
      padding: 5,
      transform: `rotate(${rotate}deg)`,
      boxShadow: '0 2px 6px rgba(70,40,15,0.25)',
      ...style,
    }}>
      <div style={{
        width: '100%', height: '100%',
        background: 'linear-gradient(160deg, #3e6c8a 0%, #2a4a64 100%)',
        color: '#efe4ca',
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'space-between',
        padding: '4px 2px',
        fontFamily: '"EB Garamond", serif',
        fontSize: 9,
      }}>
        <div style={{ fontSize: 7, opacity: 0.85 }}>{country}</div>
        <div style={{
          width: 28, height: 28, borderRadius: '50%',
          background: 'radial-gradient(circle at 35% 30%, #d9b878, #8a5a2a)',
          border: '1px solid #efe4ca',
        }} />
        <div style={{ fontSize: 10, fontWeight: 600 }}>{year}</div>
      </div>
    </div>
  );
}

// ─── Letter scan ──────────────────────────────────────────────────────
// If letter.scans[] is available, renders the real scanned page (with simple
// page-flipper for multi-page). Otherwise falls back to the synthetic
// "looks-like-a-scan" placeholder (vertical text on aged paper) used
// before the real ones were dropped in.
function LetterScan({ letter, scale = 1, page = 0, onPageChange, style }) {
  const w = 540 * scale, h = 720 * scale;
  const scans = letter.scans || [];

  // === Real-scan rendering ===
  if (scans.length > 0) {
    const [internalPage, setInternalPage] = React.useState(page);
    const cur = onPageChange ? page : internalPage;
    const setCur = onPageChange || setInternalPage;
    const total = scans.length;
    // Some envelope scans are landscape-shot but stored on a portrait canvas
    // with the content rotated 90°. Detect and auto-rotate so the writing is upright.
    const [rotate, setRotate] = React.useState(0);
    const onLoad = (e) => {
      const img = e.target;
      // CADAL scans are always 1190x1684. If the actual content is landscape
      // we'd see most ink concentrated in a wider band — too costly to detect.
      // Instead expose a manual rotate toggle (button below).
    };
    return (
      <div style={{
        width: w, height: h,
        position: 'relative',
        background: '#1a130c',
        boxShadow: '0 22px 60px rgba(0,0,0,0.55), 0 2px 8px rgba(40,20,10,0.3)',
        ...style,
      }}>
        <img src={scans[cur]} alt={`${letter.title} · 第${cur+1}页`}
          onLoad={onLoad}
          style={{
            width: '100%', height: '100%', objectFit: 'contain', display: 'block',
            background: '#0d0905',
            transform: `rotate(${rotate}deg)`,
            transition: 'transform .3s',
          }}
          onError={(e) => { e.target.style.opacity = 0.2; }} />
        {/* rotate toggle */}
        <button onClick={() => setRotate((rotate + 90) % 360)}
          title="旋转 / rotate"
          style={{
            position: 'absolute', top: 12 * scale, right: 12 * scale,
            background: 'rgba(0,0,0,0.4)', border: '1px solid rgba(224,201,138,0.4)',
            color: '#e0c98a', width: 32 * scale, height: 32 * scale,
            fontSize: 14 * scale, cursor: 'pointer', borderRadius: '50%',
            fontFamily: '"EB Garamond", serif',
          }}>↻</button>
        {/* side arrows (vertically centered, carousel style) */}
        {total > 1 && (
          <>
            <button onClick={() => setCur((cur - 1 + total) % total)}
              aria-label="上一页"
              style={{
                position: 'absolute', top: '50%', left: 14 * scale, transform: 'translateY(-50%)',
                width: 44 * scale, height: 44 * scale,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                background: 'rgba(0,0,0,0.45)', border: '1px solid rgba(224,201,138,0.4)',
                color: '#e0c98a', fontFamily: '"EB Garamond", serif',
                fontSize: 22 * scale, cursor: 'pointer', borderRadius: '50%',
                transition: 'background 0.2s',
              }}
              onMouseEnter={e => e.target.style.background = 'rgba(0,0,0,0.75)'}
              onMouseLeave={e => e.target.style.background = 'rgba(0,0,0,0.45)'}
            >‹</button>
            <button onClick={() => setCur((cur + 1) % total)}
              aria-label="下一页"
              style={{
                position: 'absolute', top: '50%', right: 14 * scale, transform: 'translateY(-50%)',
                width: 44 * scale, height: 44 * scale,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                background: 'rgba(0,0,0,0.45)', border: '1px solid rgba(224,201,138,0.4)',
                color: '#e0c98a', fontFamily: '"EB Garamond", serif',
                fontSize: 22 * scale, cursor: 'pointer', borderRadius: '50%',
                transition: 'background 0.2s',
              }}
              onMouseEnter={e => e.target.style.background = 'rgba(0,0,0,0.75)'}
              onMouseLeave={e => e.target.style.background = 'rgba(0,0,0,0.45)'}
            >›</button>
            {/* page counter at the bottom */}
            <div style={{
              position: 'absolute', bottom: 12 * scale, left: 0, right: 0,
              display: 'flex', justifyContent: 'center', pointerEvents: 'none',
            }}>
              <span style={{
                background: 'rgba(0,0,0,0.55)', border: '1px solid rgba(224,201,138,0.3)',
                color: '#e0c98a', padding: `${4 * scale}px ${12 * scale}px`,
                fontFamily: '"IBM Plex Mono", monospace', fontSize: 11 * scale, letterSpacing: 2,
              }}>{String(cur + 1).padStart(2, '0')} / {String(total).padStart(2, '0')}</span>
            </div>
          </>
        )}
      </div>
    );
  }

  // === Synthetic placeholder (original code) ===
  if (!letter.bodyZh) {
    return (
      <div style={{ width: w, height: h, display: 'flex', alignItems: 'center', justifyContent: 'center',
        background: '#1a130c', color: '#c8a45a', fontFamily: '"EB Garamond", serif', fontStyle: 'italic', ...style }}>
        no scan available
      </div>
    );
  }
  // Build vertical columns from bodyZh. We split by newline first, then chunk.
  const lines = letter.bodyZh.split('\n');
  // Roughly 14 chars/column at this scale
  const colLen = 14;
  const cols = [];
  lines.forEach((ln) => {
    for (let i = 0; i < ln.length; i += colLen) {
      cols.push(ln.slice(i, i + colLen));
    }
  });
  return (
    <div style={{
      width: w, height: h,
      position: 'relative',
      background: '#f0e3c2',
      boxShadow: '0 22px 60px rgba(40,20,10,0.4), 0 2px 8px rgba(40,20,10,0.2), inset 0 0 80px rgba(140,90,30,0.18)',
      overflow: 'hidden',
      ...style,
    }}>
      {/* paper grain overlay */}
      <div style={{ position: 'absolute', inset: 0, filter: 'url(#paper-grain)', background: '#fff', mixBlendMode: 'multiply', pointerEvents: 'none' }} />
      {/* red vertical ruling lines like real letter paper */}
      <svg viewBox={`0 0 ${w} ${h}`} style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
        {Array.from({ length: 14 }).map((_, i) => {
          const x = 36 * scale + i * ((w - 80 * scale) / 13);
          return <line key={i} x1={x} y1={50 * scale} x2={x} y2={h - 80 * scale} stroke="#a14334" strokeWidth="0.6" opacity="0.35" />;
        })}
        {/* top header band */}
        <line x1={36 * scale} y1={40 * scale} x2={w - 36 * scale} y2={40 * scale} stroke="#a14334" strokeWidth="1.2" opacity="0.55" />
        <line x1={36 * scale} y1={h - 76 * scale} x2={w - 36 * scale} y2={h - 76 * scale} stroke="#a14334" strokeWidth="1.2" opacity="0.55" />
      </svg>
      {/* top header: 緘 + sender */}
      <div style={{
        position: 'absolute', top: 14 * scale, left: 0, right: 0,
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        padding: `0 ${48 * scale}px`,
        fontFamily: '"Noto Serif SC", serif',
        color: '#5a2818',
        fontSize: 12 * scale,
        letterSpacing: 4,
        opacity: 0.75,
      }}>
        <div>僑　批　局</div>
        <div>第&nbsp;&nbsp;{Math.floor(Math.random() * 200) + 100}&nbsp;&nbsp;號</div>
      </div>
      {/* vertical text columns, right-to-left */}
      <div style={{
        position: 'absolute',
        top: 56 * scale, bottom: 84 * scale,
        left: 36 * scale, right: 36 * scale,
        display: 'flex',
        flexDirection: 'row-reverse',
        gap: 0,
        fontFamily: '"LXGW WenKai", "Noto Serif SC", serif',
        fontSize: 19 * scale,
        lineHeight: 1.55,
        color: '#23170d',
        writingMode: 'vertical-rl',
        textOrientation: 'upright',
      }}>
        {cols.slice(0, 13).map((col, i) => (
          <div key={i} style={{
            flex: '0 0 auto',
            width: (w - 80 * scale) / 13,
            textAlign: 'start',
            paddingTop: i === 0 ? 0 : 4 * scale,
          }}>
            {col}
          </div>
        ))}
      </div>
      {/* bottom: date + chop */}
      <div style={{
        position: 'absolute', bottom: 18 * scale, left: 48 * scale, right: 48 * scale,
        display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end',
      }}>
        <div style={{ fontFamily: '"Noto Serif SC", serif', fontSize: 13 * scale, color: '#4a2a18', opacity: 0.85 }}>
          {letter.postmark || ''}
        </div>
        <ChopSeal text={(letter.sender.name || '').slice(0, 2) + '印'} size={44 * scale} rotate={-6} />
      </div>
      {/* circular postmark */}
      <div style={{
        position: 'absolute', top: 80 * scale, right: 60 * scale,
        width: 70 * scale, height: 70 * scale, borderRadius: '50%',
        border: `2px solid #6a3a26`, color: '#6a3a26',
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        fontFamily: '"EB Garamond", serif', fontSize: 10 * scale,
        opacity: 0.55,
        transform: 'rotate(-8deg)',
        mixBlendMode: 'multiply',
        background: 'rgba(106,58,38,0.04)',
      }}>
        <div style={{ fontSize: 8 * scale, letterSpacing: 1 }}>{(letter.from.cityEn || '').toUpperCase()}</div>
        <div style={{ fontSize: 13 * scale, fontWeight: 700, marginTop: 2 * scale }}>{letter.year || ''}</div>
        <div style={{ fontSize: 8 * scale, letterSpacing: 1 }}>{letter.month ? String(letter.month).padStart(2, '0') + ' · ' : ''}{letter.from.country || ''}</div>
      </div>
    </div>
  );
}

// ─── Mini map ─────────────────────────────────────────────────────────
function MiniMap({ letter, w = 320, h = 200, all = [], onPick }) {
  const allLetters = all.length ? all : (letter ? [letter] : []);
  return (
    <svg viewBox={`0 0 ${w} ${h}`} style={{ width: '100%', height: '100%', display: 'block' }}>
      <defs>
        <pattern id="minimap-grid" width="20" height="20" patternUnits="userSpaceOnUse">
          <path d="M20 0 L0 0 0 20" fill="none" stroke="#9b3a2a" strokeWidth="0.3" opacity="0.15" />
        </pattern>
      </defs>
      <rect width={w} height={h} fill="url(#minimap-grid)" />
      {/* coastlines */}
      {window.COAST_PATHS.map((d, i) => {
        const pts = d.split(' ').filter(p => p.match(/^[\d.\-]+$/));
        let path = '';
        for (let j = 0; j < pts.length; j += 2) {
          const lng = parseFloat(pts[j]);
          const lat = parseFloat(pts[j + 1]);
          if (isNaN(lng) || isNaN(lat)) continue;
          const p = window.projectLngLat(lng, lat, w, h);
          path += (j === 0 ? 'M' : 'L') + ' ' + p.x.toFixed(1) + ' ' + p.y.toFixed(1) + ' ';
        }
        return <path key={i} d={path} fill="none" stroke="#6a3a26" strokeWidth="0.8" opacity="0.5" />;
      })}
      {allLetters.map((l, i) => {
        const a = window.projectLngLat(l.from.lng, l.from.lat, w, h);
        const b = window.projectLngLat(l.to.lng, l.to.lat, w, h);
        const mx = (a.x + b.x) / 2;
        const my = (a.y + b.y) / 2 - Math.abs(b.x - a.x) * 0.35;
        const active = letter && l.id === letter.id;
        return (
          <g key={l.id} onClick={onPick ? () => onPick(l) : undefined} style={{ cursor: onPick ? 'pointer' : 'default' }}>
            <path d={`M ${a.x} ${a.y} Q ${mx} ${my} ${b.x} ${b.y}`}
              fill="none"
              stroke={active ? '#9b3a2a' : '#6a3a26'}
              strokeWidth={active ? 1.6 : 0.7}
              opacity={active ? 0.95 : 0.35}
              strokeDasharray={active ? 'none' : '2 3'} />
            <circle cx={a.x} cy={a.y} r={active ? 3 : 1.8} fill="#9b3a2a" opacity={active ? 1 : 0.6} />
            <circle cx={b.x} cy={b.y} r={active ? 3 : 1.8} fill="#2a1f17" opacity={active ? 1 : 0.6} />
          </g>
        );
      })}
    </svg>
  );
}

Object.assign(window, { PaperBg, ChopSeal, Stamp, LetterScan, MiniMap });
