"use client";

import * as React from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";

import { useCustomerPortalSession } from "@/app/customer/_components/customer-portal-session-context";
import { IMPERSONATION_SCOPE_CHANGED_EVENT, OPEN_PORTFOLIO_SCOPE_EVENT, PORTFOLIO_SCOPE_DRAFT_CHANGED_EVENT, PORTFOLIO_SCOPE_SHEET_OPEN_EVENT } from "@/app/customer/_lib/admin/impersonation-scope";
import { cn } from "@/lib/utils";
import type { CustomerNavigationPayload } from "@/navigation/sidebar/dynamic-sidebar";
import type { SidebarPanelId } from "@/navigation/sidebar/frontend-items";

import { TutorialCoachBar } from "./coach-bar";
import {
  actionAdvanceStep,
  completionGateStep,
  isTaskCompletedByAction,
  coachStepTarget,
  dialogStep,
  isTaskCompletedByInteraction,
  locationAdvanceStep,
  offPageFallbackStep,
  tracksActionCompletion,
} from "./completion";
import {
  coachPrimaryLabel,
  guideTaskStep,
} from "./guide-actions";
import { TutorialHelpButton } from "./help-button";
import { useTutorialProgress } from "./progress";
import { resolveTutorialPlan } from "./resolver";
import { destroyTutorialTour, runWelcomeTour } from "./run-tour";
import { TUTORIAL_SELECTORS, TUTORIAL_TASK_DEFINITIONS } from "./catalog";
import {
  TUTORIAL_ACTION_EVENT,
  isUserDrivenNow,
  pageInteractionFromEvent,
  trackTutorialUserInput,
  type TutorialAction,
} from "./tutorial-actions";
import { TutorialTaskChecklistSheet } from "./task-checklist-sheet";
import type { ResolvedTask, TutorialTaskId } from "./types";
import { TutorialWelcomeDialog } from "./welcome-dialog";

const OPEN_CHECKLIST_EVENT = "customer-tutorial:open-checklist";
const RESTART_TOUR_EVENT = "customer-tutorial:restart-tour";
/** Route changes can take a few seconds to land (dev compiles, slow data). */
const USER_NAVIGATION_WINDOW_MS = 10_000;
/**
 * driver.js puts a click-catching overlay at z-index 10000 and disables pointer events on
 * everything else, so Show me / Back / Next would only dismiss the spotlight. driver.css is
 * unlayered and beats Tailwind's layered utilities, hence `!`.
 */
const COACH_BAR_ABOVE_SPOTLIGHT_CLASS =
  "[.driver-active_&]:z-[10001]! [.driver-active_&]:pointer-events-auto! [.driver-active_&_*]:pointer-events-auto!";
/** Settings keeps Save changes in the bottom-right footer; dock the coach bar under the header there. */
const COACH_BAR_TOP_CLASS = "top-28 bottom-auto";

function browserLocation(): { pathname: string; search: string } {
  return {
    pathname: window.location.pathname,
    search: window.location.search.replace(/^\?/, ""),
  };
}

/** Kill switch — set false to hide the whole tutorial (welcome, coachmarks, header help button). */
const CUSTOMER_TUTORIALS_ENABLED = false; // TODO: change this to true once the tutorial is enabled

type TutorialApi = {
  openChecklist: () => void;
  restartTour: () => void;
};

const TutorialContext = React.createContext<TutorialApi | null>(null);

export function useCustomerTutorial(): TutorialApi {
  const value = React.useContext(TutorialContext);
  if (!value) {
    throw new Error("useCustomerTutorial must be used within CustomerTutorialProvider");
  }
  return value;
}

/** Safe for chrome that may render outside the provider. */
export function useCustomerTutorialOptional(): TutorialApi | null {
  return React.useContext(TutorialContext);
}

export function dispatchOpenTutorialChecklist() {
  if (typeof window !== "undefined") {
    window.dispatchEvent(new Event(OPEN_CHECKLIST_EVENT));
  }
}

export function dispatchRestartTutorialTour() {
  if (typeof window !== "undefined") {
    window.dispatchEvent(new Event(RESTART_TOUR_EVENT));
  }
}

/** Header help control — does not subscribe to tutorial progress (avoids shell re-renders). */
export function CustomerTutorialHelpSlot({ className }: { className?: string }) {
  const attention = React.useSyncExternalStore(
    () => () => undefined,
    () => {
      try {
        const keys = Object.keys(localStorage).filter((key) =>
          key.startsWith("customer_tutorial_v2:"),
        );
        if (keys.length === 0) {
          return true;
        }
        return keys.some((key) => {
          const raw = localStorage.getItem(key);
          if (!raw) return true;
          const parsed = JSON.parse(raw) as {
            skippedForever?: boolean;
            welcomeCompleted?: boolean;
          };
          if (parsed.skippedForever) return false;
          return !parsed.welcomeCompleted;
        });
      } catch {
        return false;
      }
    },
    () => false,
  );

  if (!CUSTOMER_TUTORIALS_ENABLED) {   // TODO: Remove this once the tutorial is enabled
    return null;
  }

  return (
    <TutorialHelpButton
      className={className}
      attention={attention}
      onClick={dispatchOpenTutorialChecklist}
    />
  );
}

type CustomerTutorialProviderProps = {
  children: React.ReactNode;
  tenant: string;
  userKey: string;
  navigation?: CustomerNavigationPayload;
  allowedRoutes?: readonly string[];
  canUsePortfolioScope?: boolean;
};

function panelIdsFromNavigation(
  navigation?: CustomerNavigationPayload,
): SidebarPanelId[] {
  if (!navigation?.panels?.length) {
    return [];
  }
  return navigation.panels.map((panel) => panel.id);
}

/**
 * Overlay-only host: owns tutorial UI state so progress updates do NOT re-render
 * the customer shell / page tree.
 */
function CustomerTutorialOverlay({
  tenant,
  userKey,
  navigation,
  allowedRoutes = [],
  canUsePortfolioScope = false,
}: Omit<CustomerTutorialProviderProps, "children">) {
  const router = useRouter();
  const pathname = usePathname();
  const { canMutateTransactions, portfolioScope } = useCustomerPortalSession();
  const {
    progress,
    markWelcomeCompleted,
    skipForever,
    restartWelcome,
    startTask,
    setTaskStep,
    completeTask,
    completeTaskAtStep,
    clearActiveTask,
    resetAll,
  } = useTutorialProgress(tenant, userKey);

  const [welcomeOpen, setWelcomeOpen] = React.useState(false);
  const [checklistOpen, setChecklistOpen] = React.useState(false);
  const autoWelcomeShown = React.useRef(false);
  const searchParams = useSearchParams();
  const search = searchParams.toString();
  const settingsSection = searchParams.get("section");
  const location = `${pathname}?${search}`;
  const lastLocationRef = React.useRef(location);

  const panelIds = React.useMemo(
    () => panelIdsFromNavigation(navigation),
    [navigation],
  );

  const plan = React.useMemo(
    () =>
      resolveTutorialPlan({
        panelIds,
        allowedRoutes,
        canUsePortfolioScope: canUsePortfolioScope || portfolioScope,
        canMutateTransactions,
        progress,
      }),
    [
      panelIds,
      allowedRoutes,
      canUsePortfolioScope,
      portfolioScope,
      canMutateTransactions,
      progress,
    ],
  );

  const activeTaskDef: ResolvedTask | null = React.useMemo(() => {
    if (!progress.activeTask) {
      return null;
    }
    const fromPlan = plan.tasks.find((task) => task.id === progress.activeTask);
    if (fromPlan) {
      return fromPlan;
    }
    // Active task may already be marked complete in the filtered plan.
    return (
      resolveTutorialPlan({
        panelIds,
        allowedRoutes,
        canUsePortfolioScope: canUsePortfolioScope || portfolioScope,
        canMutateTransactions,
        progress: {
          ...progress,
          completedTasks: progress.completedTasks.filter(
            (id) => id !== progress.activeTask,
          ),
        },
      }).tasks.find((task) => task.id === progress.activeTask) ?? null
    );
  }, [
    progress,
    plan.tasks,
    panelIds,
    allowedRoutes,
    canUsePortfolioScope,
    portfolioScope,
    canMutateTransactions,
  ]);

  // Show welcome shortly after shell mounts (Help is also always available).
  React.useEffect(() => {
    if (autoWelcomeShown.current || !plan.showWelcome) {
      return;
    }
    autoWelcomeShown.current = true;
    const timer = window.setTimeout(() => {
      setWelcomeOpen(true);
    }, 800);
    return () => window.clearTimeout(timer);
  }, [plan.showWelcome]);

  React.useEffect(() => {
    return () => {
      destroyTutorialTour();
    };
  }, []);

  React.useEffect(() => trackTutorialUserInput(), []);

  React.useEffect(() => {
    const onOpen = () => {
      destroyTutorialTour();
      setWelcomeOpen(false);
      setChecklistOpen(true);
    };
    const onRestart = () => {
      destroyTutorialTour();
      setChecklistOpen(false);
      restartWelcome();
      autoWelcomeShown.current = true;
      setWelcomeOpen(true);
    };
    const onScopeOpened = () => {
      if (progress.activeTask === "task:scope" && progress.activeTaskStep === 0) {
        destroyTutorialTour();
        setTaskStep(1);
      }
    };
    const onScopeSheet = (event: Event) => {
      const open = Boolean(
        (event as CustomEvent<{ open?: boolean }>).detail?.open,
      );
      if (!open) return;
      // Always cancel the "Open Scope" spotlight once the sheet is visible.
      destroyTutorialTour();
      if (progress.activeTask === "task:scope" && progress.activeTaskStep === 0) {
        setTaskStep(1);
      }
    };
    const onScopeApplied = (event: Event) => {
      if (progress.activeTask !== "task:scope") return;
      destroyTutorialTour();
      const detail = (event as CustomEvent<{
        impersonatedUserIds?: number[];
        impersonatedCustomerIds?: number[];
        mandateOnly?: boolean;
      }>).detail;
      const hasScope =
        (detail?.impersonatedUserIds?.length ?? 0) > 0 ||
        (detail?.impersonatedCustomerIds?.length ?? 0) > 0 ||
        Boolean(detail?.mandateOnly);
      if (hasScope) {
        // Applied → explain filtered data, then Clear on the banner.
        setTaskStep(3);
        return;
      }
      // Cleared (banner Clear or empty Apply) → finish.
      setTaskStep(5);
    };
    const onScopeDraftChanged = () => {
      if (progress.activeTask !== "task:scope") return;
      if (progress.activeTaskStep !== 1) return;
      destroyTutorialTour();
      setTaskStep(2);
    };
    window.addEventListener(OPEN_CHECKLIST_EVENT, onOpen);
    window.addEventListener(RESTART_TOUR_EVENT, onRestart);
    window.addEventListener(OPEN_PORTFOLIO_SCOPE_EVENT, onScopeOpened);
    window.addEventListener(PORTFOLIO_SCOPE_SHEET_OPEN_EVENT, onScopeSheet);
    window.addEventListener(IMPERSONATION_SCOPE_CHANGED_EVENT, onScopeApplied);
    window.addEventListener(PORTFOLIO_SCOPE_DRAFT_CHANGED_EVENT, onScopeDraftChanged);
    return () => {
      window.removeEventListener(OPEN_CHECKLIST_EVENT, onOpen);
      window.removeEventListener(RESTART_TOUR_EVENT, onRestart);
      window.removeEventListener(OPEN_PORTFOLIO_SCOPE_EVENT, onScopeOpened);
      window.removeEventListener(PORTFOLIO_SCOPE_SHEET_OPEN_EVENT, onScopeSheet);
      window.removeEventListener(IMPERSONATION_SCOPE_CHANGED_EVENT, onScopeApplied);
      window.removeEventListener(PORTFOLIO_SCOPE_DRAFT_CHANGED_EVENT, onScopeDraftChanged);
    };
  }, [restartWelcome, progress.activeTask, progress.activeTaskStep, setTaskStep]);

  // Advance only when the user navigates to the task's own page; step changes alone
  // (e.g. clicking the Reports rail while on another report) must not skip ahead.
  React.useEffect(() => {
    const moved = lastLocationRef.current !== location;
    lastLocationRef.current = location;
    if (!moved || !progress.activeTask) {
      return;
    }
    const nextStep = locationAdvanceStep(
      progress.activeTask,
      { pathname, search },
      progress.activeTaskStep,
    );
    // Only when the user navigated here themselves (not Start / Show me / redirects).
    if (nextStep == null || !isUserDrivenNow(USER_NAVIGATION_WINDOW_MS)) {
      return;
    }
    setTaskStep(nextStep);
  }, [location, pathname, search, progress.activeTask, progress.activeTaskStep, setTaskStep]);

  // A step whose target lives on the task's page (e.g. report filters) is never shown elsewhere:
  // fall back to the step that shows how to get there. Checked when the user navigates or a
  // task (re)loads; Next / Back / target clicks already respect the page themselves.
  const lastGuardKeyRef = React.useRef("");
  React.useEffect(() => {
    if (!progress.activeTask) {
      lastGuardKeyRef.current = "";
      return;
    }
    const guardKey = `${progress.activeTask}|${location}`;
    if (lastGuardKeyRef.current === guardKey) {
      return;
    }
    lastGuardKeyRef.current = guardKey;
    const fallback = offPageFallbackStep(
      progress.activeTask,
      browserLocation(),
      progress.activeTaskStep,
    );
    if (fallback != null) {
      setTaskStep(fallback);
    }
  }, [location, progress.activeTask, progress.activeTaskStep, setTaskStep]);

  const activeTaskPendingAction = Boolean(
    progress.activeTask &&
      tracksActionCompletion(progress.activeTask) &&
      !progress.completedTasks.includes(progress.activeTask),
  );

  // Persist completion as soon as the real action happens; the coach bar then shows the closing step.
  const completeActiveTaskByAction = React.useCallback(
    (taskId: TutorialTaskId) => {
      const def = TUTORIAL_TASK_DEFINITIONS.find((task) => task.id === taskId);
      if (!def) {
        return;
      }
      destroyTutorialTour();
      completeTaskAtStep(taskId, def.steps.length - 1);
    },
    [completeTaskAtStep],
  );

  const progressRef = React.useRef(progress);
  React.useEffect(() => {
    progressRef.current = progress;
  }, [progress]);

  // Like Scope's sheet-open advance: the user's own click / open on the spotlighted target
  // moves the task to its next step, but never past the step that waits for the real action.
  const targetActivationOptions = React.useCallback(
    (taskId: TutorialTaskId, stepIndex: number) => {
      const build = () => ({
        isUserDriven: () => isUserDrivenNow(),
        onTargetActivate: (target: { element: Element; fallback: boolean }) => {
          const current = progressRef.current;
          if (current.activeTask !== taskId || current.activeTaskStep !== stepIndex) {
            return;
          }
          const def = TUTORIAL_TASK_DEFINITIONS.find((task) => task.id === taskId);
          if (!def) {
            return;
          }
          // A fallback (e.g. the Reports rail button) only reveals the real target: re-spotlight this step.
          if (target.fallback) {
            window.setTimeout(() => {
              const latest = progressRef.current;
              if (latest.activeTask !== taskId || latest.activeTaskStep !== stepIndex) {
                return;
              }
              void guideTaskStep(
                def,
                stepIndex,
                {
                  tenant,
                  pathname: window.location.pathname,
                  routerPush: (href) => router.push(href),
                },
                { navigate: false, forceHighlight: true, skipElement: target.element, ...build() },
              );
            }, 280);
            return;
          }
          if (stepIndex + 1 >= def.steps.length) {
            return;
          }
          const pending = !current.completedTasks.includes(taskId);
          if (pending && stepIndex >= completionGateStep(taskId, def.steps.length)) {
            return;
          }
          // E.g. clicked the report link: its page advances the task once it has loaded.
          if (offPageFallbackStep(taskId, browserLocation(), stepIndex + 1) != null) {
            return;
          }
          setTaskStep(stepIndex + 1);
        },
      });
      return build();
    },
    [setTaskStep, tenant, router],
  );

  React.useEffect(() => {
    const taskId = progress.activeTask;
    if (!taskId || !activeTaskPendingAction) {
      return;
    }
    const onAction = (event: Event) => {
      const action = (event as CustomEvent<TutorialAction>).detail;
      if (isTaskCompletedByAction(taskId, action, window.location.pathname)) {
        completeActiveTaskByAction(taskId);
        return;
      }
      const nextStep = actionAdvanceStep(taskId, action, progressRef.current.activeTaskStep);
      if (
        nextStep != null &&
        nextStep !== progressRef.current.activeTaskStep &&
        offPageFallbackStep(taskId, browserLocation(), nextStep) == null
      ) {
        destroyTutorialTour();
        setTaskStep(nextStep);
      }
    };
    window.addEventListener(TUTORIAL_ACTION_EVENT, onAction);
    return () => window.removeEventListener(TUTORIAL_ACTION_EVENT, onAction);
  }, [progress.activeTask, activeTaskPendingAction, completeActiveTaskByAction, setTaskStep]);

  // Search tasks follow their dialog, however the user opened it (button or ⌘J / ⌘⇧J) or closed it.
  React.useEffect(() => {
    const taskId = progress.activeTask;
    const dialog = taskId ? dialogStep(taskId) : null;
    const stepIndex = progress.activeTaskStep;
    if (!taskId || !dialog || (stepIndex !== dialog.step - 1 && stepIndex !== dialog.step)) {
      return;
    }
    let seenOpen = false;
    let timer: number | undefined;
    const check = () => {
      const open = document.querySelector(dialog.selector) != null;
      seenOpen ||= open;
      const target =
        stepIndex === dialog.step - 1 && open
          ? dialog.step
          : stepIndex === dialog.step && !open && seenOpen
            ? dialog.step - 1
            : null;
      if (target == null) {
        return;
      }
      window.clearTimeout(timer);
      // Let a same-click completion (e.g. picking a search result) land first.
      timer = window.setTimeout(() => {
        const latest = progressRef.current;
        const stillOpen = document.querySelector(dialog.selector) != null;
        if (
          latest.activeTask !== taskId ||
          latest.activeTaskStep !== stepIndex ||
          stillOpen !== (target === dialog.step)
        ) {
          return;
        }
        destroyTutorialTour();
        setTaskStep(target);
      }, 150);
    };
    const observer = new MutationObserver(check);
    observer.observe(document.body, { childList: true, subtree: true });
    check();
    return () => {
      observer.disconnect();
      window.clearTimeout(timer);
    };
  }, [progress.activeTask, progress.activeTaskStep, setTaskStep]);

  React.useEffect(() => {
    const taskId = progress.activeTask;
    if (!taskId || !activeTaskPendingAction) {
      return;
    }
    const onInteraction = (event: Event) => {
      const interaction = pageInteractionFromEvent(event);
      if (!interaction) {
        return;
      }
      if (
        isTaskCompletedByInteraction(taskId, {
          pathname: window.location.pathname,
          search: window.location.search,
          href: interaction.href,
        })
      ) {
        completeActiveTaskByAction(taskId);
      }
    };
    const types = ["click", "input", "change"];
    for (const type of types) {
      document.addEventListener(type, onInteraction, true);
    }
    return () => {
      for (const type of types) {
        document.removeEventListener(type, onInteraction, true);
      }
    };
  }, [progress.activeTask, activeTaskPendingAction, completeActiveTaskByAction]);

  // Spotlight the active step target (once per step) without remounting the shell.
  React.useEffect(() => {
    if (!activeTaskDef || checklistOpen || welcomeOpen) {
      return;
    }
    const step = activeTaskDef.steps[progress.activeTaskStep];
    if (!step?.element) {
      destroyTutorialTour();
      return;
    }
    const delay =
      step.element === TUTORIAL_SELECTORS.portfolioScopeApply ||
      step.element === TUTORIAL_SELECTORS.portfolioScopeLists
        ? 600
        : step.element === TUTORIAL_SELECTORS.portfolioScopeBanner ||
            step.element === TUTORIAL_SELECTORS.portfolioScopeClear
          ? 900
          : 280;
    const timer = window.setTimeout(() => {
      void guideTaskStep(
        activeTaskDef,
        progress.activeTaskStep,
        {
          tenant,
          pathname,
          routerPush: (href) => router.push(href),
        },
        {
          navigate: false,
          forceHighlight: true,
          ...targetActivationOptions(activeTaskDef.id, progress.activeTaskStep),
        },
      );
    }, delay);
    return () => window.clearTimeout(timer);
    // Settings sections only change `?section=`; re-target when the user switches section.
  }, [
    activeTaskDef,
    progress.activeTaskStep,
    checklistOpen,
    welcomeOpen,
    tenant,
    pathname,
    settingsSection,
    router,
    targetActivationOptions,
  ]);

  const beginWelcomeTour = React.useCallback(() => {
    setWelcomeOpen(false);
    window.setTimeout(() => {
      void runWelcomeTour(plan.welcomeSteps, {
        onDestroyed: () => {
          markWelcomeCompleted();
          setChecklistOpen(true);
        },
      });
    }, 200);
  }, [plan.welcomeSteps, markWelcomeCompleted]);

  const handleSkipWelcome = React.useCallback(() => {
    setWelcomeOpen(false);
    markWelcomeCompleted();
  }, [markWelcomeCompleted]);

  const handleSkipForever = React.useCallback(() => {
    setWelcomeOpen(false);
    skipForever();
  }, [skipForever]);

  const restartTour = React.useCallback(() => {
    destroyTutorialTour();
    setChecklistOpen(false);
    restartWelcome();
    autoWelcomeShown.current = true;
    setWelcomeOpen(true);
  }, [restartWelcome]);

  const handleStartTask = React.useCallback(
    (taskId: TutorialTaskId) => {
      const task =
        plan.tasks.find((item) => item.id === taskId) ??
        (activeTaskDef?.id === taskId ? activeTaskDef : null);
      startTask(taskId);
      setChecklistOpen(false);
      if (!task) {
        return;
      }
      // Start never navigates: step 1 spotlights the menu entry and the user goes there themselves.
      const ctx = {
        tenant,
        pathname,
        routerPush: (href: string) => router.push(href),
      };
      window.setTimeout(() => {
        void guideTaskStep(task, 0, ctx, {
          navigate: false,
          forceHighlight: true,
          ...targetActivationOptions(task.id, 0),
        });
      }, 320);
    },
    [plan.tasks, activeTaskDef, startTask, router, tenant, pathname, targetActivationOptions],
  );

  const handleOpenTarget = React.useCallback(() => {
    if (!activeTaskDef) {
      return;
    }
    const isScope = activeTaskDef.id === "task:scope";
    const isLast = progress.activeTaskStep >= activeTaskDef.steps.length - 1;
    if (isScope && isLast && progress.activeTask) {
      destroyTutorialTour();
      completeTask(progress.activeTask);
      setChecklistOpen(true);
      return;
    }

    // Scope step 0: open the sheet and advance — do not keep the "Open Scope"
    // spotlight (it races with the select-lists highlight and stacks popovers).
    if (isScope && progress.activeTaskStep === 0) {
      destroyTutorialTour();
      window.dispatchEvent(new Event(OPEN_PORTFOLIO_SCOPE_EVENT));
      setTaskStep(1);
      return;
    }

    // Show me only spotlights; the user performs the step themselves.
    const ctx = {
      tenant,
      pathname,
      routerPush: (href: string) => router.push(href),
    };
    void guideTaskStep(activeTaskDef, progress.activeTaskStep, ctx, {
      navigate: false,
      forceHighlight: true,
      ...targetActivationOptions(activeTaskDef.id, progress.activeTaskStep),
    });
  }, [
    targetActivationOptions,
    activeTaskDef,
    progress.activeTask,
    progress.activeTaskStep,
    router,
    tenant,
    pathname,
    setTaskStep,
    completeTask,
  ]);

  // Next / Finish are always available: users may skip the real action and finish the task.
  const handleCoachNext = React.useCallback(() => {
    if (!activeTaskDef || !progress.activeTask) {
      return;
    }
    const next = coachStepTarget(progress.activeTask, browserLocation(), progress.activeTaskStep, 1);
    if (next >= activeTaskDef.steps.length) {
      destroyTutorialTour();
      completeTask(progress.activeTask);
      setChecklistOpen(true);
      return;
    }
    setTaskStep(next);
  }, [activeTaskDef, progress.activeTask, progress.activeTaskStep, completeTask, setTaskStep]);

  const handleCoachBack = React.useCallback(() => {
    if (!progress.activeTask) {
      return;
    }
    const previous = coachStepTarget(progress.activeTask, browserLocation(), progress.activeTaskStep, -1);
    setTaskStep(Math.max(0, previous));
  }, [progress.activeTask, progress.activeTaskStep, setTaskStep]);

  return (
    <>
      <TutorialWelcomeDialog
        open={welcomeOpen}
        onStart={beginWelcomeTour}
        onSkip={handleSkipWelcome}
        onSkipForever={handleSkipForever}
      />
      <TutorialTaskChecklistSheet
        open={checklistOpen}
        onOpenChange={setChecklistOpen}
        tasks={plan.tasks}
        completedTasks={progress.completedTasks}
        activeTask={progress.activeTask}
        onStartTask={handleStartTask}
        onRestartTour={restartTour}
        onResetProgress={() => {
          resetAll();
          setChecklistOpen(false);
          autoWelcomeShown.current = false;
        }}
      />
      {activeTaskDef && !checklistOpen && !welcomeOpen ? (
        <TutorialCoachBar
          task={activeTaskDef}
          stepIndex={progress.activeTaskStep}
          primaryLabel={coachPrimaryLabel(
            activeTaskDef.id,
            progress.activeTaskStep,
          )}
          onBack={handleCoachBack}
          onNext={handleCoachNext}
          onSkip={() => {
            destroyTutorialTour();
            clearActiveTask();
          }}
          onOpenTarget={handleOpenTarget}
          className={cn(
            COACH_BAR_ABOVE_SPOTLIGHT_CLASS,
            activeTaskDef.id === "task:dashboard-widgets" &&
              /\/settings\/?$/.test(pathname) &&
              COACH_BAR_TOP_CLASS,
          )}
        />
      ) : null}
    </>
  );
}

const tutorialApi: TutorialApi = {
  openChecklist: dispatchOpenTutorialChecklist,
  restartTour: dispatchRestartTutorialTour,
};

/**
 * Children render once without depending on tutorial progress.
 * Overlay listens to progress / window events separately.
 */
export function CustomerTutorialProvider({
  children,
  ...overlayProps
}: CustomerTutorialProviderProps) {
  return (
    <TutorialContext.Provider value={tutorialApi}>
      {children}
      {CUSTOMER_TUTORIALS_ENABLED ? (
        <CustomerTutorialOverlay {...overlayProps} />
      ) : null}
    </TutorialContext.Provider>
  );
}
