import {
  ArrowDown,
  ArrowLeft,
  CheckCircle2,
  Clock3,
  Image,
  RotateCcw,
  Shapes,
  Sparkles,
  XCircle,
} from 'lucide-react';
import {
  type CSSProperties,
  type PointerEvent as ReactPointerEvent,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from 'react';

import type {
  DropSquareHoleShapeRequest,
  SquareHoleDropResponse,
  SquareHoleRunSnapshot,
} from '../../../packages/shared/src/contracts/squareHoleRuntime';
import { ResolvedAssetImage } from '../ResolvedAssetImage';

type SquareHoleRuntimeShellProps = {
  run: SquareHoleRunSnapshot | null;
  isBusy?: boolean;
  error?: string | null;
  embedded?: boolean;
  onBack: () => void;
  onRestart: () => void;
  onDropShape: (
    payload: DropSquareHoleShapeRequest,
  ) => Promise<SquareHoleDropResponse>;
  onOptimisticRunChange?: (run: SquareHoleRunSnapshot) => void;
  onTimeExpired?: () => void;
};

type PendingDrop = {
  clientEventId: string;
  holeId: string;
};

type DragState = {
  pointerId: number;
  startX: number;
  startY: number;
  x: number;
  y: number;
  size: number;
  moved: boolean;
};

function isRunning(run: SquareHoleRunSnapshot) {
  return run.status.toLowerCase() === 'running';
}

function formatTimer(value: number) {
  const totalSeconds = Math.max(0, Math.ceil(value / 1000));
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}

function buildClientEventId(runId: string, holeId: string) {
  return `square-hole-drop-${runId}-${holeId}-${Date.now()}-${Math.round(
    Math.random() * 1_000_000,
  )}`;
}

function clampPercent(value: number) {
  return Math.min(92, Math.max(8, value * 100));
}

function hashText(value: string) {
  let hash = 2166136261;
  for (let index = 0; index < value.length; index += 1) {
    hash ^= value.charCodeAt(index);
    hash = Math.imul(hash, 16777619);
  }
  return hash >>> 0;
}

function SquareHoleSettlement({
  run,
  onBack,
  onRestart,
}: {
  run: SquareHoleRunSnapshot;
  onBack: () => void;
  onRestart: () => void;
}) {
  if (isRunning(run)) {
    return null;
  }

  const won = run.status.toLowerCase() === 'won';
  const stopped = run.status.toLowerCase() === 'stopped';
  const title = won ? '挑战完成' : stopped ? '已停止' : '本轮失败';
  return (
    <div className="absolute inset-0 z-[80] flex items-center justify-center bg-slate-950/64 px-5 backdrop-blur-sm">
      <section
        className="w-full max-w-sm rounded-[1.5rem] border border-white/18 bg-white/94 p-5 text-slate-950 shadow-[0_26px_70px_rgba(15,23,42,0.34)]"
        role="dialog"
        aria-label={title}
      >
        <div className="mb-4 flex items-center gap-3">
          <span
            className={`flex h-11 w-11 items-center justify-center rounded-full ${
              won
                ? 'bg-emerald-100 text-emerald-700'
                : 'bg-rose-100 text-rose-700'
            }`}
          >
            {won ? <CheckCircle2 size={24} /> : <XCircle size={24} />}
          </span>
          <div>
            <h2 className="text-xl font-black">{title}</h2>
            <p className="text-sm font-semibold text-slate-500">
              {run.completedShapeCount}/{run.totalShapeCount} · {run.score} 分
            </p>
          </div>
        </div>
        <div className="grid grid-cols-2 gap-2">
          <button
            type="button"
            className="rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm font-black text-slate-700"
            onClick={onBack}
          >
            返回
          </button>
          <button
            type="button"
            className="rounded-xl bg-slate-950 px-4 py-3 text-sm font-black text-white"
            onClick={onRestart}
          >
            再来一局
          </button>
        </div>
      </section>
    </div>
  );
}

export function SquareHoleRuntimeShell({
  run,
  isBusy = false,
  error = null,
  embedded = false,
  onBack,
  onRestart,
  onDropShape,
  onOptimisticRunChange,
  onTimeExpired,
}: SquareHoleRuntimeShellProps) {
  const [pendingDrop, setPendingDrop] = useState<PendingDrop | null>(null);
  const [timeLeftMs, setTimeLeftMs] = useState(run?.remainingMs ?? 0);
  const [feedbackPulseId, setFeedbackPulseId] = useState<string | null>(null);
  const [dragState, setDragState] = useState<DragState | null>(null);
  const [isShapeArmed, setIsShapeArmed] = useState(false);
  const [dropError, setDropError] = useState<string | null>(null);

  useEffect(() => {
    setTimeLeftMs(run?.remainingMs ?? 0);
    setIsShapeArmed(false);
    setDropError(null);
  }, [run?.remainingMs, run?.snapshotVersion]);

  useEffect(() => {
    if (!run || !isRunning(run)) {
      return undefined;
    }

    const timer = window.setInterval(() => {
      setTimeLeftMs((current) => {
        const next = Math.max(0, current - 1000);
        if (next <= 0) {
          onTimeExpired?.();
        }
        return next;
      });
    }, 1000);

    return () => window.clearInterval(timer);
  }, [onTimeExpired, run]);

  useEffect(() => {
    if (!feedbackPulseId) {
      return undefined;
    }

    const timer = window.setTimeout(() => setFeedbackPulseId(null), 620);
    return () => window.clearTimeout(timer);
  }, [feedbackPulseId]);

  const progressText = useMemo(() => {
    if (!run) {
      return '0/0';
    }
    return `${run.completedShapeCount}/${run.totalShapeCount}`;
  }, [run]);

  const currentShape = run?.currentShape ?? null;
  const hintHole = useMemo(() => {
    if (!run || !currentShape) {
      return null;
    }

    const hintCandidates =
      run.holes.length > 1
        ? run.holes.filter(
            (hole) => hole.holeId !== currentShape.targetHoleId,
          )
        : run.holes;
    if (hintCandidates.length <= 0) {
      return null;
    }

    const seed = `${run.runId}:${run.snapshotVersion}:${run.completedShapeCount}:${currentShape.shapeId}`;
    return hintCandidates[hashText(seed) % hintCandidates.length] ?? null;
  }, [currentShape, run]);

  const arrowStyle = useMemo<CSSProperties>(() => {
    if (!hintHole) {
      return {};
    }
    return {
      left: `${clampPercent(hintHole.x)}%`,
      top: `${clampPercent(hintHole.y)}%`,
    };
  }, [hintHole]);

  const resolveHoleAtPoint = useCallback((clientX: number, clientY: number) => {
    const elements = document.elementsFromPoint(clientX, clientY);
    const holeElement = elements.find((element) =>
      element instanceof HTMLElement ? element.dataset.squareHoleId : false,
    ) as HTMLElement | undefined;
    return holeElement?.dataset.squareHoleId ?? null;
  }, []);

  const handleShapePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (!run || !currentShape || !isRunning(run) || pendingDrop || isBusy) {
      return;
    }
    event.currentTarget.setPointerCapture(event.pointerId);
    setDragState({
      pointerId: event.pointerId,
      startX: event.clientX,
      startY: event.clientY,
      x: event.clientX,
      y: event.clientY,
      size: event.currentTarget.getBoundingClientRect().width,
      moved: false,
    });
  };

  const handleShapePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (!dragState || dragState.pointerId !== event.pointerId) {
      return;
    }
    setDragState((current) =>
      current
        ? {
            ...current,
            x: event.clientX,
            y: event.clientY,
            moved:
              current.moved ||
              Math.hypot(
                event.clientX - current.startX,
                event.clientY - current.startY,
              ) >= 6,
          }
        : current,
    );
  };

  const handleShapePointerEnd = (event: ReactPointerEvent<HTMLDivElement>) => {
    const currentDragState = dragState;
    if (!currentDragState || currentDragState.pointerId !== event.pointerId) {
      return;
    }
    event.currentTarget.releasePointerCapture?.(event.pointerId);
    const holeId = resolveHoleAtPoint(event.clientX, event.clientY);
    setDragState(null);
    if (holeId) {
      void dropToHole(holeId);
      return;
    }
    if (!currentDragState.moved) {
      setIsShapeArmed(true);
      return;
    }
    setIsShapeArmed(false);
  };

  const dropToHole = async (holeId: string) => {
    if (!run || !isRunning(run) || pendingDrop || isBusy) {
      return;
    }

    const clientEventId = buildClientEventId(run.runId, holeId);
    setPendingDrop({ clientEventId, holeId });
    setFeedbackPulseId(null);
    setIsShapeArmed(false);
    setDropError(null);

    try {
      const response = await onDropShape({
        runId: run.runId,
        holeId,
        clientSnapshotVersion: run.snapshotVersion,
        clientEventId,
        droppedAtMs: Date.now(),
      });
      setFeedbackPulseId(clientEventId);
      onOptimisticRunChange?.(response.run);
    } catch (caughtError) {
      setDropError(
        caughtError instanceof Error ? caughtError.message : '本次投入失败',
      );
    } finally {
      setPendingDrop(null);
    }
  };

  if (!run) {
    return (
      <div
        className={`flex ${embedded ? 'h-full min-h-0 w-full' : 'min-h-dvh'} items-center justify-center bg-slate-950 text-white`}
      >
        {isBusy ? '载入中' : (error ?? '暂无运行态')}
      </div>
    );
  }

  const feedback = run.lastFeedback;

  return (
    <main
      className={`relative flex ${embedded ? 'h-full min-h-0' : 'min-h-dvh'} w-full justify-center overflow-hidden bg-[#101827] text-white`}
    >
      {run.backgroundImageSrc ? (
        <ResolvedAssetImage
          src={run.backgroundImageSrc}
          alt=""
          aria-hidden="true"
          className="absolute inset-0 h-full w-full object-cover"
        />
      ) : (
        <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_10%,rgba(125,211,252,0.32),transparent_27%),radial-gradient(circle_at_80%_80%,rgba(248,113,113,0.24),transparent_34%),linear-gradient(180deg,#1f3a5f_0%,#152238_48%,#111827_100%)]" />
      )}
      <div className="absolute inset-0 bg-slate-950/42" />
      {dragState && currentShape ? (
        <div
          className="pointer-events-none fixed z-[95] grid overflow-hidden rounded-[1.15rem] border border-white/18 bg-white/92 shadow-[0_24px_56px_rgba(15,23,42,0.46)]"
          style={{
            left: dragState.x,
            top: dragState.y,
            width: dragState.size,
            height: dragState.size,
            transform: 'translate(-50%, -50%)',
          }}
          aria-hidden="true"
        >
          {currentShape.imageSrc ? (
            <ResolvedAssetImage
              src={currentShape.imageSrc}
              alt=""
              aria-hidden="true"
              className="h-full w-full object-cover"
            />
          ) : (
            <span className="grid h-full w-full place-items-center bg-slate-100 text-slate-500">
              <Image size={30} />
            </span>
          )}
        </div>
      ) : null}
      <div
        className={`relative flex ${embedded ? 'h-full min-h-0' : 'min-h-dvh'} min-w-0 flex-col overflow-hidden px-3 pb-[calc(env(safe-area-inset-bottom,0px)+0.8rem)] pt-[calc(env(safe-area-inset-top,0px)+0.65rem)]`}
        style={{
          boxSizing: 'border-box',
          maxWidth: '100vw',
          width: 'min(100vw, 24rem)',
        }}
      >
        <header className="flex items-center justify-between gap-2">
          <button
            type="button"
            className="flex h-10 w-10 items-center justify-center rounded-full border border-white/16 bg-black/24 text-white backdrop-blur"
            onClick={onBack}
            aria-label="返回"
          >
            <ArrowLeft size={20} />
          </button>
          <div className="flex items-center gap-2 rounded-full border border-white/16 bg-black/26 px-3 py-2 text-sm font-black backdrop-blur">
            <Clock3 size={16} />
            <span>{formatTimer(timeLeftMs)}</span>
          </div>
          <button
            type="button"
            className="flex h-10 w-10 items-center justify-center rounded-full border border-white/16 bg-black/24 text-white backdrop-blur"
            onClick={onRestart}
            aria-label="重新开始"
          >
            <RotateCcw size={18} />
          </button>
        </header>

        <section className="mt-3 grid w-full min-w-0 grid-cols-3 gap-2 overflow-hidden text-center text-[0.72rem] font-black">
          <div className="rounded-2xl border border-white/14 bg-black/20 px-2 py-2 backdrop-blur">
            {progressText}
          </div>
          <div className="rounded-2xl border border-white/14 bg-black/20 px-2 py-2 backdrop-blur">
            {run.score} 分
          </div>
          <div className="rounded-2xl border border-white/14 bg-black/20 px-2 py-2 backdrop-blur">
            {run.combo} 连
          </div>
        </section>

        <section className="relative mt-3 min-h-[22rem] overflow-hidden rounded-[1.5rem] border border-white/14 bg-black/18 p-3 shadow-[0_18px_42px_rgba(15,23,42,0.28)] backdrop-blur">
          <div className="absolute inset-3 rounded-[1.25rem] border border-white/10 bg-white/8" />
          {hintHole ? (
            <div
              className="square-hole-runtime__target-arrow pointer-events-none absolute z-20 -translate-x-1/2 -translate-y-[138%] text-cyan-100 drop-shadow-[0_4px_16px_rgba(103,232,249,0.65)]"
              style={arrowStyle}
              aria-hidden="true"
            >
              <ArrowDown size={34} strokeWidth={3.2} />
            </div>
          ) : null}
          {run.holes.map((hole) => {
            const isPending = pendingDrop?.holeId === hole.holeId;
            const isHint = hintHole?.holeId === hole.holeId;
            return (
              <button
                key={hole.holeId}
                type="button"
                data-square-hole-id={hole.holeId}
                disabled={!isRunning(run) || Boolean(pendingDrop) || isBusy}
                onClick={() => {
                  void dropToHole(hole.holeId);
                }}
                className={`absolute flex h-24 w-24 -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center rounded-[1.35rem] border border-white/14 bg-black/34 p-2 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur transition disabled:cursor-not-allowed disabled:opacity-58 ${
                  isPending || isHint || isShapeArmed
                    ? 'ring-2 ring-cyan-200/70'
                    : ''
                }`}
                style={{
                  left: `${clampPercent(hole.x)}%`,
                  top: `${clampPercent(hole.y)}%`,
                }}
                aria-label={`投入 ${hole.label}`}
              >
                <span className="grid h-14 w-14 place-items-center overflow-hidden rounded-2xl bg-white/88">
                  {hole.imageSrc ? (
                    <ResolvedAssetImage
                      src={hole.imageSrc}
                      alt=""
                      aria-hidden="true"
                      className="h-full w-full object-cover"
                    />
                  ) : (
                    <span className="grid h-full w-full place-items-center bg-slate-100 text-slate-500">
                      <Image size={22} />
                    </span>
                  )}
                </span>
                <span className="mt-2 block truncate text-sm font-black">
                  {hole.label}
                </span>
              </button>
            );
          })}
        </section>

        <section className="relative mt-3 min-h-[9.4rem] rounded-[1.45rem] border border-white/14 bg-black/24 p-3 backdrop-blur">
          {currentShape ? (
            <div className="flex h-full flex-col items-center justify-center gap-3">
              <div
                className={`relative h-24 w-24 touch-none select-none overflow-hidden shadow-[0_18px_38px_rgba(15,23,42,0.34)] transition ${
                  dragState
                    ? 'opacity-35'
                    : isShapeArmed
                      ? 'ring-4 ring-cyan-200/70 active:scale-[0.98]'
                      : 'active:scale-[0.98]'
                } rounded-[1.15rem] border border-white/18 bg-white/92`}
                style={{
                  cursor:
                    isRunning(run) && !pendingDrop && !isBusy
                      ? 'grab'
                      : 'default',
                }}
                onPointerDown={handleShapePointerDown}
                onPointerMove={handleShapePointerMove}
                onPointerUp={handleShapePointerEnd}
                onPointerCancel={handleShapePointerEnd}
                role="button"
                tabIndex={0}
                aria-label={`拖拽${currentShape.label}`}
              >
                {currentShape.imageSrc ? (
                  <ResolvedAssetImage
                    src={currentShape.imageSrc}
                    alt=""
                    aria-hidden="true"
                    className="h-full w-full object-cover"
                  />
                ) : (
                  <span className="grid h-full w-full place-items-center bg-slate-100 text-slate-500">
                    <Image size={32} />
                  </span>
                )}
              </div>
              <div className="flex min-w-0 items-center gap-2 text-base font-black">
                <Shapes size={18} />
                <span className="truncate">{currentShape.label}</span>
              </div>
            </div>
          ) : (
            <div className="flex h-full items-center justify-center text-sm font-bold text-white/70">
              等待下一块
            </div>
          )}
        </section>

        <section className="mt-auto min-h-[3.5rem] pt-3">
          {feedback ? (
            <div
              className={`rounded-[1.2rem] border px-3 py-2 text-center text-sm font-black ${
                feedback.accepted
                  ? 'border-emerald-200/35 bg-emerald-400/18 text-emerald-50'
                  : 'border-rose-200/35 bg-rose-400/18 text-rose-50'
              }`}
            >
              {feedback.message}
            </div>
          ) : dropError || error ? (
            <div className="rounded-[1.2rem] border border-rose-200/35 bg-rose-400/18 px-3 py-2 text-center text-sm font-black text-rose-50">
              {dropError ?? error}
            </div>
          ) : null}
        </section>
      </div>

      {feedbackPulseId && feedback?.accepted ? (
        <div className="pointer-events-none absolute inset-0 z-[70] flex items-center justify-center">
          <div className="flex h-24 w-24 items-center justify-center rounded-full bg-white/24 text-cyan-100 shadow-[0_0_42px_rgba(255,255,255,0.72)] backdrop-blur-sm">
            <Sparkles size={42} />
          </div>
        </div>
      ) : null}

      <SquareHoleSettlement run={run} onBack={onBack} onRestart={onRestart} />
    </main>
  );
}

export default SquareHoleRuntimeShell;
