// Penny Map — frontend bootstrap
const DEFAULT_VIEW = { center: [127.0, 37.55], zoom: 4 };
const DEVICE_ID_KEY = 'pennymap:deviceId';
const LANG_KEY = 'pennymap:lang';
const PRICE_KEY = 'pennymap:maxPrice';

const getDeviceId = () => {
  let id = localStorage.getItem(DEVICE_ID_KEY);
  if (!id) {
    id = crypto.randomUUID();
    localStorage.setItem(DEVICE_ID_KEY, id);
  }
  return id;
};

const detectLang = () => {
  const saved = localStorage.getItem(LANG_KEY);
  if (saved) return saved;
  const nav = (navigator.language || 'en').toLowerCase();
  if (nav.startsWith('ko')) return 'ko';
  if (nav.startsWith('ja')) return 'ja';
  return 'en';
};

let i18n = {};
async function loadLang(lang) {
  const r = await fetch(`/i18n/${lang}.json`);
  i18n = await r.json();
  localStorage.setItem(LANG_KEY, lang);
  document.documentElement.lang = lang;
  document.querySelectorAll('[data-i18n]').forEach(el => {
    const k = el.getAttribute('data-i18n');
    if (!i18n[k]) return;
    // Never blow away nested controls (inputs/selects). Only translate elements
    // whose content is purely text.
    if (el.children.length === 0) el.textContent = i18n[k];
  });
  document.querySelectorAll('[data-i18n-attr]').forEach(el => {
    const spec = el.getAttribute('data-i18n-attr');
    const [attr, key] = spec.split(':');
    if (i18n[key]) el.setAttribute(attr, i18n[key]);
  });
}

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://tiles.openfreemap.org/styles/liberty',
  center: DEFAULT_VIEW.center,
  zoom: DEFAULT_VIEW.zoom,
  attributionControl: { compact: true }
});
map.addControl(new maplibregl.NavigationControl(), 'bottom-right');
map.addControl(new maplibregl.GeolocateControl({ positionOptions: { enableHighAccuracy: true } }), 'bottom-right');

let selectedLatLng = null;
let selectionMarker = null;
let maxPriceFilter = localStorage.getItem(PRICE_KEY) ?? '10';

map.on('load', async () => {
  // Unverified pins — small grey dots, only at higher zoom (signal-to-noise).
  map.addSource('pins-unverified', { type: 'geojson', data: emptyFc() });
  map.addLayer({
    id: 'pins-unverified-layer',
    type: 'circle',
    source: 'pins-unverified',
    minzoom: 11,
    paint: {
      'circle-radius': ['interpolate', ['linear'], ['zoom'], 11, 2.5, 16, 5],
      'circle-color': '#5a6172',
      'circle-stroke-color': '#0f1115',
      'circle-stroke-width': 1,
      'circle-opacity': 0.85
    }
  });

  // Verified pins — colored, larger.
  map.addSource('pins-verified', { type: 'geojson', data: emptyFc() });
  map.addLayer({
    id: 'pins-verified-layer',
    type: 'circle',
    source: 'pins-verified',
    paint: {
      'circle-radius': [
        'interpolate', ['linear'], ['zoom'],
        5, 4, 10, 7, 14, 11, 17, 16
      ],
      'circle-color': ['match', ['get', 'category'],
        'restaurant', '#ffb43a',
        'market', '#6dd58c',
        'cafe', '#c89cff',
        'pub', '#ff7a59',
        'fastfood', '#ff5a8a',
        'convenience', '#8aa6ff',
        '#ffb43a'],
      'circle-stroke-color': [
        'case',
        ['<=', ['coalesce', ['get', 'priceUsd'], 9999], cheapThreshold()],
        '#ffd166', '#0f1115'
      ],
      'circle-stroke-width': [
        'case',
        ['<=', ['coalesce', ['get', 'priceUsd'], 9999], cheapThreshold()],
        2.5, 1.5
      ]
    }
  });

  // Price label — only visible once zoomed in.
  map.addLayer({
    id: 'pins-verified-label',
    type: 'symbol',
    source: 'pins-verified',
    minzoom: 13,
    layout: {
      'text-field': ['concat', '$', ['to-string', ['get', 'priceUsd']]],
      'text-size': 11,
      'text-offset': [0, 1.2],
      'text-anchor': 'top',
      'text-allow-overlap': false
    },
    paint: {
      'text-color': '#ffe9b0',
      'text-halo-color': '#0f1115',
      'text-halo-width': 1.5
    }
  });

  // Value-for-money heatmap (hidden until toggled). Hotter = cheaper average price.
  map.addSource('heat', { type: 'geojson', data: emptyFc() });
  map.addLayer({
    id: 'heat-layer',
    type: 'heatmap',
    source: 'heat',
    layout: { visibility: 'none' },
    paint: {
      'heatmap-weight': ['get', 'weight'],
      'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 4, 1, 14, 3],
      'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 4, 18, 10, 30, 14, 45],
      'heatmap-opacity': 0.75,
      'heatmap-color': [
        'interpolate', ['linear'], ['heatmap-density'],
        0, 'rgba(0,0,0,0)',
        0.2, '#2c7bb6',
        0.4, '#abd9e9',
        0.6, '#ffffbf',
        0.8, '#fdae61',
        1, '#d7191c'
      ]
    }
  });

  bindPinClick('pins-verified-layer');
  bindPinClick('pins-unverified-layer');
  ['pins-verified-layer', 'pins-unverified-layer'].forEach(l => {
    map.on('mouseenter', l, () => map.getCanvas().style.cursor = 'pointer');
    map.on('mouseleave', l, () => map.getCanvas().style.cursor = '');
  });

  map.on('moveend', refreshPins);
  await refreshPins();
});

function emptyFc() { return { type: 'FeatureCollection', features: [] }; }
function cheapThreshold() {
  const v = parseFloat(maxPriceFilter);
  return Number.isFinite(v) ? v : 9999;
}

const escapeHtml = s => String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));

function bindPinClick(layerId) {
  map.on('click', layerId, e => {
    const f = e.features[0];
    const p = f.properties;
    const verified = p.verified === true || p.verified === 'true';
    const reports = Number(p.priceReportCount) || 0;
    const conf = reports > 1 ? ` <span class="conf">· ${reports} reports</span>` : '';
    const priceLine = verified
      ? `<div class="price ${Number(p.priceUsd) <= cheapThreshold() ? 'cheap' : ''}">~ $${p.priceUsd}${conf}</div>`
      : '';
    const tags = p.tags ? `<div class="tags">#${String(p.tags).split(',').map(s => s.trim()).join(' #')}</div>` : '';
    const cta = verified
      ? ''
      : `<button class="unverified-cta" data-id="${p.id}">${i18n.verifyCta || 'Add price · Verify'}</button>`;

    const popup = new maplibregl.Popup({ closeButton: true })
      .setLngLat(f.geometry.coordinates)
      .setHTML(`<div class="popup">
          <h3>${escapeHtml(p.name)}</h3>
          <div class="cat">${p.category} · ${p.countryCode}</div>
          ${priceLine}${tags}${cta}
        </div>`)
      .addTo(map);

    if (!verified) {
      popup.getElement().querySelector('.unverified-cta')?.addEventListener('click', () => {
        popup.remove();
        openVerifyDialog(p);
      });
    }
  });
}

let inflight;
async function refreshPins() {
  const b = map.getBounds();
  const params = new URLSearchParams({
    minLng: b.getWest(), minLat: b.getSouth(),
    maxLng: b.getEast(), maxLat: b.getNorth(),
    limit: '2000'
  });
  if (inflight) inflight.abort();
  inflight = new AbortController();
  try {
    const r = await fetch('/api/pins?' + params, { signal: inflight.signal });
    if (!r.ok) return;
    const data = await r.json();
    const verified = data.filter(p => p.verified);
    const unverified = data.filter(p => !p.verified);
    map.getSource('pins-verified').setData(toFc(verified));
    map.getSource('pins-unverified').setData(toFc(unverified));
  } catch (e) { if (e.name !== 'AbortError') console.warn(e); }
}

function toFc(pins) {
  return {
    type: 'FeatureCollection',
    features: pins.map(p => ({
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [p.lng, p.lat] },
      properties: { ...p, verified: p.verified === true }
    }))
  };
}

// === Pin creation flow ===
const pinDialog = document.getElementById('pinDialog');
const pinForm = document.getElementById('pinForm');
const pinLocLabel = document.getElementById('pinLocLabel');
let pickMode = false;

document.getElementById('addPinBtn').addEventListener('click', () => {
  pickMode = true;
  map.getCanvas().style.cursor = 'crosshair';
  toast(i18n.needLocation || 'Tap the map to choose a location.');
});

map.on('click', e => {
  if (!pickMode) return;
  // Don't hijack clicks that land on an existing pin layer.
  const hit = map.queryRenderedFeatures(e.point, { layers: ['pins-verified-layer', 'pins-unverified-layer'] });
  if (hit && hit.length) return;
  selectedLatLng = e.lngLat;
  if (selectionMarker) selectionMarker.remove();
  selectionMarker = new maplibregl.Marker({ color: '#ffb43a' }).setLngLat(e.lngLat).addTo(map);
  pinLocLabel.textContent = `${e.lngLat.lat.toFixed(5)}, ${e.lngLat.lng.toFixed(5)}`;
  pickMode = false;
  map.getCanvas().style.cursor = '';
  pinDialog.showModal();
});

document.getElementById('pinCancel').addEventListener('click', e => { e.preventDefault(); pinDialog.close(); });

pinForm.addEventListener('submit', async e => {
  e.preventDefault();
  if (!selectedLatLng) { toast(i18n.needLocation || 'Choose a location first.'); return; }
  const fd = new FormData(pinForm);
  const body = {
    name: fd.get('name'),
    category: fd.get('category'),
    countryCode: await guessCountryCode(selectedLatLng),
    lat: selectedLatLng.lat,
    lng: selectedLatLng.lng,
    priceUsd: fd.get('priceUsd') ? Number(fd.get('priceUsd')) : null,
    tags: fd.get('tags') || null,
    notes: fd.get('notes') || null,
    deviceId: getDeviceId()
  };
  try {
    const r = await fetch('/api/pins', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body)
    });
    if (r.ok) {
      pinForm.reset();
      if (selectionMarker) { selectionMarker.remove(); selectionMarker = null; }
      pinDialog.close();
      toast(i18n.saved || 'Saved!');
      await refreshPins();
    } else {
      const detail = await r.text().catch(() => '');
      alert('Failed to save (HTTP ' + r.status + ')\n' + detail.slice(0, 300));
    }
  } catch (err) {
    alert('Could not reach the server. Is it running?\n' + err);
  }
});

// === Verify (add price to unverified pin) flow ===
const verifyDialog = document.getElementById('verifyDialog');
const verifyForm = document.getElementById('verifyForm');
const verifyName = document.getElementById('verifyName');
let verifyingId = null;

function openVerifyDialog(props) {
  verifyingId = props.id;
  verifyName.textContent = `${props.name} · ${props.category}`;
  verifyForm.reset();
  verifyDialog.showModal();
}
document.getElementById('verifyCancel').addEventListener('click', e => { e.preventDefault(); verifyDialog.close(); });

verifyForm.addEventListener('submit', async e => {
  e.preventDefault();
  if (!verifyingId) return;
  const fd = new FormData(verifyForm);
  const body = {
    priceUsd: Number(fd.get('priceUsd')),
    tags: fd.get('tags') || null,
    deviceId: getDeviceId()
  };
  try {
    const r = await fetch('/api/pins/' + verifyingId, {
      method: 'PATCH', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body)
    });
    if (r.ok) {
      verifyDialog.close();
      toast(i18n.verified || 'Verified. Thank you!');
      verifyingId = null;
      await refreshPins();
    } else {
      const detail = await r.text().catch(() => '');
      alert('Failed to verify (HTTP ' + r.status + ')\n' + detail.slice(0, 300));
    }
  } catch (err) {
    alert('Could not reach the server. Is it running?\n' + err);
  }
});

async function guessCountryCode(lngLat) {
  try {
    const lang = document.documentElement.lang || 'en';
    const r = await fetch(`/api/geocode/reverse?lat=${lngLat.lat}&lng=${lngLat.lng}&lang=${lang}`);
    const j = await r.json();
    return (j.countryCode ? j.countryCode : 'US').toUpperCase().slice(0,2);
  } catch { return 'US'; }
}

// === Search ===
const searchInput = document.getElementById('search');
const searchResults = document.getElementById('searchResults');
let searchTimer;
searchInput.addEventListener('input', () => {
  clearTimeout(searchTimer);
  const q = searchInput.value.trim();
  if (q.length < 2) { searchResults.hidden = true; return; }
  searchTimer = setTimeout(async () => {
    const lang = document.documentElement.lang || 'en';
    const r = await fetch(`/api/geocode/search?q=${encodeURIComponent(q)}&lang=${lang}`);
    if (!r.ok) return;
    const items = await r.json();
    searchResults.innerHTML = items.map((it, i) =>
      `<div class="item" data-i="${i}"><strong>${escapeHtml(it.displayName.split(',')[0])}</strong>
        <span class="type">${it.type ?? ''}</span><br>
        <small>${escapeHtml(it.displayName)}</small></div>`).join('');
    searchResults.hidden = items.length === 0;
    searchResults.querySelectorAll('.item').forEach((el, i) => {
      el.addEventListener('click', () => {
        const it = items[i];
        map.flyTo({ center: [it.lng, it.lat], zoom: 14 });
        searchResults.hidden = true;
      });
    });
  }, 250);
});

// === Lang selector ===
const langSel = document.getElementById('lang');
langSel.value = detectLang();
loadLang(langSel.value);
langSel.addEventListener('change', () => loadLang(langSel.value));

// === Price filter ===
const priceSel = document.getElementById('priceFilter');
priceSel.value = maxPriceFilter;
priceSel.addEventListener('change', () => {
  maxPriceFilter = priceSel.value;
  localStorage.setItem(PRICE_KEY, maxPriceFilter);
  // Re-paint stroke (cheap highlight) by refreshing source data.
  refreshPins();
});

// === Value-for-money heatmap ===
const PIN_LAYERS = ['pins-verified-layer', 'pins-verified-label', 'pins-unverified-layer'];
const CHEAP_CEILING = 25; // $ — at/above this an area carries no "cheap" heat weight.
let heatmapOn = false;
let heatInflight;

function heatPrecision() {
  const z = map.getZoom();
  if (z < 6) return 0.5;
  if (z < 9) return 0.2;
  if (z < 12) return 0.05;
  return 0.02;
}

async function refreshHeat() {
  if (!heatmapOn) return;
  const b = map.getBounds();
  const params = new URLSearchParams({
    minLng: b.getWest(), minLat: b.getSouth(),
    maxLng: b.getEast(), maxLat: b.getNorth(),
    precision: heatPrecision()
  });
  if (heatInflight) heatInflight.abort();
  heatInflight = new AbortController();
  try {
    const r = await fetch('/api/stats/heatmap?' + params, { signal: heatInflight.signal });
    if (!r.ok) return;
    const cells = await r.json();
    map.getSource('heat').setData({
      type: 'FeatureCollection',
      features: cells.map(c => ({
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [c.lng, c.lat] },
        // Cheaper average → higher weight; more reports → slightly stronger signal.
        properties: {
          weight: Math.max(0, (CHEAP_CEILING - c.avgPriceUsd) / CHEAP_CEILING) *
                  Math.min(1, 0.4 + c.count / 10)
        }
      }))
    });
  } catch (e) { if (e.name !== 'AbortError') console.warn(e); }
}

const heatmapBtn = document.getElementById('heatmapBtn');
heatmapBtn.addEventListener('click', () => {
  heatmapOn = !heatmapOn;
  heatmapBtn.setAttribute('aria-pressed', String(heatmapOn));
  heatmapBtn.classList.toggle('active', heatmapOn);
  map.setLayoutProperty('heat-layer', 'visibility', heatmapOn ? 'visible' : 'none');
  PIN_LAYERS.forEach(l => map.setLayoutProperty(l, 'visibility', heatmapOn ? 'none' : 'visible'));
  if (heatmapOn) refreshHeat();
});
map.on('moveend', refreshHeat);

// === Support dialog ===
const supportDialog = document.getElementById('supportDialog');
document.getElementById('supportBtn').addEventListener('click', async () => {
  const r = await fetch('/api/support');
  const cfg = await r.json();
  document.getElementById('paypalLink').href = cfg.paypalUrl || '#';
  const list = document.getElementById('cryptoList');
  list.innerHTML = '';
  for (const [k, v] of Object.entries(cfg.crypto)) {
    if (!v) continue;
    const li = document.createElement('li');
    li.innerHTML = `<span><strong>${k.toUpperCase()}</strong> <code>${v}</code></span>`;
    const btn = document.createElement('button');
    btn.textContent = i18n.copy || 'Copy';
    btn.addEventListener('click', async () => {
      await navigator.clipboard.writeText(v);
      btn.textContent = i18n.copied || 'Copied!';
      setTimeout(() => btn.textContent = i18n.copy || 'Copy', 1200);
    });
    li.appendChild(btn);
    list.appendChild(li);
  }
  supportDialog.showModal();
});
document.getElementById('supportClose').addEventListener('click', e => { e.preventDefault(); supportDialog.close(); });

// Tiny toast
function toast(msg) {
  const el = document.createElement('div');
  el.textContent = msg;
  Object.assign(el.style, {
    position: 'fixed', bottom: '24px', left: '50%', transform: 'translateX(-50%)',
    background: 'rgba(0,0,0,.8)', color: '#fff', padding: '10px 14px',
    borderRadius: '8px', zIndex: 9999, fontSize: '13px'
  });
  document.body.appendChild(el);
  setTimeout(() => el.remove(), 2400);
}
