"use client";

import React, {
  useCallback,
  useEffect,
  useLayoutEffect,
  useRef,
  useState,
} from "react";
import Image from "next/image";
import Link from "next/link";
import toast from "react-hot-toast";
import { StoryFeedUser, StoryOverlay, StoryViewerUser } from "@/api/fetchStories";
import { buildPostLinkPath, PostLinkKind } from "@/lib/postLinkCaption";

const LINK_KIND_ICONS: Record<PostLinkKind, string> = {
  pkg: "teacher",
  project: "briefcase",
  billboard: "flash-circle",
  shop: "shop",
  shopproduct: "box",
  booking: "calendar",
};
import {
  markStoryViewed,
  updateStoryOverlays,
  likeStory,
  commentOnStory,
  fetchStoryViewers,
} from "@/api/fetchStories";
import { buildStorageUrl } from "@/components/main/BaseUrl";
import BoldIcon from "@/components/ui/BoldIcon";
import StoryEditor from "@/components/stories/StoryEditor";
import StoryRing, { verifyBadgeToTierColor } from "@/components/stories/StoryRing";
import SendStoryModal from "@/components/stories/SendStoryModal";
import useAxios from "@/hooks/useAxios";
import { useTranslation } from "react-i18next";

const IMAGE_DURATION_MS = 5000;

type StoryViewerProps = {
  feed: StoryFeedUser[];
  initialUserIndex: number;
  onClose: () => void;
  token: string;
  viewerId?: string | null;
};

export default function StoryViewer({
  feed,
  initialUserIndex,
  onClose,
  token,
  viewerId,
}: StoryViewerProps) {
  const { t } = useTranslation("common");
  const [userIndex, setUserIndex] = useState(initialUserIndex);
  const [storyIndex, setStoryIndex] = useState(0);
  const [progress, setProgress] = useState(0);
  const [paused, setPaused] = useState(false);
  const [editing, setEditing] = useState(false);
  const [draftOverlays, setDraftOverlays] = useState<StoryOverlay[]>([]);
  const [savingEdit, setSavingEdit] = useState(false);
  const [overlayOverrides, setOverlayOverrides] = useState<
    Record<string, StoryOverlay[]>
  >({});
  const [likedStories, setLikedStories] = useState<Record<string, boolean>>({});
  const [commentText, setCommentText] = useState("");
  const [liking, setLiking] = useState(false);
  const [sendingComment, setSendingComment] = useState(false);
  const [viewersOpen, setViewersOpen] = useState(false);
  const [sendOpen, setSendOpen] = useState(false);
  const [viewers, setViewers] = useState<StoryViewerUser[]>([]);
  const [loadingViewers, setLoadingViewers] = useState(false);
  const [followingMap, setFollowingMap] = useState<Record<string, boolean>>({});
  const [followLoading, setFollowLoading] = useState(false);
  const [mediaWidth, setMediaWidth] = useState(0);
  const [sessionViewedIds, setSessionViewedIds] = useState<Set<string>>(
    () => new Set()
  );
  const { request } = useAxios();
  const mediaBoxRef = useRef<HTMLDivElement | null>(null);
  const videoRef = useRef<HTMLVideoElement | null>(null);
  const timerRef = useRef<number | null>(null);
  const startRef = useRef<number>(0);
  const elapsedRef = useRef(0);

  const currentUser = feed[userIndex];
  const currentStory = currentUser?.stories[storyIndex];

  const goNext = useCallback(() => {
    if (!currentUser) return;
    if (storyIndex < currentUser.stories.length - 1) {
      setStoryIndex((i) => i + 1);
      setProgress(0);
      elapsedRef.current = 0;
      return;
    }
    if (userIndex < feed.length - 1) {
      setUserIndex((i) => i + 1);
      setStoryIndex(0);
      setProgress(0);
      elapsedRef.current = 0;
      return;
    }
    onClose();
  }, [currentUser, storyIndex, userIndex, feed.length, onClose]);

  const goPrev = useCallback(() => {
    if (storyIndex > 0) {
      setStoryIndex((i) => i - 1);
      setProgress(0);
      elapsedRef.current = 0;
      return;
    }
    if (userIndex > 0) {
      const prevUser = feed[userIndex - 1];
      setUserIndex((i) => i - 1);
      setStoryIndex(Math.max(0, prevUser.stories.length - 1));
      setProgress(0);
      elapsedRef.current = 0;
    }
  }, [storyIndex, userIndex, feed]);

  useEffect(() => {
    setCommentText("");
    setViewersOpen(false);
  }, [currentStory?._id]);

  useEffect(() => {
    const map: Record<string, boolean> = {};
    feed.forEach((item) => {
      map[item.user._id] = Boolean(item.is_following);
    });
    setFollowingMap(map);
  }, [feed]);

  useEffect(() => {
    if (!currentStory?._id) return;
    markStoryViewed(currentStory._id, token);
    setSessionViewedIds((prev) => {
      if (prev.has(currentStory._id)) return prev;
      const next = new Set(prev);
      next.add(currentStory._id);
      return next;
    });
  }, [currentStory?._id, token]);

  useLayoutEffect(() => {
    const el = mediaBoxRef.current;
    if (!el) return;
    const update = () => setMediaWidth(el.clientWidth);
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  useEffect(() => {
    if (!currentStory || paused || editing) return;

    if (currentStory.media_type === "video") {
      const video = videoRef.current;
      if (!video) return;
      video.currentTime = 0;
      video.play().catch(() => {});
      const onTimeUpdate = () => {
        if (video.duration) {
          setProgress(video.currentTime / video.duration);
        }
      };
      const onEnded = () => goNext();
      video.addEventListener("timeupdate", onTimeUpdate);
      video.addEventListener("ended", onEnded);
      return () => {
        video.removeEventListener("timeupdate", onTimeUpdate);
        video.removeEventListener("ended", onEnded);
      };
    }

    startRef.current = Date.now();
    const tick = () => {
      const elapsed = elapsedRef.current + (Date.now() - startRef.current);
      const p = Math.min(elapsed / IMAGE_DURATION_MS, 1);
      setProgress(p);
      if (p >= 1) {
        goNext();
        return;
      }
      timerRef.current = window.requestAnimationFrame(tick);
    };
    timerRef.current = window.requestAnimationFrame(tick);
    return () => {
      if (timerRef.current) cancelAnimationFrame(timerRef.current);
    };
  }, [currentStory, paused, editing, goNext]);

  useEffect(() => {
    elapsedRef.current = 0;
    setProgress(0);
  }, [userIndex, storyIndex]);

  const handlePointerDown = () => {
    setPaused(true);
    if (videoRef.current) videoRef.current.pause();
    elapsedRef.current += Date.now() - startRef.current;
  };

  const handlePointerUp = () => {
    setPaused(false);
    startRef.current = Date.now();
    if (videoRef.current && currentStory?.media_type === "video") {
      videoRef.current.play().catch(() => {});
    }
  };

  if (!currentUser || !currentStory) return null;

  const mediaSrc = currentStory.media_path.startsWith("http")
    ? currentStory.media_path
    : buildStorageUrl(currentStory.media_path);

  const currentOverlays =
    overlayOverrides[currentStory._id] ?? currentStory.overlays ?? [];
  const isOwnStory = Boolean(viewerId && currentUser.user._id === viewerId);
  const isFollowingUser =
    followingMap[currentUser.user._id] ?? Boolean(currentUser.is_following);
  const storyLiked =
    likedStories[currentStory._id] ?? Boolean(currentStory.liked);
  const headerHasUnviewed = currentUser.stories.some(
    (story) => !story.viewed && !sessionViewedIds.has(story._id)
  );

  const handleFollow = async () => {
    if (isOwnStory || isFollowingUser || followLoading) return;
    setFollowLoading(true);
    setPaused(true);
    if (videoRef.current) videoRef.current.pause();
    try {
      const res = await request<{ is_following?: boolean }>(
        "POST",
        "/posts/follow",
        { userId: currentUser.user._id },
        { noToast: true }
      );
      const confirmed = res?.is_following ?? true;
      setFollowingMap((prev) => ({
        ...prev,
        [currentUser.user._id]: confirmed,
      }));
      toast.success(confirmed ? t("stories.followSuccess") : t("stories.unfollowed"));
    } catch {
      toast.error(t("stories.followError"));
    } finally {
      setFollowLoading(false);
      setPaused(false);
      startRef.current = Date.now();
      if (videoRef.current && currentStory.media_type === "video") {
        videoRef.current.play().catch(() => {});
      }
    }
  };

  const handleLike = async () => {
    if (isOwnStory || storyLiked || liking) return;
    setLiking(true);
    setPaused(true);
    try {
      await likeStory(currentStory._id, token);
      setLikedStories((prev) => ({ ...prev, [currentStory._id]: true }));
      toast.success(t("stories.likeSent"));
    } catch (err) {
      toast.error(err instanceof Error ? err.message : t("stories.likeError"));
    } finally {
      setLiking(false);
      setPaused(false);
      startRef.current = Date.now();
    }
  };

  const handleSendComment = async () => {
    const text = commentText.trim();
    if (!text || isOwnStory || sendingComment) return;
    setSendingComment(true);
    setPaused(true);
    try {
      await commentOnStory(currentStory._id, text, token);
      setCommentText("");
      toast.success(t("stories.messageSent"));
    } catch (err) {
      toast.error(err instanceof Error ? err.message : t("stories.messageError"));
    } finally {
      setSendingComment(false);
      setPaused(false);
      startRef.current = Date.now();
    }
  };

  const openViewers = async () => {
    if (!isOwnStory) return;
    setViewersOpen(true);
    setPaused(true);
    if (videoRef.current) videoRef.current.pause();
    setLoadingViewers(true);
    try {
      const res = await fetchStoryViewers(currentStory._id, token);
      setViewers(res.viewers);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : t("stories.viewersError"));
      setViewersOpen(false);
      setPaused(false);
      startRef.current = Date.now();
    } finally {
      setLoadingViewers(false);
    }
  };

  const closeViewers = () => {
    setViewersOpen(false);
    setPaused(false);
    startRef.current = Date.now();
    if (videoRef.current && currentStory?.media_type === "video") {
      videoRef.current.play().catch(() => {});
    }
  };

  const openEditor = () => {
    setDraftOverlays(currentOverlays.map((o) => ({ ...o })));
    setEditing(true);
    setPaused(true);
    if (videoRef.current) videoRef.current.pause();
  };

  const closeEditor = () => {
    setEditing(false);
    setPaused(false);
    startRef.current = Date.now();
  };

  const saveEditor = async () => {
    setSavingEdit(true);
    try {
      await updateStoryOverlays(currentStory._id, draftOverlays, token);
      setOverlayOverrides((prev) => ({
        ...prev,
        [currentStory._id]: draftOverlays,
      }));
      toast.success(t("stories.edited"));
      closeEditor();
    } catch (err) {
      toast.error(err instanceof Error ? err.message : t("stories.editError"));
    } finally {
      setSavingEdit(false);
    }
  };

  const displayName =
    currentUser.user.user_name ||
    [currentUser.user.first_name, currentUser.user.last_name]
      .filter(Boolean)
      .join(" ");

  return (
    <div className="fixed inset-0 z-[1000] flex items-center justify-center bg-black">
      {/* Progress bars */}
      <div className="absolute left-0 right-0 top-0 z-20 flex gap-1 px-2 pt-[calc(0.5rem+env(safe-area-inset-top))]">
        {currentUser.stories.map((_, idx) => (
          <div
            key={idx}
            className="h-[2px] flex-1 overflow-hidden rounded-full bg-white/30"
          >
            <div
              className="h-full bg-white transition-all duration-100 ease-linear"
              style={{
                width:
                  idx < storyIndex
                    ? "100%"
                    : idx === storyIndex
                      ? `${progress * 100}%`
                      : "0%",
              }}
            />
          </div>
        ))}
      </div>

      {/* Header */}
      <div className="absolute left-0 right-0 top-[calc(1.25rem+env(safe-area-inset-top))] z-20 flex items-center justify-between gap-2 px-3">
        <div className="flex min-w-0 flex-1 items-center gap-2">
          <Link
            href={`/users/${currentUser.user.user_name}`}
            className="flex min-w-0 items-center gap-2"
          >
            <StoryRing
              hasStory
              hasUnviewed={headerHasUnviewed}
              tierColor={verifyBadgeToTierColor(currentUser.user.verify_badge)}
              rounded="full"
              avatarSize={32}
            >
              <div className="relative h-8 w-8 overflow-hidden rounded-full bg-neutral-800">
                <Image
                  src={
                    currentUser.user.profile_image
                      ? buildStorageUrl(currentUser.user.profile_image)
                      : "/images/fake-avatar.png"
                  }
                  alt=""
                  fill
                  className="object-cover"
                  unoptimized
                />
              </div>
            </StoryRing>
            <span className="truncate text-sm font-semibold text-white drop-shadow">
              {displayName}
            </span>
            <span className="shrink-0 text-xs text-white/70">
              {formatStoryTime(currentStory.createdAt, t)}
            </span>
          </Link>
          {!isOwnStory && !isFollowingUser ? (
            <button
              type="button"
              onClick={() => void handleFollow()}
              disabled={followLoading}
              className="shrink-0 rounded-md border border-white/80 px-2.5 py-1 text-xs font-bold text-white backdrop-blur-sm transition active:scale-95 disabled:opacity-50"
            >
              {followLoading ? t("stories.following") : t("stories.follow")}
            </button>
          ) : null}
        </div>
        <div className="flex items-center gap-1">
          {isOwnStory && (
            <button
              type="button"
              onClick={openEditor}
              className="flex h-9 items-center gap-1 rounded-full bg-white/15 px-3 text-xs font-semibold text-white backdrop-blur"
              aria-label={t("stories.editAria")}
            >
              <BoldIcon name="edit" size={16} tinted className="text-white" />
              {t("stories.edit")}
            </button>
          )}
          <button
            type="button"
            onClick={onClose}
            className="flex h-9 w-9 items-center justify-center text-white"
            aria-label={t("stories.closeAria")}
          >
            <BoldIcon name="close-circle" size={24} tinted className="text-white" />
          </button>
        </div>
      </div>

      {/* Media */}
      <div ref={mediaBoxRef} className="relative h-full w-full max-w-lg">
        {currentStory.media_type === "video" ? (
          <video
            ref={videoRef}
            src={mediaSrc}
            className="h-full w-full object-contain"
            playsInline
            muted={false}
          />
        ) : (
          <Image
            src={mediaSrc}
            alt=""
            fill
            className="object-contain"
            priority
            unoptimized
          />
        )}

        {/* اورلی‌های متن و لینک — لینک برخلاف متن باید کلیک‌پذیر باشه، پس pointerEvents:auto
            و z-index بالاتر از تپ‌زون‌های قبلی/بعدی (z-10) داره، با stopPropagation که با
            پاز/ادامه‌شدن استوری (روی تپ‌زون‌ها) تداخل نکنه */}
        {currentOverlays.map((o, idx) => {
          if (o.type === "link") {
            return (
              <Link
                key={idx}
                href={buildPostLinkPath(o.link_kind, o.link_id, o.link_title) || "#"}
                onClick={(e) => e.stopPropagation()}
                onPointerDown={(e) => e.stopPropagation()}
                onPointerUp={(e) => e.stopPropagation()}
                style={{
                  position: "absolute",
                  left: `${o.x * 100}%`,
                  top: `${o.y * 100}%`,
                  transform: `translate(-50%, -50%) rotate(${o.rotation ?? 0}deg)`,
                  zIndex: 20,
                  pointerEvents: "auto",
                  display: "flex",
                  alignItems: "center",
                  gap: 6,
                  padding: "0.4em 0.7em",
                  borderRadius: 999,
                  background: "rgba(0,0,0,0.55)",
                  color: "#fff",
                  fontSize: 13,
                  fontWeight: 700,
                  maxWidth: "80%",
                  whiteSpace: "nowrap",
                  overflow: "hidden",
                  textOverflow: "ellipsis",
                }}
              >
                <BoldIcon name={LINK_KIND_ICONS[o.link_kind]} size={14} tinted className="text-white" />
                <span className="truncate">{o.link_title}</span>
              </Link>
            );
          }
          return (
            <div
              key={idx}
              style={{
                position: "absolute",
                left: `${o.x * 100}%`,
                top: `${o.y * 100}%`,
                transform: `translate(-50%, -50%) rotate(${o.rotation ?? 0}deg)`,
                color: o.color,
                fontSize: `${Math.max(10, o.font_size * (mediaWidth || 380))}px`,
                lineHeight: 1.2,
                fontWeight: 700,
                textAlign: (o.align ?? "center") as React.CSSProperties["textAlign"],
                background: o.background || "transparent",
                padding: o.background ? "0.15em 0.35em" : 0,
                borderRadius: o.background ? "0.35em" : 0,
                maxWidth: "90%",
                whiteSpace: "pre-wrap",
                wordBreak: "break-word",
                pointerEvents: "none",
                textShadow: o.background ? "none" : "0 1px 3px rgba(0,0,0,0.55)",
              }}
            >
              {o.text}
            </div>
          );
        })}
      </div>

      {/* Tap zones */}
      <button
        type="button"
        className="absolute left-0 top-0 z-10 h-full w-[35%]"
        aria-label={t("stories.prevAria")}
        onClick={goPrev}
        onPointerDown={handlePointerDown}
        onPointerUp={handlePointerUp}
        onPointerLeave={handlePointerUp}
      />
      <button
        type="button"
        className="absolute right-0 top-0 z-10 h-full w-[65%]"
        aria-label={t("stories.nextAria")}
        onClick={goNext}
        onPointerDown={handlePointerDown}
        onPointerUp={handlePointerUp}
        onPointerLeave={handlePointerUp}
      />

      {/* Bottom interactions */}
      {!editing && !viewersOpen && currentStory?.linked_post_id ? (
        <div className="absolute bottom-[calc(4.5rem+env(safe-area-inset-bottom))] inset-x-0 z-20 flex justify-center px-4">
          <Link
            href={`/posts/${currentStory.linked_post_id}`}
            className="rounded-full bg-white px-5 py-2.5 text-sm font-bold text-black shadow-lg"
          >
            {t("stories.viewOriginalPost")}
          </Link>
        </div>
      ) : null}
      {!editing && !viewersOpen && (
        <div className="absolute bottom-0 left-0 right-0 z-20 px-3 pb-[calc(0.75rem+env(safe-area-inset-bottom))]">
          {isOwnStory ? (
            <div className="flex items-center justify-between gap-2">
              <button
                type="button"
                onClick={() => {
                  setPaused(true);
                  if (videoRef.current) videoRef.current.pause();
                  setSendOpen(true);
                }}
                className="flex h-11 items-center gap-2 rounded-full bg-black/45 px-4 text-sm font-semibold text-white backdrop-blur-md"
                aria-label={t("stories.sendStory")}
              >
                <BoldIcon name="send-2" size={20} tinted className="text-white" />
                {t("stories.sendStory")}
              </button>
              <button
                type="button"
                onClick={openViewers}
                className="flex h-11 items-center gap-2 rounded-full bg-black/45 px-4 text-sm font-semibold text-white backdrop-blur-md"
                aria-label={t("stories.viewersAria")}
              >
                <BoldIcon name="filter-search" size={20} tinted className="text-white" />
                {t("stories.views")}
              </button>
            </div>
          ) : (
            <div className="mx-auto flex max-w-lg items-center gap-2">
              <input
                value={commentText}
                onChange={(e) => setCommentText(e.target.value)}
                onFocus={() => {
                  setPaused(true);
                  if (videoRef.current) videoRef.current.pause();
                }}
                onBlur={() => {
                  if (!sendingComment) {
                    setPaused(false);
                    startRef.current = Date.now();
                    if (videoRef.current && currentStory.media_type === "video") {
                      videoRef.current.play().catch(() => {});
                    }
                  }
                }}
                onKeyDown={(e) => {
                  if (e.key === "Enter" && !e.shiftKey) {
                    e.preventDefault();
                    void handleSendComment();
                  }
                }}
                placeholder={t("stories.sendMessagePlaceholder")}
                className="min-w-0 flex-1 rounded-full border border-white/25 bg-black/35 px-4 py-2.5 text-sm text-white outline-none placeholder:text-white/50 backdrop-blur-md"
              />
              <button
                type="button"
                onClick={() => void handleSendComment()}
                disabled={!commentText.trim() || sendingComment}
                className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-white/15 text-white backdrop-blur-md disabled:opacity-40"
                aria-label={t("stories.sendAria")}
              >
                <BoldIcon name="send-2" size={20} tinted className="text-white" />
              </button>
              <button
                type="button"
                onClick={() => void handleLike()}
                disabled={storyLiked || liking}
                className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-black/35 backdrop-blur-md disabled:opacity-50"
                aria-label={t("stories.likeAria")}
              >
                <BoldIcon
                  name={storyLiked ? "heart-tick" : "heart"}
                  size={24}
                  tinted
                  className={storyLiked ? "text-red-500" : "text-white"}
                />
              </button>
            </div>
          )}
        </div>
      )}

      {/* Viewers sheet */}
      {viewersOpen && (
        <div className="absolute inset-x-0 bottom-0 z-30 max-h-[55vh] rounded-t-2xl bg-white dark:bg-neutral-900">
          <div className="flex items-center justify-between border-b border-neutral-200 px-4 py-3 dark:border-neutral-800">
            <h3 className="text-sm font-bold">{t("stories.viewersTitle")}</h3>
            <button type="button" onClick={closeViewers} aria-label={t("stories.closeAria")}>
              <BoldIcon name="close-circle" size={22} className="dark:invert" />
            </button>
          </div>
          <div className="max-h-[calc(55vh-3rem)] overflow-y-auto p-2">
            {loadingViewers ? (
              <p className="py-8 text-center text-sm text-neutral-500">{t("common.loading")}</p>
            ) : viewers.length === 0 ? (
              <p className="py-8 text-center text-sm text-neutral-500">
                {t("stories.noViewers")}
              </p>
            ) : (
              <ul className="space-y-1">
                {viewers.map((v) => {
                  const name =
                    [v.first_name, v.last_name].filter(Boolean).join(" ") ||
                    v.user_name;
                  return (
                    <li key={v._id}>
                      <Link
                        href={`/users/${v.user_name}`}
                        className="flex items-center gap-3 rounded-xl px-2 py-2 hover:bg-neutral-100 dark:hover:bg-neutral-800"
                        onClick={onClose}
                      >
                        <div className="relative h-10 w-10 overflow-hidden rounded-full bg-neutral-200">
                          <Image
                            src={
                              v.profile_image
                                ? buildStorageUrl(v.profile_image)
                                : "/images/fake-avatar.png"
                            }
                            alt=""
                            fill
                            className="object-cover"
                            unoptimized
                          />
                        </div>
                        <div className="min-w-0 flex-1">
                          <p className="truncate text-sm font-semibold">{name}</p>
                          <p className="truncate text-xs text-neutral-500">
                            @{v.user_name}
                          </p>
                        </div>
                      </Link>
                    </li>
                  );
                })}
              </ul>
            )}
          </div>
        </div>
      )}

      {/* Edit overlays modal */}
      {editing && (
        <div className="absolute inset-0 z-30 flex flex-col overflow-y-auto bg-black/95 pb-6 pt-[calc(0.75rem+env(safe-area-inset-top))]">
          <div className="flex items-center justify-between px-4 pb-2">
            <button
              type="button"
              onClick={closeEditor}
              className="text-sm text-white/70"
            >
              {t("stories.cancel")}
            </button>
            <span className="text-sm font-semibold text-white">{t("stories.editStoryTitle")}</span>
            <button
              type="button"
              onClick={saveEditor}
              disabled={savingEdit}
              className="text-sm font-semibold text-[#0095f6] disabled:opacity-40"
            >
              {savingEdit ? t("stories.saving") : t("stories.save")}
            </button>
          </div>
          <StoryEditor
            mediaUrl={mediaSrc}
            mediaType={currentStory.media_type}
            overlays={draftOverlays}
            onChange={setDraftOverlays}
          />
        </div>
      )}

      {viewerId && currentStory ? (
        <SendStoryModal
          open={sendOpen}
          onClose={() => {
            setSendOpen(false);
            setPaused(false);
            startRef.current = Date.now();
            if (videoRef.current && currentStory.media_type === "video") {
              videoRef.current.play().catch(() => {});
            }
          }}
          storyId={currentStory._id}
          previewPath={currentStory.media_path}
          mediaType={currentStory.media_type}
          currentUserId={viewerId}
        />
      ) : null}
    </div>
  );
}

function formatStoryTime(
  value: string | undefined,
  t: (key: string, options?: Record<string, unknown>) => string
): string {
  if (!value) return "";
  const diff = Date.now() - Date.parse(value);
  const hours = Math.floor(diff / (1000 * 60 * 60));
  if (hours < 1) return t("stories.timeNow");
  if (hours < 24) return t("stories.timeHours", { hours });
  return t("stories.timeYesterday");
}
