import { useState, useLayoutEffect, useRef, Suspense, Activity, type ReactNode } from "react";
import { useRouter } from "next/navigation";
import type { TrailDetailData } from "@/data/queries";
import { BackButton } from "@/app/BackButton";
import { TrailProgress } from "./TrailProgress";
import { TrailStop } from "./TrailStop";
import { TrailHeader } from "./TrailHeader";
import { TrailCompletionCard } from "./TrailCompletionCard";
import { TrailRegisterDeferred } from "./TrailRegisterDeferred";
import { TrailWalkersOverlay } from "./TrailWalkersOverlay";
import { visitStop, completeTrail, abandonWalk, deleteCompletion } from "@/data/actions";
import { useAuthAction } from "@/auth/useAuthAction";
import type { EmbedCache } from "./StopEmbed";
import "./TrailWalk.css";

import { EmbedCacheContext } from "./StopEmbed";

function RevealedStop({
  revealed,
  children,
  initialEmbed,
}: {
  revealed: boolean;
  children: ReactNode;
  initialEmbed?: [string, Promise<React.ReactElement>];
}) {
  const cacheAndSetCache = useState<EmbedCache>(() => new Map(initialEmbed ? [initialEmbed] : []));
  return (
    <EmbedCacheContext value={cacheAndSetCache}>
      <Activity mode={revealed ? "visible" : "hidden"}>{children}</Activity>
    </EmbedCacheContext>
  );
}

type Props = {
  trail: TrailDetailData;
  cleanHandle: string;
  rkey: string;
  onModeChange: (mode: "overview") => void;
  isEditMode?: boolean;
  onPublish?: () => void;
  publishError?: string[] | null;
  isPublishing?: boolean;
  initialEmbeds?: Array<[string, Promise<React.ReactElement>]>;
};

export function TrailWalk({
  trail,
  onModeChange,
  isEditMode,
  onPublish,
  publishError,
  isPublishing,
  initialEmbeds,
}: Props) {
  const { header, stops, yourWalk } = trail;
  const initialEmbedsMap = new Map(initialEmbeds);
  const router = useRouter();
  const requireAuth = useAuthAction();

  const [currentStopIndex, setCurrentStopIndex] = useState(() => {
    if (isEditMode) return 0;
    if (yourWalk) {
      // Find last visited stop that still exists
      for (let i = yourWalk.visitedStops.length - 1; i >= 0; i--) {
        const idx = stops.findIndex((s) => s.tid === yourWalk.visitedStops[i]);
        if (idx !== -1) return idx;
      }
    }
    return 0;
  });

  const [furthestStopIndex, setFurthestStopIndex] = useState(() => {
    if (isEditMode) return 0;
    if (yourWalk) {
      let furthestIndex = 0;
      for (const tid of yourWalk.visitedStops) {
        const idx = stops.findIndex((s) => s.tid === tid);
        if (idx > furthestIndex) {
          furthestIndex = idx;
        }
      }
      return furthestIndex;
    }
    return 0;
  });

  const [isCompleted, setIsCompleted] = useState(false);
  const [isHoveringContainer, setIsHoveringContainer] = useState(false);
  const [isHoveringStopContent, setIsHoveringStopContent] = useState(false);
  const [queuedScroll, setQueuedScroll] = useState<{
    pending: number | "end" | null;
  }>({ pending: currentStopIndex });

  const stopsContainerRef = useRef<HTMLDivElement>(null);
  const contentAreaRef = useRef<HTMLDivElement>(null);
  const completionCardRef = useRef<HTMLDivElement>(null);
  const [stopRects, setStopRects] = useState<
    Map<number, { top: number; left: number; width: number; height: number }>
  >(new Map());

  useLayoutEffect(() => {
    const target = queuedScroll.pending;
    if (target === null) {
      return;
    }
    setQueuedScroll({ pending: null });
    if (target === 0) {
      window.scrollTo({ top: 0, behavior: "smooth" });
    } else if (target === "end") {
      completionCardRef.current?.scrollIntoView({
        behavior: "smooth",
        block: "start",
      });
    } else {
      const stopElements = document.querySelectorAll(`.TrailStop`);
      stopElements[target]?.scrollIntoView({
        behavior: "smooth",
        block: "start",
      });
    }
  }, [queuedScroll]);

  useLayoutEffect(() => {
    const updateStopRects = () => {
      const container = stopsContainerRef.current;
      if (!container) return;

      const newRects = new Map<
        number,
        { top: number; left: number; width: number; height: number }
      >();

      Array.from(container.children).forEach((child, index) => {
        const el = child as HTMLElement;
        newRects.set(index, {
          top: el.offsetTop,
          left: el.offsetLeft,
          width: el.clientWidth,
          height: el.clientHeight,
        });
      });

      setStopRects(newRects);
    };

    const observer = new ResizeObserver(updateStopRects);

    if (stopsContainerRef.current) {
      observer.observe(stopsContainerRef.current);
      updateStopRects();
    }

    return () => observer.disconnect();
  }, [stops.length]);

  const queueScroll = (target: number | "end") => {
    setQueuedScroll({ pending: target });
  };

  const handleContinue = async () => {
    requireAuth();
    if (currentStopIndex < stops.length - 1) {
      const nextIndex = currentStopIndex + 1;
      setFurthestStopIndex(Math.max(nextIndex, furthestStopIndex));
      setCurrentStopIndex(nextIndex);
      queueScroll(nextIndex);
      if (!isEditMode && yourWalk) {
        const nextStopTid = stops[nextIndex].tid;
        await visitStop(yourWalk.uri, nextStopTid);
      }
    } else {
      if (!isEditMode && yourWalk) {
        await completeTrail(yourWalk.uri);
        router.refresh();
        setTimeout(() => {
          setIsCompleted(true);
          queueScroll("end");
        }, 100);
      }
    }
  };

  const handleGoToStop = async (index: number) => {
    setCurrentStopIndex(index);
    queueScroll(index);
    if (!isEditMode && yourWalk) {
      const stopTid = stops[index].tid;
      await visitStop(yourWalk.uri, stopTid);
    }
  };

  const handleWriteReflection = () => {
    const trailUrl = `${window.location.origin}/@${header.creator.handle}/trail/${header.rkey}`;
    const text = encodeURIComponent(
      `i just walked "${header.title}" by @${header.creator.handle}\n\n${trailUrl}`,
    );
    window.open(`https://bsky.app/intent/compose?text=${text}`, "_blank");
  };

  const handleAbandon = async () => {
    if (confirm("abandon this trail? your progress will be lost")) {
      if (yourWalk) {
        await abandonWalk(yourWalk.uri);
        router.refresh();
        onModeChange("overview");
      }
    }
  };

  const isAvatarsActive = !isHoveringStopContent && !isHoveringContainer;

  return (
    <div
      className="TrailWalk-wrapper"
      style={
        {
          "--accent-color": header.accentColor,
          "--accent-color-transparent": `${header.accentColor}33`,
          "--bg-color": header.backgroundColor,
        } as React.CSSProperties
      }
    >
      <div className="TrailWalk">
        <BackButton fallbackPath="/" variant="top" />
        <TrailHeader title={header.title} description={header.description} />

        <div className="TrailWalk-progressSticky">
          <TrailProgress
            totalSteps={stops.length}
            currentStep={currentStopIndex}
            furthestStep={isEditMode ? stops.length - 1 : furthestStopIndex}
            onStepClick={handleGoToStop}
            isWalking={!isCompleted}
            onLeaveTrail={isEditMode ? undefined : handleAbandon}
            backLink={isEditMode ? { to: "/drafts", text: "← drafts" } : undefined}
          />
          <div className="TrailWalk-progressLine" />
        </div>

        <div className="TrailWalk-contentArea" ref={contentAreaRef}>
          <TrailWalkersOverlay stops={stops} stopRects={stopRects} isActive={isAvatarsActive} />

          <div
            ref={stopsContainerRef}
            style={{ position: "relative", zIndex: 1 }}
            onPointerEnter={() => {
              setIsHoveringContainer(true);
            }}
            onPointerLeave={() => {
              setIsHoveringContainer(false);
            }}
          >
            {stops.map((stop, index) => {
              const isStepCompleted = index < currentStopIndex || isCompleted;
              const isCurrent = index === currentStopIndex && !isCompleted;
              const isVisited =
                !isCompleted && index > currentStopIndex && index <= furthestStopIndex;
              const isUnreached = index > furthestStopIndex;

              const isClickable =
                isEditMode ||
                isCompleted ||
                (index !== currentStopIndex && index <= furthestStopIndex);

              const isReorderActive = isEditMode && isCurrent && !isHoveringStopContent;

              const embedUri = stop.external?.uri;
              const initialEmbed = embedUri
                ? initialEmbedsMap.get(embedUri)
                  ? ([embedUri, initialEmbedsMap.get(embedUri)!] as [
                      string,
                      Promise<React.ReactElement>,
                    ])
                  : undefined
                : undefined;

              return (
                <RevealedStop
                  key={stop.tid}
                  revealed={isCompleted || isEditMode || !isUnreached}
                  initialEmbed={initialEmbed}
                >
                  <div
                    onPointerEnter={() => {
                      setIsHoveringStopContent(true);
                    }}
                    onPointerLeave={() => {
                      setIsHoveringStopContent(false);
                    }}
                  >
                    <TrailStop
                      stop={stop}
                      index={index}
                      totalStops={stops.length}
                      isStepCompleted={isStepCompleted}
                      isCurrent={isCurrent}
                      isNextCurrent={index + 1 === currentStopIndex}
                      isVisited={isVisited}
                      isClickable={isClickable}
                      isReorderActive={isReorderActive}
                      onGoToStop={handleGoToStop}
                      onContinue={handleContinue}
                    />
                  </div>
                </RevealedStop>
              );
            })}
          </div>

          {!isCompleted && !isEditMode && furthestStopIndex < stops.length - 1 && (
            <div className="TrailWalk-upcomingIndicator">
              <div className="TrailWalk-upcomingDots">• • •</div>
              <div className="TrailWalk-upcomingText">
                {stops.length - furthestStopIndex - 1} more{" "}
                {stops.length - furthestStopIndex - 1 === 1 ? "stop" : "stops"} ahead
              </div>
            </div>
          )}
        </div>

        {isCompleted && (
          <div ref={completionCardRef}>
            <hr className="TrailWalk-separator" />
            <TrailCompletionCard onWriteReflection={handleWriteReflection} />

            <Suspense
              fallback={
                <div className="TrailRegister">
                  <p className="TrailRegister-empty">loading...</p>
                </div>
              }
            >
              <TrailRegisterDeferred
                mode="completed"
                completions={trail.completions}
                onDelete={async (key) => {
                  if (confirm("delete this completion?")) {
                    await deleteCompletion(key);
                    router.refresh();
                  }
                }}
              />
            </Suspense>
          </div>
        )}

        {!isCompleted && !isEditMode && (
          <div className="TrailWalk-footer">
            <button onClick={handleAbandon} className="TrailWalk-abandonButton">
              abandon
            </button>
          </div>
        )}

        {isEditMode && (
          <div className="TrailWalk-footer">
            {publishError && publishError.length > 0 && (
              <div className="TrailWalk-publishError">
                <ul>
                  {publishError.map((error, i) => (
                    <li key={i}>{error}</li>
                  ))}
                </ul>
              </div>
            )}
            <button onClick={onPublish} disabled={isPublishing} className="TrailWalk-publishButton">
              {isPublishing ? "publishing..." : "publish trail"}
            </button>
          </div>
        )}
      </div>
    </div>
  );
}
