import { WebSocket } from 'partysocket';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner';
import {
    RateLimitExceededError,
	MAX_AGENT_QUERY_LENGTH,
	type BlueprintType,
	type WebSocketMessage,
	type CodeFixEdits,
	type ImageAttachment,
	type ProjectType,
	type BehaviorType,
	type FileType,
	type TemplateDetails,
	type CloudflareDeploymentErrorCode,
	getBehaviorTypeForProject,
} from '@/api-types';
import {
	createRepairingJSONParser,
	ndjsonStream,
} from '@/utils/ndjson-parser/ndjson-parser';
import { getFileType } from '@/utils/string';
import { logger } from '@/utils/logger';
import { mergeFiles } from '@/utils/file-helpers';
import { apiClient } from '@/lib/api-client';
import { appEvents } from '@/lib/app-events';
import { createWebSocketMessageHandler, type HandleMessageDeps, type BackendErrorDialogState } from '../utils/handle-websocket-message';
import { isConversationalMessage, addOrUpdateMessage, createUserMessage, handleRateLimitError, createAIMessage, type ChatMessage, type ClarifyingQuestion } from '../utils/message-helpers';
import { sendWebSocketMessage } from '../utils/websocket-helpers';
import { initialStages as defaultStages, updateStage as updateStageHelper } from '../utils/project-stage-helpers';
import type { ProjectStage } from '../utils/project-stage-helpers';
import { useLimitsContext } from '@/contexts/limits-context';

export type Edit = Omit<CodeFixEdits, 'type'>;

// New interface for phase timeline tracking
export interface PhaseTimelineItem {
	id: string;
	name: string;
	description: string;
	files: {
		path: string;
		purpose: string;
		status: 'generating' | 'completed' | 'error' | 'validating' | 'cancelled';
		contents?: string;
	}[];
	status: 'generating' | 'completed' | 'error' | 'validating' | 'cancelled';
	timestamp: number;
}

export function useChat({
	chatId: urlChatId,
	query: userQuery,
	images: userImages,
	projectType = 'app',
	behaviorType: explicitBehaviorType,
	autoStart = true,
	onDebugMessage,
	onTerminalMessage,
	onCloudflareDeployGate,
}: {
	chatId?: string;
	query: string | null;
	images?: ImageAttachment[];
	projectType?: ProjectType;
	behaviorType?: BehaviorType;
	/**
	 * Whether a brand-new session may be created automatically on mount.
	 * In-app navigation (e.g. the home prompt box) sets this true. Sessions
	 * opened from an external/pasted link leave it false so the query — which
	 * is interpolated into the LLM system prompt — requires an explicit user
	 * gesture before it runs, preventing zero-click prompt injection.
	 */
	autoStart?: boolean;
	onDebugMessage?: (type: 'error' | 'warning' | 'info' | 'websocket', message: string, details?: string, source?: string, messageType?: string, rawMessage?: unknown) => void;
	onTerminalMessage?: (log: { id: string; content: string; type: 'command' | 'stdout' | 'stderr' | 'info' | 'error' | 'warn' | 'debug'; timestamp: number; source?: string }) => void;
	onCloudflareDeployGate?: (code: CloudflareDeploymentErrorCode) => void;
}) {
	// Derive initial behavior type from explicit override or project type using feature system
	const getInitialBehaviorType = (): BehaviorType => {
		return explicitBehaviorType ?? getBehaviorTypeForProject(projectType);
	};

	const connectionStatus = useRef<'idle' | 'connecting' | 'connected' | 'failed' | 'retrying'>('idle');
	const retryCount = useRef(0);
	const maxRetries = 5;
	const retryTimeouts = useRef<NodeJS.Timeout[]>([]);
	// Track whether component is mounted and should attempt reconnects
	const shouldReconnectRef = useRef(true);
	// Track deployment timeout for cleanup
	const deploymentTimeoutRef = useRef<NodeJS.Timeout | null>(null);
	// Track the latest connection attempt to avoid handling stale socket events
	const connectAttemptIdRef = useRef(0);
	const connectWithRetryRef = useRef<
		((
			wsUrl: string,
			options?: { disableGenerate?: boolean; isRetry?: boolean },
		) => void) | null
	>(null);
	const handleConnectionFailureRef = useRef<
		((wsUrl: string, disableGenerate: boolean, reason: string) => void) | null
	>(null);
	const [chatId, setChatId] = useState<string>();
	// Phasic/agentic flows show a placeholder "Thinking..." message until
	// the backend streams the first phase. The think behavior streams the user's
	// prompt and the assistant reply directly, so the placeholder would
	// linger forever; start with an empty thread for that behavior.
	const [messages, setMessages] = useState<ChatMessage[]>(
		getInitialBehaviorType() === 'think'
			? []
			: [createAIMessage('main', 'Thinking...', true)],
	);

	const [bootstrapFiles, setBootstrapFiles] = useState<FileType[]>([]);
	const [blueprint, setBlueprint] = useState<BlueprintType>();
	const [previewUrl, setPreviewUrl] = useState<string>();
	const [query, setQuery] = useState<string>();
	const [behaviorType, setBehaviorType] = useState<BehaviorType>(getInitialBehaviorType());
	const [internalProjectType, setInternalProjectType] = useState<ProjectType>(projectType);
	const [templateDetails, setTemplateDetails] = useState<TemplateDetails | null>(null);
	// Gate for externally-sourced new sessions: true while awaiting the user's
	// explicit confirmation before the query is sent to the agent.
	const [awaitingStartConfirmation, setAwaitingStartConfirmation] = useState(false);
	const startConfirmedRef = useRef(false);
	const [startTrigger, setStartTrigger] = useState(0);

	const confirmStart = useCallback(() => {
		startConfirmedRef.current = true;
		setAwaitingStartConfirmation(false);
		setStartTrigger((n) => n + 1);
	}, []);

	const [websocket, setWebsocket] = useState<WebSocket>();

	const [isGeneratingBlueprint, setIsGeneratingBlueprint] = useState(false);
	const [isBootstrapping, setIsBootstrapping] = useState(true);

	const [projectStages, setProjectStages] = useState<ProjectStage[]>(defaultStages);

	// Get refetch function from limits context for usage updates
	const { refetch: refetchLimits } = useLimitsContext();

	// New state for phase timeline tracking
	const [phaseTimeline, setPhaseTimeline] = useState<PhaseTimelineItem[]>([]);

	const [files, setFiles] = useState<FileType[]>([]);

	const [totalFiles, setTotalFiles] = useState<number>();

	const [edit, setEdit] = useState<Omit<CodeFixEdits, 'type'>>();

	// Deployment and generation control state
	const [isDeploying, setIsDeploying] = useState(false);
	const [cloudflareDeploymentUrl, setCloudflareDeploymentUrl] = useState<string>('');
	const [deploymentError, setDeploymentError] = useState<string>();
	
	// Issue tracking and debugging state
	const [runtimeErrorCount, setRuntimeErrorCount] = useState(0);
	const [staticIssueCount, setStaticIssueCount] = useState(0);
	const [isDebugging, setIsDebugging] = useState(false);
	
	// Preview deployment state
	const [isPreviewDeploying, setIsPreviewDeploying] = useState(false);
	
	// Redeployment state - tracks when redeploy button should be enabled
	const [isRedeployReady, setIsRedeployReady] = useState(false);
	// const [lastDeploymentPhaseCount, setLastDeploymentPhaseCount] = useState(0);
	const [isGenerationPaused, setIsGenerationPaused] = useState(false);
	const [isGenerating, setIsGenerating] = useState(false);

	// Phase progress visual indicator (used to apply subtle throb on chat)
	const [isPhaseProgressActive, setIsPhaseProgressActive] = useState(false);

	const [isThinking, setIsThinking] = useState(false);
	
	// Preview refresh state - triggers preview reload after deployment
	const [shouldRefreshPreview, setShouldRefreshPreview] = useState(false);
	
	// Backend error dialog state - for showing limit errors with CTAs
	const [backendErrorDialog, setBackendErrorDialog] = useState<BackendErrorDialogState>({
		isOpen: false
	});
	
	// Track whether we've completed initial state restoration to avoid disrupting active sessions
	const [isInitialStateRestored, setIsInitialStateRestored] = useState(false);

	// Pending clarifying questions surfaced by the agent's `ask_questions` tool.
	const [clarifyingQuestions, setClarifyingQuestions] = useState<ClarifyingQuestion[] | null>(null);

	const updateStage = useCallback(
		(stageId: ProjectStage['id'], data: Partial<Omit<ProjectStage, 'id'>>) => {
			logger.debug('updateStage', { stageId, ...data });
			setProjectStages(prev => updateStageHelper(prev, stageId, data));
		},
		[],
	);

	const onCompleteBootstrap = useCallback(() => {
		updateStage('bootstrap', { status: 'completed' });
	}, [updateStage]);

	const clearEdit = useCallback(() => {
		setEdit(undefined);
	}, []);

	// Callback to clear deployment timeout (used by websocket handler)
	const clearDeploymentTimeout = useCallback(() => {
		if (deploymentTimeoutRef.current) {
			clearTimeout(deploymentTimeoutRef.current);
			deploymentTimeoutRef.current = null;
		}
	}, []);


	const sendMessage = useCallback((message: ChatMessage) => {
		// Only add conversational messages to the chat UI
		if (!isConversationalMessage(message.conversationId)) return;
		setMessages((prev: ChatMessage[]) => addOrUpdateMessage(prev, message));
	}, []);

	const sendUserMessage = useCallback((message: string) => {
		setMessages(prev => [...prev, createUserMessage(message)]);
	}, []);

	const submitClarifyingAnswers = useCallback((answers: { question: string; selected: string[]; custom: string }[]) => {
		if (!websocket) return;

		const lines = answers
			.map((a) => {
				const parts = [...a.selected];
				if (a.custom.trim()) parts.push(a.custom.trim());
				if (parts.length === 0) return null;
				return `Q: ${a.question}\nA: ${parts.join(', ')}`;
			})
			.filter((s): s is string => typeof s === 'string')
			.join('\n\n');

		if (!lines) return;

		const message = `Here are my answers:\n\n${lines}`;
		sendWebSocketMessage(websocket, 'user_suggestion', { message });
		sendUserMessage(message);
		setClarifyingQuestions(null);
	}, [websocket, sendUserMessage]);

	const dismissClarifyingQuestions = useCallback(() => {
		setClarifyingQuestions(null);
	}, []);

	const loadBootstrapFiles = useCallback((files: FileType[]) => {
		setBootstrapFiles((prev) => [
			...prev,
			...files.map((file) => ({
				...file,
				language: getFileType(file.filePath),
			})),
		]);
	}, []);

	// Create the WebSocket message handler
	const handleWebSocketMessage = useMemo(
		() =>
			createWebSocketMessageHandler({
			// State setters
			setFiles,
			setPhaseTimeline,
			setProjectStages,
			setMessages,
			setBlueprint,
			setQuery,
			setPreviewUrl,
			setTotalFiles,
			setIsRedeployReady,
			setIsPreviewDeploying,
			setIsThinking,
			setIsInitialStateRestored,
			setShouldRefreshPreview,
			setIsDeploying,
			setCloudflareDeploymentUrl,
			setDeploymentError,
			setIsGenerationPaused,
			setIsGenerating,
			setIsPhaseProgressActive,
			setRuntimeErrorCount,
			setStaticIssueCount,
			setIsDebugging,
			setBehaviorType,
			setInternalProjectType,
			setTemplateDetails,
			setBackendErrorDialog,
			setClarifyingQuestions,
			// Current state
			isInitialStateRestored,
			blueprint,
			query,
			bootstrapFiles,
			files,
			phaseTimeline,
			previewUrl,
			projectStages,
			isGenerating,
			urlChatId,
			behaviorType,
			// Functions
			updateStage,
			sendMessage,
			loadBootstrapFiles,
			refetchLimits,
			onDebugMessage,
			onTerminalMessage,
			onCloudflareDeployGate,
			clearDeploymentTimeout,
			onPresentationFileEvent: (evt) => {
				if (!evt.path.includes('/slides/')) return;
				window.dispatchEvent(new CustomEvent('presentation-file-event', { detail: evt }));
			},
		} as HandleMessageDeps),
		[
			isInitialStateRestored,
			blueprint,
			query,
			bootstrapFiles,
			files,
			phaseTimeline,
			previewUrl,
			projectStages,
			isGenerating,
			urlChatId,
			behaviorType,
			updateStage,
			sendMessage,
			loadBootstrapFiles,
			refetchLimits,
			onDebugMessage,
			onTerminalMessage,
			onCloudflareDeployGate,
			clearDeploymentTimeout,
			setClarifyingQuestions,
		],
	);

	// WebSocket connection with retry logic
	const connectWithRetry = useCallback(
		async (
			wsUrl: string,
			{ disableGenerate = false, isRetry = false }: { disableGenerate?: boolean; isRetry?: boolean } = {},
		) => {
			logger.debug(`🔌 ${isRetry ? 'Retrying' : 'Attempting'} WebSocket connection (attempt ${retryCount.current + 1}/${maxRetries + 1}):`, wsUrl);
			
			if (!wsUrl) {
				logger.error('❌ WebSocket URL is required');
				return;
			}

			connectionStatus.current = isRetry ? 'retrying' : 'connecting';

			try {
				logger.debug('🔗 Attempting WebSocket connection to:', wsUrl);
				const ws = new WebSocket(wsUrl);
				setWebsocket(ws);

				// Mark this attempt id
				const myAttemptId = ++connectAttemptIdRef.current;

				// Connection timeout - if connection doesn't open within 30 seconds
				const connectionTimeout = setTimeout(() => {
					// Only handle timeout for the latest attempt
					if (myAttemptId !== connectAttemptIdRef.current) return;
					if (ws.readyState === WebSocket.CONNECTING) {
						logger.warn('⏰ WebSocket connection timeout');
						ws.close();
						handleConnectionFailureRef.current?.(wsUrl, disableGenerate, 'Connection timeout');
					}
				}, 30000);

				ws.addEventListener('open', () => {
					// Ignore stale open events
					if (!shouldReconnectRef.current) {
						ws.close();
						return;
					}
					if (myAttemptId !== connectAttemptIdRef.current) return;
					
					clearTimeout(connectionTimeout);
					logger.info('✅ WebSocket connection established successfully!');
					connectionStatus.current = 'connected';
					
					// Reset retry count on successful connection
					retryCount.current = 0;
					
					// Clear any pending retry timeouts
					retryTimeouts.current.forEach(clearTimeout);
					retryTimeouts.current = [];

					// Send success message to user
					if (isRetry) {
						// Clear old messages on reconnect to prevent duplicates
						setMessages(() => [
							createAIMessage('websocket_reconnected', 'Seems we lost connection for a while there. Fixed now!', true)
						]);
					}

					// Always request conversation state explicitly (running/full history)
					sendWebSocketMessage(ws, 'get_conversation_state');

					// Request file generation for new chats only
					if (!disableGenerate && urlChatId === 'new') {
						logger.debug('🔄 Starting code generation for new chat');
						setIsGenerating(true);
						sendWebSocketMessage(ws, 'generate_all');
					}
				});

				ws.addEventListener('message', (event) => {
					try {
						const message: WebSocketMessage = JSON.parse(event.data);
						handleWebSocketMessage(ws, message);
					} catch (parseError) {
						logger.error('❌ Error parsing WebSocket message:', parseError, event.data);
					}
				});

				ws.addEventListener('error', (error) => {
					clearTimeout(connectionTimeout);
					// Only handle error for the latest attempt and when we should reconnect
					if (myAttemptId !== connectAttemptIdRef.current) return;
					if (!shouldReconnectRef.current) return;
					logger.error('❌ WebSocket error:', error);
					handleConnectionFailureRef.current?.(wsUrl, disableGenerate, 'WebSocket error');
				});

				ws.addEventListener('close', (event) => {
					clearTimeout(connectionTimeout);
					logger.info(
						`🔌 WebSocket connection closed with code ${event.code}: ${event.reason || 'No reason provided'}`,
						event,
					);
					// Only handle close for the latest attempt and when we should reconnect
					if (myAttemptId !== connectAttemptIdRef.current) return;
					if (!shouldReconnectRef.current) return;
					// Retry on any close while mounted (including 1000) to improve resilience
					handleConnectionFailureRef.current?.(wsUrl, disableGenerate, `Connection closed (code: ${event.code})`);
				});

				return function disconnect() {
					clearTimeout(connectionTimeout);
					ws.close();
				};
			} catch (error) {
				logger.error('❌ Error establishing WebSocket connection:', error);
				handleConnectionFailureRef.current?.(wsUrl, disableGenerate, 'Connection setup failed');
			}
		},
		[maxRetries, handleWebSocketMessage, urlChatId],
	);

	// Handle connection failures with exponential backoff retry
	const handleConnectionFailure = useCallback(
		(wsUrl: string, disableGenerate: boolean, reason: string) => {
			connectionStatus.current = 'failed';
			
			if (retryCount.current >= maxRetries) {
				logger.error(`💥 WebSocket connection failed permanently after ${maxRetries + 1} attempts`);
				sendMessage(createAIMessage('websocket_failed', `🚨 Connection failed permanently after ${maxRetries + 1} attempts.\n\n❌ Reason: ${reason}\n\n🔄 Please refresh the page to try again.`));
				
				// Debug logging for permanent failure
				onDebugMessage?.('error',
					'WebSocket Connection Failed Permanently',
					`Failed after ${maxRetries + 1} attempts. Reason: ${reason}`,
					'WebSocket Resilience'
				);
				return;
			}

			retryCount.current++;
			
			// Exponential backoff: 2^attempt * 1000ms (1s, 2s, 4s, 8s, 16s)
			const retryDelay = Math.pow(2, retryCount.current) * 1000;
			const maxDelay = 30000; // Cap at 30 seconds
			const actualDelay = Math.min(retryDelay, maxDelay);

			logger.warn(`🔄 Retrying WebSocket connection in ${actualDelay / 1000}s (attempt ${retryCount.current + 1}/${maxRetries + 1})`);
			
			sendMessage(createAIMessage('websocket_retrying', `🔄 Connection failed. Retrying in ${Math.ceil(actualDelay / 1000)} seconds... (attempt ${retryCount.current + 1}/${maxRetries + 1})\n\n❌ Reason: ${reason}`, true));

			const timeoutId = setTimeout(() => {
				connectWithRetryRef.current?.(wsUrl, { disableGenerate, isRetry: true });
			}, actualDelay);
			
			retryTimeouts.current.push(timeoutId);
			
			// Debug logging for retry attempt
			onDebugMessage?.('warning',
				'WebSocket Connection Retry',
				`Retry ${retryCount.current}/${maxRetries} in ${actualDelay / 1000}s. Reason: ${reason}`,
				'WebSocket Resilience'
			);
		},
		[maxRetries, onDebugMessage, sendMessage],
	);

	useEffect(() => {
		connectWithRetryRef.current = connectWithRetry;
		handleConnectionFailureRef.current = handleConnectionFailure;
	}, [connectWithRetry, handleConnectionFailure]);

    // No legacy wrapper; call connectWithRetry directly

	useEffect(() => {
		async function init() {
			if (!urlChatId || connectionStatus.current !== 'idle') return;

			try {
				if (urlChatId === 'new') {
					if (!userQuery) {
						const errorMsg = 'Please enter a description of what you want to build';
						logger.error('Query is required for new code generation');
						toast.error(errorMsg);
						return;
					}

					if (userQuery.length > MAX_AGENT_QUERY_LENGTH) {
						const errorMsg = `Prompt too large (${userQuery.length} characters). Maximum allowed is ${MAX_AGENT_QUERY_LENGTH} characters.`;
						toast.error(errorMsg);
						setMessages(() => [createAIMessage('main', errorMsg)]);
						return;
					}

					// Gate externally-sourced sessions: the query feeds the agent's
					// system prompt, so require an explicit user gesture before it
					// runs when the session was not started from in-app navigation.
					if (!autoStart && !startConfirmedRef.current) {
						setAwaitingStartConfirmation(true);
						return;
					}

					// Prevent duplicate session creation on rerenders while streaming
					connectionStatus.current = 'connecting';

					// Start new code generation using API client
					const response = await apiClient.createAgentSession({
						query: userQuery,
						projectType,
						behaviorType: explicitBehaviorType,
						images: userImages, // Pass images from URL params for multi-modal blueprint
					});

					const parser = createRepairingJSONParser();

					const result: {
						websocketUrl: string;
						agentId: string;
						behaviorType: BehaviorType;
						projectType: ProjectType;
						template: {
							files: FileType[];
						};
					} = {
						websocketUrl: '',
						agentId: '',
						behaviorType: 'phasic',
						projectType: 'app',
						template: {
							files: [],
						},
					};

					let startedBlueprintStream = false;
					const initialBehaviorType = getBehaviorTypeForProject(projectType);
					if (initialBehaviorType === 'phasic') {
						sendMessage(
							createAIMessage('main', "Sure, let's get started. Bootstrapping the project first...", true),
						);
					}

					for await (const obj of ndjsonStream(response.stream)) {
                        logger.debug('Received chunk from server:', obj);
						if (obj.chunk) {
							if (!startedBlueprintStream) {
								sendMessage(createAIMessage('main', 'Blueprint is being generated...', true));
								logger.info('Blueprint stream has started');
								setIsBootstrapping(false);
								setIsGeneratingBlueprint(true);
								startedBlueprintStream = true;
								updateStage('bootstrap', { status: 'completed' });
								updateStage('blueprint', { status: 'active' });
							}
							parser.feed(obj.chunk);
							try {
								const partial = parser.finalize();
								setBlueprint(partial);
							} catch (e) {
								logger.error('Error parsing JSON:', e, obj.chunk);
							}
						}
						if (obj.agentId) {
							result.agentId = obj.agentId;
						}
						if (obj.websocketUrl) {
							result.websocketUrl = obj.websocketUrl;
							logger.debug('📡 Received WebSocket URL from server:', result.websocketUrl)
						}
						if (obj.behaviorType) {
							result.behaviorType = obj.behaviorType;
							setBehaviorType(obj.behaviorType);
							logger.debug('Received behaviorType from server:', obj.behaviorType);
						}
						if (obj.projectType) {
							result.projectType = obj.projectType;
							logger.debug('Received projectType from server:', obj.projectType);
						}
						if (obj.template) {
                            logger.debug('Received template from server:', obj.template);
							result.template = obj.template;
							if (obj.template.files) {
								loadBootstrapFiles(obj.template.files);
							}
						}
					}

					updateStage('blueprint', { status: 'completed' });
					setIsGeneratingBlueprint(false);
					const finalBehaviorType = getBehaviorTypeForProject(projectType);
					if (finalBehaviorType === 'phasic') {
						sendMessage(
							createAIMessage('main', 'Blueprint generation complete. Now starting the code generation...', true),
						);
					}

					if (!result.websocketUrl || !result.agentId) {
						throw new Error('Failed to initialize agent session');
					}

					// Connect to WebSocket
					logger.debug('connecting to ws with created id');
					connectWithRetry(result.websocketUrl);
					setChatId(result.agentId); // This comes from the server response
					
					// Emit app-created event for sidebar updates
					appEvents.emitAppCreated(result.agentId, {
						title: userQuery || 'New App',
						description: userQuery,
					});
				} else if (connectionStatus.current === 'idle') {
					// Prevent duplicate connect calls on rerenders
					connectionStatus.current = 'connecting';

					setIsBootstrapping(false);
					// Show a thinking placeholder while we fetch the agent
					// summary. The think behavior rehydrates from the ThinkAgent DO via
					// `GET_CONVERSATION_STATE`, which produces the real
					// thread directly — no placeholder is needed there.
					if (getBehaviorTypeForProject(projectType) !== 'think') {
						setMessages(() => [
							createAIMessage('fetching-chat', 'Starting from where you left off...', true),
						]);
					}

					// Fetch existing agent connection details
					const response = await apiClient.connectToAgent(urlChatId);
					if (!response.success || !response.data) {
						logger.error('Failed to fetch existing chat:', { chatId: urlChatId, error: response.error });
						throw new Error(response.error?.message || 'Failed to connect to agent');
					}

					logger.debug('Existing agentId API result', response.data);
					// Set the chatId for existing chat - this enables the chat input
					setChatId(urlChatId);


					if (!response.data.websocketUrl) {
						throw new Error('Missing websocketUrl for existing agent');
					}

					logger.debug('connecting from init for existing chatId');
					connectWithRetry(response.data.websocketUrl, {
						disableGenerate: true, // We'll handle generation resume in the WebSocket open handler
					});
				}
			} catch (error) {
				// Allow retry on failure
				connectionStatus.current = 'idle';
				logger.error('Error initializing code generation:', error);
				if (error instanceof RateLimitExceededError) {
					const rateLimitMessage = handleRateLimitError(error.details, onDebugMessage);
					setMessages(prev => [...prev, rateLimitMessage]);
				}
			}
		}
		init();
	}, [
		projectType,
		explicitBehaviorType,
		connectWithRetry,
		loadBootstrapFiles,
		onDebugMessage,
		sendMessage,
		updateStage,
		urlChatId,
		userImages,
		userQuery,
		autoStart,
		startTrigger,
	]);

    // Mount/unmount: enable/disable reconnection and clear pending retries
    useEffect(() => {
        shouldReconnectRef.current = true;
        return () => {
            shouldReconnectRef.current = false;
            retryTimeouts.current.forEach(clearTimeout);
            retryTimeouts.current = [];
            // Clear deployment timeout on unmount
            if (deploymentTimeoutRef.current) {
                clearTimeout(deploymentTimeoutRef.current);
                deploymentTimeoutRef.current = null;
            }
        };
    }, []);

    // Close previous websocket on change
    useEffect(() => {
        return () => {
            websocket?.close();
        };
    }, [websocket]);

	useEffect(() => {
		if (edit) {
			// When edit is cleared, write the edit changes
			return () => {
				setFiles((prev) =>
					prev.map((file) => {
						if (file.filePath === edit.filePath) {
							file.fileContents = file.fileContents.replace(
								edit.search,
								edit.replacement,
							);
						}
						return file;
					}),
				);
			};
		}
	}, [edit]);

	// Track debugging state based on deep_debug tool events in messages
	useEffect(() => {
		const hasActiveDebug = messages.some(msg => 
			msg.role === 'assistant' && 
			msg.ui?.toolEvents?.some(event => 
				event.name === 'deep_debug' && event.status === 'start'
			)
		);
		setIsDebugging(hasActiveDebug);
	}, [messages]);

	// Control functions for deployment and generation
	const handleStopGeneration = useCallback(() => {
		sendWebSocketMessage(websocket, 'stop_generation');
	}, [websocket]);

	const handleResumeGeneration = useCallback(() => {
		sendWebSocketMessage(websocket, 'resume_generation');
	}, [websocket]);

	const handleDeployToCloudflare = useCallback(async (
		instanceId: string,
		target: 'platform' | 'user' = 'platform',
	) => {
		try {
			// Send deployment command via WebSocket instead of HTTP request
			if (sendWebSocketMessage(websocket, 'deploy', { instanceId, target })) {
				logger.debug('🚀 Deployment WebSocket message sent:', instanceId);

				// Clear any existing deployment timeout
				if (deploymentTimeoutRef.current) {
					clearTimeout(deploymentTimeoutRef.current);
					deploymentTimeoutRef.current = null;
				}
				
				// Set 1-minute timeout for deployment
				deploymentTimeoutRef.current = setTimeout(() => {
					if (isDeploying) {
						logger.warn('Deployment timeout after 1 minute');

						// Reset deployment state
						setIsDeploying(false);
						setCloudflareDeploymentUrl('');
						setIsRedeployReady(false);

						// Show timeout message
						sendMessage(createAIMessage('deployment_timeout', `Deployment timed out after 1 minute.\n\nPlease try deploying again. The server may be busy.`));

						// Debug logging for timeout
						onDebugMessage?.('warning',
							'Deployment Timeout',
							`Deployment for ${instanceId} timed out after 60 seconds`,
							'Deployment Timeout Management'
						);
					}
					deploymentTimeoutRef.current = null;
				}, 60000); // 1 minute = 60,000ms

			} else {
				throw new Error('WebSocket connection not available');
			}
		} catch (error) {
			logger.error('Error sending deployment WebSocket message:', error);

			// Set deployment state immediately for UI feedback
			setIsDeploying(true);
			// Clear any previous deployment error
			setDeploymentError('');
			setCloudflareDeploymentUrl('');
			setIsRedeployReady(false);

			sendMessage(createAIMessage('deployment_error', `Failed to initiate deployment: ${error instanceof Error ? error.message : 'Unknown error'}\n\nYou can try again.`));
		}
	}, [websocket, sendMessage, isDeploying, onDebugMessage]);

	const allFiles = useMemo(() => mergeFiles(bootstrapFiles, files), [bootstrapFiles, files]);

	return {
		messages,
		edit,
		bootstrapFiles,
		chatId,
		query,
		files,
		blueprint,
		previewUrl,
		isGeneratingBlueprint,
		isBootstrapping,
		totalFiles,
		websocket,
		sendUserMessage,
		sendAiMessage: sendMessage,
		clearEdit,
		projectStages,
		phaseTimeline,
		isThinking,
		onCompleteBootstrap,
		// Deployment and generation control
		isDeploying,
		cloudflareDeploymentUrl,
		deploymentError,
		isRedeployReady,
		isGenerationPaused,
		isGenerating,
		handleStopGeneration,
		handleResumeGeneration,
		handleDeployToCloudflare,
		// Preview refresh control
		shouldRefreshPreview,
		// Preview deployment state
		isPreviewDeploying,
		// Phase progress visual indicator
		isPhaseProgressActive,
		// Issue tracking and debugging state
		runtimeErrorCount,
		staticIssueCount,
		isDebugging,
		// Behavior type from backend
		behaviorType,
		projectType: internalProjectType,
		templateDetails,
		allFiles,
		// Backend error dialog state
		backendErrorDialog,
		setBackendErrorDialog,
		// Externally-sourced session start gate
		awaitingStartConfirmation,
		confirmStart,
		// Clarifying questions popup state
		clarifyingQuestions,
		submitClarifyingAnswers,
		dismissClarifyingQuestions,
	};
}
