// 読者向けマイページ（ポイント残高 / 抽選会ガチャ / 景品交換）
// メール内リンク /p/:token から到達。トークンが本人認証を兼ねる。
import type {
  ExchangeItem,
  ExchangeRedemption,
  Lottery,
  LotteryDraw,
  LotteryPrize,
  PointEvent,
  Subscriber,
} from "./types.js";
import { escapeHtml } from "./util.js";

export interface PortalData {
  subscriber: Subscriber;
  balance: number;
  events: PointEvent[];
  lotteries: (Lottery & { prizes: LotteryPrize[] })[];
  items: (ExchangeItem & { remaining: number | null })[];
  myDraws: (LotteryDraw & { lottery_name: string; prize_name: string | null })[];
  myRedemptions: (ExchangeRedemption & { item_name: string })[];
}

const KIND_LABEL: Record<string, string> = {
  open: "メール開封",
  click: "リンククリック",
  signup: "登録ボーナス",
  manual: "運営から付与",
  lottery: "くじ参加",
  exchange: "景品交換",
};

export function portalPage(d: PortalData, token: string): string {
  const name = d.subscriber.name ? `${escapeHtml(d.subscriber.name)} さん` : "メンバー";
  const lotteryCards = d.lotteries
    .map((l) => {
      const prizeRows = l.prizes
        .filter((p) => p.active)
        .map(
          (p) => `<li><span class="p-name">🎁 ${escapeHtml(p.name)}</span><span class="p-odds">${p.probability}%</span></li>`,
        )
        .join("");
      const disabled = d.balance < l.cost_points;
      return `
      <div class="card lottery">
        <div class="l-head">
          <h3>${escapeHtml(l.name)}</h3>
          <span class="cost">1回 <b>${l.cost_points}</b> pt</span>
        </div>
        ${l.description ? `<p class="desc">${escapeHtml(l.description)}</p>` : ""}
        <ul class="prizes">${prizeRows || "<li class='muted'>景品準備中</li>"}</ul>
        <button class="draw-btn" data-lottery="${l.id}" data-cost="${l.cost_points}" ${disabled ? "disabled" : ""}>
          ${disabled ? `あと ${l.cost_points - d.balance} pt で挑戦できます` : "🎰 くじを引く"}
        </button>
      </div>`;
    })
    .join("");

  const itemCards = d.items
    .map((it) => {
      const soldOut = it.remaining !== null && it.remaining <= 0;
      const disabled = soldOut || d.balance < it.cost_points;
      return `
      <div class="card item">
        <div class="l-head"><h3>${escapeHtml(it.name)}</h3><span class="cost"><b>${it.cost_points}</b> pt</span></div>
        ${it.description ? `<p class="desc">${escapeHtml(it.description)}</p>` : ""}
        ${it.remaining !== null ? `<p class="stock">残り ${Math.max(0, it.remaining)} 個</p>` : ""}
        <button class="ex-btn" data-item="${it.id}" ${disabled ? "disabled" : ""}>
          ${soldOut ? "在庫切れ" : d.balance < it.cost_points ? `あと ${it.cost_points - d.balance} pt` : "🎁 交換する"}
        </button>
      </div>`;
    })
    .join("");

  const historyRows = d.events
    .slice(0, 30)
    .map(
      (e) => `<tr><td>${new Date(e.created_at).toLocaleDateString("ja-JP")}</td>
      <td>${escapeHtml(KIND_LABEL[e.kind] ?? e.kind)}${e.note ? `<span class="note"> — ${escapeHtml(e.note)}</span>` : ""}</td>
      <td class="${e.points >= 0 ? "plus" : "minus"}">${e.points >= 0 ? "+" : ""}${e.points}</td></tr>`,
    )
    .join("");

  const winRows = d.myDraws
    .filter((x) => x.prize_id !== null)
    .slice(0, 20)
    .map(
      (x) => `<tr><td>${new Date(x.drawn_at).toLocaleDateString("ja-JP")}</td>
      <td>🏆 ${escapeHtml(x.prize_name ?? "")}</td>
      <td>${x.fulfilled ? "お届け済み" : "手配中"}</td></tr>`,
    )
    .join("");
  const redemptionRows = d.myRedemptions
    .slice(0, 20)
    .map(
      (x) => `<tr><td>${new Date(x.created_at).toLocaleDateString("ja-JP")}</td>
      <td>🎁 ${escapeHtml(x.item_name)}</td>
      <td>${x.status === "done" ? "お届け済み" : x.status === "canceled" ? "キャンセル" : "手配中"}</td></tr>`,
    )
    .join("");

  return `<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex">
<title>ポイントマイページ</title>
<style>
  :root { --bg:#fdf9f3; --card:#ffffff; --line:#f2e8da; --ink:#4c4237; --muted:#a3937f;
          --coral:#e56b50; --coral-soft:#ffece5; --gold:#e8a13b; --gold-soft:#fff3dd;
          --sky:#4f86e8; --sky-soft:#e6f0ff; --mint:#2fa878; --rose:#e5677e; }
  * { box-sizing:border-box; margin:0; }
  body { font-family:system-ui,-apple-system,"Hiragino Kaku Gothic ProN",sans-serif; background:
    radial-gradient(800px 400px at 85% -10%, rgba(232,161,59,.14), transparent),
    radial-gradient(600px 300px at -5% 15%, rgba(229,107,80,.10), transparent),
    radial-gradient(700px 400px at 50% 110%, rgba(79,134,232,.08), transparent), var(--bg);
    color:var(--ink); min-height:100vh; padding:20px 16px 60px; font-size:16px; }
  .wrap { max-width:640px; margin:0 auto; }
  header { text-align:center; padding:12px 0 20px; }
  header .hello { color:var(--muted); font-size:14px; }
  h1 { font-size:22px; margin-top:4px; }
  .balance { background:linear-gradient(140deg,#fff7e8,#fff 70%); border:1px solid #f7e6c8; border-radius:22px;
    padding:28px; text-align:center; box-shadow:0 16px 44px rgba(210,150,60,.14); margin-bottom:26px; }
  .balance .label { color:var(--muted); font-size:13px; letter-spacing:.14em; }
  .balance .num { font-size:50px; font-weight:800; color:var(--gold); line-height:1.2; }
  .balance .num small { font-size:19px; color:var(--muted); font-weight:600; margin-left:4px; }
  .howto { font-size:13.5px; color:var(--muted); margin-top:10px; line-height:1.8; }
  h2 { font-size:17px; margin:28px 0 12px; display:flex; align-items:center; gap:8px; }
  .card { background:var(--card); border:1px solid var(--line); border-radius:18px; padding:20px;
    margin-bottom:14px; box-shadow:0 8px 26px rgba(180,140,90,.08); }
  .l-head { display:flex; justify-content:space-between; align-items:center; gap:10px; }
  .l-head h3 { font-size:16.5px; }
  .cost { font-size:13px; color:var(--muted); white-space:nowrap; }
  .cost b { color:var(--coral); font-size:17px; }
  .desc { font-size:14px; color:var(--muted); margin-top:6px; line-height:1.8; }
  .stock { font-size:13.5px; color:var(--sky); margin-top:6px; }
  .prizes { list-style:none; padding:0; margin:12px 0; display:flex; flex-direction:column; gap:6px; }
  .prizes li { display:flex; justify-content:space-between; background:var(--gold-soft);
    border-radius:10px; padding:9px 14px; font-size:14.5px; }
  .p-odds { color:#c2914f; font-size:13px; font-weight:700; }
  button { width:100%; padding:15px; border:none; border-radius:14px; font-size:16.5px; font-weight:700;
    font-family:inherit; cursor:pointer; background:linear-gradient(135deg,var(--coral),#ef8a52); color:#fff;
    box-shadow:0 8px 20px rgba(229,107,80,.28); transition:transform .15s; }
  button:active { transform:scale(.97); }
  button:disabled { background:#f1e9dd; color:var(--muted); box-shadow:none; cursor:default; }
  .ex-btn { background:linear-gradient(135deg,var(--sky),#6d9bef); box-shadow:0 8px 20px rgba(79,134,232,.25); }
  table { width:100%; border-collapse:collapse; font-size:14px; }
  td { padding:9px 6px; border-bottom:1px solid var(--line); }
  tr:last-child td { border-bottom:none; }
  .plus { color:var(--mint); font-weight:700; text-align:right; }
  .minus { color:var(--rose); font-weight:700; text-align:right; }
  .note { color:var(--muted); }
  .muted { color:var(--muted); }
  .empty { color:var(--muted); font-size:14px; text-align:center; padding:14px; }
  /* ガチャ演出 */
  #overlay { position:fixed; inset:0; background:rgba(253,248,240,.94); display:none; align-items:center;
    justify-content:center; z-index:50; backdrop-filter:blur(6px); }
  #overlay.show { display:flex; }
  .gacha-box { text-align:center; padding:30px; }
  .capsule { font-size:96px; display:inline-block; }
  .shaking { animation:shake .55s ease-in-out infinite; }
  @keyframes shake { 0%,100%{transform:rotate(0) translateY(0)} 25%{transform:rotate(-14deg) translateY(-8px)}
    75%{transform:rotate(14deg) translateY(-8px)} }
  .result-title { font-size:28px; font-weight:800; margin-top:18px; }
  .result-title.win { color:var(--gold); text-shadow:0 2px 18px rgba(232,161,59,.35); }
  .result-sub { color:var(--muted); margin-top:8px; font-size:15.5px; line-height:1.9; }
  .close-btn { margin-top:24px; max-width:220px; }
  footer { text-align:center; color:var(--muted); font-size:12.5px; margin-top:40px; line-height:1.9; }
</style>
</head>
<body>
<div class="wrap">
  <header>
    <div class="hello">${name}</div>
    <h1>🎪 ポイントマイページ</h1>
  </header>

  <div class="balance">
    <div class="label">いまのポイント</div>
    <div class="num" id="balance">${d.balance}<small>pt</small></div>
    <div class="howto">メールを開いたり、メール内のリンクをタップするとポイントが貯まります。<br>貯まったポイントで下の抽選会に挑戦したり、景品と交換できます。</div>
  </div>

  <h2>🎰 開催中の抽選会</h2>
  ${lotteryCards || `<div class="card empty">いま開催中の抽選会はありません。次回をお楽しみに！</div>`}

  <h2>🎁 ポイント交換</h2>
  ${itemCards || `<div class="card empty">交換できる景品は準備中です。</div>`}

  ${winRows || redemptionRows ? `<h2>🏆 当選・交換したもの</h2>
  <div class="card"><table>${winRows}${redemptionRows}</table></div>` : ""}

  <h2>📖 ポイント履歴</h2>
  <div class="card">
    ${historyRows ? `<table>${historyRows}</table>` : `<div class="empty">まだ履歴がありません</div>`}
  </div>

  <footer>ポイント・景品に関するお問い合わせは、お届けしているメールへの返信でお願いします。</footer>
</div>

<div id="overlay">
  <div class="gacha-box">
    <div class="capsule" id="capsule">🎰</div>
    <div class="result-title" id="r-title"></div>
    <div class="result-sub" id="r-sub"></div>
    <button class="close-btn" id="r-close" style="display:none">とじる</button>
  </div>
</div>

<script>
const TOKEN = ${JSON.stringify(token)};
const overlay = document.getElementById("overlay");
const capsule = document.getElementById("capsule");
const rTitle = document.getElementById("r-title");
const rSub = document.getElementById("r-sub");
const rClose = document.getElementById("r-close");
rClose.addEventListener("click", () => location.reload());

document.querySelectorAll(".draw-btn").forEach((btn) => {
  btn.addEventListener("click", async () => {
    btn.disabled = true;
    overlay.classList.add("show");
    capsule.textContent = "🎰";
    capsule.classList.add("shaking");
    rTitle.textContent = ""; rSub.textContent = ""; rClose.style.display = "none";
    const started = Date.now();
    let data;
    try {
      const res = await fetch("/p/" + TOKEN + "/lottery/" + btn.dataset.lottery + "/draw", { method: "POST" });
      data = await res.json();
    } catch { data = { ok: false, error: "通信エラーが発生しました" }; }
    // 演出のため最低1.6秒はドキドキさせる
    await new Promise((r) => setTimeout(r, Math.max(0, 1600 - (Date.now() - started))));
    capsule.classList.remove("shaking");
    if (!data.ok) {
      capsule.textContent = "😢";
      rTitle.textContent = "くじを引けませんでした";
      rSub.textContent = data.error || "";
    } else if (data.win) {
      capsule.textContent = "🎉";
      rTitle.textContent = data.prize.name + " 当選！";
      rTitle.classList.add("win");
      rSub.innerHTML = "おめでとうございます！<br>景品は運営からお届けします。残り " + data.balance + " pt";
    } else {
      capsule.textContent = "🥲";
      rTitle.textContent = "はずれ…";
      rSub.textContent = "また挑戦してね！ 残り " + data.balance + " pt";
    }
    rClose.style.display = "";
  });
});

document.querySelectorAll(".ex-btn").forEach((btn) => {
  btn.addEventListener("click", async () => {
    if (!confirm("ポイントを使ってこの景品と交換しますか？")) return;
    btn.disabled = true;
    let data;
    try {
      const res = await fetch("/p/" + TOKEN + "/exchange/" + btn.dataset.item, { method: "POST" });
      data = await res.json();
    } catch { data = { ok: false, error: "通信エラーが発生しました" }; }
    if (data.ok) {
      alert("交換を受け付けました！景品は運営からお届けします。");
      location.reload();
    } else {
      alert(data.error || "交換できませんでした");
      btn.disabled = false;
    }
  });
});
</script>
</body>
</html>`;
}
