import type { FileType } from '@/api-types';
import { MonacoEditor } from '@/components/monaco-editor/lazy-monaco-editor';
import { AppLoadingSkeleton } from '@/components/shared/AppLoadingSkeleton';
import { FloatingBackgroundIcons } from '@/components/shared/FloatingBackgroundIcons';
import { GitCloneModal } from '@/components/shared/GitCloneModal';
import {
	useApp,
	useAppPreviewToken,
	useDeleteApp,
	useDeployPreview,
	useToggleAppFavorite,
	useToggleAppStar,
	useUpdateAppVisibility,
} from '@/hooks/use-app';
import { useAuth } from '@/contexts/auth-context';
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { ApiError } from '@/lib/api-client';
import { capitalizeFirstLetter, getPreviewUrl } from '@/lib/utils';
import { getFileType } from '@/utils/string';
import {
	Badge,
	Button,
	DeleteResource,
	DropdownMenu,
	LayerCard,
	Tabs,
	useKumoToastManager,
} from '@cloudflare/kumo';
import {
	BookmarkSimple,
	Code,
	CopyIcon,
	DotsThree,
	GitBranch,
	GithubLogo,
	Globe,
	Lock,
	LockOpen,
	Star,
	TrashIcon,
} from '@phosphor-icons/react';
import {
	Check,
	ChevronLeft,
	Code2,
	Copy,
	ExternalLink,
	Eye,
	MessageSquare,
	Play,
} from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router';
import { usePageHeader } from '@/components/layout/header-context';
import { FileExplorer } from '../chat/components/file-explorer';
import { PreviewIframe } from '../chat/components/preview-iframe';

// Define supported actions for OAuth redirect
type PendingAction = 'favorite' | 'bookmark' | 'star' | 'fork' | 'remix';

// Supported actions constant for validation
const SUPPORTED_ACTIONS: PendingAction[] = [
	'favorite',
	'bookmark',
	'star',
	'fork',
	'remix',
];

// Action configuration type for reusability
interface ActionConfig {
	action: PendingAction;
	context: string;
	handler: () => Promise<void>;
	errorMessage: string;
}

// Action mapping for aliases (bookmark -> favorite, remix -> fork)
const ACTION_MAP: Record<PendingAction, string> = {
	favorite: 'favorite',
	bookmark: 'favorite',
	star: 'star',
	fork: 'fork',
	remix: 'fork',
};
export default function AppView() {
	const { id } = useParams();
	const navigate = useNavigate();
	const [searchParams, setSearchParams] = useSearchParams();
	const { user } = useAuth();
	const { requireAuth } = useAuthGuard();
	const { app, loading, error } = useApp(id);
	const { copied: urlCopied, copy: copyUrl } = useCopyToClipboard();
	const { copy: copyFile } = useCopyToClipboard({
		successMessage: 'Code copied to clipboard',
	});
	const { copy: copyPrompt } = useCopyToClipboard({
		successMessage: 'Prompt copied to clipboard',
	});
	const [activeTab, setActiveTab] = useState('preview');
	const [deploymentProgress, setDeploymentProgress] = useState<string>('');
	const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
	const [deleteError, setDeleteError] = useState<string>();
	const [isGitCloneModalOpen, setIsGitCloneModalOpen] = useState(false);
	const [activeFilePath, setActiveFilePath] = useState<string>();
	const previewIframeRef = useRef<HTMLIFrameElement>(null);
	const autoDeployAppIdRef = useRef<string | undefined>(undefined);
	const [thinkPreviewUrl, setThinkPreviewUrl] = useState<string>();

	// Route reuses this component across /app/:id — clear deploy UI on switch
	useEffect(() => {
		setDeploymentProgress('');
		autoDeployAppIdRef.current = undefined;
		setThinkPreviewUrl(undefined);
		setActiveTab('preview');
		setActiveFilePath(undefined);
	}, [id]);

	const needsOwnerPreviewToken =
		!!app &&
		!!user &&
		app.userId === user.id &&
		app.visibility === 'private' &&
		!!app.deploymentId;

	const { previewUrl: ownerPreviewUrl } = useAppPreviewToken(
		app?.id,
		needsOwnerPreviewToken,
	);

	const { mutateAsync: toggleFavorite } = useToggleAppFavorite(app?.id);
	const { mutateAsync: toggleStar } = useToggleAppStar(app?.id);
	const { mutateAsync: updateVisibility, isPending: isUpdatingVisibility } =
		useUpdateAppVisibility(app?.id);
	const { mutateAsync: deleteApp, isPending: isDeleting } = useDeleteApp();
	const { mutateAsync: deployPreview, isPending: isDeploying } =
		useDeployPreview(app?.id);

	const isFavorited = app?.userFavorited || false;
	const isStarred = app?.userStarred || false;

	// Convert agent files to chat FileType format
	const files = useMemo<FileType[]>(() => {
		if (!app?.agentSummary?.generatedCode) return [];
		return app.agentSummary.generatedCode
			.filter(
				(file) =>
					file && file.filePath && typeof file.filePath === 'string',
			)
			.map((file) => ({
				filePath: file.filePath,
				fileContents: file.fileContents || '',
				explanation:
					file.filePurpose === 'Generated by think'
						? undefined
						: file.filePurpose,
				language: getFileType(file.filePath),
				isGenerating: false,
				needsFixing: false,
				hasErrors: false,
			}));
	}, [app?.agentSummary?.generatedCode]);

	// Get active file
	const activeFile = useMemo(() => {
		return files.find((file) => file.filePath === activeFilePath);
	}, [files, activeFilePath]);

	// Auto-select first file; recover if selected path vanished from the list
	useEffect(() => {
		if (files.length === 0) return;
		const stillThere = files.some(
			(file) => file.filePath === activeFilePath,
		);
		if (!activeFilePath || !stillThere) {
			setActiveFilePath(files[0].filePath);
		}
	}, [files, activeFilePath]);

	// File click handler
	const handleFileClick = useCallback((file: FileType) => {
		setActiveFilePath(file.filePath);
	}, []);

	const toast = useKumoToastManager();

	// Action configuration for reusability
	const actionConfigs: Record<string, ActionConfig> = useMemo(
		() => ({
			favorite: {
				action: 'favorite',
				context: 'to bookmark apps',
				handler: async () => {
					const newState = await toggleFavorite();
					toast.add({
						title: newState
							? 'Added to bookmarks'
							: 'Removed from bookmarks',
						variant: 'success',
					});
				},
				errorMessage: 'Failed to update bookmarks',
			},
			star: {
				action: 'star',
				context: 'to star apps',
				handler: async () => {
					const data = await toggleStar();
					toast.add({
						title: data.isStarred ? 'Starred!' : 'Unstarred',
						variant: 'success',
					});
				},
				errorMessage: 'Failed to update star',
			},
		}),
		[toast, toggleFavorite, toggleStar],
	);

	// Reusable authenticated action handler
	const createAuthenticatedHandler = useCallback(
		(configKey: string) => {
			return async () => {
				if (!app) return;

				const config = actionConfigs[configKey];
				if (!config) return;

				const currentUrl = `/app/${app.id}?action=${config.action}`;

				// Use auth guard with action parameter in intended URL
				if (
					!requireAuth({
						requireFullAuth: true,
						actionContext: config.context,
						intendedUrl: currentUrl,
					})
				) {
					return;
				}

				// User is authenticated, execute immediately
				try {
					await config.handler();
				} catch (error) {
					console.error(`${config.action} error:`, error);
					toast.add({
						title:
							error instanceof ApiError
								? error.message
								: config.errorMessage,
						variant: 'error',
					});
				}
			};
		},
		[actionConfigs, app, requireAuth, toast],
	);

	// Create action handlers using the reusable pattern
	const handleFavorite = useMemo(
		() => createAuthenticatedHandler('favorite'),
		[createAuthenticatedHandler],
	);

	const handleStar = useMemo(
		() => createAuthenticatedHandler('star'),
		[createAuthenticatedHandler],
	);

	// Handle pending actions after OAuth redirect
	const executePendingAction = useCallback(
		async (action: PendingAction) => {
			if (!app) return;

			const configKey = ACTION_MAP[action];
			if (!configKey) {
				console.warn('Unknown pending action:', action);
				return;
			}

			const config = actionConfigs[configKey];
			if (!config) {
				console.warn('No config found for action:', action);
				return;
			}

			try {
				await config.handler();
			} catch (error) {
				console.error(
					'Failed to execute pending action:',
					action,
					error,
				);
				toast.add({
					title:
						error instanceof ApiError
							? error.message
							: config.errorMessage,
					variant: 'error',
				});
			}
		},
		[actionConfigs, app, toast],
	);

	// Effect to handle pending actions after OAuth redirect
	useEffect(() => {
		if (!user || !app || loading) return;

		const actionParam = searchParams.get('action');
		if (!actionParam) return;

		// Validate action parameter against our supported types
		const action = SUPPORTED_ACTIONS.find((a) => a === actionParam);

		if (!action) {
			console.warn('Unsupported action parameter:', actionParam);
			return;
		}

		// Clear the action parameter from URL first
		const newSearchParams = new URLSearchParams(searchParams);
		newSearchParams.delete('action');
		setSearchParams(newSearchParams, { replace: true });

		// Execute the pending action
		executePendingAction(action);
	}, [
		user,
		app,
		loading,
		searchParams,
		setSearchParams,
		executePendingAction,
	]);

	const isOwner = !!app && app.userId === user?.id;
	const isThink = app?.behaviorType === 'think';
	// Think apps auto-load their live SpaceDO preview for signed-in viewers
	// (see the effect below). Anonymous viewers can't hit the auth-gated deploy
	// endpoint, so they fall back to the same manual affordance as non-Think apps.
	const isAutoLoadingPreview = isThink && !!user;
	// Think prefers its live preview over the production deployment link. For
	// signed-in viewers we withhold the deployed link until the preview resolves
	// so the preview always wins; anonymous viewers fall back to it.
	const appUrl = isThink
		? user
			? thinkPreviewUrl || ''
			: app?.cloudflareUrl || app?.previewUrl || ''
		: ownerPreviewUrl || app?.cloudflareUrl || app?.previewUrl || '';
	const promptText = app?.agentSummary?.query || app?.originalPrompt || '';

	const handleCopyUrl = () => {
		if (!appUrl) return;
		copyUrl(appUrl);
	};

	const handlePreviewDeploy = async () => {
		if (!app || isDeploying) return;

		try {
			setDeploymentProgress('Connecting to agent...');
			const data = await deployPreview();
			if (data.previewURL || data.tunnelURL) {
				setDeploymentProgress('Deployment complete!');
			}
		} catch (error) {
			console.error('Error starting deployment:', error);
			setDeploymentProgress('Failed to start deployment');
			toast.add({
				title: 'Failed to start deployment',
				variant: 'error',
			});
		}
	};

	// Auto-load the preview for Think apps so viewers don't have to click
	// "Deploy". Gated to authenticated users: the preview endpoint requires
	// auth (and applies per-user rate limiting), so anonymous visitors would
	// only get a 401. They still see the manual deploy affordance below.
	useEffect(() => {
		if (
			!app ||
			!user ||
			app.behaviorType !== 'think' ||
			thinkPreviewUrl ||
			autoDeployAppIdRef.current === app.id
		) {
			return;
		}

		autoDeployAppIdRef.current = app.id;
		setDeploymentProgress('Loading preview...');
		void deployPreview()
			.then((data) => {
				const url = getPreviewUrl(data.previewURL, data.tunnelURL);
				if (url) setThinkPreviewUrl(url);
			})
			.catch((error) => {
				console.error('Error loading Think preview:', error);
				setDeploymentProgress('Failed to load preview');
				toast.add({
					title: 'Failed to load preview',
					variant: 'error',
				});
			});
	}, [app, thinkPreviewUrl, deployPreview, toast, user]);

	const handleToggleVisibility = useCallback(async () => {
		if (!app || !user || !isOwner) {
			toast.add({
				title: 'You can only change visibility of your own apps',
				variant: 'error',
			});
			return;
		}

		try {
			const newVisibility =
				app.visibility === 'private' ? 'public' : 'private';
			const result = await updateVisibility(newVisibility);

			toast.add({
				title:
					result.message ||
					`App is now ${newVisibility === 'private' ? 'private' : 'public'}`,
				variant: 'success',
			});
		} catch (error) {
			console.error('Error updating app visibility:', error);
			toast.add({
				title:
					error instanceof ApiError
						? error.message
						: 'Failed to update visibility',
				variant: 'error',
			});
		}
	}, [app, user, isOwner, updateVisibility, toast]);

	const headerContent = useMemo(() => {
		if (loading || error || !app) return null;

		return {
			leading: (
				<div className="min-w-0 flex-1 flex items-center gap-2">
					<h1
						className="text-sm max-w-sm font-semibold truncate min-w-0"
						title={app.title}
					>
						{app.title}
					</h1>
					<Badge
						className="shrink-0"
						variant={
							app.visibility === 'private'
								? 'secondary'
								: 'success'
						}
					>
						<span className="inline-flex items-center gap-1">
							<Globe className="h-3 w-3" weight="duotone" />
							{capitalizeFirstLetter(app.visibility)}
						</span>
					</Badge>
				</div>
			),
			trailing: (
				<>
					{isOwner && (
						<Button
							variant="secondary"
							size="sm"
							onClick={handleToggleVisibility}
							disabled={isUpdatingVisibility}
							loading={isUpdatingVisibility}
							icon={
								app.visibility === 'private' ? (
									<LockOpen
										className="size-3.5"
										weight="duotone"
									/>
								) : (
									<Lock
										className="size-3.5"
										weight="duotone"
									/>
								)
							}
						>
							{app.visibility === 'private'
								? 'Make public'
								: 'Make private'}
						</Button>
					)}

					{app.githubRepositoryUrl && (
						<Button
							variant="secondary"
							size="sm"
							icon={
								<GithubLogo
									className="size-3.5"
									weight="duotone"
								/>
							}
							title={`View on GitHub (${app.githubRepositoryVisibility || 'public'})`}
							onClick={() => {
								if (app.githubRepositoryUrl) {
									window.open(
										app.githubRepositoryUrl,
										'_blank',
										'noopener,noreferrer',
									);
								}
							}}
						>
							GitHub
							{app.githubRepositoryVisibility === 'private' && (
								<Lock
									className="h-3 w-3 opacity-70"
									weight="duotone"
								/>
							)}
						</Button>
					)}

					{isOwner && (
						<>
							<Button
								variant="primary"
								size="sm"
								icon={
									<Code
										className="size-3.5"
										weight="duotone"
									/>
								}
								onClick={() => navigate(`/chat/${app.id}`)}
							>
								Make edits
							</Button>

							<DropdownMenu>
								<DropdownMenu.Trigger
									render={
										<Button
											variant="secondary"
											size="sm"
											shape="square"
											aria-label="More actions"
											icon={
												<DotsThree className="h-4 w-4" />
											}
										/>
									}
								/>
								<DropdownMenu.Content align="end">
									<DropdownMenu.Item
										icon={TrashIcon}
										variant="danger"
										onClick={() => {
											setDeleteError(undefined);
											setIsDeleteDialogOpen(true);
										}}
									>
										Delete app
									</DropdownMenu.Item>
								</DropdownMenu.Content>
							</DropdownMenu>
						</>
					)}
				</>
			),
		};
	}, [
		loading,
		error,
		app,
		isOwner,
		isUpdatingVisibility,
		handleToggleVisibility,
		navigate,
	]);

	usePageHeader(headerContent);

	const handleDeleteApp = async () => {
		if (!app) return;

		try {
			setDeleteError(undefined);
			await deleteApp(app.id);
			toast.add({
				title: 'App deleted successfully',
				variant: 'success',
			});
			setIsDeleteDialogOpen(false);

			if (window.history.length > 1) {
				navigate(-1);
			} else {
				navigate('/apps');
			}
		} catch (error) {
			console.error('Error deleting app:', error);
			setDeleteError(
				error instanceof ApiError
					? error.message
					: 'An unexpected error occurred while deleting the app',
			);
		}
	};

	if (loading) {
		return (
			<>
				<title>Loading - Build</title>
				<AppLoadingSkeleton />
			</>
		);
	}

	if (error || !app) {
		return (
			<div className="size-full flex items-center justify-center p-4">
				<title>App not found - Build</title>
				<LayerCard className="max-w-md w-full px-5 py-6">
					<div className="text-center grid gap-4">
						<div className="grid gap-1.5">
							<h2 className="text-lg font-semibold text-text-primary">
								App not found
							</h2>
							<p className="text-sm text-kumo-subtle">
								{error ||
									"The app you're looking for doesn't exist."}
							</p>
						</div>
						<div className="flex justify-center">
							<Button
								variant="secondary"
								size="sm"
								icon={<ChevronLeft className="h-4 w-4" />}
								onClick={() => navigate('/apps')}
							>
								Back to apps
							</Button>
						</div>
					</div>
				</LayerCard>
			</div>
		);
	}

	return (
		<div className="size-full flex flex-col min-h-0">
			<title>{app.title ? `${app.title} - Build` : 'App - Build'}</title>
			<div className="shrink-0 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between px-4 py-2 border-b bg-kumo-base">
				<Tabs
					value={activeTab}
					onValueChange={setActiveTab}
					className="w-fit"
					tabs={[
						{
							value: 'preview',
							label: (
								<span className="inline-flex items-center gap-1.5">
									<Eye className="size-3.5" />
									Preview
								</span>
							),
						},
						{
							value: 'code',
							label: (
								<span className="inline-flex items-center gap-1.5">
									<Code2 className="size-3.5" />
									Code
									{files.length > 0 && (
										<span className="text-kumo-subtle tabular-nums">
											{files.length}
										</span>
									)}
								</span>
							),
						},
						{
							value: 'prompt',
							label: (
								<span className="inline-flex items-center gap-1.5">
									<MessageSquare className="size-3.5" />
									Prompt
								</span>
							),
						},
					]}
				/>

				<div className="shrink-0 flex items-center gap-2 max-w-full">
					<Button
						variant="secondary"
						size="sm"
						icon={
							<BookmarkSimple
								className="size-3.5"
								weight={isFavorited ? 'fill' : 'duotone'}
							/>
						}
						onClick={() => {
							void handleFavorite();
						}}
					>
						{isFavorited ? 'Bookmarked' : 'Bookmark'}
					</Button>

					<Button
						variant="secondary"
						size="sm"
						icon={
							<Star
								className="size-3.5"
								weight={isStarred ? 'fill' : 'duotone'}
							/>
						}
						onClick={handleStar}
					>
						{isStarred ? 'Starred' : 'Star'}
						{(app.starCount || 0) > 0 && (
							<span className="text-kumo-subtle tabular-nums">
								{app.starCount}
							</span>
						)}
					</Button>

					<Button
						variant="secondary"
						size="sm"
						icon={
							<GitBranch className="size-3.5" weight="duotone" />
						}
						onClick={() => setIsGitCloneModalOpen(true)}
					>
						Clone
					</Button>
				</div>
			</div>

			{/* Full-bleed workspace */}
			<div className="flex-1 min-h-0 flex flex-col">
				{activeTab === 'preview' && (
					<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
						<div className="shrink-0 flex items-center gap-2 px-3 py-2 border-b border-kumo-line bg-kumo-elevated/40">
							<span className="text-sm font-medium text-text-primary shrink-0">
								Live preview
							</span>
							{appUrl && (
								<>
									<code className="min-w-0 flex-1 truncate font-mono text-[0.9em] text-kumo-subtle px-2">
										{appUrl}
									</code>
									<div className="flex items-center gap-0.5 shrink-0">
										<Button
											variant="ghost"
											size="sm"
											shape="square"
											aria-label={
												urlCopied
													? 'Copied'
													: 'Copy URL'
											}
											title={
												urlCopied
													? 'Copied'
													: 'Copy URL'
											}
											onClick={handleCopyUrl}
											icon={
												urlCopied ? (
													<Check className="size-3.5" />
												) : (
													<Copy className="size-3.5" />
												)
											}
										/>
										<Button
											variant="ghost"
											size="sm"
											shape="square"
											aria-label="Open in new tab"
											title="Open in new tab"
											onClick={() =>
												window.open(appUrl, '_blank', 'noopener,noreferrer')
											}
											icon={
												<ExternalLink className="size-3.5" />
											}
										/>
									</div>
								</>
							)}
						</div>
						<div className="flex-1 min-h-0 relative">
							{appUrl ? (
								<PreviewIframe
									ref={previewIframeRef}
									src={appUrl}
									className="absolute inset-0 size-full"
									title={`${app.title} Preview`}
								/>
							) : (
								<div className="absolute inset-0 flex items-center justify-center">
									<FloatingBackgroundIcons />

									<div className="relative z-10 text-center p-8 grid gap-4 max-w-md">
										<div className="grid gap-1.5">
											<h3 className="text-xl font-semibold">
												{isAutoLoadingPreview
													? 'Loading preview'
													: 'Run app'}
											</h3>
											<p className="text-kumo-subtle text-sm">
												{isAutoLoadingPreview
													? 'Preparing this app preview.'
													: 'Deploy a preview to see this app live.'}
											</p>
											{deploymentProgress && (
												<p className="text-sm text-kumo-subtle">
													{deploymentProgress}
												</p>
											)}
										</div>
										{!isAutoLoadingPreview && (
											<div className="flex justify-center">
												<Button
													variant="primary"
													onClick={
														handlePreviewDeploy
													}
													disabled={isDeploying}
													loading={isDeploying}
													icon={
														!isDeploying ? (
															<Play className="h-4 w-4" />
														) : undefined
													}
												>
													{isDeploying
														? 'Deploying...'
														: 'Deploy for preview'}
												</Button>
											</div>
										)}
									</div>
								</div>
							)}
						</div>
					</div>
				)}

				{activeTab === 'code' && (
					<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
						<div className="shrink-0 flex items-center justify-between gap-2 px-3 py-2 border-b border-kumo-line bg-kumo-elevated/40">
							<div className="min-w-0 flex items-center gap-2">
								<span className="text-sm font-medium text-text-primary shrink-0">
									Project files
								</span>
								{/*{app?.agentSummary && (
									<span className="text-xs text-kumo-subtle">
										{files.length} files
									</span>
								)}*/}
							</div>
							{activeFile && (
								<Button
									variant="ghost"
									size="sm"
									icon={
										<CopyIcon
											weight="duotone"
											className="size-3.5"
										/>
									}
									onClick={() => {
										void copyFile(activeFile.fileContents);
									}}
								>
									Copy file
								</Button>
							)}
						</div>

						{files.length > 0 ? (
							<div className="flex-1 min-h-0 flex">
								<FileExplorer
									files={files}
									currentFile={activeFile}
									onFileClick={handleFileClick}
									className="w-56 sm:w-64 max-w-none shrink-0 bg-kumo-elevated/20"
								/>

								<div className="flex-1 min-w-0 min-h-0 flex flex-col">
									{activeFile ? (
										<>
											{activeFile.explanation && (
												<div className="shrink-0 px-3 py-1.5 border-b border-kumo-line text-xs text-kumo-subtle truncate">
													{activeFile.explanation}
												</div>
											)}
											<div className="flex-1 min-h-0">
												<MonacoEditor
													className="h-full"
													path={activeFile.filePath}
													createOptions={{
														value: activeFile.fileContents,
														language:
															activeFile.language ||
															'plaintext',
														readOnly: true,
														minimap: {
															enabled: false,
														},
														lineNumbers: 'on',
														scrollBeyondLastLine: false,
														fontSize: 13,
														automaticLayout: true,
													}}
												/>
											</div>
										</>
									) : (
										<div className="flex-1 flex items-center justify-center">
											<p className="text-sm text-kumo-subtle">
												Select a file to view
											</p>
										</div>
									)}
								</div>
							</div>
						) : (
							<div className="flex-1 flex items-center justify-center">
								<p className="text-sm text-kumo-subtle">
									{app?.agentSummary === null
										? 'Loading code...'
										: 'No code has been generated yet.'}
								</p>
							</div>
						)}
					</div>
				)}

				{activeTab === 'prompt' && (
					<div className="flex-1 min-h-0 flex flex-col overflow-hidden relative">
						<div className="flex-1 min-h-0 overflow-y-auto flex items-center justify-center p-6 sm:p-10">
							{promptText ? (
								<div className="w-full max-w-2xl grid gap-5">
									<div className="flex items-end justify-between gap-4">
										<div className="grid gap-1 min-w-0">
											<div className="flex items-center gap-2">
												<span className="h-lh flex items-center shrink-0">
													<span className="rounded-md bg-brand/10 p-1.5">
														<MessageSquare className="size-3.5 text-kumo-brand" />
													</span>
												</span>
												<h2 className="text-base font-semibold text-text-primary">
													Original prompt
												</h2>
											</div>
											<p className="text-sm text-kumo-subtle pl-9">
												The idea that started this app
											</p>
										</div>
										<Button
											variant="secondary"
											size="sm"
											icon={
												<CopyIcon
													className="size-3.5"
													weight="duotone"
												/>
											}
											onClick={() => {
												void copyPrompt(promptText);
											}}
										>
											Copy
										</Button>
									</div>
									<div className="max-h-[min(60vh,28rem)] overflow-auto rounded-xl bg-kumo-elevated ring ring-kumo-line px-5 py-4">
										<p className="text-sm text-text-primary whitespace-pre-wrap break-words leading-relaxed">
											{promptText}
										</p>
									</div>
								</div>
							) : (
								<div className="grid place-items-center gap-3 text-center">
									<span className="rounded-full bg-kumo-elevated ring ring-kumo-line p-3">
										<MessageSquare className="size-5 text-kumo-subtle" />
									</span>
									<div className="grid gap-1">
										<p className="text-sm font-medium text-text-primary">
											{app?.agentSummary === null
												? 'Loading prompt'
												: 'No prompt available'}
										</p>
										{app?.agentSummary !== null && (
											<p className="text-sm text-kumo-subtle">
												This app has no saved original
												prompt
											</p>
										)}
									</div>
								</div>
							)}
						</div>
					</div>
				)}
			</div>

			{app && (
				<DeleteResource
					open={isDeleteDialogOpen}
					onOpenChange={setIsDeleteDialogOpen}
					resourceType="App"
					resourceName={app.title}
					onDelete={handleDeleteApp}
					isDeleting={isDeleting}
					errorMessage={deleteError}
					className="sm:w-[32rem]"
				/>
			)}

			<GitCloneModal
				open={isGitCloneModalOpen}
				onOpenChange={setIsGitCloneModalOpen}
				appId={app.id}
				appTitle={app.title}
				isPublic={app.visibility === 'public'}
				isOwner={isOwner}
			/>
		</div>
	);
}
