import { useCoreServices } from '@learnitive/context';
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, toast } from '@learnitive/ui-components';
import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
import { ChevronDown, Download, File, Folder, FolderUp, Maximize2, Minimize2, RefreshCw, RotateCcw, SquareTerminal, Trash2 } from 'lucide-react';
import React from 'react';
import {
  createFreshTerminalWorkspace,
  createHomepageTerminalSession,
  createLinuxTerminalSession,
  closeLinuxTerminalSessions,
  destroyLinuxTerminalSession,
  destroyTerminalWorkspace,
  fetchLinuxTerminalFile,
  getAuthState,
  getPublicSaasConfig,
  heartbeatGuestTerminalSession,
  listLinuxTerminalFiles,
  recoverTerminalWorkspace,
  refreshLinuxTerminalToken,
  uploadLinuxTerminalFiles,
  releaseGuestTerminalSessionKeepalive,
  restartTerminalWorkspace,
  SaasApiError,
  type LinuxTerminalEntry,
  type LinuxTerminalSession,
} from '../saas/saas-api';
import { synchronizeSandboxProject } from '../saas/sandbox-project-sync';
import { TerminalProjectControls } from './terminal-project-controls';
import { TERMINAL_CONSOLE_CLASS, TERMINAL_HEADER_CLASS, terminalPanelClass } from './terminal-shell';

function wsUrl(path: string, cols = 120, rows = 34): string {
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  const query = new URLSearchParams({ cols: String(cols), rows: String(rows) });
  return `${protocol}//${window.location.host}/console-gateway${path}?${query}`;
}

function saveBlob(blob: Blob, filename: string): void {
  const url = URL.createObjectURL(blob);
  const anchor = document.createElement('a');
  anchor.href = url;
  anchor.download = filename;
  document.body.append(anchor);
  anchor.click();
  anchor.remove();
  setTimeout(() => URL.revokeObjectURL(url), 10_000);
}

function formatBytes(value?: number): string {
  const bytes = Number(value || 0);
  if (!Number.isFinite(bytes) || bytes <= 0) return '—';
  if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(bytes >= 10_000_000_000 ? 0 : 1)} GB`;
  if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(bytes >= 100_000_000 ? 0 : 1)} MB`;
  return `${Math.round(bytes / 1000)} KB`;
}

function formatRemaining(expiresAt?: string): string {
  if (!expiresAt) return '';
  const ms = new Date(expiresAt).getTime() - Date.now();
  if (!Number.isFinite(ms) || ms <= 0) return 'expired';
  const minutes = Math.max(1, Math.ceil(ms / 60_000));
  if (minutes < 60) return `${minutes}m left`;
  const hours = Math.ceil(minutes / 60);
  if (hours < 48) return `${hours}h left`;
  return `${Math.ceil(hours / 24)}d left`;
}

function formatWaitSeconds(value: unknown): string {
  const seconds = Math.max(0, Math.ceil(Number(value) || 0));
  if (!seconds) return 'a moment';
  if (seconds < 60) return `${seconds} second${seconds === 1 ? '' : 's'}`;
  const minutes = Math.ceil(seconds / 60);
  if (minutes < 60) return `${minutes} minute${minutes === 1 ? '' : 's'}`;
  const hours = Math.ceil(minutes / 60);
  return `${hours} hour${hours === 1 ? '' : 's'}`;
}

type Gate = 'loading' | 'ready' | 'login' | 'disabled' | 'error' | 'recovery' | 'destroyed';
const MAX_REPLAY_CHARS = 250_000;
const TERMINAL_LIFECYCLE_CHANNEL = 'learnitive-terminal';

function terminalGateErrorDescription(error: unknown): string {
  if (error instanceof SaasApiError) {
    const details = error.details || {};
    if (error.code === 'rate_limited') return `Too many terminal requests. You can retry in ${formatWaitSeconds(details.retryAfterSeconds)}.`;
    if (error.code === 'terminal_creation_rate_limit') return `Terminal creation limit reached. ${Number(details.limit) > 0 ? `This plan allows ${Number(details.limit)} new terminal environment${Number(details.limit) === 1 ? '' : 's'} per rolling hour. ` : ''}You can create another in ${formatWaitSeconds(details.retryAfterSeconds)}.`;
    if (error.code === 'terminal_session_limit') return `Concurrent terminal safety limit reached. You already have ${Number(details.active) || 'an'} active terminal session${Number(details.active) === 1 ? '' : 's'} attached to this environment. Close active sessions and retry, or destroy the environment to reset it.`;
    if (error.code === 'terminal_busy') return 'Cloud Terminal is at deployment capacity right now. Retry shortly.';
    if (error.code === 'terminal_gateway_unavailable' || error.code === 'sandbox_create_failed') return 'Cloud Terminal could not start its Ubuntu environment. Retry shortly; if the problem continues, the runtime service needs attention.';
    if (error.code === 'terminal_allocation_expired') return 'This Cloud Terminal allocation expired. Create a fresh terminal to continue.';
    if (error.code === 'terminal_environment_destroyed') return 'This terminal environment was deliberately destroyed. Create a new terminal to start a fresh Ubuntu environment.';
    if (error.status >= 500) return `Cloud Terminal service error (${error.code}). Retry shortly.`;
    return `Cloud Terminal could not start (${error.code}).`;
  }
  if (error instanceof Error && error.message) return `Cloud Terminal could not start: ${error.message}`;
  return 'The Cloud Terminal service is temporarily unavailable.';
}

export type CloudTerminalProps = {
  wsName?: string;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  context?: 'project' | 'homepage';
  embedded?: boolean;
  initialFiles?: Record<string, string>;
  initialDirectory?: string;
  initialCommand?: string;
  sessionFactory?: () => Promise<LinuxTerminalSession>;
  onSessionChange?: (session: LinuxTerminalSession | undefined) => void;
};

export function LinuxTerminalPro({
  wsName,
  open,
  onOpenChange,
  context = 'project',
  embedded = false,
  initialFiles,
  initialDirectory,
  initialCommand,
  sessionFactory,
  onSessionChange,
}: CloudTerminalProps) {
  const core = useCoreServices();
  const { workspaceOps } = core;
  const homepage = context === 'homepage';
  const [gate, setGate] = React.useState<Gate>('loading');
  const [session, setSession] = React.useState<LinuxTerminalSession>();
  const [attachedProjectId, setAttachedProjectId] = React.useState<string>();
  const [connected, setConnected] = React.useState(false);
  const [maximized, setMaximized] = React.useState(false);
  const [filesOpen, setFilesOpen] = React.useState(false);
  const [directory, setDirectory] = React.useState('.');
  const [entries, setEntries] = React.useState<LinuxTerminalEntry[]>([]);
  const [configTools, setConfigTools] = React.useState<string[]>([]);
  const [errorMessage, setErrorMessage] = React.useState('');
  const [errorCode, setErrorCode] = React.useState('');
  const [recoveryDetails, setRecoveryDetails] = React.useState<Record<string, unknown>>({});
  const [confirmFresh, setConfirmFresh] = React.useState(false);
  const [confirmDestroy, setConfirmDestroy] = React.useState(false);
  const socketRef = React.useRef<WebSocket | null>(null);
  const sessionRef = React.useRef<LinuxTerminalSession | null>(null);
  const terminalHostRef = React.useRef<HTMLDivElement | null>(null);
  const terminalRef = React.useRef<Terminal | null>(null);
  const fitAddonRef = React.useRef<FitAddon | null>(null);
  const resizeObserverRef = React.useRef<ResizeObserver | null>(null);
  const terminalDisposablesRef = React.useRef<Array<{ dispose: () => void }>>([]);
  const replayBufferRef = React.useRef('');
  const lessonBootstrapRef = React.useRef<string>('');

  React.useEffect(() => { onSessionChange?.(session); }, [onSessionChange, session]);

  const writeTerminal = React.useCallback((text: string) => {
    replayBufferRef.current = `${replayBufferRef.current}${text}`.slice(-MAX_REPLAY_CHARS);
    terminalRef.current?.write(text);
  }, []);

  const sendResize = React.useCallback((cols: number, rows: number) => {
    const socket = socketRef.current;
    if (!socket || socket.readyState !== WebSocket.OPEN) return;
    socket.send(JSON.stringify({ type: 'resize', cols, rows }));
  }, []);

  const disposeTerminal = React.useCallback(() => {
    resizeObserverRef.current?.disconnect();
    resizeObserverRef.current = null;
    for (const disposable of terminalDisposablesRef.current.splice(0)) disposable.dispose();
    terminalRef.current?.dispose();
    terminalRef.current = null;
    fitAddonRef.current = null;
  }, []);

  const initializeTerminal = React.useCallback(() => {
    const host = terminalHostRef.current;
    if (!host || terminalRef.current) return;
    const terminal = new Terminal({
      cursorBlink: true,
      cursorStyle: 'block',
      convertEol: false,
      scrollback: 10_000,
      fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
      fontSize: 13,
      lineHeight: 1.2,
      theme: {
        background: '#300a24',
        foreground: '#eeeeec',
        cursor: '#fce94f',
        cursorAccent: '#300a24',
        selectionBackground: '#fce94f33',
        black: '#2e3436', red: '#cc0000', green: '#4e9a06', yellow: '#c4a000', blue: '#3465a4', magenta: '#75507b', cyan: '#06989a', white: '#d3d7cf',
        brightBlack: '#555753', brightRed: '#ef2929', brightGreen: '#8ae234', brightYellow: '#fce94f', brightBlue: '#729fcf', brightMagenta: '#ad7fa8', brightCyan: '#34e2e2', brightWhite: '#eeeeec',
      },
    });
    const fitAddon = new FitAddon();
    terminal.loadAddon(fitAddon);
    terminal.open(host);
    terminalRef.current = terminal;
    fitAddonRef.current = fitAddon;
    if (replayBufferRef.current) terminal.write(replayBufferRef.current);
    terminalDisposablesRef.current.push(
      terminal.onData((data) => {
        const socket = socketRef.current;
        if (socket?.readyState === WebSocket.OPEN) socket.send(data);
      }),
      terminal.onResize(({ cols, rows }) => sendResize(cols, rows)),
    );
    const fit = () => {
      try {
        fitAddon.fit();
        sendResize(terminal.cols, terminal.rows);
      } catch {
        // The host can be temporarily detached during a panel transition.
      }
    };
    if (typeof ResizeObserver !== 'undefined') {
      const observer = new ResizeObserver(() => fit());
      observer.observe(host);
      resizeObserverRef.current = observer;
    }
    window.requestAnimationFrame(() => {
      fit();
      terminal.focus();
    });
  }, [sendResize]);

  const closeSocket = React.useCallback(() => {
    const socket = socketRef.current;
    socketRef.current = null;
    if (socket && socket.readyState < WebSocket.CLOSING) socket.close();
    setConnected(false);
  }, []);

  const connect = React.useCallback(async (activeSession: LinuxTerminalSession) => {
    closeSocket();
    let credentials = activeSession;
    try {
      const refreshed = await refreshLinuxTerminalToken(activeSession.sessionId);
      credentials = { ...activeSession, ...refreshed };
    } catch {
      // A freshly-created one-time token remains usable when token refresh races creation.
    }
    const terminal = terminalRef.current;
    const socket = new WebSocket(wsUrl(credentials.wsPath, terminal?.cols, terminal?.rows), ['learnitive-console', `learnitive-auth.${credentials.token}`]);
    socketRef.current = socket;
    socket.addEventListener('open', () => {
      setConnected(true);
      const activeTerminal = terminalRef.current;
      if (activeTerminal) sendResize(activeTerminal.cols, activeTerminal.rows);
      activeTerminal?.focus();
      if (lessonBootstrapRef.current !== credentials.sessionId && (initialDirectory || initialCommand)) {
        lessonBootstrapRef.current = credentials.sessionId;
        const shellQuote = (value: string) => `'${value.replace(/'/gu, `'\''`)}'`;
        const parts: string[] = [];
        if (initialDirectory) parts.push(`cd -- ${shellQuote(initialDirectory)}`);
        if (initialCommand) parts.push(initialCommand);
        const command = parts.join(' && ');
        if (command) window.setTimeout(() => { if (socket.readyState === WebSocket.OPEN) socket.send(`${command}\r`); }, 120);
      }
    });
    socket.addEventListener('message', (event) => writeTerminal(String(event.data ?? '')));
    socket.addEventListener('close', () => setConnected(false));
    socket.addEventListener('error', () => {
      setConnected(false);
      writeTerminal('\r\n[Learnitive Terminal connection error]\r\n');
    });
  }, [closeSocket, initialCommand, initialDirectory, sendResize, writeTerminal]);

  const createSession = React.useCallback(async (): Promise<LinuxTerminalSession> => {
    if (sessionFactory) {
      setAttachedProjectId(undefined);
      return sessionFactory();
    }
    if (homepage) {
      setAttachedProjectId(undefined);
      const created = await createHomepageTerminalSession();
      const files = Object.entries(initialFiles || {}).map(([path, content]) => ({ path, content }));
      if (files.length) await uploadLinuxTerminalFiles(created.sessionId, files);
      return created;
    }
    if (!wsName) throw new Error('Project terminal requires a project workspace.');
    const workspace = await workspaceOps.getWorkspaceInfo(wsName);
    const projectId = typeof workspace?.metadata?.projectId === 'string' ? workspace.metadata.projectId : undefined;
    if (!projectId) throw new Error('This project is missing its local stable project ID. Reopen it from Projects and try again.');
    setAttachedProjectId(projectId);
    try {
      const sync = await synchronizeSandboxProject(core, wsName, projectId);
      if (sync.conflicts.length) toast.warning(`${sync.conflicts.length} project file conflict${sync.conflicts.length === 1 ? '' : 's'} detected before opening the terminal.`);
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown synchronization error';
      toast.warning(`Terminal will start without the latest project sync: ${message}`);
    }
    return createLinuxTerminalSession(projectId, wsName);
  }, [core, homepage, initialFiles, sessionFactory, workspaceOps, wsName]);

  const ensureSession = React.useCallback(async () => {
    setErrorMessage('');
    setErrorCode('');
    setRecoveryDetails({});
    setConfirmFresh(false);
    setGate('loading');
    try {
      const [auth, publicConfig] = await Promise.all([getAuthState(), getPublicSaasConfig()]);
      setConfigTools(publicConfig.linuxTerminal?.tools || []);
      if (!publicConfig.linuxTerminal?.enabled) { setGate('disabled'); return; }
      if (!homepage && !auth.authenticated) { setGate('login'); return; }
      setGate('ready');
      if (sessionRef.current) {
        setSession(sessionRef.current);
        if (socketRef.current?.readyState !== WebSocket.OPEN) await connect(sessionRef.current);
        return;
      }
      const created = await createSession();
      sessionRef.current = created;
      setSession(created);
      replayBufferRef.current = '';
      await connect(created);
    } catch (error) {
      if (error instanceof SaasApiError && error.code === 'terminal_workspace_recovery_available') {
        setRecoveryDetails(error.details || {});
        setGate('recovery');
        return;
      }
      if (error instanceof SaasApiError && error.code === 'terminal_environment_destroyed') {
        setGate('destroyed');
        return;
      }
      const message = terminalGateErrorDescription(error);
      setErrorMessage(message);
      setErrorCode(error instanceof SaasApiError ? error.code : '');
      setGate('error');
      writeTerminal(`\r\n${error instanceof Error ? error.message : message}\r\n`);
    }
  }, [connect, core, createSession, homepage, writeTerminal, wsName]);

  React.useEffect(() => {
    if (open) void ensureSession();
  }, [ensureSession, open]);

  React.useEffect(() => {
    if (!open || gate !== 'ready') {
      disposeTerminal();
      return;
    }
    initializeTerminal();
    return () => disposeTerminal();
  }, [disposeTerminal, gate, initializeTerminal, open]);

  React.useEffect(() => () => {
    disposeTerminal();
    closeSocket();
    const activeSession = sessionRef.current;
    if (!activeSession) return;
    if (activeSession.guest) releaseGuestTerminalSessionKeepalive(activeSession.sessionId);
    else void destroyLinuxTerminalSession(activeSession.sessionId).catch(() => undefined);
  }, [closeSocket, disposeTerminal]);

  React.useEffect(() => {
    if (!open || !session?.guest) return;
    const beat = () => void heartbeatGuestTerminalSession(session.sessionId).catch(() => undefined);
    beat();
    const interval = window.setInterval(beat, 20_000);
    const handlePageHide = (event: PageTransitionEvent) => {
      if (event.persisted) return;
      releaseGuestTerminalSessionKeepalive(session.sessionId);
    };
    window.addEventListener('pagehide', handlePageHide);
    return () => {
      window.clearInterval(interval);
      window.removeEventListener('pagehide', handlePageHide);
    };
  }, [open, session?.guest, session?.sessionId]);

  React.useEffect(() => {
    if (typeof BroadcastChannel === 'undefined') return;
    const channel = new BroadcastChannel(TERMINAL_LIFECYCLE_CHANNEL);
    const handleMessage = (event: MessageEvent) => {
      const data = event.data as { type?: string } | null;
      if (data?.type !== 'environment-destroyed') return;
      closeSocket();
      disposeTerminal();
      sessionRef.current = null;
      setSession(undefined);
      setEntries([]);
      replayBufferRef.current = '';
      setConfirmDestroy(false);
      setGate('destroyed');
    };
    channel.addEventListener('message', handleMessage);
    return () => { channel.removeEventListener('message', handleMessage); channel.close(); };
  }, [closeSocket, disposeTerminal]);

  const recreateSession = React.useCallback(async () => {
    closeSocket();
    disposeTerminal();
    sessionRef.current = null;
    setSession(undefined);
    replayBufferRef.current = '';
    setEntries([]);
    setGate('loading');
    await ensureSession();
  }, [closeSocket, disposeTerminal, ensureSession]);

  const closeActiveSessionsAndRetry = React.useCallback(async () => {
    try {
      const result = await closeLinuxTerminalSessions();
      const count = Number(result.closedSessions || 0);
      toast.success(`Closed ${count} terminal session${count === 1 ? '' : 's'}. The environment and workspace were preserved.`);
      await recreateSession();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not close active terminal sessions.');
    }
  }, [recreateSession]);

  const restartWorkspace = React.useCallback(async () => {
    try {
      await restartTerminalWorkspace();
      toast.success('Terminal restarted. Temporary workspace preserved.');
      await recreateSession();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not restart terminal.');
    }
  }, [recreateSession]);

  const destroyWorkspace = React.useCallback(async () => {
    try {
      const result = await destroyTerminalWorkspace();
      if (result.containerRemoved !== true) throw new Error('The terminal service did not confirm that the Docker environment was removed.');
      closeSocket();
      disposeTerminal();
      sessionRef.current = null;
      setSession(undefined);
      setEntries([]);
      replayBufferRef.current = '';
      setConfirmDestroy(false);
      setGate('destroyed');
      if (typeof BroadcastChannel !== 'undefined') {
        const channel = new BroadcastChannel(TERMINAL_LIFECYCLE_CHANNEL);
        channel.postMessage({ type: 'environment-destroyed', generation: result.generation, destroyedAt: result.destroyedAt });
        channel.close();
      }
      toast.success('Ubuntu environment and temporary workspace destroyed.');
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not destroy terminal workspace.');
    }
  }, [closeSocket, disposeTerminal]);

  const createFresh = React.useCallback(async () => {
    try {
      setConfirmFresh(false);
      await createFreshTerminalWorkspace();
      await recreateSession();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not create a fresh terminal.');
    }
  }, [recreateSession]);

  const recoverWorkspace = React.useCallback(async () => {
    try {
      await recoverTerminalWorkspace();
      await recreateSession();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not recover the terminal workspace.');
    }
  }, [recreateSession]);

  const refreshFiles = React.useCallback(async (nextDirectory = directory) => {
    if (!session) return;
    try {
      setEntries(await listLinuxTerminalFiles(session.sessionId, nextDirectory));
      setDirectory(nextDirectory);
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not list terminal files.');
    }
  }, [directory, session]);

  const downloadEntry = React.useCallback(async (entry: LinuxTerminalEntry) => {
    if (!session || entry.kind !== 'file') return;
    try {
      const file = await fetchLinuxTerminalFile(session.sessionId, entry.path);
      saveBlob(file.blob, file.filename);
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not download terminal file.');
    }
  }, [session]);

  if (!open) return null;

  if (gate !== 'ready') {
    const description = gate === 'loading'
      ? 'Starting your Learnitive cloud development environment…'
      : gate === 'login'
        ? 'Sign in to use the project-attached terminal. The homepage terminal remains available without an account.'
        : gate === 'recovery'
          ? confirmFresh
            ? 'Start fresh? The retained temporary workspace will be deleted. This cannot be undone.'
            : `Your terminal expired, but your temporary files are still available${recoveryDetails.recoveryRemainingSeconds ? ` for ${formatWaitSeconds(recoveryDetails.recoveryRemainingSeconds)}` : ''}. Recover them into a new terminal, or start with an empty workspace.`
          : gate === 'destroyed'
            ? 'This temporary terminal workspace has been destroyed. Your browser and local project files were not deleted.'
            : gate === 'disabled'
              ? 'Cloud Terminal is not enabled on this Learnitive deployment.'
              : errorMessage || 'The Cloud Terminal service is temporarily unavailable.';
    const content = (
      <div className="flex min-h-[18rem] flex-col items-center justify-center gap-4 bg-[#300a24] p-6 text-center text-[#eeeeec]" data-testid="linux-terminal-gate-dialog">
        <SquareTerminal className="size-8 text-[#fce94f]" />
        <div><strong className="block text-base">Learnitive · Ubuntu</strong><p className="mt-2 max-w-xl text-sm text-[#d3d7cf]">{description}</p></div>
        {configTools.length > 0 && <p className="max-w-xl text-xs text-[#d3d7cf]">Environment: {configTools.join(' · ')} <a className="ml-1 text-[#fce94f] underline underline-offset-2 hover:text-white" href="/software-list">View software</a></p>}
        <div className="flex flex-wrap justify-center gap-2">
          {gate === 'login' && <Button onClick={() => { window.location.href = `/login?returnTo=${encodeURIComponent(window.location.pathname + window.location.hash)}`; }}>Sign in</Button>}
          {gate === 'recovery' && (confirmFresh
            ? <><Button variant="secondary" onClick={() => setConfirmFresh(false)}>Cancel</Button><Button onClick={() => void createFresh()}>Delete and start fresh</Button></>
            : <><Button onClick={() => void recoverWorkspace()}>Recover workspace</Button><Button variant="secondary" onClick={() => setConfirmFresh(true)}>Start fresh</Button></>)}
          {gate === 'destroyed' && <Button onClick={() => void createFresh()}>Create new terminal</Button>}
          {gate === 'error' && errorCode === 'terminal_session_limit' && <>
            <Button onClick={() => void closeActiveSessionsAndRetry()}>Close active sessions &amp; retry</Button>
            <Button variant="secondary" onClick={() => setConfirmDestroy(true)}>Destroy environment</Button>
          </>}
          {(gate === 'disabled' || (gate === 'error' && errorCode !== 'terminal_session_limit')) && <Button variant="secondary" onClick={() => void ensureSession()}><RefreshCw className="mr-1 size-4" />Retry</Button>}
          {!embedded && <Button variant="secondary" onClick={() => onOpenChange(false)}>Close</Button>}
        </div>
      </div>
    );
    const destroyConfirmation = (
      <Dialog open={confirmDestroy} onOpenChange={setConfirmDestroy}>
        <DialogContent className="sm:max-w-md">
          <DialogHeader><DialogTitle>Destroy your Learnitive environment?</DialogTitle><DialogDescription>This removes your current Ubuntu container and temporary cloud workspace for all Learnitive tabs and projects. Closing terminal sessions does not do this. Your browser/local project files are not deleted.</DialogDescription></DialogHeader>
          <DialogFooter><Button variant="secondary" onClick={() => setConfirmDestroy(false)}>Cancel</Button><Button variant="destructive" onClick={() => void destroyWorkspace()}>Destroy environment</Button></DialogFooter>
        </DialogContent>
      </Dialog>
    );
    if (embedded) return <><section className={`${terminalPanelClass(false, true)}${homepage ? ' interactive-home-cloud-terminal' : ''}`}>{content}</section>{destroyConfirmation}</>;
    return (
      <>
        <Dialog open onOpenChange={(nextOpen) => { if (!nextOpen) onOpenChange(false); }}>
          <DialogContent className="interactive-linux-terminal-dialog overflow-hidden p-0 sm:max-w-2xl"><DialogHeader className="sr-only"><DialogTitle>Learnitive Terminal</DialogTitle><DialogDescription>{description}</DialogDescription></DialogHeader>{content}</DialogContent>
        </Dialog>
        {destroyConfirmation}
      </>
    );
  }

  const expiresAt = session?.sandboxExpiresAt || session?.expiresAt;
  const policy = session?.policy;

  return (
    <section className={`${terminalPanelClass(maximized, embedded)}${homepage && embedded ? ' interactive-home-cloud-terminal' : ''}`} data-maximized={maximized ? 'true' : 'false'} data-testid="linux-terminal-pro" aria-label="Terminal">
      <header className={TERMINAL_HEADER_CLASS}>
        <SquareTerminal className="size-4 text-[#fce94f]" /><strong>LEARNITIVE · UBUNTU</strong>
        <span className={`rounded px-1.5 py-0.5 text-[10px] ${connected ? 'bg-emerald-400/15 text-emerald-200' : 'bg-white/10 text-[#d3d7cf]'}`}>{connected ? 'Connected' : 'Disconnected'}</span>
        <div className="ml-auto flex items-center gap-1">
          <Button size="sm" variant={filesOpen ? 'secondary' : 'ghost'} className="text-inherit" disabled={!session} onClick={() => { const next = !filesOpen; setFilesOpen(next); if (next) void refreshFiles('.'); }}><Folder className="mr-1 size-3.5" />Files</Button>
          <Button size="icon" variant="ghost" className="text-inherit" aria-label="Restart Learnitive Terminal" title="Restart runtime and preserve the temporary workspace" onClick={() => void restartWorkspace()}><RotateCcw className="size-4" /></Button>
          <Button size="icon" variant="ghost" className="text-inherit" aria-label={maximized ? 'Restore Learnitive Terminal' : 'Maximize Learnitive Terminal'} onClick={() => setMaximized((value) => !value)}>{maximized ? <Minimize2 className="size-4" /> : <Maximize2 className="size-4" />}</Button>
          <Button size="icon" variant="ghost" className="text-inherit" aria-label="Destroy Learnitive Terminal workspace" title="Destroy runtime and temporary cloud files" onClick={() => setConfirmDestroy(true)}><Trash2 className="size-4" /></Button>
          {!embedded && <Button size="icon" variant="ghost" className="text-inherit" aria-label="Minimize Learnitive Terminal" onClick={() => onOpenChange(false)}><ChevronDown className="size-4" /></Button>}
        </div>
      </header>
      <div className="flex min-h-0 flex-1">
        <div className={`${TERMINAL_CONSOLE_CLASS} relative`}>
          <div ref={terminalHostRef} className="min-h-0 flex-1 overflow-hidden p-2" data-testid="linux-terminal-output" aria-label="Learnitive Linux terminal" />
          {!connected && session && (
            <div className="absolute right-3 top-3"><Button size="sm" variant="secondary" onClick={() => void connect(session)}><RefreshCw className="mr-1 size-3.5" />Reconnect</Button></div>
          )}
        </div>
        {filesOpen && <aside className="w-[min(24rem,45vw)] shrink-0 overflow-auto border-l border-white/10 bg-[#24101f] p-2 text-[#eeeeec]" aria-label="Learnitive Terminal files">
          <div className="mb-2 flex items-center gap-1"><strong className="min-w-0 flex-1 truncate text-xs">/workspace{directory === '.' ? '' : `/${directory}`}</strong><Button size="icon" variant="ghost" className="text-inherit" aria-label="Refresh Learnitive Terminal files" onClick={() => void refreshFiles()}><RefreshCw className="size-3.5" /></Button></div>
          {directory !== '.' && <button type="button" className="mb-1 flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs hover:bg-white/10" onClick={() => { const parent = directory.includes('/') ? directory.slice(0, directory.lastIndexOf('/')) || '.' : '.'; void refreshFiles(parent); }}><FolderUp className="size-3.5" />..</button>}
          {entries.map((entry) => <div key={entry.path} className="flex items-center gap-2 rounded px-2 py-1.5 text-xs hover:bg-white/10">{entry.kind === 'directory' ? <Folder className="size-3.5" /> : <File className="size-3.5" />}<button type="button" className="min-w-0 flex-1 truncate text-left" onClick={() => entry.kind === 'directory' ? void refreshFiles(entry.path) : void downloadEntry(entry)}>{entry.name}</button>{entry.kind === 'file' && <><span className="text-[10px] text-[#d3d7cf]">{entry.size.toLocaleString()} B</span><Button size="icon" variant="ghost" className="text-inherit" aria-label={`Download ${entry.name}`} onClick={() => void downloadEntry(entry)}><Download className="size-3.5" /></Button></>}</div>)}
          {!entries.length && <p className="p-2 text-xs text-[#d3d7cf]">No files here yet.</p>}
        </aside>}
      </div>
      <footer className="flex shrink-0 flex-wrap items-center gap-x-2 gap-y-1 border-t border-white/10 bg-[#2c071f] px-2 py-1 text-[11px] text-[#d3d7cf]">
        {session && <TerminalProjectControls core={core} session={session} showLoadProjects={homepage} attachedProject={!homepage && wsName && attachedProjectId ? { wsName, projectId: attachedProjectId } : undefined} onWorkspaceChanged={() => { if (filesOpen) void refreshFiles('.'); }} />}
        <span className="ml-1">Workspace {formatBytes(policy?.storageLimitBytes)}</span>
        <span>Network {policy?.networkMode === 'packages-git' ? 'Packages + Git' : policy?.networkMode || 'Off'}</span>
        <span>{formatRemaining(expiresAt)}</span>
        <span className="ml-auto">Permanent files stay in your browser/local folder; this cloud workspace is temporary.</span>
      </footer>
      <Dialog open={confirmDestroy} onOpenChange={setConfirmDestroy}>
        <DialogContent className="sm:max-w-md">
          <DialogHeader><DialogTitle>Destroy terminal?</DialogTitle><DialogDescription>This permanently removes the current runtime and its temporary cloud workspace. Your browser/local project files are not deleted. Sync or download anything you need first.</DialogDescription></DialogHeader>
          <DialogFooter><Button variant="secondary" onClick={() => setConfirmDestroy(false)}>Cancel</Button><Button variant="destructive" onClick={() => void destroyWorkspace()}>Destroy terminal</Button></DialogFooter>
        </DialogContent>
      </Dialog>
    </section>
  );
}
