import { fetchSessionUser, fetchBlackjackState, postBlackjackAction } from "../auth/session.js";
import { showToast } from "../components/toast.js";
import { t } from "../config/site.js";

const SUITS = { c: "♣", d: "♦", h: "♥", s: "♠" };

const BJ_MUTE_KEY = "bj_sound_muted";

function fmtCoins(n) {
  const v = Math.max(0, Math.floor(Number(n) || 0));
  try {
    return new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(v);
  } catch {
    return String(v);
  }
}

const BJ_STYLE = `
@keyframes bjCardDealIn {
  from {
    opacity: 0;
    transform: translateY(-42px) translateX(-6px) rotate(-7deg) scale(0.88);
    filter: blur(1px);
  }
  55% {
    opacity: 1;
    transform: translateY(4px) rotate(1.5deg) scale(1.02);
    filter: blur(0);
  }
  to {
    opacity: 1;
    transform: translateY(0) rotate(0) scale(1);
    filter: blur(0);
  }
}
@keyframes bjHoleFlip {
  0% {
    transform: rotateY(92deg) scale(0.92);
    filter: brightness(1.4);
  }
  55% {
    transform: rotateY(-8deg) scale(1.03);
    filter: brightness(1.15);
  }
  to {
    transform: rotateY(0) scale(1);
    filter: brightness(1);
  }
}
.bj-table-surface {
  perspective: 1100px;
}
.bj-card-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: center;
  gap: 0;
}
@media (min-width: 640px) {
  .bj-card-row {
    justify-content: flex-start;
  }
}
.bj-card-slot {
  position: relative;
}
.bj-card-slot--overlap {
  margin-left: -1.1rem;
}
@media (min-width: 640px) {
  .bj-card-slot--overlap {
    margin-left: -1.35rem;
  }
}
.bj-card-wrap {
  transform-style: preserve-3d;
  will-change: transform, opacity;
}
.bj-card-wrap--deal {
  animation: bjCardDealIn 0.52s cubic-bezier(0.22, 1, 0.32, 1) forwards;
  animation-delay: var(--bj-d, 0ms);
  opacity: 0;
}
.bj-card-wrap--hole-flip {
  animation: bjHoleFlip 0.48s cubic-bezier(0.34, 1.2, 0.64, 1) forwards;
  animation-delay: var(--bj-d, 0ms);
  transform-origin: center center;
}

.bj-card-face {
  position: relative;
  box-sizing: border-box;
  display: inline-block;
  flex-shrink: 0;
  width: 5.1rem;
  height: 7.35rem;
  overflow: hidden;
  border-radius: 10px;
  background: #f4f1ea;
  box-shadow: 0 10px 28px -6px rgba(0, 0, 0, 0.55), inset 0 1px 0 #fff;
  border: 1px solid rgba(0, 0, 0, 0.12);
}
@media (min-width: 640px) {
  .bj-card-face {
    width: 5.45rem;
    height: 7.85rem;
  }
}
.bj-card-face__shine {
  pointer-events: none;
  position: absolute;
  inset: 0;
  border-radius: 9px;
  opacity: 0.4;
  background: radial-gradient(ellipse 90% 45% at 50% 0%, rgba(255, 255, 255, 0.9), transparent 50%);
}
.bj-card-face__corner {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  font-weight: 900;
  letter-spacing: -0.02em;
}
.bj-card-face__corner--tl {
  left: 0.25rem;
  top: 0.25rem;
}
.bj-card-face__corner--br {
  right: 0.25rem;
  bottom: 0.25rem;
  transform: rotate(180deg);
}
.bj-card-face__rank {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
@media (min-width: 640px) {
  .bj-card-face__rank {
    font-size: 12px;
  }
}
.bj-card-face__suit-sm {
  margin-top: -1px;
  font-size: 12px;
  line-height: 1;
}
@media (min-width: 640px) {
  .bj-card-face__suit-sm {
    font-size: 13px;
  }
}
.bj-card-face__center {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
  font-size: 2.5rem;
  line-height: 1;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.12));
}
@media (min-width: 640px) {
  .bj-card-face__center {
    font-size: 2.85rem;
  }
}
.bj-card-face--red {
  color: #e01a1a;
}
.bj-card-face--black {
  color: #111827;
}

.bj-card-back {
  position: relative;
  box-sizing: border-box;
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 5.1rem;
  height: 7.35rem;
  overflow: hidden;
  border-radius: 10px;
  background: linear-gradient(135deg, #1c3d6e 0%, #152b52 45%, #0c1524 100%);
  box-shadow: 0 10px 28px -6px rgba(0, 0, 0, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.2);
}
@media (min-width: 640px) {
  .bj-card-back {
    width: 5.45rem;
    height: 7.85rem;
  }
}
.bj-card-back__pattern {
  position: absolute;
  inset: 5px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  opacity: 0.9;
  background: repeating-linear-gradient(-32deg, rgba(255, 255, 255, 0.07) 0, rgba(255, 255, 255, 0.07) 2px, transparent 2px, transparent 7px),
    repeating-linear-gradient(32deg, rgba(255, 255, 255, 0.05) 0, rgba(255, 255, 255, 0.05) 2px, transparent 2px, transparent 8px);
}
.bj-card-back__badge {
  position: relative;
  z-index: 1;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  background: rgba(255, 255, 255, 0.07);
  padding: 0.25rem 0.5rem;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.35em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
}
.bj-page {
  --bj-accent: #69b1ff;
  --bj-accent-dim: #1677ff;
  --bj-highlight-bright: #dbeafe;
  margin-left: auto;
  margin-right: auto;
  max-width: 52rem;
  padding-left: 0.25rem;
  padding-right: 0.25rem;
  padding-bottom: 1.5rem;
}
.bj-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  font-size: 0.875rem;
}
.bj-toolbar a {
  color: var(--bj-accent);
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.bj-toolbar a::before { content: "←"; opacity: 0.7; }
.bj-toolbar a:hover { color: var(--bj-highlight-bright); text-decoration: underline; }
.bj-toolbar-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.65rem;
}
.bj-balance-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.9rem 0.4rem 0.65rem;
  border-radius: 999px;
  font-size: 0.8125rem;
  color: #94a3b8;
  background: linear-gradient(135deg, rgba(30,42,61,0.9) 0%, rgba(8,12,20,0.95) 100%);
  border: 1px solid rgba(22,119,255,0.35);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 4px 16px -6px rgba(0,0,0,0.5);
}
.bj-balance-pill__icon {
  width: 1.5rem; height: 1.5rem; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-size: 0.75rem;
  background: linear-gradient(145deg, #b8d4f5 0%, var(--bj-accent-dim) 55%, #355a8a 100%);
  color: #0a0e14; font-weight: 800;
}
.bj-balance-pill strong {
  color: var(--bj-highlight-bright);
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: 0.9375rem;
}
@keyframes bjMarqueeBlink {
  0%, 100% { opacity: 0.35; transform: scale(0.85); }
  50% { opacity: 1; transform: scale(1); }
}
.bj-shell {
  position: relative;
  border-radius: 1.75rem;
  overflow: hidden;
  border: 2px solid rgba(22,119,255,0.35);
  background:
    radial-gradient(ellipse 120% 80% at 50% -20%, rgba(22,119,255,0.16), transparent 50%),
    linear-gradient(168deg, #1a2438 0%, #121a28 38%, #080c14 100%);
  box-shadow: 0 32px 64px -24px rgba(0,0,0,0.75), inset 0 1px 0 rgba(255,255,255,0.08);
}
.bj-marquee {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.45rem 0.65rem;
  padding: 0.85rem 1rem 0.8rem;
  margin: 0;
  min-height: 3rem;
  background: linear-gradient(180deg, rgba(22,119,255,0.16) 0%, rgba(0,0,0,0.22) 100%);
  border-bottom: 1px solid rgba(22,119,255,0.28);
}
.bj-marquee__lights { display: flex; gap: 5px; flex-shrink: 0; }
.bj-marquee__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--bj-accent);
  box-shadow: 0 0 6px var(--bj-accent);
  animation: bjMarqueeBlink 1.2s ease-in-out infinite;
}
.bj-marquee__dot:nth-child(2) { animation-delay: 0.15s; }
.bj-marquee__dot:nth-child(3) { animation-delay: 0.3s; }
.bj-marquee__dot:nth-child(4) { animation-delay: 0.45s; }
.bj-marquee__dot:nth-child(5) { animation-delay: 0.6s; }
@media (max-width: 400px) {
  .bj-marquee__lights { display: none; }
}
.bj-marquee__title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: clamp(0.6875rem, 2.4vw, 0.8125rem);
  font-weight: 800;
  letter-spacing: 0.08em;
  line-height: 1.35;
  text-align: center;
  text-transform: uppercase;
  color: var(--bj-highlight-bright);
  text-shadow: 0 0 18px rgba(22,119,255,0.4);
  padding: 0.1rem 0.35rem;
}
.bj-shell__inner { padding: 1rem 1rem 1.35rem; }
@media (min-width: 640px) { .bj-shell__inner { padding: 1.25rem 1.5rem 1.5rem; } }
.bj-machine-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.65rem;
}
.bj-badge {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bj-accent);
  border: 1px solid rgba(22,119,255,0.4);
  border-radius: 999px;
  padding: 0.3rem 0.7rem;
  background: rgba(22,119,255,0.12);
}
.bj-limits-hint { font-size: 0.6875rem; color: #64748b; }
.bj-rules {
  font-size: 0.6875rem;
  line-height: 1.55;
  color: #64748b;
  margin-bottom: 0.85rem;
  text-align: center;
}
.bj-table-wrap {
  padding: 0.55rem;
  border-radius: 1.25rem;
  background: linear-gradient(145deg, rgba(22,119,255,0.2) 0%, rgba(20,83,45,0.4) 100%);
  box-shadow: inset 0 2px 4px rgba(255,255,255,0.08), 0 8px 24px -8px rgba(0,0,0,0.45);
}
.bj-table {
  border-radius: 1rem;
  border: 2px solid rgba(0,0,0,0.35);
  background:
    radial-gradient(ellipse 90% 50% at 50% 0%, rgba(74,222,128,0.12), transparent 55%),
    linear-gradient(165deg, #166534 0%, #15803d 42%, #14532d 100%);
  padding: 1.15rem 1rem 1.25rem;
  box-shadow: inset 0 6px 28px rgba(0,0,0,0.35);
}
@media (min-width: 640px) {
  .bj-table { padding: 1.35rem 1.25rem 1.5rem; }
}
.bj-msg {
  margin-bottom: 1rem;
  text-align: center;
  font-size: 0.875rem;
  color: #e2e8f0;
  text-shadow: 0 1px 2px rgba(0,0,0,0.35);
}
.bj-msg--highlight {
  color: var(--bj-highlight-bright);
  font-weight: 600;
}
.bj-hand-label {
  margin-bottom: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(226, 232, 240, 0.85);
}
.bj-hand-label__title {
  text-transform: none;
  letter-spacing: normal;
  font-size: 11px;
  font-weight: 600;
  color: rgba(241, 245, 249, 0.9);
}
.bj-hand-total {
  display: inline-block;
  font-size: 0.8125rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: normal;
  text-transform: none;
  color: #e2e8f0;
  padding: 0.2rem 0.55rem;
  border-radius: 0.375rem;
  background: rgba(22,119,255, 0.1);
  border: 1px solid rgba(22,119,255, 0.22);
}
.bj-hand-area {
  display: flex;
  min-height: 8rem;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: center;
  padding-top: 0.25rem;
  padding-bottom: 0.25rem;
}
@media (min-width: 640px) {
  .bj-hand-area {
    justify-content: flex-start;
  }
}
.bj-console {
  margin-top: 1.15rem;
  padding: 1rem;
  border-radius: 1.1rem;
  background: linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0.15) 100%);
  border: 1px solid rgba(255,255,255,0.06);
}
.bj-controls {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.bj-bet-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
}
.bj-field {
  flex: 1;
  min-width: 8rem;
}
.bj-label {
  margin-bottom: 0.25rem;
  display: block;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  color: #64748b;
}
.bj-input {
  width: 100%;
  border-radius: 0.75rem;
  border: 1px solid rgba(22,119,255,0.28);
  background: rgba(0,0,0,0.4);
  padding: 0.7rem 0.9rem;
  font-size: 1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--bj-highlight-bright);
  outline: none;
}
.bj-input:focus {
  border-color: rgba(22,119,255,0.5);
  box-shadow: 0 0 0 3px rgba(22,119,255,0.12);
}
.bj-deal-btn {
  flex-shrink: 0;
  min-height: 3rem;
  border-radius: 1rem;
  padding: 0.75rem 1.5rem;
  font-weight: 800;
  font-size: 0.9375rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: linear-gradient(180deg, #7eb8f0 0%, var(--bj-accent-dim) 48%, #355a8a 100%);
  color: #0a0e14;
  border: 2px solid rgba(22,119,255,0.45);
  box-shadow: 0 12px 32px -8px rgba(22,119,255,0.45);
  cursor: pointer;
  transition: transform 0.15s, filter 0.15s;
}
.bj-deal-btn:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }
.bj-deal-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.bj-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  justify-content: center;
}
.bj-chip {
  min-width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  background: radial-gradient(circle at 35% 30%, #3d4f68 0%, #1a2435 55%, #0d121c 100%);
  border: 3px dashed rgba(22,119,255,0.35);
  color: var(--bj-highlight-bright);
  box-shadow: 0 4px 12px rgba(0,0,0,0.35);
  cursor: pointer;
  transition: transform 0.15s, border-color 0.2s;
}
.bj-chip:hover:not(:disabled) {
  transform: translateY(-2px) scale(1.05);
  border-color: rgba(22,119,255,0.6);
}
.bj-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.bj-sound-toggle {
  border-radius: 999px;
  border: 1px solid rgba(22,119,255,0.28);
  background: rgba(0,0,0,0.35);
  padding: 0.4rem 0.75rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: #cbd5e1;
  cursor: pointer;
}
.bj-sound-toggle:hover {
  border-color: rgba(22,119,255,0.5);
  color: var(--bj-highlight-bright);
}
.bj-dash {
  color: #64748b;
}
.bj-hand-block {
  margin-bottom: 1.5rem;
}
/* Light theme */
[data-theme="light"] .bj-page {
  --bj-highlight-bright: #0958d8;
}
[data-theme="light"] .bj-shell {
  border-color: rgba(22,119,255,0.3);
  background: linear-gradient(168deg, #fff 0%, #f1f5f9 50%, #e8eef5 100%);
  box-shadow: 0 20px 50px -20px rgba(15,23,42,0.12);
}
[data-theme="light"] .bj-marquee {
  background: linear-gradient(180deg, rgba(22,119,255,0.1) 0%, #f8fafc 100%);
}
[data-theme="light"] .bj-balance-pill {
  background: #fff;
  border-color: rgba(22,119,255,0.28);
}
[data-theme="light"] .bj-balance-pill strong { color: #0958d8; }
[data-theme="light"] .bj-rules {
  color: #64748b;
}
[data-theme="light"] .bj-toolbar a {
  color: #1677ff;
}
[data-theme="light"] .bj-toolbar a:hover {
  color: #0958d8;
}
[data-theme="light"] .bj-table-wrap {
  background: linear-gradient(145deg, rgba(22,119,255,0.12) 0%, rgba(34,197,94,0.15) 100%);
}
[data-theme="light"] .bj-table {
  border-color: rgba(21, 128, 61, 0.35);
  background: linear-gradient(165deg, #dcfce7 0%, #bbf7d0 48%, #86efac 100%);
  box-shadow: inset 0 4px 20px rgba(21, 128, 61, 0.12);
}
[data-theme="light"] .bj-msg { color: #334155; text-shadow: none; }
[data-theme="light"] .bj-console { background: #f1f5f9; border-color: rgba(22,119,255,0.12); }
[data-theme="light"] .bj-msg {
  color: #334155;
}
[data-theme="light"] .bj-hand-label,
[data-theme="light"] .bj-hand-label__title {
  color: #475569;
}
[data-theme="light"] .bj-hand-total {
  color: #003eb3;
  background: rgba(22, 119, 255, 0.12);
  border-color: rgba(22, 119, 255, 0.28);
}
[data-theme="light"] .bj-controls {
  border-color: rgba(22, 119, 255, 0.16);
  background: #ffffff;
  box-shadow: 0 4px 20px -12px rgba(15, 23, 42, 0.1);
}
[data-theme="light"] .bj-label {
  color: #64748b;
}
[data-theme="light"] .bj-input {
  color: #0f172a;
  background: #ffffff;
  border-color: rgba(22, 119, 255, 0.22);
}
[data-theme="light"] .bj-input:focus {
  border-color: rgba(22, 119, 255, 0.45);
  box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.14);
}
[data-theme="light"] .bj-chip {
  color: #334155;
  background: #f1f5f9;
  border-color: rgba(22, 119, 255, 0.18);
}
[data-theme="light"] .bj-chip:hover {
  color: #0f172a;
  background: #e2e8f0;
  border-color: rgba(22, 119, 255, 0.35);
}
[data-theme="light"] .bj-sound-toggle {
  color: #334155;
  background: #ffffff;
  border-color: rgba(22, 119, 255, 0.18);
}
[data-theme="light"] .bj-sound-toggle:hover {
  color: #0f172a;
  border-color: rgba(22, 119, 255, 0.32);
}
[data-theme="light"] .bj-dash {
  color: #64748b;
}
[data-theme="light"] .bj-guest-panel {
  color: #334155;
}
[data-theme="light"] .bj-guest-panel .bj-guest-hint {
  color: #64748b;
}
[data-theme="light"] .bj-error-panel {
  color: #334155;
  background: linear-gradient(180deg, #fff 0%, #f8fafc 100%);
  border-color: rgba(22, 119, 255, 0.2);
}
`;

function ensureBjStyles() {
  if (typeof document === "undefined") return;
  let el = document.getElementById("bj-inline-styles");
  if (!el) {
    el = document.createElement("style");
    el.id = "bj-inline-styles";
    document.head.appendChild(el);
  }
  if (el.textContent !== BJ_STYLE) el.textContent = BJ_STYLE;
}

function refreshTailwind() {
  try {
    const tw = globalThis.tailwind;
    if (tw && typeof tw.refresh === "function") tw.refresh();
  } catch {

  }
}

function rankText(r) {
  if (r === "T") return "10";
  return r;
}

function suitIsRed(s) {
  return s === "h" || s === "d";
}

function handValue(cards) {
  let total = 0;
  let aces = 0;
  for (const c of cards) {
    if (!c || !c.length) continue;
    const r = c[0];
    if (r === "A") {
      aces++;
      total += 11;
    } else if (r === "T" || r === "J" || r === "Q" || r === "K") {
      total += 10;
    } else {
      total += parseInt(r, 10) || 0;
    }
  }
  while (total > 21 && aces > 0) {
    total -= 10;
    aces--;
  }
  return { total, soft: aces > 0 };
}

function visibleUpCardValue(card) {
  if (!card || !card.length) return 0;
  const r = card[0];
  if (r === "A") return 11;
  if (r === "T" || r === "J" || r === "Q" || r === "K") return 10;
  return parseInt(r, 10) || 0;
}

function formatDealerTotal(dealerCards, holeHidden) {
  if (!dealerCards.length) return "";
  if (holeHidden && dealerCards.length >= 2) {
    const v = visibleUpCardValue(dealerCards[0]);
    return `${v} + ?`;
  }
  const { total, soft } = handValue(dealerCards);
  return soft ? `${total} ${t("bjSoft")}` : String(total);
}

function formatPlayerTotal(playerCards) {
  if (!playerCards.length) return "";
  const { total, soft } = handValue(playerCards);
  return soft ? `${total} ${t("bjSoft")}` : String(total);
}

function renderPlayingFace(code) {
  if (!code || code.length < 2) return "";
  const r = code[0];
  const s = code[1];
  const rank = rankText(r);
  const suit = SUITS[s] || "";
  const ink = suitIsRed(s) ? "bj-card-face--red" : "bj-card-face--black";

  return `<div class="bj-card-face ${ink}">
    <div class="bj-card-face__shine" aria-hidden="true"></div>
    <div class="bj-card-face__corner bj-card-face__corner--tl ${ink}">
      <span class="bj-card-face__rank">${rank}</span>
      <span class="bj-card-face__suit-sm" aria-hidden="true">${suit}</span>
    </div>
    <div class="bj-card-face__center ${ink}" aria-hidden="true">${suit}</div>
    <div class="bj-card-face__corner bj-card-face__corner--br ${ink}">
      <span class="bj-card-face__rank">${rank}</span>
      <span class="bj-card-face__suit-sm" aria-hidden="true">${suit}</span>
    </div>
  </div>`;
}

function renderCardBack() {
  return `<div class="bj-card-back" aria-hidden="true">
    <div class="bj-card-back__pattern" aria-hidden="true"></div>
    <span class="bj-card-back__badge">BJ</span>
  </div>`;
}

function planCardAnimations(prev, next) {
  const dealer = next.dealerCards || [];
  const player = next.playerCards || [];
  const pd = dealer.length;
  const pp = player.length;
  const dealerDelays = Array(pd).fill(undefined);
  const playerDelays = Array(pp).fill(undefined);
  let holeFlipIdx = -1;

  if (!prev) {
    return { dealerDelays, playerDelays, holeFlipIdx };
  }

  const prevD = prev.dealerCards || [];
  const prevP = prev.playerCards || [];
  const prevPLen = prevP.length;
  const prevDLen = prevD.length;

  const opening =
    (prev.phase === "bet" || prev.phase === "done") &&
    (next.phase === "play" || next.phase === "done") &&
    pd >= 2 &&
    pp >= 2;

  if (opening) {
    dealerDelays[0] = 0;
    playerDelays[0] = 95;
    dealerDelays[1] = 190;
    playerDelays[1] = 285;
    return { dealerDelays, playerDelays, holeFlipIdx };
  }

  if (prev.phase === "play" && next.phase === "play" && pp > prevPLen) {
    for (let i = prevPLen; i < pp; i++) playerDelays[i] = 0;
    return { dealerDelays, playerDelays, holeFlipIdx };
  }

  if (prev.phase === "play" && next.phase === "done") {
    let t = 0;
    if (pp > prevPLen) {
      for (let i = prevPLen; i < pp; i++) {
        playerDelays[i] = t;
        t += 120;
      }
    }
    const holeOpens = !!prev.dealerHoleHidden && !next.dealerHoleHidden;
    if (holeOpens) {
      holeFlipIdx = 1;
      dealerDelays[1] = t;
      t += 230;
    }
    for (let i = Math.max(prevDLen, 2); i < pd; i++) {
      dealerDelays[i] = t;
      t += 115;
    }
    return { dealerDelays, playerDelays, holeFlipIdx };
  }

  return { dealerDelays, playerDelays, holeFlipIdx };
}

function renderCardsRow(cards, hiddenIndex, delays, holeFlipIdx) {
  if (!cards.length) return '<span class="bj-dash">—</span>';
  return `<div class="bj-table-surface bj-card-row">${cards
    .map((c, i) => {
      const face = i === hiddenIndex ? renderCardBack() : renderPlayingFace(c);
      const overlap = i > 0 ? " bj-card-slot--overlap" : "";
      const z = 5 + i;
      const delayMs = delays[i];
      const isHoleFlip = i === holeFlipIdx && holeFlipIdx >= 0;
      let animClass = "bj-card-wrap";
      let style = `z-index:${z}`;
      if (delayMs !== undefined) {
        style += `;--bj-d:${delayMs}ms`;
        animClass += isHoleFlip ? " bj-card-wrap--hole-flip" : " bj-card-wrap--deal";
      }
      return `<div class="bj-card-slot${overlap} ${animClass}" style="${style}">${face}</div>`;
    })
    .join("")}</div>`;
}

let audioCtx = null;
let sfxUnlocked = false;

function isSoundMuted() {
  try {
    return globalThis.localStorage?.getItem(BJ_MUTE_KEY) === "1";
  } catch {
    return false;
  }
}

function setSoundMuted(muted) {
  try {
    globalThis.localStorage?.setItem(BJ_MUTE_KEY, muted ? "1" : "0");
  } catch {

  }
}

function getAudioCtx() {
  if (typeof globalThis.AudioContext === "undefined" && typeof globalThis.webkitAudioContext === "undefined") {
    return null;
  }
  if (!audioCtx) {
    const Ctx = globalThis.AudioContext || globalThis.webkitAudioContext;
    audioCtx = new Ctx();
  }
  return audioCtx;
}

async function unlockAudio() {
  const ctx = getAudioCtx();
  if (!ctx) return;
  try {
    if (ctx.state === "suspended") await ctx.resume();
    sfxUnlocked = true;
  } catch {

  }
}

function playTone(freq, dur, vol = 0.06, type = "sine", when = 0) {
  const ctx = getAudioCtx();
  if (!ctx || isSoundMuted()) return;
  const t0 = ctx.currentTime + when;
  const osc = ctx.createOscillator();
  const g = ctx.createGain();
  osc.type = type;
  osc.frequency.setValueAtTime(freq, t0);
  g.gain.setValueAtTime(0, t0);
  g.gain.linearRampToValueAtTime(vol, t0 + 0.012);
  g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
  osc.connect(g);
  g.connect(ctx.destination);
  osc.start(t0);
  osc.stop(t0 + dur + 0.02);
}

function playNoiseBurst(dur, vol = 0.045, when = 0) {
  const ctx = getAudioCtx();
  if (!ctx || isSoundMuted()) return;
  const t0 = ctx.currentTime + when;
  const len = Math.max(1, Math.floor(ctx.sampleRate * dur));
  const buf = ctx.createBuffer(1, len, ctx.sampleRate);
  const data = buf.getChannelData(0);
  for (let i = 0; i < len; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / len);
  const src = ctx.createBufferSource();
  src.buffer = buf;
  const bp = ctx.createBiquadFilter();
  bp.type = "bandpass";
  bp.frequency.setValueAtTime(1800, t0);
  bp.Q.setValueAtTime(0.7, t0);
  const g = ctx.createGain();
  g.gain.setValueAtTime(0, t0);
  g.gain.linearRampToValueAtTime(vol, t0 + 0.005);
  g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
  src.connect(bp);
  bp.connect(g);
  g.connect(ctx.destination);
  src.start(t0);
  src.stop(t0 + dur);
}

function sfxBet() {
  playNoiseBurst(0.045, 0.055, 0);
  playTone(520, 0.06, 0.07, "triangle", 0.01);
  playTone(380, 0.05, 0.05, "sine", 0.04);
}

function sfxCardLand(when = 0) {
  playNoiseBurst(0.028, 0.04, when);
  playTone(2100, 0.018, 0.035, "triangle", when + 0.002);
  playTone(940, 0.025, 0.028, "sine", when + 0.012);
}

function sfxFlip(when = 0) {
  playTone(320, 0.07, 0.07, "sine", when);
  playTone(680, 0.05, 0.05, "triangle", when + 0.05);
  playNoiseBurst(0.04, 0.035, when + 0.03);
}

function sfxDealerHit(when = 0) {
  playNoiseBurst(0.022, 0.032, when);
  playTone(1400, 0.02, 0.03, "sine", when + 0.005);
}

function sfxWin(when = 0) {
  playTone(523, 0.12, 0.07, "sine", when);
  playTone(659, 0.12, 0.06, "sine", when + 0.1);
  playTone(784, 0.15, 0.065, "sine", when + 0.2);
  playTone(1046, 0.2, 0.05, "sine", when + 0.32);
}

function sfxLose(when = 0) {
  playTone(220, 0.18, 0.07, "sawtooth", when);
  playTone(180, 0.22, 0.055, "sawtooth", when + 0.1);
}

function sfxPush(when = 0) {
  playTone(440, 0.1, 0.06, "sine", when);
  playTone(440, 0.1, 0.05, "sine", when + 0.12);
}

function sfxBlackjack(when = 0) {
  playTone(392, 0.1, 0.06, "sine", when);
  playTone(523, 0.1, 0.06, "sine", when + 0.08);
  playTone(659, 0.12, 0.065, "sine", when + 0.16);
  playTone(784, 0.18, 0.07, "sine", when + 0.26);
  playTone(1046, 0.25, 0.055, "sine", when + 0.4);
}

function sfxOutcome(outcome, when = 0) {
  if (outcome === "win") sfxWin(when);
  else if (outcome === "lose") sfxLose(when);
  else if (outcome === "push") sfxPush(when);
  else if (outcome === "blackjack") sfxBlackjack(when);
}

async function scheduleTransitionSfx(prev, next) {
  await unlockAudio();
  if (!prev || isSoundMuted()) return;
  const ctx = getAudioCtx();
  if (!ctx) return;

  const schedule = (fn, ms) => {
    globalThis.setTimeout(() => {
      void unlockAudio();
      fn();
    }, ms);
  };

  if ((prev.phase === "bet" || prev.phase === "done") && (next.phase === "play" || next.phase === "done")) {
    sfxBet();
    const times = [0, 95, 190, 285];
    times.forEach((ms) => schedule(() => sfxCardLand(), ms));
    if (next.phase === "done" && next.outcome) {
      schedule(() => sfxOutcome(next.outcome), 720);
    }
    return;
  }

  if (prev.phase === "play" && next.phase === "play" && (next.playerCards || []).length > (prev.playerCards || []).length) {
    schedule(() => sfxCardLand(), 0);
    return;
  }

  if (prev.phase === "play" && next.phase === "done") {
    let ms = 0;
    if ((next.playerCards || []).length > (prev.playerCards || []).length) {
      schedule(() => sfxCardLand(), 0);
      ms += 130;
    }
    if (prev.dealerHoleHidden && !next.dealerHoleHidden) {
      schedule(() => sfxFlip(), ms);
      ms += 240;
    }
    const extraDealer = (next.dealerCards || []).length - Math.max((prev.dealerCards || []).length, 2);
    for (let k = 0; k < Math.max(0, extraDealer); k++) {
      const t = ms + k * 115;
      schedule(() => sfxDealerHit(), t);
    }
    const outcomeAt = ms + Math.max(0, extraDealer) * 115 + 380;
    if (next.outcome) schedule(() => sfxOutcome(next.outcome), outcomeAt);
  }
}

function snapGame(g) {
  return {
    phase: g.phase || "bet",
    dealerCards: [...(g.dealerCards || [])],
    playerCards: [...(g.playerCards || [])],
    dealerHoleHidden: !!g.dealerHoleHidden,
    outcome: g.outcome ?? null,
  };
}

export async function renderBlackjackPage(outlet) {
  const user = await fetchSessionUser();
  if (!user) {
    ensureBjStyles();
    outlet.innerHTML = `
      <section class="bj-guest-panel bj-page mx-auto max-w-2xl space-y-6 px-1">
        <a href="#/oyunlar" class="text-sm text-accent hover:underline">${t("bjBackToGames")}</a>
        <header class="bj-header-block space-y-2">
          <h1 class="bj-title text-2xl sm:text-3xl">Blackjack</h1>
          <p class="bj-guest-hint text-sm">${t("bjGuestHint")}</p>
        </header>
        <button type="button" id="bj-open-login" class="k-btn k-btn--primary btn-site-primary rounded-xl px-5 py-2.5 text-sm font-semibold">${t("bjOpenLogin")}</button>
      </section>`;
    outlet.querySelector("#bj-open-login")?.addEventListener("click", () => globalThis.__siteOpenLogin?.());
    return;
  }

  const res = await fetchBlackjackState();
  if (!res.ok) {
    ensureBjStyles();
    outlet.innerHTML = `
      <div class="bj-error-panel mx-auto max-w-lg rounded-2xl border border-sky-500/25 bg-sky-950/20 px-6 py-8 text-center text-sm text-sky-100">
        ${t("bjFetchErrorBeforeLink")} <a href="#/oyunlar" class="font-semibold text-accent underline">${t("gamesNavigateBack")}</a> ${t("bjFetchErrorAfterLink")}
      </div>`;
    return;
  }
  const d = res.data || {};
  const limits = { min: Number(d.minBet) || 1, max: Number(d.maxBet) || 1000 };
  let coins = typeof d.coins === "number" ? d.coins : user.coins ?? 0;
  let game = d.game || { phase: "bet", message: "" };

  let prevSnap = null;

  function syncFromApi(data) {
    if (typeof data.coins === "number") coins = data.coins;
    if (data.game) game = data.game;
  }

  function paint() {
    ensureBjStyles();
    const g = game;
    const phase = g.phase || "bet";
    const dealerCards = g.dealerCards || [];
    const playerCards = g.playerCards || [];
    const bet = g.bet || 0;
    const done = phase === "done";
    const play = phase === "play";

    const animPlan = planCardAnimations(prevSnap, g);

    const rules = `<p class="bj-rules">${t("bjRules")}</p>`;

    const muted = isSoundMuted();

    const msgText = g.message || (play ? t("bjMsgYourTurn") : done ? t("bjMsgHandDone") : t("bjMsgPickBet"));
    const msgClass = play || (done && g.message) ? "bj-msg bj-msg--highlight" : "bj-msg";

    outlet.innerHTML = `
      <div class="bj-page">
        <div class="bj-toolbar">
          <a href="#/oyunlar">${t("bjBackToGames")}</a>
          <div class="bj-toolbar-tools">
            <button type="button" id="bj-toggle-sound" class="bj-sound-toggle" title="${t("bjSoundToggle")}">
              ${muted ? t("bjSoundOff") : t("bjSoundOn")}
            </button>
            <div class="bj-balance-pill">
              <span class="bj-balance-pill__icon" aria-hidden="true">₵</span>
              <span>${t("bjBalance")} <strong>${fmtCoins(coins)}</strong></span>
            </div>
          </div>
        </div>

        <div class="bj-shell">
          <div class="bj-marquee">
            <div class="bj-marquee__lights">${[1, 2, 3, 4, 5].map(() => `<span class="bj-marquee__dot"></span>`).join("")}</div>
            <span class="bj-marquee__title">Blackjack</span>
            <div class="bj-marquee__lights">${[1, 2, 3, 4, 5].map(() => `<span class="bj-marquee__dot"></span>`).join("")}</div>
          </div>
          <div class="bj-shell__inner">
            <div class="bj-machine-top">
              <span class="bj-badge">21 · 3:2</span>
              <span class="bj-limits-hint">${t("bjRulesBet").replace("{min}", String(limits.min)).replace("{max}", String(limits.max))}</span>
            </div>
            ${rules}

            <div class="bj-table-wrap">
            <div class="bj-table">
          <p class="${msgClass}">${msgText}</p>

          <div class="bj-hand-block">
            <p class="bj-hand-label">
              <span class="bj-hand-label__title">${t("bjDealer")}</span>
              ${
                dealerCards.length
                  ? `<span class="bj-hand-total" aria-label="${t("bjDealerTotalAria")}">${
                      g.dealerHoleHidden && dealerCards.length >= 2
                        ? formatDealerTotal(dealerCards, true)
                        : `${t("bjTotal")} ${formatDealerTotal(dealerCards, false)}`
                    }</span>`
                  : ""
              }
            </p>
            <div class="bj-hand-area">
              ${renderCardsRow(dealerCards, g.dealerHoleHidden ? 1 : -1, animPlan.dealerDelays, animPlan.holeFlipIdx)}
            </div>
          </div>
          <div>
            <p class="bj-hand-label">
              <span class="bj-hand-label__title">${t("bjYou")}${bet ? ` · ${bet} ${t("marketCoinWord")}` : ""}</span>
              ${playerCards.length ? `<span class="bj-hand-total" aria-label="${t("bjYourTotalAria")}">${t("bjTotal")} ${formatPlayerTotal(playerCards)}</span>` : ""}
            </p>
            <div class="bj-hand-area">
              ${renderCardsRow(playerCards, -1, animPlan.playerDelays, -1)}
            </div>
          </div>
            </div>
            </div>

        <div class="bj-console">
        <div class="bj-controls">
          ${!play && !done ? `
            <div class="bj-bet-row">
              <div class="bj-field">
                <label for="bj-bet" class="bj-label">${t("bjBetLabel")}</label>
                <input type="number" id="bj-bet" min="${limits.min}" max="${limits.max}" step="1" value="${Math.min(100, Math.max(limits.min, coins))}" class="bj-input" />
              </div>
              <button type="button" id="bj-deal" class="bj-deal-btn">${t("bjDeal")}</button>
            </div>
            <div class="bj-chips">
              ${[10, 50, 100, 250, 500]
                .filter((x) => x <= limits.max)
                .map((x) => `<button type="button" class="bj-chip" data-bet="${x}">${x}</button>`)
                .join("")}
            </div>
          ` : ""}

          ${play ? `
            <div class="bj-actions">
              <button type="button" id="bj-hit" class="k-btn k-btn--secondary btn-site-secondary rounded-xl px-4 py-2.5 text-sm font-semibold" ${g.canHit ? "" : "disabled"}>${t("bjHit")}</button>
              <button type="button" id="bj-stand" class="k-btn k-btn--secondary btn-site-secondary rounded-xl px-4 py-2.5 text-sm font-semibold" ${g.canStand ? "" : "disabled"}>${t("bjStand")}</button>
              <button type="button" id="bj-double" class="k-btn k-btn--primary btn-site-action rounded-xl px-4 py-2.5 text-sm font-semibold shadow-glow-sm" ${g.canDouble && coins >= bet ? "" : "disabled"}>${t("bjDouble")}</button>
            </div>
          ` : ""}

          ${done ? `
            <div class="bj-bet-row">
              <div class="bj-field">
                <label for="bj-bet2" class="bj-label">${t("bjNewBet")}</label>
                <input type="number" id="bj-bet2" min="${limits.min}" max="${limits.max}" step="1" value="${Math.min(Math.max(bet || 10, limits.min), Math.min(limits.max, coins))}" class="bj-input" />
              </div>
              <button type="button" id="bj-again" class="bj-deal-btn">${t("bjNewHand")}</button>
            </div>
          ` : ""}
        </div>
        </div>
          </div>
        </div>
      </div>`;

    refreshTailwind();
    requestAnimationFrame(() => refreshTailwind());

    void scheduleTransitionSfx(prevSnap, g);
    prevSnap = snapGame(g);

    const betInput = outlet.querySelector("#bj-bet");
    const bet2 = outlet.querySelector("#bj-bet2");

    outlet.querySelector("#bj-toggle-sound")?.addEventListener("click", () => {
      const m = !isSoundMuted();
      setSoundMuted(m);
      paint();
    });

    outlet.querySelectorAll(".bj-chip").forEach((btn) => {
      btn.addEventListener("click", () => {
        const v = Number(btn.getAttribute("data-bet"));
        const el = outlet.querySelector("#bj-bet");
        if (el) el.value = String(Math.min(v, coins, limits.max));
      });
    });

    const bindUnlock = (el) => el?.addEventListener("click", () => void unlockAudio());
    bindUnlock(outlet.querySelector("#bj-deal"));
    bindUnlock(outlet.querySelector("#bj-hit"));
    bindUnlock(outlet.querySelector("#bj-stand"));
    bindUnlock(outlet.querySelector("#bj-double"));
    bindUnlock(outlet.querySelector("#bj-again"));

    outlet.querySelector("#bj-deal")?.addEventListener("click", async () => {
      const raw = betInput?.value ?? "10";
      const b = Math.max(limits.min, Math.min(limits.max, parseInt(String(raw), 10) || 0));
      if (b < limits.min || b > limits.max) {
        showToast(t("bjToastBetRange").replace("{min}", String(limits.min)).replace("{max}", String(limits.max)), "error");
        return;
      }
      if (b > coins) {
        showToast(t("bjToastInsufficient"), "error");
        return;
      }
      await unlockAudio();
      const out = await postBlackjackAction({ action: "deal", bet: b });
      if (!out.ok) {
        showToast(errMsg(out.data), "error");
        return;
      }
      syncFromApi(out.data);
      void globalThis.__siteRefreshUser?.();
      paint();
    });

    outlet.querySelector("#bj-hit")?.addEventListener("click", async () => {
      await unlockAudio();
      const out = await postBlackjackAction({ action: "hit" });
      if (!out.ok) {
        showToast(errMsg(out.data), "error");
        return;
      }
      syncFromApi(out.data);
      void globalThis.__siteRefreshUser?.();
      paint();
    });

    outlet.querySelector("#bj-stand")?.addEventListener("click", async () => {
      await unlockAudio();
      const out = await postBlackjackAction({ action: "stand" });
      if (!out.ok) {
        showToast(errMsg(out.data), "error");
        return;
      }
      syncFromApi(out.data);
      void globalThis.__siteRefreshUser?.();
      paint();
    });

    outlet.querySelector("#bj-double")?.addEventListener("click", async () => {
      await unlockAudio();
      const out = await postBlackjackAction({ action: "double" });
      if (!out.ok) {
        showToast(errMsg(out.data), "error");
        return;
      }
      syncFromApi(out.data);
      void globalThis.__siteRefreshUser?.();
      paint();
    });

    outlet.querySelector("#bj-again")?.addEventListener("click", async () => {
      const raw = bet2?.value ?? "10";
      const b = Math.max(limits.min, Math.min(limits.max, parseInt(String(raw), 10) || 0));
      if (b > coins) {
        showToast(t("bjToastInsufficient"), "error");
        return;
      }
      await unlockAudio();
      const out = await postBlackjackAction({ action: "deal", bet: b });
      if (!out.ok) {
        showToast(errMsg(out.data), "error");
        return;
      }
      syncFromApi(out.data);
      void globalThis.__siteRefreshUser?.();
      paint();
    });
  }

  function errMsg(data) {
    const e = data?.error;
    if (e === "auth") return t("bjErrAuth");
    if (e === "coins") return t("bjErrCoins");
    if (e === "bet_range") return t("bjErrBetRange");
    if (e === "phase") return t("bjErrPhase");
    if (e === "double") return t("bjErrDouble");
    if (e === "in_progress") return t("bjErrInProgress");
    return t("bjErrGeneric");
  }

  paint();
}
