'use client'; import { useState, useEffect, useRef, useCallback } from 'react'; import { StoryWithCollectionContext } from '../../types/api'; import { storyApi, getImageUrl } from '../../lib/api'; import Button from '../ui/Button'; import TagDisplay from '../tags/TagDisplay'; import Link from 'next/link'; interface CollectionReadingViewProps { data: StoryWithCollectionContext; onNavigate: (storyId: string) => void; onBackToCollection: () => void; } export default function CollectionReadingView({ data, onNavigate, onBackToCollection }: CollectionReadingViewProps) { const { story, collection } = data; const [hasScrolledToPosition, setHasScrolledToPosition] = useState(false); const [readingPercentage, setReadingPercentage] = useState(0); const contentRef = useRef(null); const saveTimeoutRef = useRef(null); // Convert scroll position to approximate character position in the content const getCharacterPositionFromScroll = useCallback((): number => { if (!contentRef.current || !story) return 0; const content = contentRef.current; const scrolled = window.scrollY; const contentTop = content.offsetTop; const contentHeight = content.scrollHeight; const windowHeight = window.innerHeight; // Calculate how far through the content we are (0-1) const scrollRatio = Math.min(1, Math.max(0, (scrolled - contentTop + windowHeight * 0.3) / contentHeight )); // Convert to character position in the plain text content const textLength = story.contentPlain?.length || story.contentHtml?.length || 0; return Math.floor(scrollRatio * textLength); }, [story]); // Calculate reading percentage from character position const calculateReadingPercentage = useCallback((currentPosition: number): number => { if (!story) return 0; const totalLength = story.contentPlain?.length || story.contentHtml?.length || 0; if (totalLength === 0) return 0; return Math.round((currentPosition / totalLength) * 100); }, [story]); // Convert character position back to scroll position for auto-scroll const scrollToCharacterPosition = useCallback((position: number) => { if (!contentRef.current || !story || hasScrolledToPosition) return; const textLength = story.contentPlain?.length || story.contentHtml?.length || 0; if (textLength === 0 || position === 0) return; const ratio = position / textLength; const content = contentRef.current; const contentTop = content.offsetTop; const contentHeight = content.scrollHeight; const windowHeight = window.innerHeight; // Calculate target scroll position const targetScroll = contentTop + (ratio * contentHeight) - (windowHeight * 0.3); // Smooth scroll to position window.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' }); setHasScrolledToPosition(true); }, [story, hasScrolledToPosition]); // Debounced function to save reading position const saveReadingPosition = useCallback(async (position: number) => { if (!story || position === story.readingPosition) { console.log('Collection view - skipping save - no story or position unchanged:', { story: !!story, position, current: story?.readingPosition }); return; } console.log('Collection view - saving reading position:', position, 'for story:', story.id); try { await storyApi.updateReadingProgress(story.id, position); console.log('Collection view - reading position saved successfully'); } catch (error) { console.error('Collection view - failed to save reading position:', error); } }, [story]); // Debounced version of saveReadingPosition const debouncedSavePosition = useCallback((position: number) => { if (saveTimeoutRef.current) { clearTimeout(saveTimeoutRef.current); } saveTimeoutRef.current = setTimeout(() => { saveReadingPosition(position); }, 2000); }, [saveReadingPosition]); // Auto-scroll to saved reading position when story content is loaded useEffect(() => { if (story && !hasScrolledToPosition) { const timeout = setTimeout(() => { console.log('Collection view - initializing reading position tracking, saved position:', story.readingPosition); if (story.readingPosition && story.readingPosition > 0) { console.log('Collection view - auto-scrolling to saved position:', story.readingPosition); const initialPercentage = calculateReadingPercentage(story.readingPosition); setReadingPercentage(initialPercentage); scrollToCharacterPosition(story.readingPosition); } else { console.log('Collection view - no saved position, starting fresh tracking'); setReadingPercentage(0); setHasScrolledToPosition(true); } }, 500); return () => clearTimeout(timeout); } }, [story, scrollToCharacterPosition, calculateReadingPercentage, hasScrolledToPosition]); // Track reading progress and save position useEffect(() => { const handleScroll = () => { if (hasScrolledToPosition) { const characterPosition = getCharacterPositionFromScroll(); const percentage = calculateReadingPercentage(characterPosition); console.log('Collection view - scroll detected, character position:', characterPosition, 'percentage:', percentage); setReadingPercentage(percentage); debouncedSavePosition(characterPosition); } else { console.log('Collection view - scroll detected but not ready for tracking yet'); } }; window.addEventListener('scroll', handleScroll); return () => { window.removeEventListener('scroll', handleScroll); if (saveTimeoutRef.current) { clearTimeout(saveTimeoutRef.current); } }; }, [hasScrolledToPosition, getCharacterPositionFromScroll, calculateReadingPercentage, debouncedSavePosition]); const handlePrevious = () => { if (collection.previousStoryId) { onNavigate(collection.previousStoryId); } }; const handleNext = () => { if (collection.nextStoryId) { onNavigate(collection.nextStoryId); } }; const renderRatingStars = (rating?: number) => { if (!rating) return null; return (
{[1, 2, 3, 4, 5].map((star) => ( ))}
); }; return (
{/* Collection Context Header */}

Reading from: {collection.name}

Story {collection.currentPosition} of {collection.totalStories}

{/* Progress Bar */}
{/* Reading percentage indicator */}
{readingPercentage}%
{collection.currentPosition}/{collection.totalStories}
{/* Story Header */}
{/* Story Cover */} {story.coverPath && (
{`${story.title}
)} {/* Story Info */}

{story.title}

by {story.authorName} {story.wordCount?.toLocaleString()} words {story.rating && (
{renderRatingStars(story.rating)}
)} {story.seriesName && ( {story.seriesName} {story.volume && ` #${story.volume}`} )}
{story.summary && (

{story.summary}

)} {/* Tags */} {story.tags && story.tags.length > 0 && (
{story.tags.map((tag) => ( ))}
)}
{/* Navigation Controls */}
Story {collection.currentPosition} of {collection.totalStories}
{/* Story Content */}
{/* Bottom Navigation */}
); }