import {
  BadgeCheck,
  CircleAlert,
  Clock3,
  Loader2,
  Share2,
  Trash2,
} from 'lucide-react';
import {
  type CSSProperties,
  default as React,
  type KeyboardEvent as ReactKeyboardEvent,
  type PointerEvent as ReactPointerEvent,
  type TouchEvent as ReactTouchEvent,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import { CustomWorldCoverArtwork } from '../CustomWorldCoverArtwork';
import {
  formatPlatformWorkDisplayName,
  formatPlatformWorkDisplayTag,
} from '../rpg-entry/rpgEntryWorldPresentation';
import {
  CREATION_WORK_KIND_FALLBACK_COVER,
  type CreationWorkShelfBadgeTone,
  type CreationWorkShelfItem,
  type CreationWorkShelfMetric,
  type CreationWorkShelfMetricId,
  formatCreationMetricCount,
  formatCreationPointIncentiveTotal,
} from './creationWorkShelf';

type CustomWorldWorkCardProps = {
  item: CreationWorkShelfItem;
  previousMetricValues?: Partial<Record<CreationWorkShelfMetricId, number>>;
  onOpen: () => void;
  onDelete?: (() => void) | null;
  deleteBusy?: boolean;
  onShare?: (() => void) | null;
  onClaimPointIncentive?: (() => void) | null;
  pointIncentiveBusy?: boolean;
};

const BADGE_TONE_CLASS: Record<CreationWorkShelfBadgeTone, string> = {
  warm: 'platform-pill--warm',
  success: 'platform-pill--success',
  neutral: 'platform-pill--neutral',
};

const METRIC_ANIMATION_DURATION_MS = 820;
const SWIPE_ACTION_WIDTH_PX = 76;
const SWIPE_REVEAL_THRESHOLD_PX = 42;
const SWIPE_DIRECTION_LOCK_PX = 8;
const EMPTY_PUBLISHED_METRICS: CreationWorkShelfMetric[] = [];
function easeOutCubic(progress: number) {
  return 1 - (1 - progress) ** 3;
}

function resolveMetricStartValue(
  metric: CreationWorkShelfMetric,
  previousMetricValues?: Partial<Record<CreationWorkShelfMetricId, number>>,
) {
  const previousValue = previousMetricValues?.[metric.id];
  if (previousValue === undefined || previousValue >= metric.value) {
    return metric.value;
  }

  return Math.max(0, Math.floor(previousValue));
}

function buildMetricValueMap(
  metrics: CreationWorkShelfMetric[],
  resolveValue: (metric: CreationWorkShelfMetric) => number,
) {
  return Object.fromEntries(
    metrics.map((metric) => [metric.id, resolveValue(metric)]),
  ) as Record<CreationWorkShelfMetricId, number>;
}

function shouldAnimatePublishedMetrics() {
  if (typeof window === 'undefined') {
    return false;
  }

  return !window.navigator.userAgent.toLowerCase().includes('jsdom');
}

function clampSwipeOffset(value: number, revealWidth: number) {
  return Math.min(0, Math.max(-revealWidth, value));
}

function usePublishedMetricAnimation(
  metrics: CreationWorkShelfMetric[],
  previousMetricValues?: Partial<Record<CreationWorkShelfMetricId, number>>,
) {
  const cardRef = useRef<HTMLDivElement | null>(null);
  const [hasEnteredView, setHasEnteredView] = useState(false);
  const startValues = useMemo(
    () =>
      buildMetricValueMap(metrics, (metric) =>
        resolveMetricStartValue(metric, previousMetricValues),
      ),
    [metrics, previousMetricValues],
  );
  const endValues = useMemo(
    () => buildMetricValueMap(metrics, (metric) => metric.value),
    [metrics],
  );
  const deltas = useMemo(
    () =>
      buildMetricValueMap(metrics, (metric) =>
        Math.max(0, metric.value - startValues[metric.id]),
      ),
    [metrics, startValues],
  );
  const hasGrowth = useMemo(
    () => Object.values(deltas).some((delta) => delta > 0),
    [deltas],
  );
  const [displayValues, setDisplayValues] = useState(endValues);
  const [showGrowth, setShowGrowth] = useState(false);

  useEffect(() => {
    setShowGrowth(false);
    setHasEnteredView(false);
    setDisplayValues(hasGrowth ? startValues : endValues);
  }, [endValues, hasGrowth, startValues]);

  useEffect(() => {
    const element = cardRef.current;
    if (!element || !hasGrowth) {
      setHasEnteredView(true);
      return;
    }

    if (typeof window === 'undefined' || !('IntersectionObserver' in window)) {
      setHasEnteredView(true);
      return;
    }

    // 中文注释：指标增长只在卡片进入视口后启动，避免列表刷新时离屏卡片提前播放。
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries.some((entry) => entry.isIntersecting)) {
          setHasEnteredView(true);
          observer.disconnect();
        }
      },
      { rootMargin: '0px 0px -10% 0px', threshold: 0.28 },
    );
    observer.observe(element);
    return () => observer.disconnect();
  }, [hasGrowth]);

  useEffect(() => {
    if (!hasEnteredView) {
      return;
    }

    if (!hasGrowth || !shouldAnimatePublishedMetrics()) {
      setDisplayValues(endValues);
      if (hasGrowth) {
        setShowGrowth(true);
      }
      return;
    }

    if (typeof window === 'undefined') {
      setDisplayValues(endValues);
      setShowGrowth(true);
      return;
    }

    let animationFrameId = 0;
    const startTime = window.performance.now();
    const tick = (now: number) => {
      const progress = Math.min(
        1,
        (now - startTime) / METRIC_ANIMATION_DURATION_MS,
      );
      const easedProgress = easeOutCubic(progress);
      setDisplayValues(
        buildMetricValueMap(metrics, (metric) => {
          const startValue = startValues[metric.id];
          const endValue = endValues[metric.id];
          return Math.round(
            startValue + (endValue - startValue) * easedProgress,
          );
        }),
      );

      if (progress < 1) {
        animationFrameId = window.requestAnimationFrame(tick);
        return;
      }

      setDisplayValues(endValues);
      setShowGrowth(true);
    };

    animationFrameId = window.requestAnimationFrame(tick);
    return () => {
      window.cancelAnimationFrame(animationFrameId);
    };
  }, [endValues, hasEnteredView, hasGrowth, metrics, startValues]);

  return { cardRef, deltas, displayValues, showGrowth };
}

export function CustomWorldWorkCard({
  item,
  previousMetricValues,
  onOpen,
  onDelete = null,
  deleteBusy = false,
  onShare = null,
  onClaimPointIncentive = null,
  pointIncentiveBusy = false,
}: CustomWorldWorkCardProps) {
  const suppressOpenResetTimerRef = useRef<number | null>(null);
  const suppressOpenRef = useRef(false);
  const swipeGestureRef = useRef<{
    pointerId: number;
    startX: number;
    startY: number;
    startOffset: number;
    isDragging: boolean;
  } | null>(null);
  const lastSwipeOffsetRef = useRef(0);
  const [isSwipeDragging, setIsSwipeDragging] = useState(false);
  const [isSwipeActionRevealed, setIsSwipeActionRevealed] = useState(false);
  const [swipeOffset, setSwipeOffset] = useState(0);
  const isPublished = item.status === 'published';
  const canUseShareAction =
    isPublished && item.canShare && Boolean(item.sharePath) && Boolean(onShare);
  const swipeActionCount = onDelete ? 1 : 0;
  const swipeRevealWidth = swipeActionCount * SWIPE_ACTION_WIDTH_PX;
  const canClaimPointIncentive =
    Boolean(onClaimPointIncentive) &&
    (item.pointIncentive?.claimablePoints ?? 0) > 0;
  const displayTitle = formatPlatformWorkDisplayName(item.title);
  const fallbackCoverImageSrc = CREATION_WORK_KIND_FALLBACK_COVER[item.kind];
  const { cardRef, deltas, displayValues, showGrowth } =
    usePublishedMetricAnimation(
      isPublished ? item.metrics : EMPTY_PUBLISHED_METRICS,
      previousMetricValues,
    );
  const coverFadeStyle = {
    WebkitMaskImage:
      'linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.04) 18%, rgba(0, 0, 0, 0.18) 42%, rgba(0, 0, 0, 0.48) 70%, rgba(0, 0, 0, 0.72) 100%)',
    maskImage:
      'linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.04) 18%, rgba(0, 0, 0, 0.18) 42%, rgba(0, 0, 0, 0.48) 70%, rgba(0, 0, 0, 0.72) 100%)',
  } as CSSProperties;
  const currentSwipeOffset = isSwipeDragging
    ? swipeOffset
    : isSwipeActionRevealed
      ? -swipeRevealWidth
      : 0;
  const cardSurfaceStyle = {
    '--creation-work-card-swipe-offset': `${currentSwipeOffset}px`,
    '--creation-work-card-cover-fallback': `url(${fallbackCoverImageSrc})`,
  } as CSSProperties;
  const swipeShellStyle = {
    '--creation-work-card-action-opacity': `${
      swipeRevealWidth > 0
        ? Math.min(1, Math.abs(currentSwipeOffset) / swipeRevealWidth)
        : 0
    }`,
  } as CSSProperties;

  useEffect(() => {
    return () => {
      if (suppressOpenResetTimerRef.current !== null) {
        window.clearTimeout(suppressOpenResetTimerRef.current);
      }
    };
  }, []);

  const closeSwipeActions = () => {
    setIsSwipeActionRevealed(false);
    setSwipeOffset(0);
    lastSwipeOffsetRef.current = 0;
  };

  const revealSwipeActions = () => {
    if (swipeRevealWidth <= 0) {
      closeSwipeActions();
      return;
    }

    setIsSwipeActionRevealed(true);
    setSwipeOffset(-swipeRevealWidth);
    lastSwipeOffsetRef.current = -swipeRevealWidth;
  };

  const updateSwipeOffset = (
    gesture: NonNullable<typeof swipeGestureRef.current>,
    clientX: number,
    clientY: number,
    preventDefault: () => void,
  ) => {
    const deltaX = clientX - gesture.startX;
    const deltaY = clientY - gesture.startY;
    if (!gesture.isDragging) {
      if (
        Math.abs(deltaX) < SWIPE_DIRECTION_LOCK_PX &&
        Math.abs(deltaY) < SWIPE_DIRECTION_LOCK_PX
      ) {
        return;
      }

      if (Math.abs(deltaY) > Math.abs(deltaX)) {
        swipeGestureRef.current = null;
        return;
      }

      gesture.isDragging = true;
      setIsSwipeDragging(true);
    }

    // 中文注释：横向手势只移动卡片表层，删除动作保持在底层，避免列表滚动时误触。
    preventDefault();
    suppressOpenRef.current = true;
    const nextOffset = clampSwipeOffset(
      gesture.startOffset + deltaX,
      swipeRevealWidth,
    );
    lastSwipeOffsetRef.current = nextOffset;
    setSwipeOffset(nextOffset);
  };

  const finishSwipeGesture = (wasDragging: boolean) => {
    setIsSwipeDragging(false);
    if (!wasDragging) {
      return;
    }

    const shouldReveal =
      lastSwipeOffsetRef.current <=
      -Math.min(SWIPE_REVEAL_THRESHOLD_PX, swipeRevealWidth * 0.45);
    if (shouldReveal) {
      revealSwipeActions();
    } else {
      closeSwipeActions();
    }
    suppressOpenRef.current = true;
    scheduleOpenSuppressReset();
  };

  const scheduleOpenSuppressReset = () => {
    if (typeof window === 'undefined') {
      return;
    }

    if (suppressOpenResetTimerRef.current !== null) {
      window.clearTimeout(suppressOpenResetTimerRef.current);
    }

    suppressOpenResetTimerRef.current = window.setTimeout(() => {
      suppressOpenResetTimerRef.current = null;
      suppressOpenRef.current = false;
    }, 260);
  };

  useEffect(() => {
    if (swipeActionCount > 0) {
      return;
    }

    closeSwipeActions();
  }, [swipeActionCount]);

  const beginSwipeGesture = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (swipeRevealWidth <= 0) {
      return;
    }

    if (event.pointerType === 'mouse' && event.button !== 0) {
      return;
    }

    swipeGestureRef.current = {
      pointerId: event.pointerId,
      startX: event.clientX,
      startY: event.clientY,
      startOffset: isSwipeActionRevealed ? -swipeRevealWidth : 0,
      isDragging: false,
    };
    event.currentTarget.setPointerCapture?.(event.pointerId);
  };

  const updateSwipeGesture = (event: ReactPointerEvent<HTMLDivElement>) => {
    const gesture = swipeGestureRef.current;
    if (!gesture || gesture.pointerId !== event.pointerId) {
      return;
    }

    updateSwipeOffset(gesture, event.clientX, event.clientY, () =>
      event.preventDefault(),
    );
  };

  const endSwipeGesture = (event: ReactPointerEvent<HTMLDivElement>) => {
    const gesture = swipeGestureRef.current;
    if (!gesture || gesture.pointerId !== event.pointerId) {
      return;
    }

    event.currentTarget.releasePointerCapture?.(event.pointerId);
    swipeGestureRef.current = null;
    finishSwipeGesture(gesture.isDragging);
  };

  const cancelSwipeGesture = (event: ReactPointerEvent<HTMLDivElement>) => {
    const gesture = swipeGestureRef.current;
    if (gesture?.pointerId === event.pointerId) {
      event.currentTarget.releasePointerCapture?.(event.pointerId);
    }

    swipeGestureRef.current = null;
    setIsSwipeDragging(false);
    if (isSwipeActionRevealed) {
      revealSwipeActions();
    } else {
      closeSwipeActions();
    }
  };

  const beginTouchSwipeGesture = (event: ReactTouchEvent<HTMLDivElement>) => {
    if (swipeRevealWidth <= 0) {
      return;
    }

    const touch = event.touches[0];
    if (!touch) {
      return;
    }

    swipeGestureRef.current = {
      pointerId: -1,
      startX: touch.clientX,
      startY: touch.clientY,
      startOffset: isSwipeActionRevealed ? -swipeRevealWidth : 0,
      isDragging: false,
    };
  };

  const updateTouchSwipeGesture = (event: ReactTouchEvent<HTMLDivElement>) => {
    const gesture = swipeGestureRef.current;
    const touch = event.touches[0];
    if (!gesture || gesture.pointerId !== -1 || !touch) {
      return;
    }

    updateSwipeOffset(gesture, touch.clientX, touch.clientY, () =>
      event.preventDefault(),
    );
  };

  const endTouchSwipeGesture = () => {
    const gesture = swipeGestureRef.current;
    if (!gesture || gesture.pointerId !== -1) {
      return;
    }

    swipeGestureRef.current = null;
    finishSwipeGesture(gesture.isDragging);
  };

  const cancelTouchSwipeGesture = () => {
    const gesture = swipeGestureRef.current;
    if (!gesture || gesture.pointerId !== -1) {
      return;
    }

    swipeGestureRef.current = null;
    setIsSwipeDragging(false);
    if (isSwipeActionRevealed) {
      revealSwipeActions();
    } else {
      closeSwipeActions();
    }
  };

  const handleCardOpen = () => {
    if (isSwipeActionRevealed) {
      closeSwipeActions();
      return;
    }

    onOpen();
  };

  const handleCardKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {
    if (
      (event.key === 'ArrowLeft' ||
        event.key === 'ContextMenu' ||
        (event.shiftKey && event.key === 'F10')) &&
      swipeRevealWidth > 0
    ) {
      event.preventDefault();
      revealSwipeActions();
      return;
    }

    if (event.key === 'Escape' && isSwipeActionRevealed) {
      event.preventDefault();
      closeSwipeActions();
      return;
    }

    if (event.key !== 'Enter' && event.key !== ' ') {
      return;
    }

    event.preventDefault();
    handleCardOpen();
  };

  return (
    <div
      ref={cardRef}
      style={swipeShellStyle}
      className={`creation-work-card-shell ${
        isSwipeDragging || isSwipeActionRevealed
          ? 'creation-work-card-shell--actions-visible'
          : ''
      }`}
    >
      {swipeActionCount > 0 ? (
        <div
          aria-hidden={!isSwipeActionRevealed}
          className="creation-work-card__swipe-underlay"
        >
          <div className="creation-work-card__swipe-actions">
            {onDelete ? (
              <button
                type="button"
                tabIndex={isSwipeActionRevealed ? 0 : -1}
                onClick={(event) => {
                  event.stopPropagation();
                  suppressOpenRef.current = false;
                  closeSwipeActions();
                  onDelete();
                }}
                onKeyDown={(event) => {
                  event.stopPropagation();
                }}
                disabled={deleteBusy}
                aria-label={deleteBusy ? '删除中' : '删除'}
                title={deleteBusy ? '删除中' : '删除作品'}
                className="creation-work-card__swipe-button creation-work-card__swipe-button--danger"
              >
                {deleteBusy ? (
                  <span className="text-xs leading-none">...</span>
                ) : (
                  <Trash2 aria-hidden="true" className="h-4 w-4" />
                )}
              </button>
            ) : null}
          </div>
        </div>
      ) : null}

      <div
        role="button"
        tabIndex={0}
        aria-label={`${item.openActionLabel}《${item.title}》${item.isGenerating ? '，生成中' : ''}`}
        onClick={(event) => {
          if (suppressOpenRef.current) {
            event.preventDefault();
            suppressOpenRef.current = false;
            return;
          }

          handleCardOpen();
        }}
        onKeyDown={handleCardKeyDown}
        onPointerDown={beginSwipeGesture}
        onPointerMove={updateSwipeGesture}
        onPointerUp={endSwipeGesture}
        onPointerCancel={cancelSwipeGesture}
        onTouchStart={beginTouchSwipeGesture}
        onTouchMove={updateTouchSwipeGesture}
        onTouchEnd={endTouchSwipeGesture}
        onTouchCancel={cancelTouchSwipeGesture}
        onContextMenu={(event) => {
          if (swipeRevealWidth <= 0) {
            return;
          }

          event.preventDefault();
          revealSwipeActions();
        }}
        style={cardSurfaceStyle}
        className={`creation-work-card platform-category-game-item platform-interactive-card cursor-pointer overflow-hidden text-left ${isPublished ? 'creation-work-card--published' : 'creation-work-card--draft'} ${item.isGenerating ? 'creation-work-card--generating' : ''} ${isSwipeDragging ? 'creation-work-card--swiping' : ''}`}
      >
        <div className="creation-work-card__body platform-category-game-item__body">
          <div className="creation-work-card__title-row platform-category-game-item__title-row">
            <div className="creation-work-card__title-lockup">
              <span
                aria-label={
                  item.isGenerating
                    ? '生成中'
                    : item.status === 'published'
                      ? '已发布'
                      : '草稿'
                }
                className={`creation-work-card__state-mark creation-work-card__state-mark--${
                  item.isGenerating ? 'generating' : item.status
                }`}
              >
                {item.isGenerating ? (
                  <Loader2 aria-hidden="true" className="h-3.5 w-3.5" />
                ) : item.status === 'published' ? (
                  <BadgeCheck aria-hidden="true" className="h-3.5 w-3.5" />
                ) : (
                  <Clock3 aria-hidden="true" className="h-3.5 w-3.5" />
                )}
              </span>
              <span className="creation-work-card__title platform-category-game-item__title">
                {displayTitle}
              </span>
            </div>
            {canUseShareAction ? (
              <div className="creation-work-card__quick-actions">
                <button
                  type="button"
                  onClick={(event) => {
                    event.stopPropagation();
                    suppressOpenRef.current = false;
                    closeSwipeActions();
                    onShare?.();
                  }}
                  onKeyDown={(event) => {
                    event.stopPropagation();
                  }}
                  onPointerDown={(event) => {
                    event.stopPropagation();
                  }}
                  onTouchStart={(event) => {
                    event.stopPropagation();
                  }}
                  title="分享作品"
                  aria-label="分享"
                  className="creation-work-card__quick-action-button"
                >
                  <Share2 aria-hidden="true" className="h-4 w-4" />
                </button>
              </div>
            ) : null}
          </div>

          <div className="creation-work-card__meta platform-category-game-item__meta">
            {item.badges.slice(1).map((badge) => (
              <span
                key={`${item.id}-${badge.id}`}
                className={`creation-work-card__badge platform-pill ${BADGE_TONE_CLASS[badge.tone]}`}
              >
                {formatPlatformWorkDisplayTag(badge.label)}
              </span>
            ))}
          </div>

          <div className="creation-work-card__summary platform-category-game-item__summary">
            {item.summary}
          </div>

          {item.hasGenerationFailure ? (
            <div
              aria-label={item.generationFailureSummary ?? '生成失败'}
              className="creation-work-card__failure-status"
            >
              <CircleAlert aria-hidden="true" className="h-3.5 w-3.5" />
              <span>{item.generationFailureSummary ?? '生成失败'}</span>
            </div>
          ) : null}

          {isPublished ? (
            <div className="creation-work-card__published-info">
              {item.pointIncentive ? (
                <div className="creation-work-card-incentive">
                  <div
                    aria-label={`积分激励总数 ${formatCreationPointIncentiveTotal(item.pointIncentive.totalPoints)} 泥点`}
                    className="creation-work-card-incentive__metric"
                  >
                    <span className="creation-work-card-incentive__label">
                      积分激励
                    </span>
                    <span className="creation-work-card-incentive__value">
                      {formatCreationPointIncentiveTotal(
                        item.pointIncentive.totalPoints,
                      )}
                    </span>
                  </div>
                  <div
                    aria-label={`待领取积分 ${item.pointIncentive.claimablePoints} 泥点`}
                    className="creation-work-card-incentive__metric"
                  >
                    <span className="creation-work-card-incentive__label">
                      待领取
                    </span>
                    <span className="creation-work-card-incentive__value">
                      {formatCreationMetricCount(
                        item.pointIncentive.claimablePoints,
                      )}
                    </span>
                  </div>
                  <button
                    type="button"
                    disabled={!canClaimPointIncentive || pointIncentiveBusy}
                    onClick={(event) => {
                      event.stopPropagation();
                      onClaimPointIncentive?.();
                    }}
                    onKeyDown={(event) => {
                      event.stopPropagation();
                    }}
                    className="creation-work-card-incentive__button"
                  >
                    {pointIncentiveBusy ? '领取中' : '领取积分'}
                  </button>
                </div>
              ) : null}

              <div className="creation-work-card__metrics">
                {item.metrics.map((metric) => (
                  <div
                    key={`${item.id}-${metric.id}`}
                    aria-label={`${metric.label} ${displayValues[metric.id] ?? metric.value}${metric.unit}`}
                    className={`creation-work-card-stat creation-work-card-stat--${metric.tone}`}
                  >
                    <span className="creation-work-card-stat__label">
                      {metric.label}
                    </span>
                    <span className="creation-work-card-stat__value">
                      <span className="creation-work-card-stat__number">
                        {formatCreationMetricCount(
                          displayValues[metric.id] ?? metric.value,
                        )}
                      </span>
                      <span className="creation-work-card-stat__unit">
                        {metric.unit}
                      </span>
                    </span>
                    {showGrowth && deltas[metric.id] > 0 ? (
                      <span className="creation-work-card-stat__growth">
                        <span aria-hidden="true">↑</span>
                        {formatCreationMetricCount(deltas[metric.id])}
                      </span>
                    ) : null}
                  </div>
                ))}
              </div>
            </div>
          ) : null}
        </div>

        <div
          className="creation-work-card__side-cover"
          style={coverFadeStyle}
          aria-hidden="true"
        >
          <div className="creation-work-card__side-cover-inner">
            <CustomWorldCoverArtwork
              imageSrc={item.coverImageSrc}
              fallbackImageSrc={fallbackCoverImageSrc}
              title={item.title}
              fallbackLabel="封面"
              renderMode={item.coverRenderMode}
              characterImageSrcs={item.coverCharacterImageSrcs}
              className="absolute inset-0"
            />
          </div>
        </div>
        {item.hasUnreadUpdate ? (
          <span
            aria-label="新生成完成"
            className="creation-work-card__unread-dot"
          />
        ) : null}

        {item.isGenerating ? (
          <div
            className="creation-work-card__generating-mask"
            aria-hidden="true"
          >
            <span className="creation-work-card__spinner" />
            <span>生成中...</span>
          </div>
        ) : null}
      </div>
    </div>
  );
}
