import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
import { request } from '../lib/api';
import bgmSrc from '../assets/big-screen/dukang-bgm.mp3';
import sfxTier1 from '../assets/big-screen/celebrate-t1.mp3';
import sfxTier2 from '../assets/big-screen/celebrate-t2.mp3';
import sfxTier3 from '../assets/big-screen/celebrate-t3.mp3';

export type BigScreenOrder = {
  id: string;
  orderNo: string;
  payAmount: number;
  items: string;
  /** 展示用时间（付款成功时间） */
  createdAt: string;
  paidAt?: string;
  userPhoneMasked: string | null;
};

type AmountTier = 1 | 2 | 3;

const POLL_MS = 3000;
const ROW_MS = 2500;
/** 不足此条数时重复填充，保证滚动连贯 */
const MIN_SCROLL_ROWS = 10;
const FX_DURATION_MS = 10_000;
const WEEKDAYS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];

const CELEBRATE_SFX: Record<AmountTier, string> = {
  1: sfxTier1,
  2: sfxTier2,
  3: sfxTier3,
};

/** 浏览器自动播放策略：需用户手势后才能出声 */
let audioUnlocked = false;
const sharedAudio = typeof Audio !== 'undefined' ? new Audio() : null;
const BGM_VOLUME = 0.32;
const BGM_DUCK_VOLUME = 0.08;
let bgmAudio: HTMLAudioElement | null = null;
let bgmDucked = false;

function getBgmAudio(): HTMLAudioElement | null {
  if (typeof Audio === 'undefined') return null;
  if (!bgmAudio) {
    bgmAudio = new Audio(bgmSrc);
    bgmAudio.loop = true;
    bgmAudio.preload = 'auto';
    bgmAudio.volume = BGM_VOLUME;
  }
  return bgmAudio;
}

function applyBgmVolume() {
  if (!bgmAudio) return;
  bgmAudio.volume = bgmDucked ? BGM_DUCK_VOLUME : BGM_VOLUME;
}

function duckBgm() {
  bgmDucked = true;
  applyBgmVolume();
}

function unduckBgm() {
  bgmDucked = false;
  applyBgmVolume();
}

function setBgmPlaying(on: boolean): Promise<boolean> {
  const audio = getBgmAudio();
  if (!audio) return Promise.resolve(false);
  applyBgmVolume();
  if (!on) {
    audio.pause();
    return Promise.resolve(false);
  }
  return audio
    .play()
    .then(() => true)
    .catch(() => false);
}

function unlockCelebrateAudio() {
  if (audioUnlocked || !sharedAudio) return;
  sharedAudio.muted = true;
  sharedAudio.src = CELEBRATE_SFX[1];
  void sharedAudio
    .play()
    .then(() => {
      sharedAudio.pause();
      sharedAudio.currentTime = 0;
      sharedAudio.muted = false;
      audioUnlocked = true;
    })
    .catch(() => {
      /* 等待下次手势 */
    });
}

function playCelebrateSfx(tier: AmountTier): () => void {
  if (!sharedAudio) return () => undefined;
  duckBgm();
  try {
    sharedAudio.pause();
    sharedAudio.currentTime = 0;
    sharedAudio.src = CELEBRATE_SFX[tier];
    sharedAudio.volume = tier === 3 ? 0.85 : tier === 2 ? 0.75 : 0.65;
    sharedAudio.muted = false;
    void sharedAudio.play().catch(() => {
      /* 未解锁时静默失败 */
    });
  } catch {
    /* ignore */
  }
  return () => {
    try {
      sharedAudio.pause();
      sharedAudio.currentTime = 0;
    } catch {
      /* ignore */
    }
    unduckBgm();
  };
}

function pad(n: number) {
  return String(n).padStart(2, '0');
}

function formatClock(d: Date) {
  return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}

function formatDateLine(d: Date) {
  return `${d.getFullYear()} / ${pad(d.getMonth() + 1)} / ${pad(d.getDate())}  ${WEEKDAYS[d.getDay()]}`;
}

function formatOrderTime(iso: string): string {
  try {
    return formatClock(new Date(iso));
  } catch {
    return iso;
  }
}

// function formatAmount(n: number): string {
//   return Number(n || 0).toLocaleString('zh-CN', {
//     minimumFractionDigits: 0,
//     maximumFractionDigits: 0,
//   });
// }

function formatAmount(n: number): string {
  const num = Number(n || 0);
  // 先保留两位小数，再分割处理
  const fixed = num.toFixed(2);
  const [intStr, decStr] = fixed.split('.');
  const intFormatted = Number(intStr).toLocaleString('zh-CN');
  return decStr === '00' ? intFormatted : `${intFormatted}.${decStr}`;
}

function amountCssClass(payAmount: number, base: string): string {
  const wide = Math.floor(Math.abs(Number(payAmount || 0))) >= 100_000;
  return wide ? `${base} ${base}--6d` : base;
}

export function amountTier(payAmount: number): AmountTier {
  if (payAmount >= 1000) return 3;
  if (payAmount >= 500) return 2;
  return 1;
}

function LiveClock() {
  const [now, setNow] = useState(() => new Date());
  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);
  return (
    <div className="big-screen-hero">
      <div className="big-screen-clock">{formatClock(now)}</div>
      <div className="big-screen-date">{formatDateLine(now)}</div>
    </div>
  );
}

function OrderRow({
  order,
  latest,
}: {
  order: BigScreenOrder;
  latest?: boolean;
}) {
  const tier = amountTier(order.payAmount);
  return (
    <div
      className={`big-screen-row big-screen-row--t${tier}${latest ? ' is-latest' : ''}`}
    >
      {latest ? <span className="big-screen-row-mark" /> : null}
      <span className={amountCssClass(order.payAmount, 'big-screen-amount')}>
        ¥ {formatAmount(order.payAmount)}
      </span>
      <span className="big-screen-items">{order.items || '—'}</span>
      <span className="big-screen-time">{formatOrderTime(order.paidAt ?? order.createdAt)}</span>
      <span className="big-screen-phone">{order.userPhoneMasked || '—'}</span>
    </div>
  );
}

type Particle = {
  x: number;
  y: number;
  vx: number;
  vy: number;
  rot: number;
  vr: number;
  w: number;
  h: number;
  color: string;
  life: number;
  kind: 'rect' | 'ribbon' | 'spark';
  rain?: boolean;
};

const TIER_COLORS: Record<AmountTier, string[]> = {
  1: ['#e6f7ff', '#91d5ff', '#40a9ff', '#ffffff', '#69c0ff'],
  2: ['#fff1b8', '#ffe58f', '#ffd666', '#fffbe6', '#ffe7ba'],
  3: ['#ffd666', '#faad14', '#ffec3d', '#fff1b8', '#ff4d4f', '#ff7a45', '#ffffff'],
};

function spawnParticles(tier: AmountTier, w: number, h: number): Particle[] {
  const colors = TIER_COLORS[tier];
  const count = tier === 3 ? 180 : tier === 2 ? 110 : 70;
  const out: Particle[] = [];
  const cx = w / 2;
  const cy = h * 0.42;
  for (let i = 0; i < count; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = (tier === 3 ? 8 : tier === 2 ? 6 : 4) * (0.4 + Math.random());
    const kind: Particle['kind'] =
      tier === 3 && Math.random() < 0.25 ? 'ribbon' : Math.random() < 0.2 ? 'spark' : 'rect';
    out.push({
      x: cx + (Math.random() - 0.5) * 80,
      y: cy + (Math.random() - 0.5) * 40,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed - (tier === 3 ? 6 : 3),
      rot: Math.random() * 360,
      vr: (Math.random() - 0.5) * 18,
      w: kind === 'ribbon' ? 10 + Math.random() * 16 : kind === 'spark' ? 2 : 6 + Math.random() * 8,
      h: kind === 'ribbon' ? 28 + Math.random() * 24 : kind === 'spark' ? 10 + Math.random() * 8 : 4 + Math.random() * 6,
      color: colors[Math.floor(Math.random() * colors.length)],
      life: 1,
      kind,
    });
  }
  if (tier >= 2) {
    for (let i = 0; i < (tier === 3 ? 80 : 40); i++) {
      out.push({
        x: Math.random() * w,
        y: -20 - Math.random() * 80,
        vx: (Math.random() - 0.5) * 1.4,
        vy: 3 + Math.random() * 5,
        rot: Math.random() * 360,
        vr: (Math.random() - 0.5) * 10,
        w: 5 + Math.random() * 8,
        h: 10 + Math.random() * 14,
        color: colors[Math.floor(Math.random() * colors.length)],
        life: 1,
        kind: 'rect',
        rain: true,
      });
    }
  }
  return out;
}

function CelebrateFx({ order, onDone }: { order: BigScreenOrder; onDone: () => void }) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const onDoneRef = useRef(onDone);
  onDoneRef.current = onDone;
  const tier = amountTier(order.payAmount);
  const [displayAmount, setDisplayAmount] = useState(tier === 3 ? 0 : order.payAmount);

  useEffect(() => {
    const timer = window.setTimeout(() => onDoneRef.current(), FX_DURATION_MS);
    return () => window.clearTimeout(timer);
  }, [order.id]);

  useEffect(() => {
    const stop = playCelebrateSfx(tier);
    return stop;
  }, [order.id, tier]);

  useEffect(() => {
    if (tier !== 3) return;
    const start = performance.now();
    const dur = 900;
    let raf = 0;
    const tick = (now: number) => {
      const p = Math.min(1, (now - start) / dur);
      const eased = 1 - (1 - p) ** 3;
      setDisplayAmount(Math.round(order.payAmount * eased));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [order.payAmount, order.id, tier]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    if (!ctx) return;
    const resize = () => {
      canvas.width = canvas.clientWidth * devicePixelRatio;
      canvas.height = canvas.clientHeight * devicePixelRatio;
      ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
    };
    resize();
    const particles = spawnParticles(tier, canvas.clientWidth, canvas.clientHeight);
    let raf = 0;
    let last = performance.now();
    const started = performance.now();
    const gravity = tier === 3 ? 0.18 : 0.14;
    const loop = (now: number) => {
      const dt = Math.min(32, now - last) / 16.6;
      last = now;
      ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
      for (const p of particles) {
        p.vy += gravity * dt;
        p.x += p.vx * dt;
        p.y += p.vy * dt;
        p.rot += p.vr * dt;
        p.life -= (tier === 3 ? 0.002 : 0.003) * dt;
        if (
          p.rain &&
          (p.y > canvas.clientHeight + 30 || p.life <= 0) &&
          now - started < FX_DURATION_MS - 400
        ) {
          p.x = Math.random() * canvas.clientWidth;
          p.y = -16 - Math.random() * 60;
          p.vx = (Math.random() - 0.5) * 1.4;
          p.vy = 3 + Math.random() * 5;
          p.life = 1;
        }
        if (p.life <= 0) continue;
        ctx.save();
        ctx.translate(p.x, p.y);
        ctx.rotate((p.rot * Math.PI) / 180);
        ctx.globalAlpha = Math.max(0, p.life);
        ctx.fillStyle = p.color;
        if (p.kind === 'spark') {
          ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
        } else {
          ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
        }
        ctx.restore();
      }
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    const onResize = () => resize();
    window.addEventListener('resize', onResize);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', onResize);
    };
  }, [order.id, tier]);

  return (
    <div className={`big-screen-fx big-screen-fx--t${tier}`} role="presentation">
      <canvas ref={canvasRef} className="big-screen-fx-canvas" />
      {tier === 3 ? <div className="big-screen-fx-shock" /> : null}
      {tier === 3 ? <div className="big-screen-fx-shock big-screen-fx-shock--late" /> : null}
      {tier >= 2 ? <div className="big-screen-fx-sweep" /> : null}
      {tier >= 2 ? <div className="big-screen-fx-sweep big-screen-fx-sweep--alt" /> : null}
      <div className={`big-screen-fx-card big-screen-fx-card--t${tier}`}>
        <div className="big-screen-fx-kicker">{tier === 3 ? '高额成交' : tier === 2 ? '大额成交' : '新成交'}</div>
        <div className={amountCssClass(displayAmount || order.payAmount, 'big-screen-fx-amount')}>
          ¥ {formatAmount(displayAmount)}
        </div>
        <div className="big-screen-fx-items">{order.items || '—'}</div>
        <div className="big-screen-fx-meta">
          <span>{order.userPhoneMasked || '—'}</span>
          <span>{formatOrderTime(order.paidAt ?? order.createdAt)}</span>
        </div>
      </div>
    </div>
  );
}

function BgmToggleButton({ on, onToggle }: { on: boolean; onToggle: () => void }) {
  return (
    <button
      type="button"
      className={`big-screen-bgm${on ? ' is-on' : ''}`}
      onClick={onToggle}
      aria-pressed={on}
      aria-label={on ? '暂停背景音乐' : '播放背景音乐'}
    >
      <span className="big-screen-bgm-icon" aria-hidden>
        {on ? (
          <svg viewBox="0 0 24 24" width="16" height="16">
            <path
              fill="currentColor"
              d="M9 18V6h2v12H9zm4 0V6h2v12h-2z"
            />
          </svg>
        ) : (
          <svg viewBox="0 0 24 24" width="16" height="16">
            <path fill="currentColor" d="M8 5.14v13.72L19.06 12 8 5.14z" />
          </svg>
        )}
      </span>
      {on ? 'BGM 播放中' : '播放 BGM'}
    </button>
  );
}

export default function BigScreenPage() {
  const [items, setItems] = useState<BigScreenOrder[]>([]);
  const [loadError, setLoadError] = useState('');
  const [paused, setPaused] = useState(false);
  const [bgmOn, setBgmOn] = useState(false);
  const bgmWantedRef = useRef(false);
  const [celebrate, setCelebrate] = useState<BigScreenOrder | null>(null);
  const seenIdsRef = useRef<Set<string> | null>(null);
  const celebratedIdsRef = useRef<Set<string>>(new Set());
  const queueRef = useRef<BigScreenOrder[]>([]);
  const celebratingRef = useRef(false);
  const viewportRef = useRef<HTMLDivElement | null>(null);
  const dayKeyRef = useRef('');
  const [viewportH, setViewportH] = useState(0);

  const playNext = useCallback(() => {
    const next = queueRef.current.shift() ?? null;
    celebratingRef.current = !!next;
    setCelebrate(next);
    setPaused(!!next);
  }, []);

  const enqueueNew = useCallback(
    (fresh: BigScreenOrder[]) => {
      const toCelebrate = fresh.filter((o) => !celebratedIdsRef.current.has(o.id));
      if (!toCelebrate.length) return;
      for (const o of toCelebrate) celebratedIdsRef.current.add(o.id);
      const ranked = [...toCelebrate].sort((a, b) => {
        const td = amountTier(b.payAmount) - amountTier(a.payAmount);
        if (td !== 0) return td;
        return +new Date(b.paidAt ?? b.createdAt) - +new Date(a.paidAt ?? a.createdAt);
      });
      queueRef.current.push(...ranked);
      if (!celebratingRef.current) playNext();
    },
    [playNext],
  );

  const fetchData = useCallback(() => {
    const now = new Date();
    const todayKey = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
    if (dayKeyRef.current && dayKeyRef.current !== todayKey) {
      seenIdsRef.current = null;
      celebratedIdsRef.current = new Set();
      queueRef.current = [];
      celebratingRef.current = false;
      setCelebrate(null);
      setPaused(false);
    }
    dayKeyRef.current = todayKey;

    return request<{ items: BigScreenOrder[] }>('/admin/orders/big-screen?limit=2000')
      .then((d) => {
        const list = d.items ?? [];
        setItems(list);
        setLoadError('');
        const seen = seenIdsRef.current;
        if (!seen) {
          seenIdsRef.current = new Set(list.map((o) => o.id));
          for (const o of list) celebratedIdsRef.current.add(o.id);
          return;
        }
        const fresh = list.filter((o) => !seen.has(o.id));
        for (const o of fresh) seen.add(o.id);
        enqueueNew(fresh);
      })
      .catch((e) => {
        setLoadError(e instanceof Error ? e.message : '加载失败');
      });
  }, [enqueueNew]);

  useEffect(() => {
    void fetchData();
    const id = setInterval(() => void fetchData(), POLL_MS);
    return () => clearInterval(id);
  }, [fetchData]);

  const toggleBgm = useCallback(() => {
    unlockCelebrateAudio();
    const next = !bgmWantedRef.current;
    bgmWantedRef.current = next;
    if (!next) {
      void setBgmPlaying(false);
      setBgmOn(false);
      return;
    }
    setBgmOn(true);
    void setBgmPlaying(true).then((playing) => {
      if (!bgmWantedRef.current) return;
      setBgmOn(playing);
    });
  }, []);

  useEffect(() => {
    unlockCelebrateAudio();
    const unlock = () => unlockCelebrateAudio();
    window.addEventListener('pointerdown', unlock, { once: true });
    window.addEventListener('keydown', unlock, { once: true });
    return () => {
      window.removeEventListener('pointerdown', unlock);
      window.removeEventListener('keydown', unlock);
      bgmWantedRef.current = false;
      void setBgmPlaying(false);
    };
  }, []);

  useEffect(() => {
    const html = document.documentElement;
    const prevHtml = html.style.overflow;
    const prevBody = document.body.style.overflow;
    html.style.overflow = 'hidden';
    document.body.style.overflow = 'hidden';
    return () => {
      html.style.overflow = prevHtml;
      document.body.style.overflow = prevBody;
    };
  }, []);

  useEffect(() => {
    const el = viewportRef.current;
    if (!el) return;
    const measure = () => setViewportH(el.clientHeight);
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  const unitItems = useMemo(() => {
    if (!items.length) return [];
    const rowH = 80;
    const visible = Math.max(1, Math.ceil((viewportH || 480) / rowH));
    const minCount = Math.max(MIN_SCROLL_ROWS, visible + 1);
    const unit: BigScreenOrder[] = [];
    while (unit.length < minCount) unit.push(...items);
    return unit;
  }, [items, viewportH]);

  const trackItems = useMemo(() => unitItems.concat(unitItems), [unitItems]);
  const marqueeMs = Math.max(unitItems.length, 1) * ROW_MS;
  const rolling = items.length > 0 && unitItems.length > 0;
  const latestId = items[0]?.id;

  return (
    <div className="big-screen-page">
      <div className="big-screen-stars" aria-hidden />
      <div className="big-screen-frame" aria-hidden>
        <span className="big-screen-corner big-screen-corner--tl" />
        <span className="big-screen-corner big-screen-corner--tr" />
        <span className="big-screen-corner big-screen-corner--bl" />
        <span className="big-screen-corner big-screen-corner--br" />
      </div>

      <header className="big-screen-header">
        <div className="big-screen-brand">
          <h1 className="big-screen-title">杜康好客</h1>
          <span className="big-screen-subtitle">· 发布会现场</span>
        </div>
        <div className="big-screen-live-wrap">
          <BgmToggleButton on={bgmOn} onToggle={toggleBgm} />
          <span className="big-screen-live-label">实时成交</span>
          <span className="big-screen-live">
            <span className="big-screen-live-dot" />
            LIVE
          </span>
        </div>
      </header>

      <LiveClock />

      <div className="big-screen-list">
        <div className="big-screen-list-body" ref={viewportRef}>
          {items.length === 0 ? null : (
            <div
              className={`big-screen-track${rolling ? ' is-rolling' : ''}${paused ? ' is-paused' : ''}`}
              style={rolling ? ({ ['--marquee-ms']: `${marqueeMs}ms` } as CSSProperties) : undefined}
            >
              {trackItems.map((o, idx) => (
                <OrderRow
                  key={`${o.id}-${idx}`}
                  order={o}
                  latest={idx % items.length === 0 && o.id === latestId}
                />
              ))}
            </div>
          )}
        </div>
      </div>

      {items.length === 0 ? (
        <div className="big-screen-empty">{loadError || '当日暂无订单'}</div>
      ) : null}

      {celebrate ? (
        <CelebrateFx
          key={celebrate.id}
          order={celebrate}
          onDone={playNext}
        />
      ) : null}
    </div>
  );
}
