/* 照野 — 显影（命门）
   把真实照片读成光：EXIF 定位 + 时间 → 聚类成地点 → 在地图上显影。
   若一张照片落在已有「道标」附近，那枚冷脉冲就地点燃成暖光（道标 → 显影）。
   没有定位的照片不假装知道它在哪 —— 只如实记一笔（来源诚实，§1.4）。 */

const ZyDevelop = (() => {
  const D = () => window.ZY_DATA;

  // ---- 距离 ----
  const toR = (x) => x * Math.PI / 180;
  function haversineKm(lat1, lng1, lat2, lng2) {
    const R = 6371;
    const dLat = toR(lat2 - lat1), dLng = toR(lng2 - lng1);
    const a = Math.sin(dLat / 2) ** 2 + Math.cos(toR(lat1)) * Math.cos(toR(lat2)) * Math.sin(dLng / 2) ** 2;
    return 2 * R * Math.asin(Math.sqrt(a));
  }

  // ---- EXIF：优先 exifr，失败再用内置 JPEG 解析（离线兜底）----
  async function readExif(file) {
    if (window.exifr) {
      try {
        const gps = await window.exifr.gps(file).catch(() => null);
        let time = null;
        try {
          const m = await window.exifr.parse(file, ["DateTimeOriginal", "CreateDate", "ModifyDate"]);
          time = (m && (m.DateTimeOriginal || m.CreateDate || m.ModifyDate)) || null;
        } catch (e) { /* 时间可缺 */ }
        if (gps && isFinite(gps.latitude) && isFinite(gps.longitude)) {
          return { lat: gps.latitude, lng: gps.longitude, time: toDate(time) };
        }
        if (time) return { lat: null, lng: null, time: toDate(time) };
      } catch (e) { /* 落到兜底 */ }
    }
    try {
      const buf = await file.arrayBuffer();
      return parseJpegExif(buf);
    } catch (e) { return { lat: null, lng: null, time: null }; }
  }

  function toDate(v) {
    if (!v) return null;
    if (v instanceof Date) return isNaN(v) ? null : v;
    // "YYYY:MM:DD HH:MM:SS"
    const m = String(v).match(/(\d{4}):(\d{2}):(\d{2})[ T](\d{2}):(\d{2}):(\d{2})/);
    if (m) return new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]);
    const d = new Date(v);
    return isNaN(d) ? null : d;
  }

  // 内置 JPEG/TIFF EXIF 解析（仅取 GPS + DateTimeOriginal），全程兜底不抛
  function parseJpegExif(buf) {
    try {
      const dv = new DataView(buf);
      if (dv.getUint16(0) !== 0xFFD8) return { lat: null, lng: null, time: null };
      let off = 2; const len = dv.byteLength;
      while (off + 4 < len) {
        const marker = dv.getUint16(off);
        if ((marker & 0xFF00) !== 0xFF00) break;
        const size = dv.getUint16(off + 2);
        if (marker === 0xFFE1 && dv.getUint32(off + 4) === 0x45786966) { // "Exif"
          return parseTiff(dv, off + 10);
        }
        off += 2 + size;
      }
    } catch (e) { /* ignore */ }
    return { lat: null, lng: null, time: null };
  }

  function parseTiff(dv, tiff) {
    const le = dv.getUint16(tiff) === 0x4949;          // II = little-endian
    const u16 = (o) => dv.getUint16(o, le);
    const u32 = (o) => dv.getUint32(o, le);
    const readIFD = (start) => {
      const n = u16(start), ents = {};
      for (let i = 0; i < n; i++) {
        const e = start + 2 + i * 12;
        ents[u16(e)] = { type: u16(e + 2), cnt: u32(e + 4), valOff: e + 8 };
      }
      return ents;
    };
    const e0 = readIFD(tiff + u32(tiff + 4));
    let lat = null, lng = null, time = null;
    if (e0[0x8825]) {                                   // GPS IFD
      const g = readIFD(tiff + u32(e0[0x8825].valOff));
      const rat3 = (ent) => { const o = tiff + u32(ent.valOff);
        const r = (k) => u32(o + k * 8) / (u32(o + k * 8 + 4) || 1);
        return r(0) + r(1) / 60 + r(2) / 3600; };
      if (g[2] && g[4]) {
        lat = rat3(g[2]); lng = rat3(g[4]);
        if (g[1] && String.fromCharCode(dv.getUint8(g[1].valOff)) === "S") lat = -lat;
        if (g[3] && String.fromCharCode(dv.getUint8(g[3].valOff)) === "W") lng = -lng;
      }
    }
    if (e0[0x8769]) {                                   // Exif IFD → DateTimeOriginal
      const ex = readIFD(tiff + u32(e0[0x8769].valOff));
      const dto = ex[0x9003] || ex[0x9004];
      if (dto) { const o = tiff + u32(dto.valOff); let s = "";
        for (let i = 0; i < 19; i++) s += String.fromCharCode(dv.getUint8(o + i)); time = s; }
    }
    return { lat, lng, time: toDate(time) };
  }

  // ---- 聚类：同一处的照片归为一个地点 ----
  const CLUSTER_KM = 1.4;
  function cluster(items) {
    const geo = items.filter(p => p.lat != null && p.lng != null && isFinite(p.lat) && isFinite(p.lng));
    const clusters = [];
    for (const p of geo) {
      let best = null, bd = Infinity;
      for (const c of clusters) {
        const d = haversineKm(c.c[1], c.c[0], p.lat, p.lng);
        if (d < bd) { bd = d; best = c; }
      }
      if (best && bd <= CLUSTER_KM) {
        best.items.push(p);
        best.c = [avg(best.items, "lng"), avg(best.items, "lat")];   // 重新求心
      } else {
        clusters.push({ c: [p.lng, p.lat], items: [p] });
      }
    }
    return clusters;
  }
  const avg = (arr, k) => arr.reduce((s, x) => s + x[k], 0) / arr.length;

  // ---- 道标命中：照片是否到达了某枚已有道标 ----
  function waypointHit(c) {
    for (const w of D().worldWaypoints) {
      if (haversineKm(c[1], c[0], w.c[1], w.c[0]) <= 15)
        return { id: w.id, name: w.name, scope: "world", c: w.c };
    }
    const cw = D().cityWaypoint;
    if (cw && haversineKm(c[1], c[0], cw.c[1], cw.c[0]) <= 2)
      return { id: cw.id, name: cw.name, scope: "city", c: cw.c };
    return null;
  }

  // ---- 记忆卡：把一处聚类写成可点开的记忆（来源诚实）----
  const fmtMonth = (d) => d ? `${d.getFullYear()} 年 ${d.getMonth() + 1} 月` : "时间不详（照片未带时间）";
  const dms = (v, pos, neg) => `${Math.abs(v).toFixed(3)}°${v >= 0 ? pos : neg}`;

  function authorPlace(id, cl, hit) {
    const n = cl.items.length;
    const times = cl.items.map(p => p.time).filter(Boolean).sort((a, b) => a - b);
    const earliest = times[0] || null;
    const yeyu = hit
      ? `你曾把「${hit.name}」收作道标。\n现在你真的到过了——它从冷光里，暖暖地亮了起来。`
      : (n >= 3
        ? `你在这附近留下了 ${n} 张照片。\n刚才它们一起，从暗处慢慢显影。`
        : `这处地方，刚从你的照片里显影。\n光不强，但它确实亮了。`);
    D().places[id] = {
      title: hit ? hit.name : (n > 1 ? `${n} 张照片的地方` : "一张照片的地方"),
      loc: `${dms(cl.c[1], "N", "S")} · ${dms(cl.c[0], "E", "W")}`,
      yeyu,
      arrived: fmtMonth(earliest),
      who: "—",
      source: "照片",
      srcNote: hit ? "由相册 EXIF 定位 · 原为道标" : "由相册 EXIF 定位",
      thumb: cl.items[0].url,
    };
  }

  function brightness(cl, hit) {
    let b = Math.min(1, 0.34 + 0.12 * (cl.items.length - 1));
    if (hit) b = Math.max(b, 0.7);            // 真的到了 —— 让它明确地亮
    return b;
  }

  // ---- 编排：聚类 → 写卡 → 注入地图 → 点燃道标 → 取景 ----
  function develop(items) {
    const clusters = cluster(items);
    const placeFeatures = [], photoFeatures = [], coords = [], hits = [];

    clusters.forEach((cl, ci) => {
      const hit = waypointHit(cl.c);
      if (hit) { cl.c = hit.c.slice(); hits.push(hit); }     // 吸附到道标，让暖光与点燃环重合
      const id = `imp_${ci}`;
      authorPlace(id, cl, hit);
      placeFeatures.push({ id, c: cl.c, b: brightness(cl, hit), scope: hit ? hit.scope : "city" });
      cl.items.forEach((p, pi) => photoFeatures.push({ id: `iph_${ci}_${pi}`, c: [p.lng, p.lat], place: id }));
      coords.push(cl.c);
    });

    if (placeFeatures.length) ZyMap.developPlaces(placeFeatures, photoFeatures);
    hits.forEach(h => ZyMap.warmWaypoint(h.id, h.c, h.scope));
    if (coords.length) setTimeout(() => ZyMap.fitTo(coords), 420);   // 先让光开始浮现，相机再缓缓收拢

    const geoCount = items.filter(p => p.lat != null && p.lng != null).length;
    return {
      developed: clusters.length,
      photos: geoCount,
      noGps: items.length - geoCount,
      total: items.length,
      hits,
      coords,
    };
  }

  async function fromFiles(fileList) {
    const files = Array.from(fileList).filter(f =>
      /^image\//.test(f.type) || /\.(jpe?g|png|heic|heif|tiff?)$/i.test(f.name || ""));
    const items = [];
    for (const f of files) {
      const ex = await readExif(f);
      items.push({ name: f.name, url: URL.createObjectURL(f), lat: ex.lat, lng: ex.lng, time: ex.time });
    }
    return develop(items);
  }

  // 示例：拉取仓库里的示例照片（让魔法在没有自带地理照片时也能演示）
  const SAMPLE_FILES = [
    "01-suzhou-creek-dawn.jpg", "02-suzhou-creek-bridge.jpg", "03-suzhou-creek-bend.jpg",
    "04-bund-railing.jpg", "05-bund-acrossriver.jpg",
    "06-wukang-corner.jpg", "07-wukang-plane-tree.jpg",
    "08-yangpu-riverside.jpg", "09-kyoto-north-rain.jpg", "10-no-location-night.jpg",
  ];
  async function fromSamples() {
    const files = [];
    for (const name of SAMPLE_FILES) {
      try {
        const r = await fetch(`sample-photos/${name}`);
        if (r.ok) files.push(new File([await r.blob()], name, { type: "image/jpeg" }));
      } catch (e) { /* 跳过取不到的 */ }
    }
    if (!files.length) return { developed: 0, photos: 0, noGps: 0, total: 0, hits: [], coords: [], error: "示例照片未找到" };
    return fromFiles(files);
  }

  return { fromFiles, fromSamples, readExif };
})();

window.ZyDevelop = ZyDevelop;
