const { useState, useEffect, useRef } = React;

const APP_STORE_URLS = {
  en: 'https://apps.apple.com/us/app/ski-slopes-maps-neu/id6476763152',
  es: 'https://apps.apple.com/es/app/mapas-de-esqu%C3%AD-3d-neu/id6476763152',
  ca: 'https://apps.apple.com/es/app/mapes-desqu%C3%AD-3d-neu/id6476763152?l=ca',
};
const APP_QR_CODE_URL = '/src/assets/NEU-QR-Code.png';
function appStoreUrl(lang) { return APP_STORE_URLS[lang] || APP_STORE_URLS.en; }

function ResortHeader({ resort }) {
  const { lang, t } = useI18n();
  const [logoOk, setLogoOk] = useState(true);
  const hotelSearchUrl = resort.hotelsUrl || `https://www.google.com/search?q=${encodeURIComponent(`hotels in ${resort.name}`)}`;
  useEffect(() => { setLogoOk(true); }, [resort.id]);
  return (
    <div className="resort-header">
      <div className="resort-header__left">
        {resort.logo && logoOk ? (
          <div className="resort-header__logo">
            <img src={resort.logo} alt="" onError={() => setLogoOk(false)} />
          </div>
        ) : (
          <div className="resort-header__logo resort-header__logo--fallback">
            <Icon name="mountain" size={24} color="#007AFF" stroke={1.4}/>
          </div>
        )}
        <div>
          <div className="neu-h1 resort-header__title">{resort.name}</div>
          <div className="neu-body resort-header__subtitle">{translatePlace(resort.region, lang)}, {translatePlace(resort.country, lang)}</div>
        </div>
      </div>
      <div className="resort-header__actions">
        {resort.website && (
          <a className="neu-btn-primary neu-btn-primary--light" href={resort.website} target="_blank" rel="noopener noreferrer">
            {t('resort.visitWebsite', { name: resort.name })}
          </a>
        )}
        <a className="neu-btn-primary" href={hotelSearchUrl} target="_blank" rel="noopener noreferrer">
          {t('resort.findHotels', { name: resort.name })}
        </a>
      </div>
    </div>
  );
}

function Resort3DViewer({ resort }) {
  const { lang, t } = useI18n();
  const modelBaseUrl = (window.NEU_CONFIG?.modelBaseUrl || '/src/3D Assets').replace(/\/$/, '');
  const glbUrl = `${modelBaseUrl}/glb/${resort.id}.glb`;
  const usdzUrl = `${modelBaseUrl}/${resort.id}.usdz`;
  const sectionRef = useRef(null);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [showProGate, setShowProGate] = useState(false);
  useEffect(() => {
    const onFsChange = () => setIsFullscreen(document.fullscreenElement === sectionRef.current);
    document.addEventListener('fullscreenchange', onFsChange);
    onFsChange();
    return () => document.removeEventListener('fullscreenchange', onFsChange);
  }, []);
  useEffect(() => {
    setShowProGate(false);
    if (!resort.isPro) return undefined;
    const timer = window.setTimeout(() => setShowProGate(true), 5000);
    return () => window.clearTimeout(timer);
  }, [resort.id, resort.isPro]);
  const fullscreenLabel = isFullscreen ? t('widgets.viewer.exitFullscreen') : t('widgets.viewer.fullscreen');
  const handleFullscreen = () => {
    const el = sectionRef.current;
    if (!el) return;
    if (document.fullscreenElement) document.exitFullscreen();
    else el.requestFullscreen?.();
  };
  return (
    <section ref={sectionRef} className="neu-card resort-3d-viewer">
      <div className="resort-3d-viewer__stage">
        <model-viewer
          key={resort.id}
          src={glbUrl}
          ios-src={usdzUrl}
          alt={t('widgets.viewer.alt', { name: resort.name })}
          camera-controls
          auto-rotate
          ar
          ar-modes="quick-look webxr scene-viewer"
          shadow-intensity="1"
          exposure="1"
          environment-image="neutral"
          style={{width:'100%', height:'100%', background:'#fff'}}
        />
      </div>
      {showProGate && (
        <div className="resort-3d-viewer__gate">
          <a className="resort-3d-viewer__ar-banner" href={appStoreUrl(lang)} target="_blank" rel="noopener noreferrer">
            <img className="resort-3d-viewer__qr" src={APP_QR_CODE_URL} alt="QR code to download NEU Ski Maps" />
            <span>{t('widgets.viewer.arBanner')}</span>
          </a>
        </div>
      )}
      <div className="resort-3d-viewer__copy">© NEU 2026</div>
      <button className="resort-3d-viewer__full" onClick={handleFullscreen} aria-label={fullscreenLabel}>
        <svg className="fullscreen-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0A0A0A" strokeWidth="2.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          {isFullscreen ? (
            <>
              <polyline points="9 3 9 9 3 9"/><line x1="9" y1="9" x2="3" y2="3"/>
              <polyline points="15 21 15 15 21 15"/><line x1="15" y1="15" x2="21" y2="21"/>
            </>
          ) : (
            <>
              <polyline points="3 9 3 3 9 3"/><line x1="3" y1="3" x2="9" y2="9"/>
              <polyline points="21 15 21 21 15 21"/><line x1="21" y1="21" x2="15" y2="15"/>
            </>
          )}
        </svg>
        <span>{fullscreenLabel}</span>
      </button>
    </section>
  );
}

function SnowfallMap({ resort }) {
  const t = useT();
  const { temperatureUnit } = window.usePrefs();
  const metricTemp = temperatureUnit === 'f' ? '°F' : '°C';
  const url =
    `https://embed.windy.com/embed.html` +
    `?overlay=snowAccu&level=surface&marker=true&detail=true` +
    `&metricTemp=${encodeURIComponent(metricTemp)}` +
    `&lat=${resort.latitude}&lon=${resort.longitude}` +
    `&detailLat=${resort.latitude}&detailLon=${resort.longitude}` +
    `&zoom=8`;
  return (
    <section className="neu-card snowfall-map">
      <div className="neu-h4">{t('widgets.snowfall.title')}</div>
      <iframe
        key={resort.id}
        src={url}
        loading="lazy"
        allow="fullscreen"
        className="snowfall-map__frame"
        title={t('widgets.snowfall.iframeTitle', { name: resort.name })}
      />
    </section>
  );
}

function WebcamsWidget({ resort }) {
  const t = useT();
  const cams = Array.isArray(resort.webcams) ? resort.webcams : [];
  const [idx, setIdx] = useState(0);
  useEffect(() => { setIdx(0); }, [resort.id]);

  if (cams.length === 0) {
    return (
      <section className="neu-card webcams webcams--empty">
        <div className="neu-h4">{t('widgets.webcams.title')}</div>
        <div className="webcams__empty">{t('states.noWebcams')}</div>
      </section>
    );
  }

  const total = cams.length;
  const safeIdx = Math.min(idx, total - 1);
  const activeId = cams[safeIdx];
  const prev = () => setIdx((safeIdx - 1 + total) % total);
  const next = () => setIdx((safeIdx + 1) % total);

  return (
    <section className="neu-card webcams">
      <div className="neu-h4">{t('widgets.webcams.title')}</div>
      <div className="webcams__stage">
        <iframe
          key={`${resort.id}-${activeId}`}
          src={`https://webcams.windy.com/webcams/public/embed/player/${activeId}/day`}
          loading="lazy"
          allow="fullscreen"
          className="webcams__cam"
          title={`Webcam ${activeId}`}
        />
        {total > 1 && (
          <>
            <button
              type="button"
              className="webcams__nav webcams__nav--prev"
              onClick={prev}
              aria-label={t('widgets.webcams.prev')}
            >
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
            </button>
            <button
              type="button"
              className="webcams__nav webcams__nav--next"
              onClick={next}
              aria-label={t('widgets.webcams.next')}
            >
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
            </button>
          </>
        )}
      </div>
      {total > 1 && (
        <div className="webcams__dots" role="tablist" aria-label={t('widgets.webcams.selectList')}>
          {cams.map((id, i) => (
            <button
              key={id}
              type="button"
              role="tab"
              aria-selected={i === safeIdx}
              aria-label={t('widgets.webcams.dotAria', { i: i + 1, total })}
              className={`webcams__dot ${i === safeIdx ? 'webcams__dot--active' : ''}`}
              onClick={() => setIdx(i)}
            />
          ))}
        </div>
      )}
    </section>
  );
}

function PhotoCarousel({ src }) {
  return (
    <div className="photo-carousel">
      <img key={src} src={src} alt="" />
    </div>
  );
}

function useCountUp(target, duration = 700, integer = true) {
  const [value, setValue] = useState(target);
  const fromRef = useRef(target);
  const startRef = useRef(0);
  const rafRef = useRef(0);

  useEffect(() => {
    cancelAnimationFrame(rafRef.current);
    const from = fromRef.current;
    const to = target;
    if (from === to) return;
    startRef.current = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - startRef.current) / duration);
      const eased = 1 - Math.pow(1 - t, 3);
      const raw = from + (to - from) * eased;
      setValue(integer ? Math.round(raw) : raw);
      if (t < 1) rafRef.current = requestAnimationFrame(tick);
      else fromRef.current = to;
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [target, duration, integer]);

  return value;
}

/* Tween an arbitrary array of segment values (length-stable per render).
 * The `signature` arg is a stable string (e.g. resort.id) that triggers a re-tween
 * when the dataset identity changes, even if the length stays the same. */
function useTweenedValues(values, signature, duration = 700) {
  const [out, setOut] = useState(values);
  const fromRef = useRef(values);
  const rafRef = useRef(0);

  useEffect(() => {
    cancelAnimationFrame(rafRef.current);
    const target = values.slice();
    const from = fromRef.current.length === target.length
      ? fromRef.current.slice()
      : target.map(() => 0);
    if (from.every((v, i) => v === target[i])) {
      fromRef.current = target;
      setOut(target);
      return;
    }
    const start = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = 1 - Math.pow(1 - t, 3);
      const v = target.map((to, i) => from[i] + (to - from[i]) * eased);
      setOut(v);
      if (t < 1) rafRef.current = requestAnimationFrame(tick);
      else fromRef.current = target;
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [signature, values.length, duration]);

  return out;
}

/* Slope-count chart palette (4-segment EU/RoW mode). */
const DIFFICULTY_COLORS = {
  beg:   '#34C759',
  inter: '#007AFF',
  adv:   '#FF3B30',
  exp:   '#000000',
};

/* Km chart palette (3-segment US/Canada mode), per iOS app spec. */
const KM_DIFFICULTY_COLORS = {
  easy:      '#007AFF', // blue
  inter:     '#FF3B30', // red
  difficult: '#000000', // black
};

function roundedSegmentPath(cx, cy, Ri, Ro, t1, t2, cr) {
  const sweep = t2 - t1;
  const maxR = Math.min(cr, (Ro - Ri) / 2, (sweep * Ri) / 4, (sweep * Ro) / 4);
  const r = Math.max(0.01, maxR);
  const dθo = r / Ro;
  const dθi = r / Ri;
  const pt = (rr, th) => [cx + rr * Math.cos(th), cy + rr * Math.sin(th)];

  const A1 = pt(Ro, t1 + dθo);
  const A2 = pt(Ro, t2 - dθo);
  const C1 = pt(Ro - r, t2);
  const C2 = pt(Ri + r, t2);
  const B1 = pt(Ri, t2 - dθi);
  const B2 = pt(Ri, t1 + dθi);
  const C3 = pt(Ri + r, t1);
  const C4 = pt(Ro - r, t1);

  const outerLarge = (sweep - 2 * dθo) > Math.PI ? 1 : 0;
  const innerLarge = (sweep - 2 * dθi) > Math.PI ? 1 : 0;

  return [
    `M ${A1[0]} ${A1[1]}`,
    `A ${Ro} ${Ro} 0 ${outerLarge} 1 ${A2[0]} ${A2[1]}`,
    `A ${r} ${r} 0 0 1 ${C1[0]} ${C1[1]}`,
    `L ${C2[0]} ${C2[1]}`,
    `A ${r} ${r} 0 0 1 ${B1[0]} ${B1[1]}`,
    `A ${Ri} ${Ri} 0 ${innerLarge} 0 ${B2[0]} ${B2[1]}`,
    `A ${r} ${r} 0 0 1 ${C3[0]} ${C3[1]}`,
    `L ${C4[0]} ${C4[1]}`,
    `A ${r} ${r} 0 0 1 ${A1[0]} ${A1[1]}`,
    'Z',
  ].join(' ');
}

/* Generic donut renderer.
 *  segments: [{ key, value, color }, ...]
 *  centerValue / centerLabel: text shown in the donut hole.
 *  centerInteger: round the count-up animation to integers (true for slope counts, false for km decimals).
 *  signature: stable string (resort id + mode) so tween restarts cleanly when the resort changes. */
function StatsDonut({ segments, centerValue, centerLabel, centerInteger = true, signature }) {
  const tweenedValues = useTweenedValues(segments.map(s => s.value), signature);
  const tweenedSegs = segments.map((s, i) => ({ ...s, value: tweenedValues[i] }));
  const total = tweenedSegs.reduce((acc, s) => acc + s.value, 0);
  const visible = total > 0 ? tweenedSegs.filter(s => (s.value / total) * 100 > 0.3) : [];
  const gapPct = visible.length > 1 ? 1.6 : 0;
  const R = 14.5;
  const SW = 4.8;
  const Ri = R - SW / 2;
  const Ro = R + SW / 2;
  const cornerR = 0.5;
  const cx = 18, cy = 18;
  const TAU = 2 * Math.PI;
  let cursor = 0;
  const displayCenter = useCountUp(centerValue, 700, centerInteger);
  return (
    <div className="stats-donut">
      <svg viewBox="0 0 36 36">
        {visible.length === 1 ? (
          <circle cx={cx} cy={cy} r={R} fill="transparent"
                  stroke={visible[0].color} strokeWidth={SW}
                  className="stats-donut__seg"/>
        ) : visible.map((s) => {
          const pct = (s.value / total) * 100;
          const effectiveGap = Math.min(gapPct, pct * 0.5);
          const t1 = ((cursor + effectiveGap / 2) / 100) * TAU - Math.PI / 2;
          const t2 = ((cursor + pct - effectiveGap / 2) / 100) * TAU - Math.PI / 2;
          cursor += pct;
          return (
            <path key={s.key} d={roundedSegmentPath(cx, cy, Ri, Ro, t1, t2, cornerR)}
                  fill={s.color} className="stats-donut__seg"/>
          );
        })}
      </svg>
      <div className="stats-donut__center">
        <div className="neu-display">{centerInteger ? displayCenter : displayCenter.toFixed(displayCenter >= 100 ? 0 : 1)}</div>
        <div className="stats-donut__center-lbl">{centerLabel}</div>
      </div>
    </div>
  );
}

function LegendValue({ n, c, l, integer = true }) {
  const v = useCountUp(n, 700, integer);
  const display = integer ? v : (v >= 100 ? v.toFixed(0) : v.toFixed(1));
  // Decimal km values ("25.5") need smaller text to fit the square with breathing room.
  const sqClass = `stats-legend__sq${integer ? '' : ' stats-legend__sq--sm'}`;
  return (
    <div className="stats-legend__row">
      <span className={sqClass} style={{background: c}}>{display}</span>
      <span className="neu-body">{l}</span>
    </div>
  );
}

function ResortStatistics({ resort }) {
  const t = useT();
  const { distanceUnit } = window.usePrefs();
  const dash = t('units.dash');
  const useKm = !!resort.difficultyKm;

  // Build the donut/legend segments + center text. Two modes:
  //   Km (US/Canada): 3 segments — Easy / Intermediate / Difficult — center = totalSlopesKm + "km".
  //   Counts (everything else): 4 segments — Beginner / Intermediate / Advanced / Expert — center = totalSlopesNumber + "Slopes".
  const segments = useKm
    ? [
        { key: 'easy',      value: window.kmToDisplay(resort.difficultyKm.easy, distanceUnit),  color: KM_DIFFICULTY_COLORS.easy,      label: t('widgets.stats.difficulty.easy') },
        { key: 'inter',     value: window.kmToDisplay(resort.difficultyKm.inter, distanceUnit), color: KM_DIFFICULTY_COLORS.inter,     label: t('widgets.stats.difficulty.intermediate') },
        { key: 'difficult', value: window.kmToDisplay(resort.difficultyKm.hard, distanceUnit),  color: KM_DIFFICULTY_COLORS.difficult, label: t('widgets.stats.difficulty.difficult') },
      ]
    : [
        { key: 'beginner',     value: resort.difficulty.beg,   color: DIFFICULTY_COLORS.beg,   label: t('widgets.stats.difficulty.beginner') },
        { key: 'intermediate', value: resort.difficulty.inter, color: DIFFICULTY_COLORS.inter, label: t('widgets.stats.difficulty.intermediate') },
        { key: 'advanced',     value: resort.difficulty.adv,   color: DIFFICULTY_COLORS.adv,   label: t('widgets.stats.difficulty.advanced') },
        { key: 'expert',       value: resort.difficulty.exp,   color: DIFFICULTY_COLORS.exp,   label: t('widgets.stats.difficulty.expert') },
      ];

  const centerValue = useKm ? window.kmToDisplay(resort.kmTotal, distanceUnit) : resort.slopes;
  const centerLabel = useKm ? window.distanceLabel(distanceUnit) : t('widgets.stats.center');

  return (
    <section className="neu-card neu-card--stats">
      <div className="neu-h4">{t('widgets.stats.title')}</div>
      <div className="stats-row">
        <StatsDonut
          segments={segments}
          centerValue={centerValue}
          centerLabel={centerLabel}
          centerInteger={!useKm}
          signature={`${resort.id}|${useKm ? distanceUnit : 'count'}`}
        />
        <div className={`stats-legend${useKm ? ' stats-legend--km' : ''}`}>
          {segments.map(s => (
            <LegendValue key={s.key} n={s.value} c={s.color} l={s.label} integer={!useKm}/>
          ))}
        </div>
      </div>
      <div className="stats-kvs">
        {useKm ? (
          <div className="kv"><span>{t('widgets.stats.slopes')}</span><span>{resort.slopes || dash}</span></div>
        ) : (
          <div className="kv"><span>{t('widgets.stats.totalSlopes')}</span><span>{resort.slopes}</span></div>
        )}
        <div className="kv"><span>{t('widgets.stats.totalLifts')}</span><span>{resort.lifts}</span></div>
        <div className="kv"><span>{t('widgets.stats.totalDistance')}</span><span>{resort.km ? window.formatDistanceKm(resort.kmTotal, distanceUnit) : dash}</span></div>
        <div className="kv"><span>{t('widgets.stats.dayTicket')}</span><span>{resort.price || dash}</span></div>
        {resort.websiteDisplay && (
          <div className="kv">
            <span>{t('widgets.stats.website')}</span>
            <a className="kv__link" href={resort.website} target="_blank" rel="noreferrer">{resort.websiteDisplay}</a>
          </div>
        )}
      </div>
    </section>
  );
}

function SkiInsuranceWidget() {
  const t = useT();
  const href = 'https://www.intermundial.es/seguros-deportivos/seguro-wintersports?utm_source=Affiliates&utm_content=6772eab4b5005&utm_medium=ad08e6b4';
  return (
    <section className="neu-card ski-insurance">
      <div className="neu-h4">{t('widgets.insurance.title')}</div>
      <a className="ski-insurance__link" href={href} target="_blank" rel="noopener noreferrer sponsored">
        <img src={`${ASSETS}/Intermundial.jpg`} alt={t('widgets.insurance.alt')}/>
        <div className="ski-insurance__overlay">
          <span className="ski-insurance__pct">{t('widgets.insurance.percent')}</span>
          <span className="ski-insurance__text">{t('widgets.insurance.discount')}</span>
        </div>
      </a>
    </section>
  );
}

function TrailMap({ image }) {
  const { lang, t } = useI18n();
  return (
    <section className="neu-card">
      <div className="neu-h4">{t('widgets.trail.title')}</div>
      <div className="trail">
        <img src={image} alt="" />
        <a className="trail__btn" href={appStoreUrl(lang)} target="_blank" rel="noopener noreferrer">
          {t('widgets.trail.open')}
        </a>
      </div>
    </section>
  );
}

function pinHtml(resort, selected) {
  const color = selected ? '#007AFF' : '#000000';
  const clipId = `pin-clip-${resort.id}`;
  const logoMark = resort.logo
    ? `<defs><clipPath id="${clipId}"><circle cx="22" cy="22" r="13"/></clipPath></defs>
       <circle cx="22" cy="22" r="15" fill="#fff"/>
       <image href="${resort.logo}" x="9" y="9" width="26" height="26"
              preserveAspectRatio="xMidYMid meet" clip-path="url(#${clipId})"
              onerror="this.remove()"/>`
    : '';
  return `
    <div class="pin ${selected ? 'pin--sel' : ''}">
      <svg class="pin__svg" viewBox="0 0 44 60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <path fill="${color}" stroke="#fff" stroke-width="2" stroke-linejoin="round"
              d="M 6.41 31 A 18 18 0 1 1 37.59 31 L 22 58 Z"/>
        ${logoMark}
      </svg>
    </div>`;
}

function RegionMap({ resorts, activeId, onPick }) {
  const { lang, t } = useI18n();
  const active = resorts.find(r => r.id === activeId);
  const hostRef = useRef(null);
  const mapRef = useRef(null);
  const markersRef = useRef({});
  const [isFullscreen, setIsFullscreen] = useState(false);

  useEffect(() => {
    if (!hostRef.current || mapRef.current) return;
    const map = L.map(hostRef.current, {
      zoomControl: false,
      attributionControl: false,
      scrollWheelZoom: true,        // cursor-anchored zoom on wheel
      wheelPxPerZoomLevel: 100,     // finer steps → smoother feel
      wheelDebounceTime: 40,
      worldCopyJump: true,
    }).setView([20, 0], 2);
    L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
      subdomains: 'abcd',
      maxZoom: 19,
    }).addTo(map);
    L.control.attribution({ prefix: false, position: 'bottomright' })
      .addAttribution('© OpenStreetMap, © CARTO')
      .addTo(map);
    mapRef.current = map;
    return () => { map.remove(); mapRef.current = null; };
  }, []);

  useEffect(() => {
    const map = mapRef.current;
    if (!map || !resorts.length) return;

    Object.values(markersRef.current).forEach(m => m.remove());
    markersRef.current = {};

    resorts.forEach(r => {
      const selected = r.id === activeId;
      const icon = L.divIcon({
        html: pinHtml(r, selected),
        className: 'pin-icon',
        iconSize: [66, 90],
        iconAnchor: [33, 87],
      });
      const marker = L.marker([r.latitude, r.longitude], { icon, zIndexOffset: selected ? 1000 : 0 })
        .addTo(map)
        .on('click', () => onPick(r.id));
      markersRef.current[r.id] = marker;
    });
  }, [resorts]);

  useEffect(() => {
    const map = mapRef.current;
    if (!map || !active) return;
    map.flyTo([active.latitude, active.longitude], 9, { duration: 0.8 });
  }, [activeId]);

  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    Object.entries(markersRef.current).forEach(([id, marker]) => {
      const r = resorts.find(x => x.id === id);
      if (!r) return;
      const selected = id === activeId;
      marker.setIcon(L.divIcon({
        html: pinHtml(r, selected),
        className: 'pin-icon',
        iconSize: [66, 90],
        iconAnchor: [33, 87],
      }));
      marker.setZIndexOffset(selected ? 1000 : 0);
    });
  }, [activeId]);

  useEffect(() => {
    const onFsChange = () => {
      const m = mapRef.current;
      setIsFullscreen(document.fullscreenElement === hostRef.current?.closest('.region-map'));
      if (!m) return;
      setTimeout(() => m.invalidateSize(), 100);
    };
    document.addEventListener('fullscreenchange', onFsChange);
    onFsChange();
    return () => document.removeEventListener('fullscreenchange', onFsChange);
  }, []);
  const fullscreenLabel = isFullscreen ? t('widgets.regionMap.exitFullscreen') : t('widgets.regionMap.fullscreen');

  const handleZoom = (delta) => {
    const m = mapRef.current;
    if (m) m.setZoom(m.getZoom() + delta);
  };
  const handleFullscreen = () => {
    const el = hostRef.current?.closest('.region-map');
    if (!el) return;
    if (document.fullscreenElement) document.exitFullscreen();
    else el.requestFullscreen?.();
  };

  return (
    <section className="neu-card region-map">
      <div ref={hostRef} className="region-map__canvas"/>
      {active && (
        <div className="region-map__banner">
          <Icon name="pin" size={14} color="#E7000B"/>
          <div>
            <div className="neu-caption-medium" style={{fontSize: 12.25}}>{active.name}</div>
            <div className="neu-caption" style={{color:'var(--neu-fg-4)'}}>{translatePlace(active.region, lang)}, {translatePlace(active.country, lang)}</div>
          </div>
        </div>
      )}
      <div className="region-map__zoom">
        <button onClick={() => handleZoom(+1)} aria-label={t('widgets.regionMap.zoomIn')}><Icon name="plus" size={14} color="#0A0A0A" stroke={1.8}/></button>
        <button onClick={() => handleZoom(-1)} aria-label={t('widgets.regionMap.zoomOut')}><Icon name="minus" size={14} color="#0A0A0A" stroke={1.8}/></button>
      </div>
      <div className="region-map__hint">
        <Icon name="pin" size={12} color="#E7000B"/>
        <span>{t('widgets.regionMap.hint')}</span>
      </div>
      <button className="region-map__full" onClick={handleFullscreen} aria-label={fullscreenLabel}>
        <svg className="fullscreen-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0A0A0A" strokeWidth="2.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          {isFullscreen ? (
            <>
              <polyline points="9 3 9 9 3 9"/><line x1="9" y1="9" x2="3" y2="3"/>
              <polyline points="15 21 15 15 21 15"/><line x1="15" y1="15" x2="21" y2="21"/>
            </>
          ) : (
            <>
              <polyline points="3 9 3 3 9 3"/><line x1="3" y1="3" x2="9" y2="9"/>
              <polyline points="21 15 21 21 15 21"/><line x1="21" y1="21" x2="15" y2="15"/>
            </>
          )}
        </svg>
        <span>{fullscreenLabel}</span>
      </button>
    </section>
  );
}

Object.assign(window, { ResortHeader, Resort3DViewer, SnowfallMap, WebcamsWidget, PhotoCarousel, ResortStatistics, SkiInsuranceWidget, TrailMap, RegionMap });
