// Naming, voting and share-poster flow.
const NAMING_LS = "dz_nominations_v1";
const NAMING_VOTES_LS = "dz_nomination_votes_v1";
const NAMING_LIKES_LS = "dz_nomination_likes_v1";

function normalizeNomination(n = {}) {
  const animalId = n.animal_id || n.animalId || "unknown";
  const animal = (window.DEER_DATA || []).find(d => d.id === animalId) || (window.DEER_DATA || [])[0] || {};
  const votes = Number(n.votes || 0) + Number(n.admin_weight || n.adminWeight || 0);
  return {
    id: n.id,
    animalId,
    animalName: animal.name || n.animal_name || n.animalName || "待建档个体",
    species: animal.species || n.species || "城市野生动物",
    proposedName: n.proposed_name || n.proposedName || "未命名",
    story: n.story || "",
    keywords: n.keywords || "",
    photoUrl: n.photo_url || n.photoUrl || animal.photo || "",
    photoDataUrl: n.photo_data_url || n.photoDataUrl || "",
    contributorName: n.contributor_name || n.contributorName || "匿名观察员",
    status: n.status || "approved",
    moderationReason: n.moderation_reason || n.moderationReason || "",
    honorLabel: n.honor_label || n.honorLabel || "",
    votes,
    likes: Number(n.likes || 0),
    createdAt: n.created_at || n.createdAt || new Date().toISOString(),
    source: n.source || "server",
    stories: n.stories || [],
  };
}

function namingSeeds() {
  return (window.DEER_DATA || []).filter(d => d.featured || d.candidate || d.verified).slice(0, 8).map((d, i) => normalizeNomination({
    id: "seed-" + d.id,
    animalId: d.id,
    proposedName: d.name || ["星湾", "山糖", "海风", "小橘"][i % 4],
    story: d.story || `在${d.region || "大连"}被多位市民记录。这个名字来自第一次清晰相遇，也提醒我们保持距离、轻轻路过。`,
    photoUrl: d.photo,
    contributorName: i % 2 ? "动物城档案员" : "城市观察员",
    votes: Math.max(12, Number(d.sightings || 0) + 8 - i),
    likes: Math.max(4, Number(d.contributors || 0) + 3),
    status: "approved",
    source: "seed",
    createdAt: d.lastSeen || new Date().toISOString(),
  }));
}

function localNominations() {
  try {
    const raw = localStorage.getItem(NAMING_LS);
    const list = raw ? JSON.parse(raw) : [];
    return list.map(normalizeNomination);
  } catch (e) {
    return [];
  }
}

function saveLocalNomination(n) {
  const list = localNominations().filter(x => x.id !== n.id);
  list.unshift(normalizeNomination(n));
  try { localStorage.setItem(NAMING_LS, JSON.stringify(list.slice(0, 80))); } catch (e) {}
}

async function namingApi(path, options = {}) {
  const headers = { ...(options.headers || {}) };
  let body = options.body;
  if (body && typeof body !== "string") {
    headers["Content-Type"] = headers["Content-Type"] || "application/json";
    body = JSON.stringify(body);
  }
  const resp = await fetch("/api/app" + path, { ...options, headers, body, credentials: "same-origin" });
  const json = await resp.json().catch(() => ({}));
  return { ...json, ok: resp.ok && json.ok !== false, httpStatus: resp.status };
}

const dzNaming = {
  async list({ status = "approved", animalId = "" } = {}) {
    const query = new URLSearchParams({ status });
    if (animalId) query.set("animalId", animalId);
    try {
      const r = await namingApi("/nominations?" + query.toString(), { method: "GET" });
      if (r.ok && Array.isArray(r.nominations)) {
        const rows = r.nominations.map(normalizeNomination);
        return rows.length ? rows : [...localNominations(), ...namingSeeds()];
      }
    } catch (e) {}
    return [...localNominations(), ...namingSeeds()];
  },

  async get(id) {
    if (!id) return null;
    try {
      const r = await namingApi("/nominations/" + encodeURIComponent(id), { method: "GET" });
      if (r.ok && r.nomination) {
        const n = normalizeNomination(r.nomination);
        n.stories = r.stories || [];
        return n;
      }
    } catch (e) {}
    return [...localNominations(), ...namingSeeds()].find(n => n.id === id) || null;
  },

  async create(payload) {
    const nomination = normalizeNomination({
      id: "local-" + Date.now().toString(36),
      animalId: payload.animalId,
      proposedName: payload.proposedName,
      story: payload.story,
      keywords: Array.isArray(payload.keywords) ? payload.keywords.join(" · ") : payload.keywords,
      photoDataUrl: payload.photoDataUrl,
      photoUrl: payload.photoUrl,
      contributorName: payload.contributorName,
      status: "approved",
      source: "local",
      votes: 0,
      likes: 0,
    });
    try {
      const r = await namingApi("/nominations", {
        method: "POST",
        body: {
          animalId: payload.animalId,
          proposedName: payload.proposedName,
          story: payload.story,
          keywords: payload.keywords || [],
          photoDataUrl: payload.photoDataUrl,
          photoUrl: payload.photoUrl,
        },
      });
      if (r.ok) {
        nomination.id = r.id;
        nomination.status = r.status || "pending";
        nomination.source = "server";
        saveLocalNomination(nomination);
        return { ok: true, nomination, server: true };
      }
      if (r.error === "unauthorized") return { ok: false, error: "login_required", message: "登录后就能把命名送入真实投票池。" };
      if (r.error && !["setup_required", "method_not_allowed", "not_found"].includes(r.error) && ![404, 405, 503].includes(r.httpStatus)) {
        return { ok: false, error: r.error, message: r.message || "命名提交失败" };
      }
    } catch (e) {}
    saveLocalNomination(nomination);
    return { ok: true, nomination, server: false };
  },

  async vote(id) {
    try {
      const r = await namingApi("/nominations/" + encodeURIComponent(id) + "/vote", { method: "POST", body: { clientId: getClientId() } });
      if (r.ok) return r;
    } catch (e) {}
    return localBump(id, NAMING_VOTES_LS, "votes");
  },

  async like(id) {
    try {
      const r = await namingApi("/nominations/" + encodeURIComponent(id) + "/like", { method: "POST", body: { clientId: getClientId() } });
      if (r.ok) return r;
    } catch (e) {}
    return localBump(id, NAMING_LIKES_LS, "likes");
  },

  async story(id, story) {
    try {
      const r = await namingApi("/nominations/" + encodeURIComponent(id) + "/stories", { method: "POST", body: { story } });
      if (r.ok) return r;
      if (r.error === "unauthorized") return { ok: false, error: "login_required", message: "登录后可以续写它的城市故事。" };
    } catch (e) {}
    return { ok: true, status: "approved" };
  },
};

function getClientId() {
  const key = "dz_client_id_v1";
  let id = localStorage.getItem(key);
  if (!id) {
    id = "web-" + Math.random().toString(36).slice(2) + Date.now().toString(36);
    try { localStorage.setItem(key, id); } catch (e) {}
  }
  return id;
}

function localBump(id, storeKey, countKey) {
  const voted = new Set(JSON.parse(localStorage.getItem(storeKey) || "[]"));
  if (voted.has(id)) return { ok: true, already: true };
  voted.add(id);
  try { localStorage.setItem(storeKey, JSON.stringify([...voted].slice(-300))); } catch (e) {}
  const list = localNominations();
  const item = list.find(n => n.id === id);
  if (item) {
    item[countKey] = Number(item[countKey] || 0) + 1;
    saveLocalNomination(item);
    return { ok: true, votes: item.votes, likes: item.likes };
  }
  return { ok: true };
}

function shareUrlFor(nomination) {
  const route = "name:" + nomination.id;
  return `${location.origin}${location.pathname}#${encodeURIComponent(route)}`;
}

function imageForNomination(nomination, animal) {
  return nomination?.photoDataUrl || nomination?.photoUrl || animal?.photo || "";
}

function SharePosterCard({ nomination, animal, compact = false, onOpen }) {
  const [busy, setBusy] = React.useState(false);
  if (!nomination) return null;
  const actualAnimal = animal || (window.DEER_DATA || []).find(d => d.id === nomination.animalId) || {};
  const url = shareUrlFor(nomination);
  const title = nomination.proposedName || "未命名";

  const downloadPoster = async () => {
    setBusy(true);
    try {
      const canvas = document.createElement("canvas");
      canvas.width = 1080;
      canvas.height = 1500;
      const ctx = canvas.getContext("2d");
      await drawPoster(ctx, canvas, nomination, actualAnimal, url);
      const blob = await new Promise(resolve => canvas.toBlob(resolve, "image/png", 0.95));
      const a = document.createElement("a");
      a.href = URL.createObjectURL(blob);
      a.download = `达里尼动物城-${title}-命名卡.png`;
      a.click();
      setTimeout(() => URL.revokeObjectURL(a.href), 1200);
    } catch (err) {
      alert("海报生成失败，请换一张图片或稍后重试。");
      console.warn(err);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className={"share-poster-card" + (compact ? " compact" : "")}>
      <div className="share-poster-preview">
        <div className="share-poster-image" style={{ backgroundImage: `url(${imageForNomination(nomination, actualAnimal)})` }}>
          <div className="share-poster-badge">城市命名卡</div>
        </div>
        <div className="share-poster-body">
          <div className="share-poster-title">我想叫它「{title}」</div>
          <p>{nomination.story || "一次温柔的城市相遇，正在等待更多人一起记住。"}</p>
          <div className="share-poster-meta">
            <span>{nomination.species || actualAnimal.species || "野生动物"}</span>
            <span>{nomination.contributorName}</span>
          </div>
        </div>
      </div>
      <div className="share-poster-actions">
        <button className="btn btn-accent" onClick={downloadPoster} disabled={busy}>{busy ? "生成中..." : "保存分享海报"}</button>
        <button className="btn btn-ghost" onClick={() => onOpen ? onOpen() : navigator.clipboard?.writeText(url)}>打开投票页</button>
      </div>
    </div>
  );
}

async function drawPoster(ctx, canvas, nomination, animal, url) {
  const w = canvas.width;
  const h = canvas.height;
  const photo = await loadPosterImage(imageForNomination(nomination, animal)).catch(() => null);
  const grad = ctx.createLinearGradient(0, 0, w, h);
  grad.addColorStop(0, "#48d0c1");
  grad.addColorStop(.46, "#ffe084");
  grad.addColorStop(1, "#ff7fa8");
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, w, h);
  ctx.fillStyle = "rgba(255,255,255,.38)";
  for (let i = 0; i < 34; i++) {
    const x = (i * 137) % w;
    const y = (i * 211) % h;
    ctx.beginPath();
    ctx.arc(x, y, 18 + (i % 5) * 5, 0, Math.PI * 2);
    ctx.fill();
  }
  roundRect(ctx, 60, 60, 960, 1380, 54, "rgba(255,255,255,.9)");
  if (photo) drawCoverImage(ctx, photo, 100, 108, 880, 760, 38);
  else roundRect(ctx, 100, 108, 880, 760, 38, "#244a42");
  roundRect(ctx, 126, 134, 250, 58, 29, "rgba(255,255,255,.88)");
  ctx.fillStyle = "#244a42";
  ctx.font = "700 28px Noto Sans SC, sans-serif";
  ctx.fillText("达里尼动物城", 158, 172);
  ctx.font = "96px Noto Serif SC, serif";
  ctx.fillStyle = "#172d2d";
  ctx.fillText(`「${nomination.proposedName}」`, 104, 990);
  ctx.font = "34px Noto Sans SC, sans-serif";
  ctx.fillStyle = "#35625e";
  ctx.fillText(`${animal.species || nomination.species || "城市野生动物"} · ${nomination.contributorName || "城市观察员"}的命名提案`, 110, 1052);
  wrapText(ctx, nomination.story || "我在城市里遇见了它，也希望更多人用温柔的方式记住它。", 110, 1130, 650, 44, 3, "#2f4445", "34px Noto Serif SC, serif");
  const qr = await qrDataUrl(url);
  const qrImg = await loadPosterImage(qr).catch(() => null);
  roundRect(ctx, 760, 1085, 190, 190, 26, "#ffffff");
  if (qrImg) ctx.drawImage(qrImg, 780, 1105, 150, 150);
  ctx.font = "600 24px Noto Sans SC, sans-serif";
  ctx.fillStyle = "#172d2d";
  ctx.fillText("扫码为名字投票", 746, 1318);
  ctx.font = "24px JetBrains Mono, monospace";
  ctx.fillStyle = "#5c7272";
  ctx.fillText("Named & Known · Dalian Wildlife Memory Archive", 110, 1380);
}

function loadPosterImage(src) {
  return new Promise((resolve, reject) => {
    if (!src) { reject(new Error("empty image")); return; }
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}

async function qrDataUrl(url) {
  if (window.QRCode?.toDataURL) {
    return window.QRCode.toDataURL(url, { width: 320, margin: 1, errorCorrectionLevel: "M", color: { dark: "#172d2d", light: "#ffffff" } });
  }
  return "https://api.qrserver.com/v1/create-qr-code/?size=320x320&data=" + encodeURIComponent(url);
}

function roundRect(ctx, x, y, w, h, r, fill) {
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + h, r);
  ctx.arcTo(x + w, y + h, x, y + h, r);
  ctx.arcTo(x, y + h, x, y, r);
  ctx.arcTo(x, y, x + w, y, r);
  ctx.closePath();
  ctx.fillStyle = fill;
  ctx.fill();
}

function drawCoverImage(ctx, img, x, y, w, h, r) {
  ctx.save();
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + h, r);
  ctx.arcTo(x + w, y + h, x, y + h, r);
  ctx.arcTo(x, y + h, x, y, r);
  ctx.arcTo(x, y, x + w, y, r);
  ctx.closePath();
  ctx.clip();
  const scale = Math.max(w / img.width, h / img.height);
  const dw = img.width * scale;
  const dh = img.height * scale;
  ctx.drawImage(img, x + (w - dw) / 2, y + (h - dh) / 2, dw, dh);
  ctx.restore();
}

function wrapText(ctx, text, x, y, maxWidth, lineHeight, maxLines, fill, font) {
  ctx.font = font;
  ctx.fillStyle = fill;
  const chars = String(text || "").split("");
  let line = "";
  let lines = 0;
  for (const ch of chars) {
    const test = line + ch;
    if (ctx.measureText(test).width > maxWidth && line) {
      ctx.fillText(line, x, y + lines * lineHeight);
      line = ch;
      lines += 1;
      if (lines >= maxLines) return;
    } else {
      line = test;
    }
  }
  if (line && lines < maxLines) ctx.fillText(line, x, y + lines * lineHeight);
}

function NamingPage({ nominationId, onNav }) {
  const [items, setItems] = React.useState([]);
  const [selected, setSelected] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [msg, setMsg] = React.useState("");
  const [story, setStory] = React.useState("");

  const load = async () => {
    setLoading(true);
    const list = await dzNaming.list({ status: "approved" });
    setItems(list);
    const detail = nominationId ? await dzNaming.get(nominationId) : null;
    setSelected(detail || list[0] || null);
    setLoading(false);
  };

  React.useEffect(() => { load(); }, [nominationId]);

  const pick = async (item) => {
    const detail = await dzNaming.get(item.id);
    setSelected(detail || item);
    onNav && onNav("name:" + item.id);
  };

  const vote = async () => {
    if (!selected) return;
    const r = await dzNaming.vote(selected.id);
    setSelected(s => ({ ...s, votes: r.votes != null ? Number(r.votes) : (r.already ? s.votes : Number(s.votes || 0) + 1) }));
    setMsg(r.already ? "你已经为这个名字加过声量了。" : "已把你的声量加入命名榜。");
  };

  const like = async () => {
    if (!selected) return;
    const r = await dzNaming.like(selected.id);
    setSelected(s => ({ ...s, likes: r.likes != null ? Number(r.likes) : (r.already ? s.likes : Number(s.likes || 0) + 1) }));
  };

  const addStory = async () => {
    if (!story.trim()) return;
    const r = await dzNaming.story(selected.id, story);
    if (!r.ok) { setMsg(r.message || "续写失败"); return; }
    setMsg(r.status === "pending" ? "故事已送审，通过后会出现。" : "故事已加入这张城市记忆卡。");
    setSelected(s => ({ ...s, stories: [{ id: "tmp-" + Date.now(), author_name: "你", story, created_at: new Date().toISOString() }, ...(s.stories || [])] }));
    setStory("");
  };

  const animal = selected ? (window.DEER_DATA || []).find(d => d.id === selected.animalId) : null;

  return (
    <div className="page naming-page">
      <section className="naming-hero">
        <div className="app naming-hero-grid">
          <div>
            <div className="kicker"><span className="dot">●</span>NAMING / 城市命名权</div>
            <h1 className="serif">给它一个名字，<br />让一次相遇有回声。</h1>
            <p>名字不是占有，而是一次认真记住。上传照片、写下故事、邀请朋友投票，让温柔的影响力把动物城档案慢慢点亮。</p>
            <div className="naming-hero-actions">
              <button className="btn btn-accent" onClick={() => onNav("upload")}>上传并发起命名</button>
              <button className="btn btn-ghost" onClick={() => onNav("archive")}>查看动物档案</button>
            </div>
          </div>
          <div className="naming-emoji-wall" aria-hidden="true">
            <span>🦌</span><span>🦊</span><span>🦭</span><span>🐦</span><span>🌊</span><span>✨</span>
          </div>
        </div>
      </section>

      <section className="section">
        <div className="app naming-layout">
          <aside className="naming-list">
            <div className="naming-list-head">
              <strong>命名榜</strong>
              <span>{loading ? "同步中..." : `${items.length} 个名字`}</span>
            </div>
            {items.map((item, i) => (
              <button key={item.id} className={"naming-row" + (selected?.id === item.id ? " active" : "")} onClick={() => pick(item)}>
                <img src={imageForNomination(item, (window.DEER_DATA || []).find(d => d.id === item.animalId))} alt="" />
                <span>
                  <b>{item.proposedName}</b>
                  <small>{item.species} · {item.votes} 票</small>
                </span>
                <em>#{i + 1}</em>
              </button>
            ))}
          </aside>

          <main className="naming-detail">
            {selected && (
              <>
                <div className="naming-photo-card">
                  <img src={imageForNomination(selected, animal)} alt="" />
                  <div className="naming-floating-card">
                    <span>{selected.species || animal?.species}</span>
                    <strong>我想叫它「{selected.proposedName}」</strong>
                    <small>{selected.contributorName} 发起 · {String(selected.createdAt).slice(0, 10)}</small>
                  </div>
                </div>

                <div className="naming-vote-panel">
                  <div>
                    <div className="naming-score">{selected.votes}</div>
                    <div className="mono">NAMING VOTES</div>
                  </div>
                  <div>
                    <h2 className="serif">为「{selected.proposedName}」加一点声量</h2>
                    <p>{selected.story}</p>
                    {msg && <div className="naming-msg">{msg}</div>}
                    <div className="naming-actions">
                      <button className="btn btn-accent" onClick={vote}>投它一票</button>
                      <button className="btn btn-ghost" onClick={like}>喜欢这个故事 · {selected.likes}</button>
                      <button className="btn btn-ghost" onClick={() => navigator.clipboard?.writeText(shareUrlFor(selected))}>复制投票链接</button>
                    </div>
                  </div>
                </div>

                <div className="naming-two-col">
                  <SharePosterCard nomination={selected} animal={animal} onOpen={() => onNav("name:" + selected.id)} />
                  <div className="naming-story-box">
                    <h3>续写它的城市故事</h3>
                    <p>你也见过它，或者想给这个名字留一句祝福？写下来，审核通过后会成为这张档案卡的一部分。</p>
                    <textarea value={story} onChange={e => setStory(e.target.value)} placeholder="例如：它从灌木后面抬头看了一眼，我们没有靠近，只在原地等它慢慢离开。" />
                    <button className="btn btn-accent" onClick={addStory}>提交故事</button>
                    <div className="naming-stories">
                      {(selected.stories || []).slice(0, 4).map(s => (
                        <div key={s.id}>
                          <b>{s.author_name || s.authorName || "城市观察员"}</b>
                          <span>{s.story}</span>
                        </div>
                      ))}
                    </div>
                  </div>
                </div>
              </>
            )}
          </main>
        </div>
      </section>
    </div>
  );
}

Object.assign(window, { dzNaming, NamingPage, SharePosterCard, normalizeNomination });
