import { ArrowLeft, Hand, Loader2, RotateCcw } from 'lucide-react';
import {
  type CSSProperties,
  type PointerEvent,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import type {
  JumpHopPlatform,
  JumpHopRuntimeRunSnapshotResponse,
  JumpHopTileAsset,
  JumpHopWorkProfileResponse,
} from '../../../packages/shared/src/contracts/jumpHop';

type JumpHopRuntimeShellProps = {
  profile?: JumpHopWorkProfileResponse | null;
  run?: JumpHopRuntimeRunSnapshotResponse | null;
  snapshot?: JumpHopRuntimeRunSnapshotResponse | null;
  isBusy?: boolean;
  error?: string | null;
  onJump: (payload: { chargeMs: number }) => Promise<unknown>;
  onRestart: () => void;
  onExit?: () => void;
  onBack?: () => void;
};

type VisiblePlatform = {
  platform: JumpHopPlatform;
  index: number;
  screenX: number;
  screenY: number;
  scale: number;
  asset: JumpHopTileAsset | null;
};

const MAX_CHARGE_RATIO = 1;
const DEFAULT_MAX_CHARGE_MS = 1800;
const VISIBLE_FORWARD_COUNT = 6;

const tileToneByType: Record<string, string> = {
  accent: '#e0f2fe',
  bonus: '#fef3c7',
  finish: '#dcfce7',
  normal: '#f8fafc',
  start: '#e0f2fe',
  target: '#fee2e2',
};

function clamp(value: number, min: number, max: number) {
  return Math.min(max, Math.max(min, value));
}

function getRun(
  run: JumpHopRuntimeRunSnapshotResponse | null | undefined,
  snapshot: JumpHopRuntimeRunSnapshotResponse | null | undefined,
) {
  return run ?? snapshot ?? null;
}

function buildTileAssetMap(tileAssets: JumpHopTileAsset[] | undefined) {
  const map = new Map<string, JumpHopTileAsset>();
  for (const asset of tileAssets ?? []) {
    if (!map.has(asset.tileType)) {
      map.set(asset.tileType, asset);
    }
  }
  return map;
}

function getStatusLabel(
  status: JumpHopRuntimeRunSnapshotResponse['status'] | undefined,
) {
  if (status === 'cleared') {
    return '通关';
  }
  if (status === 'failed') {
    return '失败';
  }
  return '进行中';
}

function getJumpFeedback(run: JumpHopRuntimeRunSnapshotResponse | null) {
  const result = run?.lastJump?.result;
  if (result === 'perfect') {
    return 'Perfect';
  }
  if (result === 'finish') {
    return 'Finish';
  }
  if (result === 'hit') {
    return 'Hit';
  }
  if (result === 'miss') {
    return 'Miss';
  }
  return null;
}

function projectPlatformPath(
  platforms: JumpHopPlatform[],
  currentIndex: number,
  tileAssetMap: Map<string, JumpHopTileAsset>,
) {
  const current = platforms[currentIndex] ?? platforms[0];
  if (!current) {
    return [];
  }

  const start = Math.max(0, currentIndex - 1);
  const end = Math.min(platforms.length, currentIndex + VISIBLE_FORWARD_COUNT);
  const visible = platforms.slice(start, end);
  const worldScale = 0.86;

  return visible.map((platform, offset): VisiblePlatform => {
    const index = start + offset;
    const dx = platform.x - current.x;
    const dy = platform.y - current.y;
    const isoX = (dx - dy) * worldScale;
    const isoY = (dx + dy) * 0.46 * worldScale;
    const depth = index - currentIndex;

    return {
      platform,
      index,
      screenX: 50 + isoX,
      screenY: 58 + isoY - depth * 0.8,
      scale: clamp(1 - Math.max(0, depth) * 0.035, 0.78, 1.08),
      asset:
        tileAssetMap.get(platform.tileType) ??
        tileAssetMap.get('normal') ??
        tileAssetMap.get('start') ??
        null,
    };
  });
}

function getCharacterPosition(
  run: JumpHopRuntimeRunSnapshotResponse | null,
  platforms: VisiblePlatform[],
) {
  if (!run) {
    return null;
  }

  const landedPlatform = platforms.find(
    (item) => item.index === run.currentPlatformIndex,
  );
  if (landedPlatform) {
    return {
      x: landedPlatform.screenX,
      y: landedPlatform.screenY - 8,
      isMiss: false,
    };
  }

  const lastJump = run.lastJump;
  if (lastJump && run.status === 'failed') {
    const targetPlatform = platforms.find(
      (item) => item.index === lastJump.targetPlatformIndex,
    );
    if (targetPlatform) {
      return {
        x: targetPlatform.screenX + 8,
        y: targetPlatform.screenY - 2,
        isMiss: true,
      };
    }
  }

  return null;
}

function IsometricFallbackTile({ platform }: { platform: JumpHopPlatform }) {
  const tone = tileToneByType[platform.tileType] ?? tileToneByType.normal;
  const style = {
    '--jump-hop-tile-tone': tone,
  } as CSSProperties;

  return (
    <div
      className="jump-hop-runtime__fallback-tile"
      style={style}
      aria-hidden="true"
    >
      <div className="jump-hop-runtime__fallback-top" />
      <div className="jump-hop-runtime__fallback-side jump-hop-runtime__fallback-side--left" />
      <div className="jump-hop-runtime__fallback-side jump-hop-runtime__fallback-side--right" />
    </div>
  );
}

export function JumpHopRuntimeShell({
  profile = null,
  run,
  snapshot,
  isBusy = false,
  error = null,
  onExit,
  onBack,
  onRestart,
  onJump,
}: JumpHopRuntimeShellProps) {
  const activeRun = getRun(run, snapshot);
  const [isCharging, setIsCharging] = useState(false);
  const [chargeMs, setChargeMs] = useState(0);
  const chargeStartRef = useRef<number | null>(null);

  const maxChargeMs =
    activeRun?.path.scoring.maxChargeMs &&
    activeRun.path.scoring.maxChargeMs > 0
      ? activeRun.path.scoring.maxChargeMs
      : DEFAULT_MAX_CHARGE_MS;
  const chargeRatio = clamp(chargeMs / maxChargeMs, 0, MAX_CHARGE_RATIO);
  const canJump = Boolean(
    activeRun && activeRun.status === 'playing' && !isBusy,
  );
  const exitHandler = onExit ?? onBack;
  const tileAssetMap = useMemo(
    () => buildTileAssetMap(profile?.tileAssets),
    [profile?.tileAssets],
  );
  const visiblePlatforms = useMemo(
    () =>
      projectPlatformPath(
        activeRun?.path.platforms ?? [],
        activeRun?.currentPlatformIndex ?? 0,
        tileAssetMap,
      ),
    [activeRun?.currentPlatformIndex, activeRun?.path.platforms, tileAssetMap],
  );
  const characterPosition = getCharacterPosition(activeRun, visiblePlatforms);
  const jumpFeedback = getJumpFeedback(activeRun);
  const isSettled =
    activeRun?.status === 'failed' || activeRun?.status === 'cleared';

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

    const timer = window.setInterval(() => {
      if (chargeStartRef.current == null) {
        return;
      }
      setChargeMs(clamp(Date.now() - chargeStartRef.current, 0, maxChargeMs));
    }, 16);

    return () => window.clearInterval(timer);
  }, [isCharging, maxChargeMs]);

  useEffect(() => {
    setIsCharging(false);
    chargeStartRef.current = null;
    setChargeMs(0);
  }, [activeRun?.runId, activeRun?.currentPlatformIndex, activeRun?.status]);

  const beginCharge = (event: PointerEvent<HTMLElement>) => {
    if (!canJump) {
      return;
    }
    event.currentTarget.setPointerCapture?.(event.pointerId);
    chargeStartRef.current = Date.now();
    setIsCharging(true);
    setChargeMs(0);
  };

  const finishCharge = async () => {
    if (!isCharging) {
      return;
    }

    const nextChargeMs = clamp(
      chargeStartRef.current ? Date.now() - chargeStartRef.current : chargeMs,
      0,
      maxChargeMs,
    );
    chargeStartRef.current = null;
    setIsCharging(false);
    setChargeMs(nextChargeMs);
    await onJump({ chargeMs: nextChargeMs });
  };

  const cancelCharge = () => {
    chargeStartRef.current = null;
    setIsCharging(false);
    setChargeMs(0);
  };

  return (
    <div className="platform-remap-surface jump-hop-runtime relative flex h-full min-h-0 w-full flex-col overflow-hidden bg-[#fffdf9] text-slate-950">
      <div className="jump-hop-runtime__sky" />
      <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_18%,rgba(255,255,255,0.82),transparent_30%),linear-gradient(180deg,rgba(255,255,255,0.18),rgba(234,204,179,0.24))]" />

      <header className="relative z-20 flex items-center justify-between gap-2 px-3 pb-2 pt-[max(0.75rem,env(safe-area-inset-top))] sm:px-4">
        <button
          type="button"
          onClick={exitHandler}
          className="platform-button platform-button--ghost min-h-0 rounded-full bg-white/80 px-3 py-2 text-sm shadow-sm backdrop-blur"
        >
          <ArrowLeft className="h-4 w-4" />
          返回
        </button>
        <div className="flex items-center gap-2 rounded-full border border-white/70 bg-white/82 px-3 py-2 text-sm font-black shadow-sm backdrop-blur">
          <span>{activeRun?.score ?? 0}</span>
          <span className="h-1 w-1 rounded-full bg-slate-300" />
          <span>{activeRun?.combo ?? 0}x</span>
        </div>
        <button
          type="button"
          onClick={onRestart}
          disabled={isBusy}
          className="platform-button platform-button--ghost min-h-0 rounded-full bg-white/80 px-3 py-2 text-sm shadow-sm backdrop-blur"
        >
          <RotateCcw className="h-4 w-4" />
          重开
        </button>
      </header>

      <main className="relative z-10 mx-auto flex w-full max-w-[30rem] flex-1 flex-col px-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] sm:px-4">
        <section
          className="jump-hop-runtime__stage relative min-h-0 flex-1 touch-none select-none overflow-hidden rounded-[1.5rem] border border-white/70 bg-white/40 shadow-[0_24px_70px_rgba(44,125,182,0.2)]"
          onPointerDown={beginCharge}
          onPointerUp={() => void finishCharge()}
          onPointerCancel={cancelCharge}
          onPointerLeave={() => {
            if (isCharging) {
              void finishCharge();
            }
          }}
        >
          <div className="jump-hop-runtime__horizon" />
          <div className="jump-hop-runtime__path-shadow" />

          {visiblePlatforms.map((item) => {
            const width =
              clamp(item.platform.width * 0.92, 58, 112) * item.scale;
            const height =
              clamp(item.platform.height * 0.72, 46, 86) * item.scale;
            const style = {
              left: `${item.screenX}%`,
              top: `${item.screenY}%`,
              width,
              height,
              zIndex: 20 + item.index,
            } as CSSProperties;
            const isCurrent = item.index === activeRun?.currentPlatformIndex;

            return (
              <div
                key={item.platform.platformId}
                className="jump-hop-runtime__platform"
                style={style}
                data-current={isCurrent ? 'true' : 'false'}
              >
                <div className="jump-hop-runtime__platform-shadow" />
                {item.asset?.imageSrc ? (
                  <img
                    src={item.asset.imageSrc}
                    alt=""
                    draggable={false}
                    className="jump-hop-runtime__tile-image"
                  />
                ) : (
                  <IsometricFallbackTile platform={item.platform} />
                )}
              </div>
            );
          })}

          {characterPosition ? (
            <div
              className="jump-hop-runtime__character"
              data-charging={isCharging ? 'true' : 'false'}
              data-miss={characterPosition.isMiss ? 'true' : 'false'}
              style={
                {
                  left: `${characterPosition.x}%`,
                  top: `${characterPosition.y}%`,
                  '--jump-hop-charge': chargeRatio,
                } as CSSProperties
              }
            >
              <div className="jump-hop-runtime__character-shadow" />
              {profile?.characterAsset?.imageSrc ? (
                <img
                  src={profile.characterAsset.imageSrc}
                  alt=""
                  draggable={false}
                  className="jump-hop-runtime__character-image"
                />
              ) : (
                <div className="jump-hop-runtime__character-fallback" />
              )}
            </div>
          ) : null}

          {jumpFeedback ? (
            <div
              key={`${activeRun?.currentPlatformIndex}-${activeRun?.lastJump?.result}`}
              className="jump-hop-runtime__feedback"
            >
              {jumpFeedback}
            </div>
          ) : null}

          {isCharging ? (
            <div className="jump-hop-runtime__charge-orbit" aria-hidden="true">
              <div
                className="jump-hop-runtime__charge-fill"
                style={{ transform: `scaleX(${chargeRatio})` }}
              />
            </div>
          ) : null}

          {!activeRun ? (
            <div className="absolute inset-0 grid place-items-center bg-white/35 text-sm font-black text-slate-600 backdrop-blur-sm">
              等待开局
            </div>
          ) : null}

          {isSettled ? (
            <div className="absolute inset-0 z-50 grid place-items-center bg-slate-950/28 px-6 backdrop-blur-[2px]">
              <div className="w-full max-w-[18rem] rounded-[1.25rem] border border-white/70 bg-white/90 p-4 text-center shadow-[0_18px_50px_rgba(15,23,42,0.22)]">
                <div className="text-2xl font-black">
                  {getStatusLabel(activeRun?.status)}
                </div>
                <div className="mt-2 flex justify-center gap-4 text-sm font-bold text-slate-600">
                  <span>{activeRun?.score ?? 0} 分</span>
                  <span>{activeRun?.combo ?? 0}x</span>
                </div>
                <div className="mt-4 grid grid-cols-2 gap-2">
                  <button
                    type="button"
                    onClick={onRestart}
                    disabled={isBusy}
                    className="platform-button platform-button--primary min-h-11 px-3 py-2 text-sm"
                  >
                    重开
                  </button>
                  <button
                    type="button"
                    onClick={exitHandler}
                    className="platform-button platform-button--ghost min-h-11 bg-white px-3 py-2 text-sm"
                  >
                    返回
                  </button>
                </div>
              </div>
            </div>
          ) : null}
        </section>

        <footer className="relative z-20 mt-3 flex items-center justify-between gap-3">
          <div className="min-w-0 text-sm font-bold text-slate-700">
            {error ? (
              <span className="text-[var(--platform-button-danger-text)]">
                {error}
              </span>
            ) : (
              <span>{getStatusLabel(activeRun?.status)}</span>
            )}
          </div>
          <button
            type="button"
            disabled={!canJump}
            onPointerDown={beginCharge}
            onPointerUp={() => void finishCharge()}
            onPointerCancel={cancelCharge}
            className="platform-button platform-button--primary min-h-12 shrink-0 gap-2 rounded-full px-5 py-3 text-sm shadow-[0_12px_28px_rgba(182,98,63,0.22)]"
          >
            {isBusy ? (
              <Loader2 className="h-4 w-4 animate-spin" />
            ) : (
              <Hand className="h-4 w-4" />
            )}
            起跳
          </button>
        </footer>
      </main>

      <style>{`
        .jump-hop-runtime__sky {
          position: absolute;
          inset: 0;
          background:
            radial-gradient(circle at 18% 18%, rgba(253, 230, 138, 0.36), transparent 24%),
            radial-gradient(circle at 82% 22%, rgba(226, 171, 134, 0.34), transparent 28%),
            linear-gradient(180deg, #fffdf9 0%, #f8efe7 52%, #f4e5d7 100%);
        }

        .jump-hop-runtime__stage {
          min-height: 31rem;
          cursor: pointer;
        }

        .jump-hop-runtime__horizon {
          position: absolute;
          inset: 10% 5% 8%;
          border-radius: 999px;
          background:
            radial-gradient(ellipse at center, rgba(255, 255, 255, 0.86) 0%, rgba(255, 255, 255, 0.32) 38%, transparent 70%);
          transform: rotate(-8deg);
        }

        .jump-hop-runtime__path-shadow {
          position: absolute;
          left: 21%;
          right: 16%;
          top: 55%;
          height: 18%;
          border-radius: 999px;
          background: radial-gradient(ellipse at center, rgba(45, 118, 170, 0.16), transparent 68%);
          transform: rotate(22deg);
        }

        .jump-hop-runtime__platform {
          position: absolute;
          transform: translate(-50%, -50%);
          display: grid;
          place-items: center;
          transition:
            left 220ms ease,
            top 220ms ease,
            transform 220ms ease;
        }

        .jump-hop-runtime__platform[data-current='true'] {
          transform: translate(-50%, -50%) scale(1.07);
        }

        .jump-hop-runtime__platform-shadow {
          position: absolute;
          left: 8%;
          right: 8%;
          bottom: -16%;
          height: 32%;
          border-radius: 999px;
          background: rgba(15, 82, 126, 0.18);
          filter: blur(7px);
        }

        .jump-hop-runtime__tile-image {
          position: relative;
          width: 124%;
          height: 124%;
          object-fit: contain;
          image-rendering: auto;
          filter: drop-shadow(0 16px 16px rgba(24, 75, 112, 0.18));
        }

        .jump-hop-runtime__fallback-tile {
          position: relative;
          width: 100%;
          height: 100%;
          transform: rotateX(58deg) rotateZ(45deg);
          transform-style: preserve-3d;
        }

        .jump-hop-runtime__fallback-top {
          position: absolute;
          inset: 8%;
          border-radius: 14%;
          background:
            linear-gradient(135deg, rgba(255,255,255,0.8), transparent 44%),
            var(--jump-hop-tile-tone);
          border: 2px solid rgba(255, 255, 255, 0.86);
          box-shadow: inset -8px -8px 14px rgba(15, 23, 42, 0.08);
        }

        .jump-hop-runtime__fallback-side {
          position: absolute;
          background: color-mix(in srgb, var(--jump-hop-tile-tone) 72%, #0f766e);
          opacity: 0.88;
        }

        .jump-hop-runtime__fallback-side--left {
          left: 8%;
          bottom: -9%;
          width: 84%;
          height: 18%;
          transform: skewX(45deg);
          transform-origin: top;
        }

        .jump-hop-runtime__fallback-side--right {
          right: -9%;
          top: 8%;
          width: 18%;
          height: 84%;
          transform: skewY(45deg);
          transform-origin: left;
        }

        .jump-hop-runtime__character {
          position: absolute;
          z-index: 80;
          width: 4.7rem;
          height: 5.5rem;
          transform:
            translate(-50%, -86%)
            scaleY(calc(1 - (var(--jump-hop-charge) * 0.16)))
            scaleX(calc(1 + (var(--jump-hop-charge) * 0.08)));
          transform-origin: 50% 100%;
          transition:
            left 240ms ease,
            top 240ms ease,
            transform 120ms ease,
            opacity 160ms ease;
        }

        .jump-hop-runtime__character[data-miss='true'] {
          opacity: 0.78;
          transform: translate(-50%, -60%) rotate(18deg) scale(0.88);
        }

        .jump-hop-runtime__character-shadow {
          position: absolute;
          left: 20%;
          right: 20%;
          bottom: 0.16rem;
          height: 0.86rem;
          border-radius: 999px;
          background: rgba(15, 23, 42, 0.18);
          filter: blur(3px);
          transform: scaleX(calc(1 + (var(--jump-hop-charge) * 0.42)));
        }

        .jump-hop-runtime__character-image {
          position: absolute;
          inset: 0;
          width: 100%;
          height: 100%;
          object-fit: contain;
          filter: drop-shadow(0 12px 10px rgba(15, 23, 42, 0.18));
        }

        .jump-hop-runtime__character-fallback {
          position: absolute;
          left: 50%;
          bottom: 0.68rem;
          width: 2.3rem;
          height: 3.3rem;
          transform: translateX(-50%);
          border-radius: 999px 999px 0.9rem 0.9rem;
          background:
            radial-gradient(circle at 50% 22%, #fff 0 17%, transparent 18%),
            linear-gradient(180deg, #c7653d 0%, #df7f40 100%);
          box-shadow: inset 0 0 0 2px rgba(255,255,255,0.72), 0 12px 18px rgba(190, 80, 40, 0.24);
        }

        .jump-hop-runtime__feedback {
          position: absolute;
          left: 50%;
          top: 23%;
          z-index: 90;
          transform: translateX(-50%);
          border-radius: 999px;
          background: rgba(15, 23, 42, 0.74);
          color: white;
          padding: 0.42rem 0.86rem;
          font-size: 0.85rem;
          font-weight: 900;
          letter-spacing: 0;
          animation: jump-hop-feedback 900ms ease both;
        }

        .jump-hop-runtime__charge-orbit {
          position: absolute;
          left: 1.1rem;
          right: 1.1rem;
          bottom: 1rem;
          z-index: 85;
          height: 0.62rem;
          overflow: hidden;
          border-radius: 999px;
          background: rgba(15, 23, 42, 0.16);
          box-shadow: inset 0 0 0 1px rgba(255,255,255,0.65);
        }

        .jump-hop-runtime__charge-fill {
          width: 100%;
          height: 100%;
          transform-origin: left center;
          border-radius: inherit;
          background: linear-gradient(90deg, #6e8d42, #f0cba9, #c7653d);
        }

        @keyframes jump-hop-feedback {
          0% {
            opacity: 0;
            transform: translate(-50%, 0.8rem) scale(0.96);
          }
          20%, 72% {
            opacity: 1;
            transform: translate(-50%, 0) scale(1);
          }
          100% {
            opacity: 0;
            transform: translate(-50%, -0.6rem) scale(1.02);
          }
        }

        @media (max-width: 430px) {
          .jump-hop-runtime__stage {
            min-height: min(68vh, 36rem);
            border-radius: 1.25rem;
          }

          .jump-hop-runtime__character {
            width: 4.15rem;
            height: 4.95rem;
          }
        }

        @media (prefers-reduced-motion: reduce) {
          .jump-hop-runtime__feedback {
            animation: none;
          }

          .jump-hop-runtime__platform,
          .jump-hop-runtime__character {
            transition: none;
          }
        }
      `}</style>
    </div>
  );
}

export default JumpHopRuntimeShell;
