import { useEffect, useRef, useState } from 'react'
import { useParams, useNavigate, useLocation } from 'react-router-dom'
import { treesApi } from '@/api/trees'
import { sessionsApi } from '@/api/sessions'
import { useTreeNavigationShortcuts } from '@/hooks/useKeyboardShortcuts'
import { useCustomStepFlow } from '@/hooks/useCustomStepFlow'
import { useSessionTimer } from '@/hooks/useSessionTimer'
import type { Tree, Session, DecisionRecord, TreeStructure, SessionOutcome } from '@/types'
import { cn, safeGetItem, safeSetItem } from '@/lib/utils'
import { MarkdownContent } from '@/components/ui/MarkdownContent'
import { CustomStepModal } from '@/components/step-library/CustomStepModal'
import { PostStepActionModal, ContinuationModal, ForkTreeModal, ScratchpadSidebar, SessionOutcomeModal } from '@/components/session'
import { Plus, CheckCircle, ArrowRight, Clock, Terminal, Clipboard, Check, Copy, HelpCircle, Link2, ChevronDown, Settings } from 'lucide-react'
import { Spinner } from '@/components/common/Spinner'
import { toast } from '@/lib/toast'
import { Modal } from '@/components/common/Modal'
import { ShareSessionModal } from '@/components/session/ShareSessionModal'
import { CSATModal } from '@/components/session/CSATModal'
import { hasBeenRated } from '@/components/session/csatUtils'
import { StepFeedback } from '@/components/session/StepFeedback'
import { buildSessionShareUrl, getLatestActiveShareForSession } from '@/lib/sessionShare'
import { CopilotPanel } from '@/components/copilot/CopilotPanel'
import { CopilotToggle } from '@/components/copilot/CopilotToggle'
import { SupportingDataPanel } from '@/components/session/SupportingDataPanel'
import { Button } from '@/components/ui/Button'
import { integrationsApi, sessionPsaApi } from '@/api/integrations'
import { TicketPickerModal } from '@/components/session/TicketPickerModal'
import { TicketLinkIndicator } from '@/components/session/TicketLinkIndicator'
import { UpdateTicketModal } from '@/components/session/UpdateTicketModal'
import type { PSATicketInfo } from '@/types/integrations'
import { addRecentFlow } from '@/lib/recentFlows'
import { analytics } from '@/lib/analytics'
import { useTicketContext } from '@/hooks/useTicketContext'
import { TicketContextPanel } from '@/components/session/TicketContextPanel'

interface LocationState {
  sessionId?: string
  prefillClientName?: string
  prefillTicketNumber?: string
}

type CompletionSource = 'standard' | 'custom'

export function TreeNavigationPage() {
  const { id: treeId } = useParams<{ id: string }>()
  const navigate = useNavigate()
  const location = useLocation()
  const locationState = location.state as LocationState | undefined

  const [tree, setTree] = useState<Tree | null>(null)
  const [session, setSession] = useState<Session | null>(null)
  const [currentNodeId, setCurrentNodeId] = useState<string>('')
  const [pathTaken, setPathTaken] = useState<string[]>([])
  const [decisions, setDecisions] = useState<DecisionRecord[]>([])
  const [currentStepEnteredAt, setCurrentStepEnteredAt] = useState<string>(new Date().toISOString())
  const [notes, setNotes] = useState<string>('')
  const [commandOutput, setCommandOutput] = useState<string>('')
  const [commandOutputOpen, setCommandOutputOpen] = useState(false)
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [isCompleting, setIsCompleting] = useState(false)
  const [showOutcomeModal, setShowOutcomeModal] = useState(false)
  const [pendingCompletionDecision, setPendingCompletionDecision] = useState<DecisionRecord | null>(null)
  const [completionSource, setCompletionSource] = useState<CompletionSource>('standard')
  const [copiedCommand, setCopiedCommand] = useState<string | null>(null)
  const [shortcutsModalOpen, setShortcutsModalOpen] = useState(false)
  const [selectingOption, setSelectingOption] = useState<string | null>(null)
  const [copiedForTicket, setCopiedForTicket] = useState(false)
  const [isCopyingForTicket, setIsCopyingForTicket] = useState(false)
  const [showSharePopover, setShowSharePopover] = useState(false)
  const [showShareModal, setShowShareModal] = useState(false)
  const [showCsatModal, setShowCsatModal] = useState(false)
  const [copiedShareLink, setCopiedShareLink] = useState(false)
  const [isCopyingShareLink, setIsCopyingShareLink] = useState(false)
  const sharePopoverRef = useRef<HTMLDivElement>(null)
  const [copilotOpen, setCopilotOpen] = useState(false)

  // PSA ticket link state
  const [hasConnection, setHasConnection] = useState(false)
  const [showTicketPicker, setShowTicketPicker] = useState(false)
  const [showUpdateModal, setShowUpdateModal] = useState(false)
  const [ticketInfo, setTicketInfo] = useState<PSATicketInfo | null>(null)

  // PSA ticket context
  const { context: ticketContext, loading: ticketContextLoading, error: ticketContextError, refresh: refreshTicketContext } = useTicketContext(
    session?.psa_ticket_id,
    session?.psa_connection_id
  )

  const handleCopyCommand = (text: string) => {
    navigator.clipboard.writeText(text)
    setCopiedCommand(text)
    setTimeout(() => setCopiedCommand(null), 2000)
  }

  const handleCopyForTicket = async () => {
    if (!session || isCopyingForTicket) return
    setIsCopyingForTicket(true)
    try {
      const content = await sessionsApi.export(session.id, {
        format: 'psa',
        include_timestamps: true,
        include_tree_info: true,
      })
      if (content) {
        await navigator.clipboard.writeText(content)
        setCopiedForTicket(true)
        setTimeout(() => setCopiedForTicket(false), 2000)
        toast.success('Copied progress notes to clipboard')
      }
    } catch (err) {
      console.error('Copy for ticket failed:', err)
      toast.error('Failed to copy notes')
    } finally {
      setIsCopyingForTicket(false)
    }
  }

  const handleCopyShareLink = async () => {
    if (!session || isCopyingShareLink) return
    setIsCopyingShareLink(true)
    try {
      const allShares = await sessionsApi.listMyShares()
      const existingShare = getLatestActiveShareForSession(allShares, session.id)
      let shareUrl: string
      if (existingShare) {
        shareUrl = buildSessionShareUrl(existingShare)
      } else {
        const newShare = await sessionsApi.createShare(session.id, { visibility: 'account' })
        shareUrl = buildSessionShareUrl(newShare)
      }
      await navigator.clipboard.writeText(shareUrl)
      setCopiedShareLink(true)
      toast.success('Share link copied to clipboard')
      setTimeout(() => setCopiedShareLink(false), 2000)
    } catch (err) {
      console.error('Copy share link failed:', err)
      toast.error('Failed to copy share link')
    } finally {
      setIsCopyingShareLink(false)
    }
  }

  // Close share popover on outside click
  useEffect(() => {
    if (!showSharePopover) return
    const handleMouseDown = (e: MouseEvent) => {
      if (sharePopoverRef.current && !sharePopoverRef.current.contains(e.target as Node)) {
        setShowSharePopover(false)
      }
    }
    document.addEventListener('mousedown', handleMouseDown)
    return () => document.removeEventListener('mousedown', handleMouseDown)
  }, [showSharePopover])

  // Close share popover on Escape key
  useEffect(() => {
    if (!showSharePopover) return
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        setShowSharePopover(false)
      }
    }
    document.addEventListener('keydown', handleKeyDown)
    return () => document.removeEventListener('keydown', handleKeyDown)
  }, [showSharePopover])

  // Session metadata (prefill from Repeat Last Session)
  const [ticketNumber, setTicketNumber] = useState<string>(locationState?.prefillTicketNumber || '')
  const [clientName, setClientName] = useState<string>(locationState?.prefillClientName || '')
  const [showMetadataForm, setShowMetadataForm] = useState(true)

  // Session timer
  const timerDisplay = useSessionTimer(session?.started_at)

  // Scratchpad state
  const [scratchpadOpen, setScratchpadOpen] = useState(() => {
    return safeGetItem('scratchpad-collapsed') === 'false'
  })

  const findNode = (nodeId: string, structure?: TreeStructure): TreeStructure | null => {
    if (!structure) return null
    if (structure.id === nodeId) return structure
    if (structure.children) {
      for (const child of structure.children) {
        const found = findNode(nodeId, child)
        if (found) return found
      }
    }
    return null
  }

  const calculateDurationSeconds = (enteredAtIso: string, exitedAtIso: string): number => {
    const enteredAtMs = new Date(enteredAtIso).getTime()
    const exitedAtMs = new Date(exitedAtIso).getTime()
    if (Number.isNaN(enteredAtMs) || Number.isNaN(exitedAtMs)) return 0
    return Math.max(0, Math.floor((exitedAtMs - enteredAtMs) / 1000))
  }

  const deriveCurrentStepEnteredAt = (sessionData: Session): string => {
    if (!sessionData.decisions || sessionData.decisions.length === 0) {
      return sessionData.started_at || new Date().toISOString()
    }
    const lastDecision = sessionData.decisions[sessionData.decisions.length - 1]
    return lastDecision.exited_at || lastDecision.timestamp || sessionData.started_at || new Date().toISOString()
  }

  const openCompletionModal = (completionDecision: DecisionRecord, source: CompletionSource) => {
    const exitedAt = new Date().toISOString()
    const enteredAt = currentStepEnteredAt || session?.started_at || exitedAt
    setPendingCompletionDecision({
      ...completionDecision,
      timestamp: exitedAt,
      entered_at: enteredAt,
      exited_at: exitedAt,
      duration_seconds: calculateDurationSeconds(enteredAt, exitedAt),
    })
    setCompletionSource(source)
    setShowOutcomeModal(true)
  }

  const closeOutcomeModal = () => {
    if (isCompleting) return
    setShowOutcomeModal(false)
    setPendingCompletionDecision(null)
    setCompletionSource('standard')
  }

  const handleCsatClose = () => {
    setShowCsatModal(false)
    if (session) {
      navigate(`/sessions/${session.id}`)
    }
  }

  // Custom step flow (creation, post-step actions, continuation, branching, forking)
  const customStepFlow = useCustomStepFlow({
    tree,
    session,
    currentNodeId,
    pathTaken,
    decisions,
    notes,
    findNode,
    setCurrentNodeId,
    setPathTaken,
    setDecisions,
    setNotes,
    setError,
    onEnterNode: setCurrentStepEnteredAt,
    isCompleting,
    onRequestCompletion: (completionDecision) => {
      openCompletionModal(completionDecision, 'custom')
    },
  })

  // Inject command_output into the last decision (for custom steps) before continuing
  const updateLastDecisionWithCommandOutput = async () => {
    const output = commandOutput.trim() || null
    if (!output || !session || decisions.length === 0) return
    const updatedDecisions = [...decisions]
    updatedDecisions[updatedDecisions.length - 1] = {
      ...updatedDecisions[updatedDecisions.length - 1],
      command_output: output,
    }
    setDecisions(updatedDecisions)
    try {
      await sessionsApi.update(session.id, { decisions: updatedDecisions })
    } catch (err) {
      console.error('Failed to update decision with command output:', err)
    }
  }

  const handleCustomContinueToDescendant = async () => {
    await updateLastDecisionWithCommandOutput()
    setCommandOutput('')
    setCommandOutputOpen(false)
    customStepFlow.handleContinueToDescendant()
  }

  const handleCustomBranchCompleteWithOutput = async () => {
    await updateLastDecisionWithCommandOutput()
    customStepFlow.handleCustomBranchComplete()
  }

  const handleScratchpadSave = async (content: string) => {
    if (!session) return
    await sessionsApi.updateScratchpad(session.id, content)
  }

  useEffect(() => {
    if (treeId) {
      loadTreeAndSession()
    }
  }, [treeId])

  // Check for PSA connection on mount
  useEffect(() => {
    integrationsApi.getConnection()
      .then((conn) => setHasConnection(!!conn))
      .catch(() => setHasConnection(false))
  }, [])

  const handleTicketLinked = (linkedTicketId: string, ticket: PSATicketInfo) => {
    setTicketInfo(ticket)
    setSession((prev) => prev ? { ...prev, psa_ticket_id: linkedTicketId } : prev)
    setShowTicketPicker(false)
    toast.success(`Linked to CW #${linkedTicketId}`)
  }

  const handleTicketUnlink = async () => {
    if (!session) return
    try {
      await sessionPsaApi.linkTicket(session.id, null)
      setSession((prev) => prev ? { ...prev, psa_ticket_id: null } : prev)
      setTicketInfo(null)
      toast.success('Ticket unlinked')
    } catch {
      toast.error('Failed to unlink ticket')
    }
  }

  const loadTreeAndSession = async () => {
    setIsLoading(true)
    setError(null)
    try {
      const treeData = await treesApi.get(treeId!)

      // Safety redirect: procedural trees should use the procedural navigator
      if (treeData.tree_type === 'procedural') {
        navigate(`/flows/${treeId}/navigate`, {
          replace: true,
          state: locationState,
        })
        return
      }

      setTree(treeData)
      addRecentFlow({ id: treeData.id, name: treeData.name, tree_type: treeData.tree_type })
      analytics.flowViewed({ flow_id: treeData.id, flow_type: treeData.tree_type, flow_name: treeData.name })

      // If resuming a session
      if (locationState?.sessionId) {
        const sessionData = await sessionsApi.get(locationState.sessionId)
        setSession(sessionData)
        setPathTaken(sessionData.path_taken)
        setCurrentNodeId(sessionData.path_taken[sessionData.path_taken.length - 1] || treeData.tree_structure?.id || 'root')
        setDecisions(sessionData.decisions as DecisionRecord[])
        setCurrentStepEnteredAt(deriveCurrentStepEnteredAt(sessionData))
        customStepFlow.initCustomSteps(sessionData.custom_steps || [])
        setTicketNumber(sessionData.ticket_number || '')
        setClientName(sessionData.client_name || '')
        setShowMetadataForm(false)
      }
    } catch (err) {
      setError('Failed to load tree')
      console.error(err)
    } finally {
      setIsLoading(false)
    }
  }

  const startSession = async () => {
    if (!tree) return
    setIsLoading(true)
    try {
      const newSession = await sessionsApi.create({
        tree_id: tree.id,
        ticket_number: ticketNumber || undefined,
        client_name: clientName || undefined,
      })
      setSession(newSession)
      analytics.sessionStarted({ session_id: newSession.id, flow_id: tree.id, flow_type: tree.tree_type })
      // Initialize currentNodeId to the tree's actual root (may not be 'root')
      const rootId = tree.tree_structure?.id || 'root'
      setCurrentNodeId(rootId)
      setPathTaken([rootId])
      setCurrentStepEnteredAt(newSession.started_at || new Date().toISOString())
      setShowMetadataForm(false)
      // Save for "Repeat Last Session"
      safeSetItem('last-session', JSON.stringify({
        tree_id: tree.id,
        tree_name: tree.name,
        client_name: clientName || '',
        ticket_number: ticketNumber || '',
      }))
    } catch (err) {
      setError('Failed to start session')
      console.error(err)
    } finally {
      setIsLoading(false)
    }
  }

  const handleSelectOption = async (_optionId: string, optionLabel: string, nextNodeId: string) => {
    if (!session || !tree || selectingOption) return

    setSelectingOption(_optionId)

    const node = findNode(currentNodeId, tree.tree_structure)
    if (!node) { setSelectingOption(null); return }

    const exitedAt = new Date().toISOString()
    const enteredAt = currentStepEnteredAt || session.started_at || exitedAt
    const newDecision: DecisionRecord = {
      node_id: currentNodeId,
      question: node.question || null,
      answer: optionLabel,
      action_performed: null,
      notes: notes || null,
      automation_used: false,
      timestamp: exitedAt,
      entered_at: enteredAt,
      exited_at: exitedAt,
      duration_seconds: calculateDurationSeconds(enteredAt, exitedAt),
      attachments: [],
    }

    const newPath = [...pathTaken, nextNodeId]
    const newDecisions = [...decisions, newDecision]

    setPathTaken(newPath)
    setDecisions(newDecisions)
    setCurrentNodeId(nextNodeId)
    setCurrentStepEnteredAt(exitedAt)
    setNotes('')
    setCommandOutput('')
    setCommandOutputOpen(false)

    try {
      await sessionsApi.update(session.id, {
        path_taken: newPath,
        decisions: newDecisions,
      })
    } catch (err) {
      console.error('Failed to update session:', err)
    } finally {
      setSelectingOption(null)
    }
  }

  const handleContinue = async (actionPerformed?: string) => {
    if (!session || !tree) return

    const node = findNode(currentNodeId, tree.tree_structure)
    if (!node || !node.next_node_id) return

    const exitedAt = new Date().toISOString()
    const enteredAt = currentStepEnteredAt || session.started_at || exitedAt
    const newDecision: DecisionRecord = {
      node_id: currentNodeId,
      question: null,
      answer: null,
      action_performed: actionPerformed || node.title || 'Action completed',
      notes: notes || null,
      command_output: commandOutput.trim() || null,
      automation_used: false,
      timestamp: exitedAt,
      entered_at: enteredAt,
      exited_at: exitedAt,
      duration_seconds: calculateDurationSeconds(enteredAt, exitedAt),
      attachments: [],
    }

    const newPath = [...pathTaken, node.next_node_id]
    const newDecisions = [...decisions, newDecision]

    setPathTaken(newPath)
    setDecisions(newDecisions)
    setCurrentNodeId(node.next_node_id)
    setCurrentStepEnteredAt(exitedAt)
    setNotes('')
    setCommandOutput('')
    setCommandOutputOpen(false)

    try {
      await sessionsApi.update(session.id, {
        path_taken: newPath,
        decisions: newDecisions,
      })
    } catch (err) {
      console.error('Failed to update session:', err)
    }
  }

  const handleComplete = async () => {
    if (!session || !tree) return
    const node = findNode(currentNodeId, tree.tree_structure)
    if (!node) return
    const completionDecision: DecisionRecord = {
      node_id: currentNodeId,
      question: null,
      answer: null,
      action_performed: node.title || 'Session completed',
      notes: notes || null,
      command_output: commandOutput.trim() || null,
      automation_used: false,
      timestamp: new Date().toISOString(),
      attachments: [],
    }
    openCompletionModal(completionDecision, 'standard')
  }

  const handleSubmitOutcome = async (data: { outcome: SessionOutcome; outcome_notes?: string; next_steps?: string }) => {
    if (!session) return
    setIsCompleting(true)
    setError(null)
    try {
      let finalDecisions = decisions
      if (pendingCompletionDecision) {
        finalDecisions = [...decisions, pendingCompletionDecision]
        setDecisions(finalDecisions)
        await sessionsApi.update(session.id, {
          decisions: finalDecisions,
        })
      }

      await sessionsApi.complete(session.id, data)
      analytics.sessionCompleted({ session_id: session.id, flow_type: tree?.tree_type || 'troubleshooting', outcome: data.outcome })

      setShowOutcomeModal(false)
      setPendingCompletionDecision(null)
      if (completionSource === 'custom' && customStepFlow.customSteps.length > 0) {
        customStepFlow.setShowForkModal(true)
      } else if (!hasBeenRated(session.id)) {
        setShowCsatModal(true)
      } else {
        navigate(`/sessions/${session.id}`)
      }
    } catch (err) {
      console.error('Failed to complete session:', err)
      setError('Failed to complete session. Check console for details.')
    } finally {
      setIsCompleting(false)
    }
  }

  const handleGoBack = () => {
    if (pathTaken.length <= 1) return
    const newPath = pathTaken.slice(0, -1)
    const removedDecision = decisions[decisions.length - 1]
    const newDecisions = decisions.slice(0, -1)
    setPathTaken(newPath)
    setDecisions(newDecisions)
    setCurrentNodeId(newPath[newPath.length - 1])
    setCurrentStepEnteredAt(new Date().toISOString())
    // Preload fields from the removed decision when revisiting
    const prevOutput = removedDecision?.command_output || ''
    setCommandOutput(prevOutput)
    setCommandOutputOpen(!!prevOutput)
  }

  const handleBreadcrumbJump = (nodeId: string, index: number) => {
    setPathTaken(prev => prev.slice(0, index + 1))
    setDecisions(prev => prev.slice(0, index))
    setCurrentNodeId(nodeId)
    setCurrentStepEnteredAt(new Date().toISOString())
    setNotes('')
    setCommandOutput('')
    setCommandOutputOpen(false)
  }

  // Compute current node for keyboard shortcuts (must be before any returns for hooks rules)
  const currentNode = tree ? findNode(currentNodeId, tree.tree_structure) : null
  const currentCustomStep = customStepFlow.findCustomStep(currentNodeId)
  const currentOptions = currentNode?.options || []

  // Keyboard shortcuts - must be called unconditionally (React hooks rules)
  useTreeNavigationShortcuts({
    onSelectOption: (index) => {
      const option = currentOptions[index]
      if (option && session && tree && !selectingOption) {
        handleSelectOption(option.id, option.label, option.next_node_id)
      }
    },
    onGoBack: handleGoBack,
    onShowShortcuts: () => setShortcutsModalOpen(true),
    onContinue: () => {
      if (currentNode?.type === 'action' && currentNode.next_node_id) {
        handleContinue()
      } else if (currentNode?.type === 'solution') {
        handleComplete()
      }
    },
    optionCount: currentOptions.length,
    canGoBack: pathTaken.length > 1 && !showMetadataForm && !isLoading && !selectingOption,
    canContinue: !showMetadataForm && !isLoading && !selectingOption && (currentNode?.type === 'action' || currentNode?.type === 'solution'),
  })

  if (isLoading) {
    return (
      <div className="flex h-64 items-center justify-center">
        <Spinner />
      </div>
    )
  }

  if (error || !tree) {
    return (
      <div className="container mx-auto px-4 py-8">
        <div className="rounded-md bg-red-400/10 p-4 text-red-400">
          {error || 'Tree not found'}
        </div>
        <button
          onClick={() => navigate('/trees')}
          className="mt-4 text-muted-foreground hover:text-foreground hover:underline"
        >
          Back to trees
        </button>
      </div>
    )
  }

  // Session metadata form
  if (showMetadataForm) {
    return (
      <div className="container mx-auto max-w-lg px-4 py-8">
        <h1 className="mb-2 text-2xl font-bold font-heading text-foreground">{tree.name}</h1>
        <p className="mb-6 text-muted-foreground">{tree.description}</p>

        <div className="bg-card border border-border rounded-xl space-y-4 p-6">
          <h2 className="font-semibold text-foreground">Session Details</h2>
          <p className="text-sm text-muted-foreground">
            Optional: Add ticket and client info for easier tracking
          </p>

          <div>
            <label className="block text-sm font-medium text-foreground">
              Ticket Number
            </label>
            <input
              type="text"
              value={ticketNumber}
              onChange={(e) => setTicketNumber(e.target.value)}
              placeholder="e.g., INC0012345"
              className={cn(
                'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
                'text-foreground placeholder:text-muted-foreground',
                'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
              )}
            />
          </div>

          <div>
            <label className="block text-sm font-medium text-foreground">
              Client Name
            </label>
            <input
              type="text"
              value={clientName}
              onChange={(e) => setClientName(e.target.value)}
              placeholder="e.g., Acme Corp"
              className={cn(
                'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
                'text-foreground placeholder:text-muted-foreground',
                'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
              )}
            />
          </div>

          <Button onClick={startSession} className="w-full">
            Start Troubleshooting
          </Button>
        </div>
      </div>
    )
  }

  if (!currentNode && !currentCustomStep) {
    return (
      <div className="container mx-auto px-4 py-8">
        <div className="rounded-md bg-red-400/10 p-4 text-red-400">
          Invalid tree structure
        </div>
      </div>
    )
  }

  return (
    <div className="h-full flex overflow-hidden">
      {/* Main Content */}
      <div className={cn('h-full flex-1 min-w-0 overflow-y-auto px-4 py-8 transition-[padding] duration-200', scratchpadOpen && 'sm:pr-[440px]')}>
      <div className="mx-auto max-w-4xl">
      {/* Header */}
      <div className="mb-6 flex items-center justify-between">
        <div>
          <div className="flex items-center gap-3">
            <h1 className="text-xl font-bold font-heading text-foreground">{tree.name}</h1>
            {timerDisplay && (
              <span className="flex items-center gap-1.5 rounded-full bg-accent px-2 py-0.5 text-[0.6875rem] font-sans text-xs text-muted-foreground">
                <Clock className="h-4 w-4" />
                {timerDisplay}
              </span>
            )}
            <button
              type="button"
              onClick={() => setShortcutsModalOpen(true)}
              className="flex items-center justify-center rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
              title="Keyboard shortcuts"
            >
              <HelpCircle className="h-4 w-4" />
            </button>
          </div>
          {(ticketNumber || clientName) && (
            <p className="text-sm text-muted-foreground">
              {ticketNumber && `Ticket: ${ticketNumber}`}
              {ticketNumber && clientName && ' · '}
              {clientName && `Client: ${clientName}`}
            </p>
          )}
          {session && (
            <TicketLinkIndicator
              session={session}
              hasConnection={hasConnection}
              onLinkClick={() => setShowTicketPicker(true)}
              onUnlink={handleTicketUnlink}
              onUpdateClick={session.psa_ticket_id ? () => setShowUpdateModal(true) : undefined}
              ticketInfo={ticketInfo}
            />
          )}
        </div>
        <div className="flex items-center gap-2">
          {/* Share Progress Popover */}
          <div className="relative" ref={sharePopoverRef}>
            <button
              onClick={() => setShowSharePopover(!showSharePopover)}
              className={cn(
                'flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground',
                'hover:bg-accent hover:text-foreground transition-colors'
              )}
            >
              <Copy className="h-3.5 w-3.5" />
              Share Progress
              <ChevronDown className="h-3 w-3" />
            </button>
            {showSharePopover && (
              <div className="absolute right-0 mt-1 z-50 bg-card border border-border rounded-xl p-1 min-w-[220px]">
                {/* Copy Progress Summary */}
                <button
                  onClick={() => {
                    handleCopyForTicket()
                    setShowSharePopover(false)
                  }}
                  disabled={isCopyingForTicket}
                  className={cn(
                    'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground w-full text-left cursor-pointer transition-colors',
                    'hover:bg-accent hover:text-foreground disabled:opacity-50'
                  )}
                >
                  {copiedForTicket ? <Check className="h-4 w-4 text-emerald-400" /> : <Clipboard className="h-4 w-4" />}
                  {copiedForTicket ? 'Copied!' : 'Copy Progress Summary'}
                </button>
                {/* Copy Share Link */}
                <button
                  onClick={() => {
                    handleCopyShareLink()
                    setShowSharePopover(false)
                  }}
                  disabled={isCopyingShareLink}
                  className={cn(
                    'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground w-full text-left cursor-pointer transition-colors',
                    'hover:bg-accent hover:text-foreground disabled:opacity-50'
                  )}
                >
                  {copiedShareLink ? <Check className="h-4 w-4 text-emerald-400" /> : <Link2 className="h-4 w-4" />}
                  {isCopyingShareLink ? 'Loading...' : copiedShareLink ? 'Copied!' : 'Copy Share Link'}
                </button>
                {/* Divider */}
                <div className="border-t border-border my-1" />
                {/* Manage Share Links */}
                <button
                  onClick={() => {
                    setShowSharePopover(false)
                    setShowShareModal(true)
                  }}
                  className={cn(
                    'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground w-full text-left cursor-pointer transition-colors',
                    'hover:bg-accent hover:text-foreground'
                  )}
                >
                  <Settings className="h-4 w-4" />
                  Manage Share Links...
                </button>
              </div>
            )}
          </div>
          <button
            onClick={() => navigate('/sessions')}
            className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
          >
            Exit
          </button>
        </div>
      </div>

      {/* PSA Ticket Context Panel */}
      {session?.psa_ticket_id && (
        <div className="mb-6">
          <TicketContextPanel
            context={ticketContext}
            loading={ticketContextLoading}
            error={ticketContextError}
            onRefresh={refreshTicketContext}
          />
        </div>
      )}

      {/* Breadcrumb */}
      <div className="mb-6 flex items-center gap-2 overflow-x-auto text-sm">
        {pathTaken.map((nodeId, index) => {
          const node = findNode(nodeId, tree?.tree_structure)
          const customStep = customStepFlow.findCustomStep(nodeId)
          const label = node?.question || node?.title || customStep?.step_data.title || nodeId
          const truncatedLabel = label.length > 30 ? `${label.slice(0, 30)}...` : label
          return (
            <span key={nodeId} className="flex items-center gap-2 whitespace-nowrap">
              {index > 0 && <span className="text-muted-foreground">→</span>}
              {index < pathTaken.length - 1 ? (
                <button
                  type="button"
                  onClick={() => handleBreadcrumbJump(nodeId, index)}
                  className="text-muted-foreground hover:text-foreground hover:underline"
                >
                  {truncatedLabel}
                </button>
              ) : (
                <span className="font-medium text-foreground">
                  {truncatedLabel}
                </span>
              )}
            </span>
          )
        })}
      </div>

      {/* Current Node */}
      <div className="bg-card border border-border rounded-xl p-6 shadow-xs">
        {/* Answer placeholder guard */}
        {currentNode && currentNode.type === 'answer' && (
          <div className="rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-6 text-center">
            <p className="text-sm font-medium text-yellow-400">
              This tree contains an unresolved placeholder node. Please contact the tree author to complete it before use.
            </p>
          </div>
        )}

        {/* Decision Node */}
        {currentNode && currentNode.type === 'decision' && (
          <>
            <h2 className="mb-2 text-xl font-semibold font-heading text-foreground">
              {currentNode.question}
            </h2>
            {currentNode.help_text && (
              <div className="mb-4 text-sm text-muted-foreground">
                <MarkdownContent content={currentNode.help_text} />
              </div>
            )}
            <div className="mb-4 space-y-2">
              {currentNode.options?.map((option, index) => (
                <button
                  key={option.id}
                  onClick={() => handleSelectOption(option.id, option.label, option.next_node_id)}
                  disabled={!!selectingOption}
                  className={cn(
                    'w-full rounded-md border border-border p-3 text-left text-foreground transition-colors',
                    'hover:border-primary/30 hover:bg-accent',
                    'flex items-center gap-3',
                    selectingOption && selectingOption !== option.id && 'opacity-50 pointer-events-none'
                  )}
                >
                  {index < 9 && (
                    selectingOption === option.id ? (
                      <span className="flex h-6 w-6 shrink-0 items-center justify-center">
                        <Spinner size="sm" className="h-4 w-4 border-t-foreground" />
                      </span>
                    ) : (
                      <span className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-accent text-xs font-medium text-muted-foreground">
                        {index + 1}
                      </span>
                    )
                  )}
                  <span>{option.label}</span>
                </button>
              ))}
            </div>
            {/* Previously-created custom steps at this node */}
            {customStepFlow.customSteps.filter(cs => cs.inserted_after_node_id === currentNodeId).length > 0 && (
              <div className="mt-2 space-y-2">
                <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
                  Your Custom Steps
                </p>
                {customStepFlow.customSteps
                  .filter(cs => cs.inserted_after_node_id === currentNodeId)
                  .map(cs => (
                    <button
                      type="button"
                      key={cs.id}
                      onClick={() => customStepFlow.handleNavigateToCustomStep(cs)}
                      className={cn(
                        'w-full rounded-md border border-primary/30 bg-accent-dim p-3 text-left text-foreground transition-colors',
                        'hover:border-primary/50 hover:bg-primary/20',
                        'flex items-center gap-3'
                      )}
                    >
                      <span className="shrink-0 rounded-full bg-primary/20 px-2 py-0.5 text-xs font-medium text-primary">
                        Custom
                      </span>
                      <span>{cs.step_data.title}</span>
                    </button>
                  ))}
              </div>
            )}

            {/* Add Custom Step Button */}
            <button
              onClick={() => customStepFlow.setShowCustomStepModal(true)}
              className="mt-2 inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
            >
              <Plus className="h-3.5 w-3.5" />
              Add Custom Step
            </button>
          </>
        )}

        {/* Custom Step Node */}
        {currentCustomStep && (
          <div className="rounded-lg border border-primary/30 bg-accent-dim p-4">
            {/* Custom Step Badge */}
            <span className="mb-2 inline-block rounded-full bg-primary/20 px-2 py-1 text-xs font-medium text-primary">
              Custom Step
            </span>

            <h2 className="mb-2 text-xl font-semibold font-heading text-foreground">
              {currentCustomStep.step_data.title}
            </h2>

            {currentCustomStep.step_data.content.instructions && (
              <div className="mb-4 text-muted-foreground">
                <MarkdownContent content={currentCustomStep.step_data.content.instructions} />
              </div>
            )}

            {currentCustomStep.step_data.content.help_text && (
              <div className="mb-4 rounded bg-blue-500/10 p-3 text-sm">
                <MarkdownContent content={currentCustomStep.step_data.content.help_text} />
              </div>
            )}

            {currentCustomStep.step_data.content.commands && currentCustomStep.step_data.content.commands.length > 0 && (
              <div className="mb-4">
                <p className="mb-2 text-sm font-medium text-foreground">Commands:</p>
                <div className="space-y-2">
                  {currentCustomStep.step_data.content.commands.map((cmd, index) => (
                    <div key={index}>
                      <p className="mb-1 text-xs text-muted-foreground">{cmd.label}</p>
                      <div className="group relative">
                        <code className="block rounded bg-code p-2 pr-8 text-sm font-mono">
                          {cmd.command}
                        </code>
                        <button
                          type="button"
                          onClick={() => handleCopyCommand(cmd.command)}
                          className="absolute right-1.5 top-1.5 opacity-0 transition-opacity group-hover:opacity-100"
                          title="Copy command"
                        >
                          {copiedCommand === cmd.command ? (
                            <Check className="h-3.5 w-3.5 text-green-400" />
                          ) : (
                            <Clipboard className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
                          )}
                        </button>
                      </div>
                    </div>
                  ))}
                </div>
                {/* Command Output Capture */}
                <div className="mt-3">
                  <button
                    type="button"
                    onClick={() => setCommandOutputOpen(!commandOutputOpen)}
                    className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
                  >
                    <Terminal className="h-3.5 w-3.5" />
                    <span>Paste Output (Optional)</span>
                    <span className="text-xs">{commandOutputOpen ? '▾' : '▸'}</span>
                  </button>
                  {commandOutputOpen && (
                    <div className="mt-2">
                      <textarea
                        value={commandOutput}
                        onChange={(e) => setCommandOutput(e.target.value.slice(0, 10000))}
                        placeholder="Paste command output here..."
                        rows={4}
                        maxLength={10000}
                        className={cn(
                          'block w-full rounded-md border border-border bg-accent px-3 py-2',
                          'font-mono text-sm text-foreground placeholder:text-muted-foreground',
                          'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
                        )}
                      />
                      <p className="mt-1 text-right text-xs text-muted-foreground">
                        {commandOutput.length.toLocaleString()} / 10,000
                      </p>
                    </div>
                  )}
                </div>
              </div>
            )}

            {/* Continue to selected descendant */}
            {customStepFlow.pendingContinuationNodeId && !customStepFlow.customBranchMode && (() => {
              const targetNode = findNode(customStepFlow.pendingContinuationNodeId, tree?.tree_structure)
              const targetLabel = targetNode?.question || targetNode?.title || 'next step'
              return (
                <div className="mt-6 border-t border-primary/30 pt-4">
                  <Button
                    onClick={handleCustomContinueToDescendant}
                    className="w-full justify-between"
                    size="lg"
                  >
                    <span>Continue to: {targetLabel.length > 50 ? `${targetLabel.slice(0, 50)}...` : targetLabel}</span>
                    <ArrowRight className="h-4 w-4 shrink-0" />
                  </Button>
                </div>
              )
            })()}

            {/* Custom Branch Controls */}
            {customStepFlow.customBranchMode && (
              <div className="mt-6 border-t border-primary/30 pt-4">
                <p className="mb-3 text-sm text-amber-400">
                  Building custom branch - add steps until the issue is resolved
                </p>
                <div className="flex flex-wrap gap-3">
                  <button
                    onClick={() => customStepFlow.setShowCustomStepModal(true)}
                    className={cn(
                      'flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
                      'hover:bg-accent hover:text-foreground'
                    )}
                  >
                    <Plus className="h-4 w-4" />
                    Add Another Step
                  </button>
                  <button
                    onClick={handleCustomBranchCompleteWithOutput}
                    disabled={isCompleting}
                    className={cn(
                      'flex items-center gap-2 rounded-md bg-green-600 px-4 py-2 text-sm font-medium text-white',
                      'hover:bg-green-700 disabled:opacity-50'
                    )}
                  >
                    <CheckCircle className="h-4 w-4" />
                    {isCompleting ? 'Completing...' : 'This Solves My Issue'}
                  </button>
                </div>
              </div>
            )}
          </div>
        )}

        {/* Action Node */}
        {currentNode && currentNode.type === 'action' && (
          <>
            <h2 className="mb-2 text-xl font-semibold font-heading text-foreground">
              {currentNode.title}
            </h2>
            {currentNode.description && (
              <div className="mb-4 text-muted-foreground">
                <MarkdownContent content={currentNode.description} />
              </div>
            )}
            {currentNode.commands && currentNode.commands.length > 0 && (
              <div className="mb-4">
                <p className="mb-2 text-sm font-medium text-foreground">Commands:</p>
                <div className="space-y-1">
                  {currentNode.commands.map((cmd, index) => (
                    <div key={index} className="group relative">
                      <code className="block rounded bg-code p-2 pr-8 text-sm font-mono">
                        {cmd}
                      </code>
                      <button
                        type="button"
                        onClick={() => handleCopyCommand(cmd)}
                        className="absolute right-1.5 top-1.5 opacity-0 transition-opacity group-hover:opacity-100"
                        title="Copy command"
                      >
                        {copiedCommand === cmd ? (
                          <Check className="h-3.5 w-3.5 text-green-400" />
                        ) : (
                          <Clipboard className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
                        )}
                      </button>
                    </div>
                  ))}
                </div>
                {/* Command Output Capture */}
                <div className="mt-3">
                  <button
                    type="button"
                    onClick={() => setCommandOutputOpen(!commandOutputOpen)}
                    className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
                  >
                    <Terminal className="h-3.5 w-3.5" />
                    <span>Paste Output (Optional)</span>
                    <span className="text-xs">{commandOutputOpen ? '▾' : '▸'}</span>
                  </button>
                  {commandOutputOpen && (
                    <div className="mt-2">
                      <textarea
                        value={commandOutput}
                        onChange={(e) => setCommandOutput(e.target.value.slice(0, 10000))}
                        placeholder="Paste command output here..."
                        rows={4}
                        maxLength={10000}
                        className={cn(
                          'block w-full rounded-md border border-border bg-accent px-3 py-2',
                          'font-mono text-sm text-foreground placeholder:text-muted-foreground',
                          'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
                        )}
                      />
                      <p className="mt-1 text-right text-xs text-muted-foreground">
                        {commandOutput.length.toLocaleString()} / 10,000
                      </p>
                    </div>
                  )}
                </div>
              </div>
            )}
            {currentNode.expected_outcome && (
              <p className="mb-4 text-sm text-muted-foreground">
                <strong>Expected outcome:</strong> {currentNode.expected_outcome}
              </p>
            )}
            {currentNode.next_node_id && (
              <Button onClick={() => handleContinue()}>
                Continue
              </Button>
            )}
          </>
        )}

        {/* Solution Node */}
        {currentNode && currentNode.type === 'solution' && (
          <>
            <div className="mb-4 flex items-center gap-2">
              <span className="rounded-full bg-green-900/30 px-2 py-1 text-xs font-medium text-green-400">
                Solution
              </span>
            </div>
            <h2 className="mb-2 text-xl font-semibold font-heading text-foreground">
              {currentNode.title}
            </h2>
            {currentNode.description && (
              <div className="mb-4 text-muted-foreground">
                <MarkdownContent content={currentNode.description} />
              </div>
            )}
            {currentNode.resolution_steps && currentNode.resolution_steps.length > 0 && (
              <div className="mb-4">
                <p className="mb-2 text-sm font-medium text-foreground">Resolution steps:</p>
                <ol className="list-inside list-decimal space-y-1 text-sm text-muted-foreground">
                  {currentNode.resolution_steps.map((step, index) => (
                    <li key={index}>{step}</li>
                  ))}
                </ol>
              </div>
            )}
            <button
              onClick={handleComplete}
              disabled={isCompleting}
              className={cn(
                'rounded-md bg-green-600 px-4 py-2 text-sm font-medium text-white',
                'hover:bg-green-700 disabled:opacity-50'
              )}
            >
              {isCompleting ? 'Completing...' : 'Complete Session'}
            </button>
          </>
        )}

        {/* Step Feedback */}
        {session && (currentNode || currentCustomStep) && (
          <div className="mt-3 flex justify-end border-t border-border pt-3">
            <StepFeedback stepId={currentCustomStep?.id || currentNodeId} sessionId={session.id} />
          </div>
        )}

        {/* Notes */}
        <div className="mt-6 border-t border-border pt-4">
          <label className="block text-sm font-medium text-foreground">
            Notes (optional)
          </label>
          <textarea
            id="session-notes"
            value={notes}
            onChange={(e) => setNotes(e.target.value)}
            placeholder="Add any notes for this step..."
            rows={2}
            className={cn(
              'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
              'text-foreground placeholder:text-muted-foreground',
              'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
            )}
          />
        </div>

        {/* Supporting Data */}
        {session && (
          <div className="mt-4">
            <SupportingDataPanel sessionId={session.id} />
          </div>
        )}

        {/* Back Button */}
        {pathTaken.length > 1 && (
          <button
            onClick={handleGoBack}
            className="mt-4 text-sm text-muted-foreground hover:text-foreground"
          >
            ← Go back <span className="text-xs text-muted-foreground">[Esc]</span>
          </button>
        )}

        {/* Keyboard Shortcuts Hint */}
        {currentNode && (
          <div className="mt-4 border-t border-border pt-3 text-xs text-muted-foreground">
            <span className="font-medium">Keyboard:</span>{' '}
            {currentNode.type === 'decision' && currentOptions.length > 0 && (
              <span>1-{Math.min(currentOptions.length, 9)} select option</span>
            )}
            {pathTaken.length > 1 && <span>, Esc go back</span>}
            {(currentNode.type === 'action' || currentNode.type === 'solution') && (
              <span>, Enter {currentNode.type === 'solution' ? 'complete' : 'continue'}</span>
            )}
            <span>, Tab notes</span>
          </div>
        )}
      </div>

      {/* Custom Step Modal */}
      <CustomStepModal
        isOpen={customStepFlow.showCustomStepModal}
        onClose={() => customStepFlow.setShowCustomStepModal(false)}
        onInsertStep={customStepFlow.handleStepCreated}
      />

      {/* Post Step Action Modal */}
      {customStepFlow.pendingStep && (
        <PostStepActionModal
          isOpen={customStepFlow.showPostStepModal}
          onClose={customStepFlow.resetPendingStep}
          step={customStepFlow.pendingStep}
          onSaveForLater={customStepFlow.handleSaveForLater}
          onUseNow={customStepFlow.handleUseNow}
          onBoth={customStepFlow.handleBoth}
          isFromLibrary={customStepFlow.pendingStepIsFromLibrary}
          isSaving={customStepFlow.isSavingStep}
        />
      )}

      {/* Continuation Modal */}
      <ContinuationModal
        isOpen={customStepFlow.showContinuationModal}
        onClose={() => customStepFlow.setShowContinuationModal(false)}
        descendantNodes={customStepFlow.branchOriginNodeId ? customStepFlow.getDescendantNodes(customStepFlow.branchOriginNodeId) : []}
        onSelectNode={customStepFlow.handleSelectDescendant}
        onBuildCustomBranch={customStepFlow.handleBuildCustomBranch}
      />

      {/* Fork Tree Modal */}
      <ForkTreeModal
        isOpen={customStepFlow.showForkModal}
        onClose={() => customStepFlow.setShowForkModal(false)}
        originalTreeName={tree?.name || 'Tree'}
        onFork={customStepFlow.handleForkTree}
        onSkip={customStepFlow.handleSkipFork}
      />

      <SessionOutcomeModal
        isOpen={showOutcomeModal}
        onClose={closeOutcomeModal}
        onSubmit={handleSubmitOutcome}
        isSubmitting={isCompleting}
      />

      {session && (
        <CSATModal
          isOpen={showCsatModal}
          onClose={handleCsatClose}
          sessionId={session.id}
        />
      )}

      {/* Keyboard Shortcuts Modal */}
      <Modal
        isOpen={shortcutsModalOpen}
        onClose={() => setShortcutsModalOpen(false)}
        title="Keyboard Shortcuts"
        size="sm"
      >
        <div className="space-y-3 text-sm">
          <div className="flex items-center justify-between">
            <span className="text-muted-foreground">Select option</span>
            <span className="rounded bg-white/[0.08] px-2 py-0.5 font-mono text-xs text-foreground">1-9</span>
          </div>
          <div className="flex items-center justify-between">
            <span className="text-muted-foreground">Go back</span>
            <span className="rounded bg-white/[0.08] px-2 py-0.5 font-mono text-xs text-foreground">Esc</span>
          </div>
          <div className="flex items-center justify-between">
            <span className="text-muted-foreground">Continue / Complete</span>
            <span className="rounded bg-white/[0.08] px-2 py-0.5 font-mono text-xs text-foreground">Enter</span>
          </div>
          <div className="flex items-center justify-between">
            <span className="text-muted-foreground">Focus notes</span>
            <span className="rounded bg-white/[0.08] px-2 py-0.5 font-mono text-xs text-foreground">Tab</span>
          </div>
          <div className="flex items-center justify-between">
            <span className="text-muted-foreground">Show shortcuts</span>
            <span className="rounded bg-white/[0.08] px-2 py-0.5 font-mono text-xs text-foreground">?</span>
          </div>
        </div>
      </Modal>

      {/* Share Session Modal */}
      {session && (
        <ShareSessionModal
          sessionId={session.id}
          sessionLabel={ticketNumber || tree?.name || 'Session'}
          isOpen={showShareModal}
          onClose={() => setShowShareModal(false)}
        />
      )}

      {/* Ticket Picker Modal */}
      {session && (
        <TicketPickerModal
          open={showTicketPicker}
          onClose={() => setShowTicketPicker(false)}
          sessionId={session.id}
          onLinked={handleTicketLinked}
        />
      )}
      {session && (
        <UpdateTicketModal
          open={showUpdateModal}
          onClose={() => setShowUpdateModal(false)}
          sessionId={session.id}
        />
      )}
      </div>
      </div>

      {/* AI Copilot - in-flow panel */}
      {treeId && copilotOpen && (
        <CopilotPanel
          isOpen={copilotOpen}
          onClose={() => setCopilotOpen(false)}
          treeId={treeId}
          sessionId={session?.id}
          currentNodeId={currentNodeId}
        />
      )}

      {/* Scratchpad Sidebar */}
      {session && (
        <ScratchpadSidebar
          sessionId={session.id}
          initialContent={session.scratchpad ?? ''}
          onSave={handleScratchpadSave}
          onOpenChange={setScratchpadOpen}
        />
      )}

      {/* AI Copilot toggle button */}
      {treeId && (
        <CopilotToggle isOpen={copilotOpen} onToggle={() => setCopilotOpen(true)} />
      )}
    </div>
  )
}

export default TreeNavigationPage
