import { useCallback, useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
import { getCurrent } from '@tauri-apps/plugin-deep-link';
import { useEnv } from '@/context/EnvContext';
import { useLibraryStore } from '@/store/libraryStore';
import { useReaderStore } from '@/store/readerStore';
import { isTauriAppPlatform } from '@/services/environment';
import { navigateToReader } from '@/utils/nav';
import { eventDispatcher } from '@/utils/event';
import { parseAnnotationDeepLink, AnnotationDeepLink } from '@/utils/deeplink';
import { useTranslation } from './useTranslation';

// Module-scoped — survives hook remounts (library → reader → library on
// book close). Tauri's getCurrent() keeps returning the launch URL for the
// lifetime of the app session, so without this flag every remount would
// re-process the cold-start URL and navigate back to the deep-link target
// in a loop.
let coldStartConsumed = false;

/**
 * Receive annotation deep links and navigate the reader accordingly.
 *
 * Architecture:
 *   - useOpenWithBooks owns the Tauri URL channels (onOpenUrl,
 *     single-instance, shared-intent, open-files) and re-broadcasts every
 *     URL as the 'app-incoming-url' event. This hook subscribes to that
 *     event for the warm-start / live path.
 *   - For cold-start (app launched FROM the URL), getCurrent() is read
 *     once at module scope. useOpenWithBooks doesn't do this — its
 *     channels only fire for live deliveries.
 *   - Library-load deferral: on cold-start the URL may arrive before the
 *     library store has hydrated. Stash and replay once libraryLoaded.
 *
 * Supported URL shapes (see src/utils/deeplink.ts):
 *   readest://book/{hash}/annotation/{id}?cfi=...
 *   https://web.readest.com/o/book/{hash}/annotation/{id}?cfi=...
 *   readest://annotation/{hash}/{id}            (legacy Readwise sync)
 *
 * Already-open shortcut: if the target book has a mounted view, jump in
 * place via view.goTo(cfi). router.push to the same /reader path with a
 * different cfi query does NOT re-run the reader's init effect, so
 * navigation alone wouldn't move the view in that case.
 */
export function useOpenAnnotationLink() {
  const _ = useTranslation();
  const router = useRouter();
  const { appService } = useEnv();
  const getBookByHash = useLibraryStore((s) => s.getBookByHash);
  const libraryLoaded = useLibraryStore((s) => s.libraryLoaded);
  const pending = useRef<AnnotationDeepLink | null>(null);

  const resolveAndNavigate = useCallback(
    (parsed: AnnotationDeepLink) => {
      const { bookHash, cfi } = parsed;
      const book = getBookByHash(bookHash);
      if (!book) {
        eventDispatcher.dispatch('toast', {
          type: 'warning',
          message: _('Book not in your library'),
          timeout: 2500,
        });
        return;
      }

      // Only books in the current bookKeys are actually displayed. viewStates
      // accumulates stale entries for books switched away from in place (their
      // views are detached from the DOM, never cleared on switch), so matching
      // against all viewStates would goTo a dead view and the reader would stay
      // stuck on the current book when switching back to a previously-open one
      // (#4887). Scope the "already open" check to the displayed bookKeys.
      const { viewStates, bookKeys, setPreviewMode } = useReaderStore.getState();
      const openKey = bookKeys.find((key) => key.startsWith(bookHash) && viewStates[key]?.view);
      if (openKey) {
        if (cfi) {
          viewStates[openKey]!.view!.goTo(cfi);
          setPreviewMode(openKey, true);
        }
        return;
      }

      // A reader is already mounted showing a different book. router.push to the
      // same /reader route does NOT re-run the reader's one-shot init effect, so
      // navigateToReader is a no-op and the reader stays on the current book
      // (#4887). Switch the book in place via useBooksManager, carrying the cfi
      // so the freshly-opened view jumps to the annotation once it is ready.
      if (window.location.pathname.startsWith('/reader')) {
        eventDispatcher.dispatch('open-book-in-reader', { bookHash, cfi });
        return;
      }

      // No reader mounted (library / cold start) - navigate fresh; FoliateViewer
      // reads ?cfi= at init.
      const queryParams = cfi ? `cfi=${encodeURIComponent(cfi)}` : undefined;
      navigateToReader(router, [bookHash], queryParams);
    },
    [_, getBookByHash, router],
  );

  useEffect(() => {
    if (!isTauriAppPlatform() || !appService) return;

    const handle = (url: string) => {
      const parsed = parseAnnotationDeepLink(url);
      if (!parsed) return;
      if (!useLibraryStore.getState().libraryLoaded) {
        pending.current = parsed;
        return;
      }
      resolveAndNavigate(parsed);
    };

    if (!coldStartConsumed) {
      coldStartConsumed = true;
      getCurrent()
        .then((urls) => urls?.forEach(handle))
        .catch(() => {
          // Plugin not available on this platform — live channel still works.
        });
    }

    const onIncoming = (event: CustomEvent) => {
      const { urls } = event.detail as { urls: string[] };
      urls.forEach(handle);
    };
    eventDispatcher.on('app-incoming-url', onIncoming);

    return () => {
      eventDispatcher.off('app-incoming-url', onIncoming);
    };
  }, [appService, resolveAndNavigate]);

  // Replay any deferred deep link once the library hydrates.
  useEffect(() => {
    if (!libraryLoaded || !pending.current) return;
    const parsed = pending.current;
    pending.current = null;
    resolveAndNavigate(parsed);
  }, [libraryLoaded, resolveAndNavigate]);
}
