/**
 * Onboarding tour — first-visit welcome walkthrough.
 *
 * Hand-rolled to avoid pulling in `react-joyride` (no new npm dep).
 * Anchors to elements via [data-tour="<id>"] attributes. The tour is
 * controlled by a single useState pair: `step` (0..N) and a
 * `dismissed` boolean persisted in localStorage.
 *
 * Behaviour:
 *   - First visit (no localStorage key): auto-shows on Dashboard mount.
 *   - User clicks "Got it" on the last step → writes the key, hides.
 *   - User clicks "Skip" on any step → same as "Got it".
 *   - User presses Escape → same as "Skip".
 *   - User presses 1..N (when tour is visible) → jumps to that step.
 *   - User presses ArrowLeft / ArrowRight → steps back / forward.
 *   - User adds `?reset-tour=1` to any URL → clears the key, shows.
 *   - Subsequent visits (key is set) → tour is hidden.
 *
 * Tour re-shows when the user navigates to a new step's anchor page
 * (the anchor for step N must be on the current route; otherwise the
 * step is rendered as a centered card with a "Go to <page>" button).
 *
 * Accessibility:
 *   - role="dialog", aria-modal="true", aria-labelledby points to the
 *     step title.
 *   - Focus is moved to the primary action button when the step opens.
 *   - The previous focused element is restored on dismiss.
 *   - Tooltip is a real <div> not a portal, so screen readers find it.
 */
import { useCallback, useEffect, useRef, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { CalendarDays, ShoppingBasket, SlidersHorizontal, Truck, X } from 'lucide-react'

const STORAGE_KEY = 'mealplanner:onboarding-complete'
const RESET_PARAM = 'reset-tour'

export type TourStepId = 'dashboard' | 'pantry' | 'recipes' | 'shopping-list'

interface TourStep {
  id: TourStepId
  route: string
  title: string
  body: string
  icon: typeof CalendarDays
}

const STEPS: TourStep[] = [
  {
    id: 'dashboard',
    route: '/',
    title: 'Your weekly meal plan',
    body: 'The dashboard shows the upcoming Mon–Sun week. Approve, deny, or skip meals, and the planner learns what your family likes.',
    icon: CalendarDays,
  },
  {
    id: 'pantry',
    route: '/pantry',
    title: 'What you have in stock',
    body: 'Pantry tracks ingredients already at home. The planner uses it to avoid duplicates and the shopping list subtracts from it.',
    icon: Truck,
  },
  {
    id: 'recipes',
    route: '/recipes',
    title: 'Browse + filter recipes',
    body: 'Open the Filters panel to narrow by cuisine, protein, dietary tag, prep time, spice, or calories. Search by name or ingredient.',
    icon: SlidersHorizontal,
  },
  {
    id: 'shopping-list',
    route: '/shopping-list',
    title: 'Plan → shop → restock',
    body: 'The shopping list is generated from the plan, minus what is in your pantry. Check items and bulk-add them back to pantry when you return from the store.',
    icon: ShoppingBasket,
  },
]

function readComplete(): boolean {
  try {
    return localStorage.getItem(STORAGE_KEY) === '1'
  } catch {
    return false
  }
}

function writeComplete(): void {
  try {
    localStorage.setItem(STORAGE_KEY, '1')
  } catch {
    // localStorage may be disabled (private mode, etc.) — silently
    // skip; the tour will just re-show on the next visit.
  }
}

function clearComplete(): void {
  try {
    localStorage.removeItem(STORAGE_KEY)
  } catch {
    // see above
  }
}

export function useOnboarding(): {
  reset: () => void
  show: () => void
  markComplete: () => void
  isComplete: boolean
} {
  const [isComplete, setIsComplete] = useState<boolean>(readComplete)

  // Used by ?reset-tour=1 and any other "re-show" path. Clears the
  // localStorage key and flips the App-level flag so the tour re-appears.
  const reset = useCallback(() => {
    clearComplete()
    setIsComplete(false)
  }, [])

  const show = useCallback(() => {
    clearComplete()
    setIsComplete(false)
  }, [])

  // Used by the tour's dismiss path (X / Skip / Esc / "Got it" on the
  // last step). Called from App when onComplete fires. Just flips
  // the App-level flag to true so the tour's early-return fires on
  // the next render. The localStorage key is already written by the
  // tour's finish() before it invokes onComplete.
  const markComplete = useCallback(() => {
    setIsComplete(true)
  }, [])

  return { reset, show, markComplete, isComplete }
}

export function OnboardingTour({
  isComplete,
  onComplete,
  onReset,
}: {
  isComplete: boolean
  /** Called when the user dismisses the tour (X / Skip / Esc / "Got it"). */
  onComplete: () => void
  /** Called when the tour re-shows (e.g. ?reset-tour=1). Inverse of onComplete. */
  onReset: () => void
}) {
  const location = useLocation()
  const navigate = useNavigate()
  const [step, setStep] = useState(0)
  const [anchorRect, setAnchorRect] = useState<DOMRect | null>(null)
  const dialogRef = useRef<HTMLDivElement | null>(null)
  const primaryRef = useRef<HTMLButtonElement | null>(null)
  const previouslyFocused = useRef<HTMLElement | null>(null)

  const currentStep = STEPS[step]
  const isLast = step === STEPS.length - 1

  // Handle ?reset-tour=1 (clears the key; tour shows on next render).
  // Calls onReset() to flip the App-level flag to false (the inverse
  // of onComplete, which flips it to true on dismiss).
  useEffect(() => {
    const sp = new URLSearchParams(location.search)
    if (sp.get(RESET_PARAM) === '1') {
      clearComplete()
      onReset()
      // Strip the param so a refresh doesn't re-trigger the reset.
      sp.delete(RESET_PARAM)
      const next = sp.toString()
      navigate(`${location.pathname}${next ? `?${next}` : ''}`, { replace: true })
      setStep(0)
    }
    // We intentionally don't depend on `onReset` (changes per render)
    // — the only effect we want is when the URL search changes.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [location.search])

  // Auto-show on first visit (Dashboard route). Don't auto-show on
  // every route — only on `/`, which is the app's landing page.
  useEffect(() => {
    if (isComplete) return
    if (location.pathname === '/') {
      setStep(0)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isComplete, location.pathname])

  // Track the anchor element's position. We poll on a rAF loop while
  // the tour is open because the user can resize the window and the
  // page can scroll. requestAnimationFrame keeps the tooltip glued to
  // the anchor without burning CPU.
  useEffect(() => {
    if (isComplete) return
    if (!currentStep) return
    let raf = 0
    const tick = () => {
      const el = document.querySelector<HTMLElement>(`[data-tour="${currentStep.id}"]`)
      if (el) {
        setAnchorRect(el.getBoundingClientRect())
      } else {
        setAnchorRect(null)
      }
      raf = requestAnimationFrame(tick)
    }
    raf = requestAnimationFrame(tick)
    return () => cancelAnimationFrame(raf)
  }, [isComplete, currentStep])

  // Capture focus on open, restore on close.
  useEffect(() => {
    if (isComplete) return
    previouslyFocused.current = document.activeElement as HTMLElement | null
    // Defer focus to next tick so the dialog is in the DOM.
    const id = window.setTimeout(() => primaryRef.current?.focus(), 0)
    return () => {
      window.clearTimeout(id)
      previouslyFocused.current?.focus?.()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isComplete, step])

  // Keyboard: 1..N jump, ←/→ step, Esc dismiss.
  useEffect(() => {
    if (isComplete) return
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        e.preventDefault()
        finish()
        return
      }
      if (e.key === 'ArrowRight') {
        e.preventDefault()
        if (isLast) {
          finish()
        } else {
          setStep(s => s + 1)
        }
        return
      }
      if (e.key === 'ArrowLeft') {
        e.preventDefault()
        setStep(s => Math.max(0, s - 1))
        return
      }
      // 1..N jump
      if (e.key.length === 1 && /^[1-9]$/.test(e.key)) {
        const idx = parseInt(e.key, 10) - 1
        if (idx >= 0 && idx < STEPS.length) {
          e.preventDefault()
          setStep(idx)
        }
      }
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isComplete, isLast])

  const finish = useCallback(() => {
    writeComplete()
    onComplete()
  }, [onComplete])

  const goNext = useCallback(() => {
    if (isLast) finish()
    else setStep(s => s + 1)
  }, [isLast, finish])

  const goJumpToAnchor = useCallback(() => {
    navigate(currentStep.route)
  }, [navigate, currentStep])

  if (isComplete || !currentStep) return null

  const Icon = currentStep.icon
  const isOffRoute = location.pathname !== currentStep.route

  // If the user is on the wrong route, render a centered card with a
  // "Go to <page>" CTA. The tooltip-pointer layout needs a real anchor
  // to point at; without one we'd just be a floating rectangle.
  if (isOffRoute || !anchorRect) {
    return (
      <div
        ref={dialogRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="onboarding-title"
        className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 animate-fade-in"
      >
        <div className="bg-white rounded-2xl shadow-2xl border border-surface-200 w-full max-w-md p-6 space-y-4">
          <div className="flex items-start justify-between gap-3">
            <div className="flex items-center gap-3">
              <div className="w-10 h-10 rounded-xl bg-primary-50 flex items-center justify-center flex-shrink-0">
                <Icon className="w-5 h-5 text-primary-600" />
              </div>
              <h2 id="onboarding-title" className="text-lg font-semibold text-surface-900">
                {currentStep.title}
              </h2>
            </div>
            <button
              type="button"
              onClick={finish}
              aria-label="Dismiss tour"
              className="p-1 rounded hover:bg-surface-100 text-surface-500 focus:outline-none focus:ring-2 focus:ring-primary-400"
            >
              <X className="w-4 h-4" />
            </button>
          </div>
          <p className="text-sm text-surface-600 leading-relaxed">{currentStep.body}</p>
          <p className="text-xs text-surface-500">
            Step {step + 1} of {STEPS.length} — open the highlighted page to see it in context.
          </p>
          <div className="flex items-center justify-between gap-2 pt-2">
            <button
              type="button"
              onClick={finish}
              className="text-xs text-surface-500 hover:text-surface-700 focus:outline-none focus:ring-2 focus:ring-primary-400 rounded px-1"
            >
              Skip tour
            </button>
            <div className="flex items-center gap-2">
              <button
                type="button"
                onClick={goJumpToAnchor}
                className="text-xs font-medium px-3 py-1.5 rounded-lg bg-primary-600 text-white hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-400"
              >
                Open {currentStep.route === '/' ? 'Dashboard' : currentStep.route.slice(1)}
              </button>
              <button
                ref={primaryRef}
                type="button"
                onClick={goNext}
                className="text-xs font-medium px-3 py-1.5 rounded-lg border border-primary-300 text-primary-700 hover:bg-primary-50 focus:outline-none focus:ring-2 focus:ring-primary-400"
              >
                {isLast ? 'Got it' : 'Next'}
              </button>
            </div>
          </div>
        </div>
      </div>
    )
  }

  // Anchored tooltip: position the card 16px below the anchor (or above
  // if it would clip the viewport). Clamp horizontally to keep the card
  // on-screen. Mobile: prefer the top of the viewport so the card never
  // gets clipped.
  const isMobile = typeof window !== 'undefined' && window.innerWidth < 640
  const ANCHOR_GAP = 16
  const CARD_WIDTH = 320
  const viewportH = typeof window !== 'undefined' ? window.innerHeight : 800
  const viewportW = typeof window !== 'undefined' ? window.innerWidth : 1024
  const placeBelow = !isMobile && anchorRect.bottom + ANCHOR_GAP + 200 < viewportH
  const top = placeBelow
    ? anchorRect.bottom + ANCHOR_GAP
    : Math.max(16, anchorRect.top - ANCHOR_GAP - 200)
  const left = isMobile
    ? 16
    : Math.max(16, Math.min(viewportW - CARD_WIDTH - 16, anchorRect.left + anchorRect.width / 2 - CARD_WIDTH / 2))

  return (
    <>
      {/* Soft scrim. The page is still visible — this is a hint, not a
          modal. We don't use a full overlay because the user must be
          able to see the page element being explained. */}
      <div
        className="fixed inset-0 z-40 bg-black/20 pointer-events-none animate-fade-in"
        aria-hidden="true"
      />
      {/* Anchor highlight ring. Purely decorative; the tooltip is the
          real "look here" signal. */}
      <div
        className="fixed z-40 pointer-events-none rounded-xl ring-4 ring-primary-400 ring-offset-2 ring-offset-white animate-fade-in"
        style={{
          top: anchorRect.top - 4,
          left: anchorRect.left - 4,
          width: anchorRect.width + 8,
          height: anchorRect.height + 8,
        }}
        aria-hidden="true"
      />
      <div
        ref={dialogRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="onboarding-title"
        className="fixed z-50 animate-fade-in"
        style={{ top, left, width: CARD_WIDTH }}
      >
        <div className="bg-white rounded-2xl shadow-2xl border border-surface-200 p-5 space-y-3">
          <div className="flex items-start justify-between gap-3">
            <div className="flex items-center gap-2.5">
              <div className="w-8 h-8 rounded-lg bg-primary-50 flex items-center justify-center flex-shrink-0">
                <Icon className="w-4 h-4 text-primary-600" />
              </div>
              <h2 id="onboarding-title" className="text-base font-semibold text-surface-900 leading-tight">
                {currentStep.title}
              </h2>
            </div>
            <button
              type="button"
              onClick={finish}
              aria-label="Dismiss tour"
              className="p-1 rounded hover:bg-surface-100 text-surface-500 focus:outline-none focus:ring-2 focus:ring-primary-400 -mt-1 -mr-1"
            >
              <X className="w-4 h-4" />
            </button>
          </div>
          <p className="text-sm text-surface-600 leading-relaxed">{currentStep.body}</p>
          <div className="flex items-center gap-1 pt-1" aria-label="Tour progress">
            {STEPS.map((s, i) => (
              <span
                key={s.id}
                className={`h-1.5 flex-1 rounded-full transition-colors ${
                  i <= step ? 'bg-primary-500' : 'bg-surface-200'
                }`}
              />
            ))}
          </div>
          <div className="flex items-center justify-between gap-2 pt-1">
            <button
              type="button"
              onClick={finish}
              className="text-xs text-surface-500 hover:text-surface-700 focus:outline-none focus:ring-2 focus:ring-primary-400 rounded px-1"
            >
              Skip tour
            </button>
            <div className="flex items-center gap-2">
              {step > 0 && (
                <button
                  type="button"
                  onClick={() => setStep(s => s - 1)}
                  className="text-xs font-medium px-2.5 py-1.5 rounded-lg border border-surface-300 text-surface-700 hover:bg-surface-50 focus:outline-none focus:ring-2 focus:ring-primary-400"
                >
                  Back
                </button>
              )}
              <button
                ref={primaryRef}
                type="button"
                onClick={goNext}
                className="text-xs font-medium px-3 py-1.5 rounded-lg bg-primary-600 text-white hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-400"
              >
                {isLast ? 'Got it' : 'Next'}
              </button>
            </div>
          </div>
        </div>
      </div>
    </>
  )
}
