import { Share2, Trash2 } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';

import { copyTextToClipboard } from '../../services/clipboard';
import { CustomWorldCoverArtwork } from '../CustomWorldCoverArtwork';
import {
  formatPlatformWorkDisplayName,
  formatPlatformWorkDisplayTag,
} from '../rpg-entry/rpgEntryWorldPresentation';
import {
  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;
  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 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 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,
  onClaimPointIncentive = null,
  pointIncentiveBusy = false,
}: CustomWorldWorkCardProps) {
  const [shareState, setShareState] = useState<'idle' | 'copied' | 'failed'>(
    'idle',
  );
  const shareResetTimerRef = useRef<number | null>(null);
  const isPublished = item.status === 'published';
  const canClaimPointIncentive =
    Boolean(onClaimPointIncentive) &&
    (item.pointIncentive?.claimablePoints ?? 0) > 0;
  const displayTitle = formatPlatformWorkDisplayName(item.title);
  const { cardRef, deltas, displayValues, showGrowth } =
    usePublishedMetricAnimation(
      isPublished ? item.metrics : EMPTY_PUBLISHED_METRICS,
      previousMetricValues,
    );
  const copyShareText = () => {
    const publicWorkCode = item.publicWorkCode?.trim();
    const sharePath = item.sharePath?.trim();
    if (!publicWorkCode || !sharePath) {
      return;
    }

    const shareUrl =
      typeof window === 'undefined'
        ? sharePath
        : new URL(sharePath, window.location.origin).href;
    const shareText = `邀请你来玩《${item.title}》\n作品号：${publicWorkCode}\n${shareUrl}`;
    void copyTextToClipboard(shareText).then((copied) => {
      setShareState(copied ? 'copied' : 'failed');
      if (shareResetTimerRef.current !== null) {
        window.clearTimeout(shareResetTimerRef.current);
      }
      shareResetTimerRef.current = window.setTimeout(() => {
        shareResetTimerRef.current = null;
        setShareState('idle');
      }, 1400);
    });
  };
  useEffect(
    () => () => {
      if (shareResetTimerRef.current !== null) {
        window.clearTimeout(shareResetTimerRef.current);
      }
    },
    [],
  );
  return (
    <div
      ref={cardRef}
      role="button"
      tabIndex={0}
      aria-label={`${item.openActionLabel}《${item.title}》`}
      onClick={onOpen}
      onKeyDown={(event) => {
        if (event.key !== 'Enter' && event.key !== ' ') {
          return;
        }

        event.preventDefault();
        onOpen();
      }}
      className={`platform-surface platform-interactive-card relative min-h-[9.5rem] cursor-pointer overflow-hidden px-2.5 py-2.5 text-left sm:min-h-[12.5rem] sm:px-4 sm:py-4 xl:min-h-[11.25rem] xl:px-4 xl:py-3.5 ${isPublished ? 'col-span-2 sm:col-span-1' : ''}`}
    >
      <CustomWorldCoverArtwork
        imageSrc={item.coverImageSrc}
        title={item.title}
        fallbackLabel="封面"
        renderMode={item.coverRenderMode}
        characterImageSrcs={item.coverCharacterImageSrcs}
        className="platform-cover-artwork absolute inset-0 opacity-70 saturate-[1.08]"
      />
      <div className="absolute inset-0 bg-[var(--platform-card-overlay-strong)]" />
      <div className="absolute inset-0 bg-[radial-gradient(circle_at_100%_100%,rgba(255,255,255,0.18),transparent_34%),linear-gradient(180deg,rgba(255,255,255,0.08),rgba(0,0,0,0.08))]" />
      <div className="pointer-events-none relative z-20 flex min-h-[8rem] flex-col sm:min-h-[10.5rem] xl:min-h-[9.75rem]">
        <div className="pointer-events-auto absolute right-0 top-0 z-30 flex items-center gap-1">
          {onDelete ? (
            <button
              type="button"
              onClick={(event) => {
                event.stopPropagation();
                onDelete();
              }}
              onKeyDown={(event) => {
                event.stopPropagation();
              }}
              disabled={deleteBusy}
              aria-label={deleteBusy ? '删除中' : '删除'}
              title={deleteBusy ? '删除中' : '删除作品'}
              className="grid h-7 w-7 place-items-center text-[var(--platform-text-soft)] transition hover:text-[var(--platform-button-danger-text)] disabled:cursor-not-allowed disabled:opacity-55 sm:h-8 sm:w-8"
            >
              {deleteBusy ? (
                <span className="text-xs leading-none">…</span>
              ) : (
                <Trash2 aria-hidden="true" className="h-3.5 w-3.5" />
              )}
            </button>
          ) : null}
          {isPublished ? (
            <button
              type="button"
              onClick={(event) => {
                event.stopPropagation();
                copyShareText();
              }}
              onKeyDown={(event) => {
                event.stopPropagation();
              }}
              disabled={!item.canShare || !item.sharePath}
              title={
                !item.canShare || !item.sharePath
                  ? '暂不可分享'
                  : shareState === 'copied'
                    ? '已复制'
                    : shareState === 'failed'
                      ? '复制失败'
                      : '分享作品'
              }
              aria-label={
                !item.canShare || !item.sharePath
                  ? '暂不可分享'
                  : shareState === 'copied'
                    ? '分享内容已复制'
                    : shareState === 'failed'
                      ? '分享内容复制失败'
                      : '分享'
              }
              className="inline-flex h-7 min-w-7 items-center justify-center gap-1 whitespace-nowrap px-1.5 text-[var(--platform-text-soft)] transition hover:text-[var(--platform-cool-text)] disabled:cursor-not-allowed disabled:opacity-55 sm:h-8 sm:min-w-8"
            >
              {shareState === 'idle' ? (
                <Share2 aria-hidden="true" className="h-3.5 w-3.5" />
              ) : (
                <span className="text-[10px] font-semibold leading-none">
                  {shareState === 'copied' ? '已复制' : '复制失败'}
                </span>
              )}
            </button>
          ) : null}
        </div>

        <div className="flex items-start justify-between gap-2 pr-12 sm:gap-3 sm:pr-14">
          <div className="flex max-h-[3rem] min-w-0 flex-wrap gap-1 overflow-hidden sm:max-h-none sm:gap-2">
            {item.badges.map((badge) => (
              <span
                key={`${item.id}-${badge.id}`}
                className={`platform-pill ${BADGE_TONE_CLASS[badge.tone]} max-w-full truncate px-2 py-0.5 text-[9px] sm:px-3 sm:py-1 sm:text-[10px]`}
              >
                {formatPlatformWorkDisplayTag(badge.label)}
              </span>
            ))}
          </div>
        </div>

        <div className="mt-3 min-h-0 sm:mt-4 xl:mt-3">
          <div className="line-clamp-1 break-words text-base font-black leading-tight text-[var(--platform-text-strong)] sm:text-2xl xl:text-xl">
            {displayTitle}
          </div>
          <div className="mt-2 line-clamp-2 break-words text-[11px] leading-4 text-[color:color-mix(in_srgb,var(--platform-text-base)_92%,transparent)] sm:mt-3 sm:text-sm sm:leading-6 xl:mt-2">
            {item.summary}
          </div>
        </div>

        {isPublished ? (
          <div className="mt-auto space-y-2 pt-3 sm:pt-4 xl:pt-3">
            {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="pointer-events-auto creation-work-card-incentive__button"
                >
                  {pointIncentiveBusy ? '领取中' : '领取积分'}
                </button>
              </div>
            ) : null}

            <div className="grid grid-cols-3 gap-1.5 sm:gap-2">
              {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>
  );
}
