import { useState, useCallback } from 'react' import { aiSessionsApi } from '@/api' import type { AISessionCreateRequest, AISessionCreateResponse, AISessionDetail, AISessionStepResponse, StepResponseRequest, StepResponseResponse, ResolveSessionRequest, EscalateSessionRequest, SessionDocumentation, StatusUpdateRequest, StatusUpdateResponse, } from '@/types/ai-session' import { toast } from '@/lib/toast' export interface UseFlowPilotSession { // State session: AISessionDetail | null currentStep: AISessionStepResponse | null allSteps: AISessionStepResponse[] isLoading: boolean isProcessing: boolean error: string | null // Actions startSession: (intake: AISessionCreateRequest) => Promise respondToStep: (response: StepResponseRequest) => Promise resolveSession: (data: ResolveSessionRequest) => Promise escalateSession: (data: EscalateSessionRequest) => Promise pauseSession: () => Promise resumeOwnSession: () => Promise abandonSession: () => Promise rateSession: (rating: number, feedback?: string) => Promise generateStatusUpdate: (data: StatusUpdateRequest) => Promise loadSession: (sessionId: string) => Promise // Derived isActive: boolean canResolve: boolean canEscalate: boolean // Post-close documentation: SessionDocumentation | null psaPushStatus: string | null psaPushError: string | null memberMappingWarning: string | null } export function useFlowPilotSession(): UseFlowPilotSession { const [session, setSession] = useState(null) const [currentStep, setCurrentStep] = useState(null) const [allSteps, setAllSteps] = useState([]) const [isLoading, setIsLoading] = useState(false) const [isProcessing, setIsProcessing] = useState(false) const [error, setError] = useState(null) const [documentation, setDocumentation] = useState(null) const [psaPushStatus, setPsaPushStatus] = useState(null) const [psaPushError, setPsaPushError] = useState(null) const [memberMappingWarning, setMemberMappingWarning] = useState(null) const startSession = useCallback(async (intake: AISessionCreateRequest) => { setIsLoading(true) setError(null) try { const result: AISessionCreateResponse = await aiSessionsApi.createSession(intake) const firstStep = result.first_step setSession({ id: result.session_id, session_type: 'guided', title: null, status: result.status, intake_type: intake.intake_type, intake_content: intake.intake_content, problem_summary: result.problem_summary, problem_domain: result.problem_domain, confidence_tier: result.confidence_tier, step_count: 1, session_rating: null, created_at: new Date().toISOString(), resolved_at: null, matched_flow_id: result.matched_flow_id, match_score: result.match_score, resolution_summary: null, resolution_action: null, escalation_reason: null, session_feedback: null, psa_ticket_id: intake.psa_ticket_id ?? null, psa_connection_id: intake.psa_connection_id ?? null, ticket_data: null, steps: [firstStep], conversation_messages: [], pending_task_lane: null, is_branching: false, active_branch_id: null, }) setAllSteps([firstStep]) setCurrentStep(firstStep) } catch (e: unknown) { // Prefer the backend's detail message over the generic axios status string const axiosErr = e as { response?: { status?: number; data?: { detail?: unknown } } } const detail = axiosErr?.response?.data?.detail const message = typeof detail === 'string' ? detail : (e instanceof Error ? e.message : 'Failed to start session') setError(message) // Global axios interceptor already shows a toast for 5xx — skip duplicate const status = axiosErr?.response?.status if (!status || status < 500) { toast.error(message) } } finally { setIsLoading(false) } }, []) const respondToStep = useCallback(async (response: StepResponseRequest) => { if (!session) return setIsProcessing(true) setError(null) try { const result: StepResponseResponse = await aiSessionsApi.respondToStep(session.id, response) setSession(prev => prev ? { ...prev, status: result.status, confidence_tier: result.confidence_tier, step_count: prev.step_count + 1, } : null) if (result.next_step) { setAllSteps(prev => [...prev, result.next_step!]) setCurrentStep(result.next_step) } } catch (e: unknown) { const message = e instanceof Error ? e.message : 'Failed to process response' setError(message) toast.error(message) } finally { setIsProcessing(false) } }, [session]) const resolveSession = useCallback(async (data: ResolveSessionRequest): Promise => { if (!session) throw new Error('No active session') setIsProcessing(true) try { const result = await aiSessionsApi.resolveSession(session.id, data) setSession(prev => prev ? { ...prev, status: 'resolved' } : null) setDocumentation(result.documentation) setPsaPushStatus(result.psa_push_status) setPsaPushError(result.psa_push_error) setMemberMappingWarning(result.member_mapping_warning) setCurrentStep(null) toast.success('Session resolved') return result.documentation } catch (e: unknown) { const message = e instanceof Error ? e.message : 'Failed to resolve session' toast.error(message) throw e } finally { setIsProcessing(false) } }, [session]) const escalateSession = useCallback(async (data: EscalateSessionRequest): Promise => { if (!session) throw new Error('No active session') setIsProcessing(true) try { const result = await aiSessionsApi.escalateSession(session.id, data) setSession(prev => prev ? { ...prev, status: 'requesting_escalation' } : null) setDocumentation(result.documentation) setPsaPushStatus(result.psa_push_status) setPsaPushError(result.psa_push_error) setMemberMappingWarning(result.member_mapping_warning) setCurrentStep(null) toast.success('Session escalated') return result.documentation } catch (e: unknown) { const message = e instanceof Error ? e.message : 'Failed to escalate session' toast.error(message) throw e } finally { setIsProcessing(false) } }, [session]) const pauseSession = useCallback(async () => { if (!session) return try { await aiSessionsApi.pauseSession(session.id) setSession(prev => prev ? { ...prev, status: 'paused' } : null) toast.success('Session paused') } catch { toast.error('Failed to pause session') } }, [session]) const resumeOwnSession = useCallback(async () => { if (!session) return try { await aiSessionsApi.resumeSession(session.id) setSession(prev => prev ? { ...prev, status: 'active' } : null) toast.success('Session resumed') } catch { toast.error('Failed to resume session') } }, [session]) const abandonSession = useCallback(async () => { if (!session) return try { await aiSessionsApi.abandonSession(session.id) setSession(prev => prev ? { ...prev, status: 'abandoned' } : null) setCurrentStep(null) toast.success('Session closed') } catch { toast.error('Failed to close session') } }, [session]) const rateSession = useCallback(async (rating: number, feedback?: string) => { if (!session) return try { await aiSessionsApi.rateSession(session.id, { rating, feedback }) setSession(prev => prev ? { ...prev, session_rating: rating } : null) toast.success('Thanks for your feedback!') } catch { toast.error('Failed to submit rating') } }, [session]) const generateStatusUpdate = useCallback(async (data: StatusUpdateRequest): Promise => { if (!session) throw new Error('No active session') try { return await aiSessionsApi.generateStatusUpdate(session.id, data) } catch (e: unknown) { const message = e instanceof Error ? e.message : 'Failed to generate status update' toast.error(message) throw e } }, [session]) const loadSession = useCallback(async (sessionId: string) => { setIsLoading(true) setError(null) try { const detail = await aiSessionsApi.getSession(sessionId) setSession(detail) setAllSteps(detail.steps) // Set current step for active and paused sessions (paused can be resumed) setCurrentStep( detail.status === 'active' || detail.status === 'paused' ? detail.steps[detail.steps.length - 1] ?? null : null ) if (detail.status === 'resolved' || detail.status === 'escalated') { const doc = await aiSessionsApi.getDocumentation(sessionId) setDocumentation(doc) } } catch (e: unknown) { const message = e instanceof Error ? e.message : 'Failed to load session' setError(message) toast.error(message) } finally { setIsLoading(false) } }, []) const isActive = session?.status === 'active' const canResolve = isActive && allSteps.length >= 1 const canEscalate = isActive && allSteps.length >= 1 return { session, currentStep, allSteps, isLoading, isProcessing, error, startSession, respondToStep, resolveSession, escalateSession, pauseSession, resumeOwnSession, abandonSession, rateSession, generateStatusUpdate, loadSession, isActive, canResolve, canEscalate, documentation, psaPushStatus, psaPushError, memberMappingWarning, } }