import { useCallback, useRef } from 'react';
import { NavigateOptions, useLocation, useNavigate } from 'react-router-dom';
import { useAtomValue } from 'jotai';
import { getCanonicalAliasOrRoomId } from '../utils/matrix';
import { getChannelsRoomPath, getDirectRoomPath, getSpacePath } from '../pages/pathUtils';
import { useMatrixClient } from './useMatrixClient';
import { getOrphanParents, guessPerfectParent, isSpace } from '../utils/room';
import { roomToParentsAtom } from '../state/room/roomToParents';
import { useSelectedSpace } from './router/useSelectedSpace';
import { settingsAtom } from '../state/settings';
import { useSetting } from '../state/hooks/settings';

export const useRoomNavigate = () => {
  const navigate = useNavigate();
  const location = useLocation();
  const mx = useMatrixClient();
  const roomToParents = useAtomValue(roomToParentsAtom);
  const spaceSelectedId = useSelectedSpace();
  const [developerTools] = useSetting(settingsAtom, 'developerTools');

  // navigate(samePath) creates a fresh history entry in react-router-v6 even
  // though the screen does not change — repeated taps on the same chat from
  // search/inbox/notifications would inflate the back-stack tracked by
  // useAndroidBackButton. Force replace when the target matches the current
  // pathname so callers do not need to pass it everywhere.
  //
  // Pathname tracking has two sources:
  //   1. useLocation → pathnameRef on every render, for consistency with the
  //      router after it commits a navigation we did not initiate.
  //   2. Optimistic write inside safeNavigate, so two calls in the same tick
  //      (before React commits the first) collapse correctly: the second sees
  //      the target the first just navigated to and forces replace.
  // Without (2), two simultaneous navigateRoom(samePath) calls — e.g. two
  // notifications dispatched back-to-back, or a double-clicked list item —
  // both compare against the pre-navigation pathname and both PUSH. Without
  // (1), an external navigate() between our calls would leave pathnameRef
  // ahead of the actual route.
  //
  // The callback identity stays stable (deps = [navigate] only) so consumers
  // that wrap navigateRoom in their own useCallback do not capture a stale
  // closure on every route change.
  const pathnameRef = useRef(location.pathname);
  pathnameRef.current = location.pathname;
  const safeNavigate = useCallback(
    (target: string, opts?: NavigateOptions) => {
      const finalOpts = pathnameRef.current === target ? { ...opts, replace: true } : opts;
      navigate(target, finalOpts);
      pathnameRef.current = target;
    },
    [navigate]
  );

  const navigateSpace = useCallback(
    (roomId: string) => {
      const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, roomId);
      safeNavigate(getSpacePath(roomIdOrAlias));
    },
    [mx, safeNavigate]
  );

  const navigateRoom = useCallback(
    (roomId: string, eventId?: string, opts?: NavigateOptions) => {
      const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, roomId);

      // Developer-tools edge case: clicking the rail-selected space row
      // opens the space's own timeline as a pseudo-room. Route through
      // /channels/<space>/<space>/ so the dev view shares the channels
      // SpaceRouteRoomProvider — its `developerTools && isSpaceRoom &&
      // room.roomId === space.roomId` branch already permits this.
      if (developerTools && spaceSelectedId === roomId) {
        safeNavigate(getChannelsRoomPath(roomIdOrAlias, roomIdOrAlias, eventId), opts);
        return;
      }

      const orphanParents = getOrphanParents(roomToParents, roomId);
      if (orphanParents.length > 0) {
        let parentSpace: string;
        if (spaceSelectedId && orphanParents.includes(spaceSelectedId)) {
          parentSpace = spaceSelectedId;
        } else {
          parentSpace = guessPerfectParent(mx, roomId, orphanParents) ?? orphanParents[0];
        }

        const pSpaceIdOrAlias = getCanonicalAliasOrRoomId(mx, parentSpace);

        safeNavigate(getChannelsRoomPath(pSpaceIdOrAlias, roomIdOrAlias, eventId), opts);
        return;
      }

      // After P3c the Direct tab is universal — every non-space leaf room
      // routes through /direct/. Space roots stay on their own /{spaceId}/
      // route handled by navigateSpace.
      const targetRoom = mx.getRoom(roomId);
      if (targetRoom && isSpace(targetRoom)) {
        safeNavigate(getSpacePath(roomIdOrAlias), opts);
        return;
      }

      safeNavigate(getDirectRoomPath(roomIdOrAlias, eventId), opts);
    },
    [mx, safeNavigate, spaceSelectedId, roomToParents, developerTools]
  );

  return {
    navigateSpace,
    navigateRoom,
  };
};
