// metro-maps.jsx — 日本地鐵圖（Leaflet 互動地圖）

// ── MetroMapViewer ────────────────────────────────────────────
// Uses refs + direct DOM manipulation for 60fps smooth zoom/pan.
// Image is rendered at natural pixel dimensions; initial scale fits screen.
// Zooming in approaches 1:1 = full resolution (no blurriness).

function MetroMapViewer({ map, onClose }) {
  const imgRef       = React.useRef(null);
  const containerRef = React.useRef(null);
  const st = React.useRef({
    scale: 1, tx: 0, ty: 0,
    lastDist: null, startScale: 1,
    startTouchX: 0, startTouchY: 0, startTx: 0, startTy: 0,
    pinchCx: 0, pinchCy: 0,
    naturalW: 0, naturalH: 0,
  });

  // transformOrigin: '0 0' — image top-left is the anchor.
  // State (tx, ty) = image top-left position in container space.
  // Point at image pixel (px, py) → container pos: (tx + px*scale, ty + py*scale)
  // Pinch-to-point math:
  //   After scale change s1→s2, container point (cx,cy) stays fixed:
  //   tx_new = cx - (cx - tx) * (s2/s1)
  //   ty_new = cy - (cy - ty) * (s2/s1)

  function applyTransform() {
    if (!imgRef.current) return;
    const { scale, tx, ty } = st.current;
    imgRef.current.style.transform = `translate(${tx}px,${ty}px) scale(${scale})`;
  }

  function fitScale() {
    const c = containerRef.current;
    const s = st.current;
    if (!c || !s.naturalW) return 1;
    return Math.min(c.clientWidth / s.naturalW, c.clientHeight / s.naturalH);
  }

  function resetToFit() {
    const c = containerRef.current;
    const s = st.current;
    if (!c || !s.naturalW) return;
    const fs = fitScale();
    s.scale = fs;
    // Center the image in the container
    s.tx = (c.clientWidth  - s.naturalW * fs) / 2;
    s.ty = (c.clientHeight - s.naturalH * fs) / 2;
    applyTransform();
  }

  function onImgLoad(e) {
    const s = st.current;
    s.naturalW = e.target.naturalWidth;
    s.naturalH = e.target.naturalHeight;
    e.target.style.width  = s.naturalW + 'px';
    e.target.style.height = s.naturalH + 'px';
    resetToFit();
  }

  function getTouchDist(e) {
    if (e.touches.length < 2) return null;
    return Math.hypot(e.touches[0].clientX-e.touches[1].clientX, e.touches[0].clientY-e.touches[1].clientY);
  }

  function onTS(e) {
    const s = st.current;
    if (e.touches.length >= 2) {
      s.lastDist = getTouchDist(e);
    } else {
      s.lastDist = null;
      s.startTouchX = e.touches[0].clientX;
      s.startTouchY = e.touches[0].clientY;
      s.startTx = s.tx; s.startTy = s.ty;
    }
  }

  function onTM(e) {
    e.preventDefault();
    const s = st.current;
    if (e.touches.length === 2 && s.lastDist) {
      const newDist = getTouchDist(e);
      const scaleRatio = newDist / s.lastDist;
      const fs = fitScale();
      const newScale = Math.min(4.0, Math.max(fs * 0.9, s.scale * scaleRatio));
      const actualRatio = newScale / s.scale;

      // Current pinch midpoint in container space (recalculated each frame)
      const rect = containerRef.current.getBoundingClientRect();
      const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2 - rect.left;
      const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2 - rect.top;

      // Zoom toward current midpoint (incremental)
      s.tx = midX - (midX - s.tx) * actualRatio;
      s.ty = midY - (midY - s.ty) * actualRatio;
      s.scale = newScale;
      s.lastDist = newDist;   // ← update each frame (incremental, not absolute)
      applyTransform();
    } else if (e.touches.length === 1 && !s.lastDist) {
      s.tx = s.startTx + (e.touches[0].clientX - s.startTouchX);
      s.ty = s.startTy + (e.touches[0].clientY - s.startTouchY);
      applyTransform();
    }
  }

  function onTE(e) {
    if (e.touches.length < 2) {
      st.current.lastDist = null;
      // Update pan start pos if still one finger on screen
      if (e.touches.length === 1) {
        const s = st.current;
        s.startTouchX = e.touches[0].clientX;
        s.startTouchY = e.touches[0].clientY;
        s.startTx = s.tx; s.startTy = s.ty;
      }
    }
  }

  // Attach passive:false listener so preventDefault works on mobile
  React.useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const opts = { passive: false };
    el.addEventListener('touchmove', onTM, opts);
    return () => el.removeEventListener('touchmove', onTM, opts);
  }, []);

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9800, background:'#111', display:'flex', flexDirection:'column' }}>
      {/* Header */}
      <div style={{ display:'flex', alignItems:'center', padding:'calc(env(safe-area-inset-top,0px)+10px) 16px 10px', background:'rgba(0,0,0,0.75)', backdropFilter:'blur(12px)', flexShrink:0, gap:10 }}>
        <span style={{ fontSize:20 }}>{map.emoji}</span>
        <span style={{ fontFamily:tabiSerif, fontSize:18, fontWeight:700, color:'#fff', flex:1 }}>{map.city_name}</span>
        <button onClick={resetToFit} style={{ padding:'6px 12px', borderRadius:8, border:'1px solid rgba(255,255,255,0.3)', background:'rgba(255,255,255,0.1)', color:'#fff', fontSize:12, cursor:'pointer' }}>重設</button>
        <button onClick={onClose} style={{ width:34, height:34, borderRadius:17, border:'1px solid rgba(255,255,255,0.3)', background:'rgba(255,255,255,0.1)', color:'#fff', fontSize:18, cursor:'pointer', display:'grid', placeItems:'center' }}>×</button>
      </div>

      {/* Map viewer */}
      <div ref={containerRef}
        style={{ flex:1, overflow:'hidden', position:'relative', touchAction:'none' }}
        onTouchStart={onTS}
        onTouchEnd={onTE}
      >
        <img ref={imgRef} src={map.image_url} alt={map.city_name}
          onLoad={onImgLoad}
          draggable={false}
          style={{
            transformOrigin: '0 0',
            userSelect:'none', WebkitUserSelect:'none',
            imageRendering: 'auto',
          }}
        />
      </div>

      <div style={{ padding:'8px 0 calc(env(safe-area-inset-bottom,0px)+8px)', textAlign:'center', color:'rgba(255,255,255,0.4)', fontSize:11, flexShrink:0 }}>
        雙指縮放・單指拖動・點「重設」回復
      </div>
    </div>,
    document.body
  );
}

// ── MetroMapsScreen ───────────────────────────────────────────

function MetroMapsScreen({ go }) {
  const [maps, setMaps]       = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [viewing, setViewing] = React.useState(null);
  const [search, setSearch]   = React.useState('');
  const [leafletReady, setLeafletReady] = React.useState(false);
  const mapDivRef  = React.useRef(null);
  const leafletMap = React.useRef(null);
  const markers    = React.useRef([]);

  // Load maps data
  React.useEffect(() => {
    fetch('/api/metro-maps', { headers: { Authorization: `Bearer ${window.tabiToken()}` } })
      .then(r => r.ok ? r.json() : [])
      .then(d => { setMaps(Array.isArray(d) ? d : []); setLoading(false); })
      .catch(() => setLoading(false));
  }, []);

  // Load Leaflet (hosted locally)
  React.useEffect(() => {
    if (window.L) { setLeafletReady(true); return; }
    const link = document.createElement('link');
    link.rel = 'stylesheet'; link.href = '/vendor/leaflet.css';
    document.head.appendChild(link);
    const script = document.createElement('script');
    script.src = '/vendor/leaflet.min.js';
    script.onload = () => setLeafletReady(true);
    document.body.appendChild(script);
  }, []);

  // Init / update map when ready + maps loaded
  React.useEffect(() => {
    if (!leafletReady || !mapDivRef.current || maps.length === 0) return;
    const L = window.L;

    if (!leafletMap.current) {
      const m = L.map(mapDivRef.current, {
        center: [37.5, 137.5], zoom: 5,
        zoomControl: false,        // ← remove +/- from top-left
        attributionControl: false,
      });
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 18,
      }).addTo(m);
      // No zoom control on mobile — pinch to zoom is natural
      leafletMap.current = m;
    }

    // Clear old markers
    markers.current.forEach(mk => mk.remove());
    markers.current = [];

    // Add city markers — small clean pin style
    const filtered = search ? maps.filter(m => m.city_name.includes(search) || m.city_key.includes(search.toLowerCase())) : maps;

    filtered.forEach(city => {
      if (!city.lat || !city.lng) return;
      const icon = L.divIcon({
        className: '',
        html: `<div style="
          width:36px; height:36px; border-radius:50%;
          background:#fff; border:2.5px solid #B94A35;
          display:flex; align-items:center; justify-content:center;
          font-size:18px; box-shadow:0 2px 8px rgba(0,0,0,0.25);
          cursor:pointer;
        ">${city.emoji}</div>`,
        iconSize: [36, 36],
        iconAnchor: [18, 18],
        tooltipAnchor: [18, -20],
      });
      const mk = L.marker([city.lat, city.lng], { icon })
        .addTo(leafletMap.current)
        .bindTooltip(city.city_name, {
          permanent: false, direction: 'top', offset: [0, -4],
          className: 'metro-tooltip',
        })
        .on('click', () => setViewing(city));
      markers.current.push(mk);
    });

    // If search match, fly to first result
    if (search && filtered.length > 0 && filtered[0].lat) {
      leafletMap.current.flyTo([filtered[0].lat, filtered[0].lng], 11, { duration: 0.8 });
    }
  }, [leafletReady, maps, search]);

  // Cleanup on unmount
  React.useEffect(() => () => {
    if (leafletMap.current) {
      leafletMap.current.remove();
      leafletMap.current = null;
    }
  }, []);

  return (
    <div style={{
      position:'absolute', inset:0,   /* fills tabi-screen exactly, no scroll */
      background:TABI_COLORS.bg, display:'flex', flexDirection:'column', overflow:'hidden',
    }}>

      {/* ① 頁頭：返回 + 標題 */}
      <div style={{
        padding:'calc(env(safe-area-inset-top,0px)+12px) 16px 10px',
        background:'rgba(247,240,229,0.92)', backdropFilter:'blur(18px)',
        flexShrink:0, zIndex:10,
      }}>
        <div style={{ display:'flex', alignItems:'center', gap:10 }}>
          <button onClick={() => go('home')} style={{
            width:40, height:40, borderRadius:20, border:`1px solid ${TABI_COLORS.line}`,
            background:'rgba(255,250,242,0.76)', color:TABI_COLORS.ink,
            fontSize:22, cursor:'pointer', display:'grid', placeItems:'center',
          }}>‹</button>
          <div style={{ flex:1 }}>
            <div style={{ fontSize:10.5, fontWeight:600, letterSpacing:'0.14em', textTransform:'uppercase', color:TABI_COLORS.red }}>METRO</div>
            <div style={{ fontFamily:tabiSerif, fontSize:20, fontWeight:700, color:TABI_COLORS.ink, lineHeight:1.1 }}>日本地鐵圖</div>
          </div>
        </div>
      </div>

      {/* ② 搜尋欄 */}
      <div style={{
        padding:'8px 16px', background:'rgba(247,240,229,0.92)', backdropFilter:'blur(18px)',
        flexShrink:0, zIndex:10, borderBottom:`1px solid ${TABI_COLORS.line}`,
      }}>
        <div style={{ position:'relative' }}>
          <span style={{ position:'absolute', left:10, top:'50%', transform:'translateY(-50%)', fontSize:14, pointerEvents:'none' }}>🔍</span>
          <input value={search} onChange={e => setSearch(e.target.value)}
            placeholder="搜尋城市，例：東京、大阪"
            style={{
              width:'100%', padding:'9px 12px 9px 34px', borderRadius:10, border:`1px solid ${TABI_COLORS.line}`,
              background:TABI_COLORS.bg, fontSize:16, color:TABI_COLORS.ink,
              outline:'none', fontFamily:'inherit', boxSizing:'border-box',
            }} />
          {search && (
            <button onClick={() => setSearch('')} style={{
              position:'absolute', right:8, top:'50%', transform:'translateY(-50%)',
              background:'transparent', border:'none', cursor:'pointer', fontSize:15, color:TABI_COLORS.ink3,
            }}>×</button>
          )}
        </div>
      </div>

      {/* ③ 城市快捷列 */}
      {!loading && maps.length > 0 && (
        <div style={{
          display:'flex', gap:6, overflowX:'auto', scrollbarWidth:'none',
          padding:'8px 16px', flexShrink:0, zIndex:10,
          background:'rgba(247,240,229,0.92)', backdropFilter:'blur(18px)',
          borderBottom:`1px solid ${TABI_COLORS.line}`,
        }}>
          {maps.map(m => (
            <button key={m.city_key}
              onClick={() => {
                setViewing(m);
                if (leafletMap.current && m.lat) leafletMap.current.flyTo([m.lat, m.lng], 12, { duration: 0.8 });
              }}
              style={{
                flexShrink:0, height:34, padding:'0 11px', borderRadius:17,
                fontSize:12.5, fontWeight:700, cursor:'pointer',
                border:`1px solid ${TABI_COLORS.line}`,
                background:TABI_COLORS.card, color:TABI_COLORS.ink,
                display:'flex', alignItems:'center', gap:4, whiteSpace:'nowrap',
              }}>
              <span style={{ fontSize:14 }}>{m.emoji}</span><span>{m.city_name}</span>
            </button>
          ))}
        </div>
      )}

      {/* Leaflet tooltip CSS */}
      <style>{`.metro-tooltip{background:#18140F;color:#fff;border:none;border-radius:8px;padding:4px 10px;font-size:13px;font-weight:700;box-shadow:0 4px 12px rgba(0,0,0,0.3);font-family:inherit;}.metro-tooltip::before{border-top-color:#18140F !important;}`}</style>

      {/* ④ 地圖（fills remaining height） */}
      <div style={{ flex:1, position:'relative', minHeight:0 }}>
        {loading ? (
          <div style={{ position:'absolute', inset:0, display:'grid', placeItems:'center', color:TABI_COLORS.ink3 }}>載入中…</div>
        ) : maps.length === 0 ? (
          <div style={{ position:'absolute', inset:0, display:'grid', placeItems:'center', textAlign:'center', color:TABI_COLORS.ink3 }}>
            <div><div style={{ fontSize:44, marginBottom:10 }}>🗺️</div>尚無地鐵圖</div>
          </div>
        ) : (
          <div ref={mapDivRef} style={{ position:'absolute', inset:0 }} />
        )}
      </div>

      {viewing && <MetroMapViewer map={viewing} onClose={() => setViewing(null)} />}
    </div>
  );
}
