'use client';

import dynamic from 'next/dynamic';
import { useParams, useRouter } from 'next/navigation';
import Link from 'next/link';
import { useCallback, useEffect, useRef, useState, type MouseEvent } from 'react';
import { useTTS } from '@/contexts/TTSContext';
import { DocumentSettings } from '@/components/documents/DocumentSettings';
import { DocumentHeaderMenu } from '@/components/documents/DocumentHeaderMenu';
import { SegmentsSidebar } from '@/components/reader/SegmentsSidebar';
import { Header } from '@/components/Header';
import { AudiobookExportModal } from '@/components/AudiobookExportModal';
import { ConfirmDialog } from '@/components/ConfirmDialog';
import type { TTSAudiobookChapter } from '@/types/tts';
import type { AudiobookGenerationSettings } from '@/types/client';
import TTSPlayer from '@/components/player/TTSPlayer';
import { RateLimitPauseButton } from '@/components/player/RateLimitPauseButton';
import { resolveDocumentId } from '@/lib/client/dexie';
import { RateLimitBanner } from '@/components/auth/RateLimitBanner';
import { useAuthRateLimit } from '@/contexts/AuthRateLimitContext';
import { useFeatureFlag } from '@/contexts/RuntimeConfigContext';
import { LoadingSpinner } from '@/components/Spinner';
import {
  FORCE_REPARSE_CONFIRM_MESSAGE,
  FORCE_REPARSE_CONFIRM_TEXT,
  FORCE_REPARSE_CONFIRM_TITLE,
  isForceReparseDisabled,
} from '@/lib/client/pdf/force-reparse';
import { usePdfDocument } from './usePdfDocument';

// Dynamic import for client-side rendering only
const PDFViewer = dynamic(
  () => import('@/components/views/PDFViewer').then((module) => module.PDFViewer),
  {
    ssr: false,
    loading: () => null
  }
);

const PARSE_LOADER_EXPAND_DELAY_MS = 320;

export default function PDFViewerPage() {
  const canExportAudiobook = useFeatureFlag('enableAudiobookExport');
  const { id } = useParams();
  const router = useRouter();
  const pdfState = usePdfDocument();
  const {
    setCurrentDocument,
    currDocName,
    clearCurrDoc,
    currDocPage,
    currDocPages,
    parseStatus,
    parseProgress,
    documentSettings,
    updateDocumentSettings,
    parsedOverlayEnabled,
    setParsedOverlayEnabled,
    forceReparseParsedPdf,
    createFullAudioBook: createPDFAudioBook,
    regenerateChapter: regeneratePDFChapter,
  } = pdfState;
  const { stop } = useTTS();
  const { isAtLimit } = useAuthRateLimit();
  const [error, setError] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isPdfViewerReady, setIsPdfViewerReady] = useState(false);
  const [zoomLevel, setZoomLevel] = useState<number>(100);
  const [activeSidebar, setActiveSidebar] = useState<null | 'settings' | 'audiobook' | 'segments'>(null);
  const [showForceReparseConfirm, setShowForceReparseConfirm] = useState(false);
  const [showDetailedParseLoader, setShowDetailedParseLoader] = useState(false);
  const [containerHeight, setContainerHeight] = useState<string>('auto');
  const inFlightDocIdRef = useRef<string | null>(null);
  const loadedDocIdRef = useRef<string | null>(null);
  const backNavTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const clearCurrDocRef = useRef(clearCurrDoc);
  const [isNavigatingBack, setIsNavigatingBack] = useState(false);
  const parseUiState: NonNullable<typeof parseStatus> = parseStatus ?? 'pending';
  const hasResolvedParseStatus = parseStatus !== null;
  const isParseReady = parseUiState === 'ready';
  const forceReparseDisabled = isForceReparseDisabled(parseStatus);
  const hasRealParseProgress = !!parseProgress
    && parseProgress.totalPages > 0
    && parseProgress.pagesParsed >= 0;
  const shouldShowExpandedParseLoader = !isLoading
    && hasResolvedParseStatus
    && (parseUiState === 'pending' || parseUiState === 'running' || parseUiState === 'failed' || hasRealParseProgress);

  useEffect(() => {
    setIsLoading(true);
    setIsPdfViewerReady(false);
    setError(null);
    setActiveSidebar(null);
    inFlightDocIdRef.current = null;
    loadedDocIdRef.current = null;
  }, [id]);

  const loadDocument = useCallback(async () => {
    if (!isLoading) return; // Prevent calls when not loading new doc
    console.log('Loading new document (from page.tsx)');
    let didRedirect = false;
    let startedLoad = false;
    let loadSucceeded = false;
    try {
      if (!id) {
        setError('Document not found');
        return;
      }
      const resolved = await resolveDocumentId(id as string);
      if (resolved !== (id as string)) {
        didRedirect = true;
        router.replace(`/pdf/${resolved}`);
        return;
      }

      if (loadedDocIdRef.current === resolved) {
        return;
      }
      if (inFlightDocIdRef.current === resolved) {
        return;
      }

      startedLoad = true;
      inFlightDocIdRef.current = resolved;
      stop(); // Reset TTS when loading new document
      for (let attempt = 0; attempt < 2; attempt += 1) {
        const loaded = await setCurrentDocument(resolved);
        if (loaded) {
          loadSucceeded = true;
          loadedDocIdRef.current = resolved;
          break;
        }
        if (attempt === 0) {
          await new Promise((resolve) => setTimeout(resolve, 250));
        }
      }
      if (!loadSucceeded) {
        throw new Error(`Failed to load PDF document ${resolved}`);
      }
    } catch (err) {
      console.error('Error loading document:', err);
      setError('Failed to load document');
    } finally {
      if (startedLoad) {
        inFlightDocIdRef.current = null;
      }
      if (!didRedirect && startedLoad && loadSucceeded) {
        setIsLoading(false);
      }
    }
  }, [isLoading, id, router, setCurrentDocument, stop]);

  useEffect(() => {
    loadDocument();
  }, [loadDocument]);

  useEffect(() => {
    clearCurrDocRef.current = clearCurrDoc;
  }, [clearCurrDoc]);

  useEffect(() => {
    return () => {
      if (backNavTimeoutRef.current) {
        clearTimeout(backNavTimeoutRef.current);
      }
      clearCurrDocRef.current();
    };
  }, []);

  useEffect(() => {
    if (isLoading) return;
    if (isParseReady) return;
    stop();
  }, [isLoading, isParseReady, stop]);

  useEffect(() => {
    if (!shouldShowExpandedParseLoader) {
      // Keep the current loader variant stable during the final
      // parse-ready -> first-frame handoff to avoid a visual flash.
      if (!isLoading && isParseReady && !isPdfViewerReady) {
        return;
      }
      setShowDetailedParseLoader(false);
      return;
    }
    const timeout = window.setTimeout(() => {
      setShowDetailedParseLoader(true);
    }, PARSE_LOADER_EXPAND_DELAY_MS);
    return () => window.clearTimeout(timeout);
  }, [shouldShowExpandedParseLoader, id, isLoading, isParseReady, isPdfViewerReady]);

  // Compute available height = viewport - (header height + tts bar height)
  useEffect(() => {
    const compute = () => {
      const header = document.querySelector('[data-app-header]') as HTMLElement | null;
      const ttsbar = document.querySelector('[data-app-ttsbar]') as HTMLElement | null;
      const headerH = header ? header.getBoundingClientRect().height : 0;
      const ttsH = ttsbar ? ttsbar.getBoundingClientRect().height : 0;
      const vh = window.innerHeight;
      const h = Math.max(0, vh - headerH - ttsH);
      // Avoid locking the reader at 0px during transient startup layout states.
      if (h > 0) {
        setContainerHeight(`${h}px`);
      }
    };
    compute();
    const settleT1 = window.setTimeout(compute, 0);
    const settleT2 = window.setTimeout(compute, 120);
    window.addEventListener('resize', compute);
    return () => {
      window.removeEventListener('resize', compute);
      window.clearTimeout(settleT1);
      window.clearTimeout(settleT2);
    };
  }, [isLoading, isParseReady, isAtLimit, activeSidebar]);

  const handleZoomIn = () => setZoomLevel(prev => Math.min(prev + 10, 300));
  const handleZoomOut = () => setZoomLevel(prev => Math.max(prev - 10, 50));

  const handleBackToDocuments = useCallback((event?: MouseEvent) => {
    event?.preventDefault();
    if (isNavigatingBack) return;
    setIsNavigatingBack(true);
    stop();
    const hadOpenSidebar = activeSidebar !== null;
    setActiveSidebar(null);
    const delayMs = hadOpenSidebar ? 220 : 0;
    if (backNavTimeoutRef.current) {
      clearTimeout(backNavTimeoutRef.current);
    }
    backNavTimeoutRef.current = setTimeout(() => {
      router.push('/app');
    }, delayMs);
  }, [isNavigatingBack, stop, activeSidebar, router]);

  const requestForceReparse = useCallback(() => {
    if (forceReparseDisabled) return;
    setShowForceReparseConfirm(true);
  }, [forceReparseDisabled]);

  const confirmForceReparse = useCallback(() => {
    setShowForceReparseConfirm(false);
    void forceReparseParsedPdf();
  }, [forceReparseParsedPdf]);

  const handleGenerateAudiobook = useCallback(async (
    onProgress: (progress: number) => void,
    signal: AbortSignal,
    onChapterComplete: (chapter: TTSAudiobookChapter) => void,
    settings: AudiobookGenerationSettings
  ) => {
    if (!isParseReady) {
      throw new Error('PDF parsing is not ready yet.');
    }
    return createPDFAudioBook(onProgress, signal, onChapterComplete, id as string, settings.format, settings);
  }, [createPDFAudioBook, id, isParseReady]);

  const handleRegenerateChapter = useCallback(async (
    chapterIndex: number,
    bookId: string,
    settings: AudiobookGenerationSettings,
    signal: AbortSignal
  ) => {
    if (!isParseReady) {
      throw new Error('PDF parsing is not ready yet.');
    }
    return regeneratePDFChapter(chapterIndex, bookId, settings.format, signal, settings);
  }, [regeneratePDFChapter, isParseReady]);

  if (error) {
    return (
      <div className="flex flex-col items-center justify-center min-h-screen">
        <p className="text-red-500 mb-4">{error}</p>
        <Link
          href="/app"
          onClick={handleBackToDocuments}
          className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition-all duration-200 ease-in-out hover:scale-[1.04] hover:text-accent"
        >
          <svg className="w-4 h-4 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
          </svg>
          Back to Documents
        </Link>
      </div>
    );
  }

  const renderPdfStatusLoader = () => {
    const compactLabel = isLoading
      ? 'Opening PDF...'
      : (parseUiState === 'ready' ? 'Rendering pages...' : 'Preparing PDF layout...');
    const compactSubLabel = isLoading
      ? 'Loading document data'
      : (parseUiState === 'ready' ? 'Preparing first frame' : 'Queueing parser and preparing page extraction');

    const totalPages = parseProgress?.totalPages ?? 0;
    const pagesParsed = parseProgress?.pagesParsed ?? 0;
    const progressPercent = totalPages > 0
      ? Math.max(0, Math.min(100, (pagesParsed / totalPages) * 100))
      : 0;
    const hasMeasuredProgress = totalPages > 0;
    const isMerging = parseProgress?.phase === 'merge';

    let statusText = 'Loading PDF...';
    let statusSubText = 'Initializing document renderer';
    if (!isLoading) {
      if (parseUiState === 'pending') {
        statusText = 'Preparing PDF layout...';
        statusSubText = parseProgress?.phase === 'merge'
          ? 'Finalizing stitched block structure'
          : 'Queueing parser and preparing page extraction';
      } else if (parseUiState === 'running') {
        statusText = 'Parsing PDF layout blocks...';
        statusSubText = parseProgress?.phase === 'merge'
          ? 'Merging cross-page sections'
          : 'Inferring reading order and text regions';
      } else if (parseUiState === 'failed') {
        statusText = 'PDF parsing failed. Retry to continue.';
        statusSubText = 'The parser could not build a usable layout map';
      }
    }

    const stageLabel = parseUiState === 'failed'
      ? 'Stage: blocked'
      : (parseUiState === 'pending'
        ? 'Stage: prepare'
        : (isMerging ? 'Stage: merge' : 'Stage: infer'));

    return (
      <div className="h-full w-full bg-base">
        <div className={`mx-auto flex h-full items-center px-4 py-6 transition-all duration-300 ease-out ${showDetailedParseLoader ? 'max-w-lg' : 'max-w-md'}`}>
          {showDetailedParseLoader ? (
            <div className="w-full rounded-xl border border-offbase bg-offbase/95 shadow-sm overflow-hidden">
              <div className="h-1 bg-[linear-gradient(90deg,var(--accent),transparent_80%)]" />
              <div className="p-3.5 sm:p-4">
                <div className="space-y-1.5">
                  <div className="inline-flex items-center gap-2 rounded-md border border-offbase bg-base/70 px-2.5 py-1">
                    <LoadingSpinner className="h-3.5 w-3.5 text-accent" />
                    <span className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted">PDF Layout Parse</span>
                  </div>
                  <p className="text-sm font-semibold text-foreground">{statusText}</p>
                </div>

                <div className="mt-3 rounded-lg border border-offbase bg-base/75 p-2.5">
                  <div className="mb-1.5 flex items-end justify-between gap-2">
                    <p className="text-[11px] font-semibold text-foreground">
                      {hasMeasuredProgress ? `Page ${pagesParsed} / ${totalPages}` : 'Awaiting first page'}
                    </p>
                    <p className="text-[10px] text-muted">{stageLabel}</p>
                  </div>
                  <div className="h-2 w-full rounded-full bg-offbase overflow-hidden">
                    <div
                      className="h-full bg-accent transition-all duration-300 ease-out"
                      style={{ width: `${hasMeasuredProgress ? progressPercent : 6}%` }}
                    />
                  </div>
                  <p className="mt-1.5 text-[10px] text-muted">
                    {hasMeasuredProgress ? `${Math.round(progressPercent)}% complete` : statusSubText}
                  </p>
                </div>

                {!isLoading && parseUiState === 'failed' ? (
                  <div className="mt-3 flex justify-start">
                    <button
                      type="button"
                      onClick={requestForceReparse}
                      className="inline-flex items-center rounded-md border border-offbase bg-base px-3 py-1.5 text-xs font-medium text-foreground hover:text-accent transition-colors"
                    >
                      Retry Parse
                    </button>
                  </div>
                ) : null}
              </div>
            </div>
          ) : (
            <div className="w-full rounded-xl border border-offbase bg-offbase/95 p-4 shadow-sm transition-all duration-300 ease-out overflow-hidden">
              <div className="h-0.5 -mx-4 -mt-4 mb-3 bg-[linear-gradient(90deg,var(--accent),transparent_75%)]" />
              <div className="flex items-start justify-between gap-3">
                <div className="min-w-0">
                  <p className="text-sm font-semibold text-foreground">{compactLabel}</p>
                  <p className="mt-1 text-xs text-muted">{compactSubLabel}</p>
                </div>
                <span className="inline-flex items-center justify-center rounded-md border border-offbase bg-base p-1.5">
                  <LoadingSpinner className="h-3.5 w-3.5 text-accent" />
                </span>
              </div>
              <div className="mt-3 grid grid-cols-3 gap-1.5">
                <span className="h-1.5 rounded-full bg-accent/30 animate-pulse" />
                <span className="h-1.5 rounded-full bg-accent/20 animate-pulse [animation-delay:120ms]" />
                <span className="h-1.5 rounded-full bg-accent/15 animate-pulse [animation-delay:220ms]" />
              </div>
            </div>
          )}
        </div>
      </div>
    );
  };

  return (
    <>
      <Header
        left={
          <Link
            href="/app"
            onClick={handleBackToDocuments}
            className="inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase transition-all duration-200 ease-in-out hover:scale-[1.04] hover:text-accent"
            aria-label="Back to documents"
          >
            <svg className="w-3 h-3 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
            </svg>
            Documents
          </Link>
        }
        title={isLoading ? 'Loading…' : (currDocName || '')}
        right={
          <div className="flex items-center gap-2">
            <DocumentHeaderMenu
              zoomLevel={zoomLevel}
              onZoomIncrease={handleZoomIn}
              onZoomDecrease={handleZoomOut}
              onOpenSettings={() => setActiveSidebar((prev) => prev === 'settings' ? null : 'settings')}
              onOpenAudiobook={() => setActiveSidebar((prev) => prev === 'audiobook' ? null : 'audiobook')}
              onOpenSegments={() => setActiveSidebar((prev) => prev === 'segments' ? null : 'segments')}
              isSettingsOpen={activeSidebar === 'settings'}
              isAudiobookOpen={activeSidebar === 'audiobook'}
              isSegmentsOpen={activeSidebar === 'segments'}
              showAudiobookExport={canExportAudiobook}
              minZoom={50}
              maxZoom={300}
            />
          </div>
        }
      />
      <div className="relative overflow-hidden" style={{ height: containerHeight }}>
        {isParseReady ? (
          <div className={isPdfViewerReady ? 'h-full' : 'h-full opacity-0 pointer-events-none'}>
            <PDFViewer
              zoomLevel={zoomLevel}
              onDocumentReady={() => setIsPdfViewerReady(true)}
              pdfState={pdfState}
            />
          </div>
        ) : null}
        {isLoading || !isParseReady || !isPdfViewerReady ? (
          <div className="absolute inset-0 z-10" data-testid="pdf-status-loader">
            {renderPdfStatusLoader()}
          </div>
        ) : null}
      </div>
      {canExportAudiobook && (
        <AudiobookExportModal
          isOpen={activeSidebar === 'audiobook'}
          setIsOpen={(isOpen) => setActiveSidebar((prev) => isOpen ? 'audiobook' : (prev === 'audiobook' ? null : prev))}
          documentType="pdf"
          documentId={id as string}
          onGenerateAudiobook={handleGenerateAudiobook}
          onRegenerateChapter={handleRegenerateChapter}
        />
      )}
      {isAtLimit ? (
        <div className="sticky bottom-0 z-30 w-full border-t border-offbase bg-base" data-app-ttsbar>
          <div className="px-2 md:px-3 pt-1 pb-1.5 flex items-center justify-center gap-1 min-h-10">
            <RateLimitPauseButton />
            <RateLimitBanner />
          </div>
        </div>
      ) : isParseReady ? (
        <TTSPlayer currentPage={currDocPage} numPages={currDocPages} />
      ) : null}
      <DocumentSettings
        isOpen={activeSidebar === 'settings'}
        setIsOpen={(isOpen) => setActiveSidebar((prev) => isOpen ? 'settings' : (prev === 'settings' ? null : prev))}
        pdf={{
          parseStatus,
          parsedOverlayEnabled,
          skipBlockKinds: documentSettings.pdf?.skipBlockKinds ?? [],
          onToggleOverlay: (enabled) => setParsedOverlayEnabled(enabled),
          onToggleSkipKind: (kind, enabled) => {
            const current = new Set(documentSettings.pdf?.skipBlockKinds ?? []);
            if (enabled) current.add(kind);
            else current.delete(kind);
            void updateDocumentSettings({
              ...documentSettings,
              schemaVersion: 1,
              pdf: {
                ...(documentSettings.pdf ?? {}),
                skipBlockKinds: Array.from(current),
              },
            });
          },
          onForceReparse: requestForceReparse,
        }}
      />
      <ConfirmDialog
        isOpen={showForceReparseConfirm}
        onClose={() => setShowForceReparseConfirm(false)}
        onConfirm={confirmForceReparse}
        title={FORCE_REPARSE_CONFIRM_TITLE}
        message={FORCE_REPARSE_CONFIRM_MESSAGE}
        confirmText={FORCE_REPARSE_CONFIRM_TEXT}
        cancelText="Cancel"
      />
      <SegmentsSidebar
        isOpen={activeSidebar === 'segments'}
        setIsOpen={(isOpen) => setActiveSidebar((prev) => isOpen ? 'segments' : (prev === 'segments' ? null : prev))}
        documentId={id as string}
      />
    </>
  );
}
