import { useCoreServices } from '@learnitive/context';
import { projectModeForProfile, type ExecutionDiagnostic, type ProjectProfile } from '@learnitive/project-runtime';
import { Button } from '@learnitive/ui-components';
import { WsPath } from '@learnitive/ws-path';
import { useAtomValue } from 'jotai';
import { AlertCircle, CheckCircle2, Clock3, Keyboard, TerminalSquare, X } from 'lucide-react';
import React from 'react';
import { requestProjectEditorReveal } from '../project-compile-ui';
import {
  getProjectSandboxPreviewAccess,
  listProjectSandboxProcesses,
  projectSandboxPreviewUrl,
  startProjectSandboxProcess,
} from '../saas/saas-api';
import { synchronizeSandboxProject } from '../saas/sandbox-project-sync';
import { clearPreviewDiagnostics, recordPreviewDiagnostic } from '../saas/preview-diagnostics';

type PreviewConsoleEntry = { level: 'log' | 'info' | 'warn' | 'error'; text: string };

function diagnosticLocation(diagnostic: ExecutionDiagnostic): string {
  if (!diagnostic.file) return '';
  return `${diagnostic.file}${diagnostic.line ? `:${diagnostic.line}${diagnostic.column ? `:${diagnostic.column}` : ''}` : ''}`;
}

function sandboxPreviewProcess(profile: ProjectProfile | undefined, projectId: string): { command: string; port: number } | undefined {
  const projectType = String(profile?.projectType || '');
  const nodeInstall = 'test -d node_modules || npm install --no-audit --no-fund';
  if (['react-vite', 'typescript-vite', 'vue-vite', 'svelte-vite'].includes(projectType)) {
    return { command: `${nodeInstall}; exec npx vite --host 0.0.0.0 --port 5173 --base /api/projects/${projectId}/processes/dev/preview/`, port: 5173 };
  }
  if (projectType === 'next') return { command: `${nodeInstall}; exec npm run dev -- -p 3000`, port: 3000 };
  if (projectType === 'express') return { command: `${nodeInstall}; PORT=3000 exec node server.js`, port: 3000 };
  const pythonEnv = 'mkdir -p .learnitive; test -x .learnitive/venv/bin/python || python -m venv .learnitive/venv; .learnitive/venv/bin/pip install -q -r requirements.txt';
  if (projectType === 'flask') return { command: `${pythonEnv}; exec .learnitive/venv/bin/python app.py`, port: 3000 };
  if (projectType === 'fastapi') return { command: `${pythonEnv}; exec .learnitive/venv/bin/uvicorn main:app --host 0.0.0.0 --port 3000`, port: 3000 };
  if (projectType === 'django') return { command: `${pythonEnv}; exec .learnitive/venv/bin/python manage.py runserver 0.0.0.0:3000`, port: 3000 };
  return undefined;
}

export function ProjectExecutionPane({ wsName }: { wsName: string }) {
  const core = useCoreServices();
  const { navigation, projectCompileOrchestrator, projectExecution } = core;
  const state = useAtomValue(projectCompileOrchestrator.$state);
  const [tab, setTab] = React.useState<'preview' | 'console' | 'input'>('console');
  const [stdin, setStdin] = React.useState(() => projectExecution.getStdin(wsName));
  const [previewConsole, setPreviewConsole] = React.useState<PreviewConsoleEntry[]>([]);
  const [livePreviewUrl, setLivePreviewUrl] = React.useState<string>();
  const [livePreviewStatus, setLivePreviewStatus] = React.useState<'idle' | 'starting' | 'ready' | 'error'>('idle');
  const frameRef = React.useRef<HTMLIFrameElement>(null);
  const previewProjectIdRef = React.useRef<string>();
  const relevant = state.wsName === wsName ? state : undefined;
  const projectMode = projectModeForProfile(relevant?.profile);
  const isWebProject = projectMode?.projectType === 'web';
  const isMarkdownProject = projectMode?.id === 'markdown' || relevant?.profile?.projectType === 'markdown';
  const supportsStdin = Boolean(projectMode?.supportsStdin);
  const previewAvailable = (relevant?.resultKind === 'web-preview' || relevant?.resultKind === 'markdown-preview') && Boolean(relevant.previewHtml);

  React.useEffect(() => {
    setStdin(projectExecution.getStdin(wsName));
    setTab('console');
  }, [projectExecution, wsName]);

  React.useEffect(() => {
    if (tab === 'input' && !supportsStdin) setTab(isWebProject || isMarkdownProject ? 'preview' : 'console');
  }, [isMarkdownProject, isWebProject, supportsStdin, tab]);

  React.useEffect(() => {
    if (!relevant || relevant.phase !== 'success' || !isWebProject || isMarkdownProject) {
      setLivePreviewUrl(undefined);
      setLivePreviewStatus('idle');
      return;
    }
    let cancelled = false;
    const run = async () => {
      setLivePreviewStatus('starting');
      const workspace = await core.workspaceOps.getWorkspaceInfo(wsName);
      const projectId = typeof workspace?.metadata?.projectId === 'string' ? workspace.metadata.projectId : undefined;
      if (!projectId) { setLivePreviewStatus('idle'); return; }
      previewProjectIdRef.current = projectId;
      const processSpec = sandboxPreviewProcess(relevant.profile, projectId);
      if (!processSpec) { setLivePreviewStatus('idle'); return; }
      await synchronizeSandboxProject(core, wsName, projectId);
      const current = await listProjectSandboxProcesses(projectId).catch(() => []);
      const existing = current.find((item) => item.processKey === 'dev' && item.status === 'running');
      if (!existing) {
        await startProjectSandboxProcess(projectId, { processKey: 'dev', ...processSpec });
      }
      const url = projectSandboxPreviewUrl(projectId, 'dev');
      for (let attempt = 0; attempt < 40 && !cancelled; attempt += 1) {
        try {
          const response = await fetch(`${url}?health=${Date.now()}`, { credentials: 'same-origin', cache: 'no-store' });
          if (response.ok) {
            const access = await getProjectSandboxPreviewAccess(projectId, 'dev');
            const separator = access.url.includes('?') ? '&' : '?';
            setLivePreviewUrl(`${access.url}${separator}run=${encodeURIComponent(String(relevant.startedAt ?? Date.now()))}`);
            setLivePreviewStatus('ready');
            return;
          }
        } catch { /* process may still be installing dependencies */ }
        await new Promise((resolve) => setTimeout(resolve, 500));
      }
      if (!cancelled) setLivePreviewStatus('error');
    };
    void run().catch((error) => {
      console.warn('[Learnitive] Live Preview unavailable; static build Preview remains available.', error);
      if (!cancelled) setLivePreviewStatus('error');
    });
    return () => { cancelled = true; };
  }, [core, isMarkdownProject, isWebProject, relevant?.phase, relevant?.startedAt, relevant?.profile, wsName]);

  React.useEffect(() => {
    if (!relevant) return;
    setTab((isWebProject || isMarkdownProject || relevant.resultKind === 'web-preview' || relevant.resultKind === 'markdown-preview') && relevant.phase !== 'error' ? 'preview' : 'console');
    setPreviewConsole([]);
    if (previewProjectIdRef.current) clearPreviewDiagnostics(previewProjectIdRef.current);
  }, [isMarkdownProject, isWebProject, relevant?.startedAt, relevant?.resultKind, relevant?.phase === 'error']);

  React.useEffect(() => {
    const onMessage = (event: MessageEvent) => {
      if (event.source !== frameRef.current?.contentWindow) return;
      const payload = event.data as { source?: string; level?: string; text?: string } | undefined;
      if (!payload || payload.source !== 'learnitive-preview' || typeof payload.text !== 'string') return;
      if (!['log', 'info', 'warn', 'error'].includes(payload.level ?? '')) return;
      const entry = { level: payload.level as PreviewConsoleEntry['level'], text: payload.text! };
      setPreviewConsole((current) => [...current.slice(-499), entry]);
      if (previewProjectIdRef.current) recordPreviewDiagnostic(previewProjectIdRef.current, entry);
    };
    window.addEventListener('message', onMessage);
    return () => window.removeEventListener('message', onMessage);
  }, []);

  if (!relevant || !relevant.outputOpen) return null;

  const busy = ['saving', 'queued', 'preparing', 'compiling', 'running'].includes(relevant.phase);
  const failed = relevant.phase === 'error';
  const duration = relevant.duration ?? relevant.result?.duration;
  const stdout = relevant.result?.stdout ?? '';
  const stderr = relevant.result?.stderr ?? '';
  const diagnostics = relevant.result?.diagnostics ?? [];
  const latestPreviewError = [...previewConsole].reverse().find((entry) => entry.level === 'error');

  const openDiagnostic = (diagnostic: ExecutionDiagnostic) => {
    if (!diagnostic.file) return;
    try {
      const wsPath = WsPath.fromParts(wsName, diagnostic.file).wsPath;
      if (diagnostic.line) requestProjectEditorReveal(wsPath, diagnostic.line, diagnostic.column);
      navigation.goWsFile(wsPath);
    } catch {
      // Diagnostics from build tools may refer to generated/outside-root files.
    }
  };

  return (
    <section className="flex min-h-0 min-w-0 flex-1 flex-col border-l bg-muted/10" data-testid="project-execution-pane">
      <header className="flex min-h-10 shrink-0 items-center gap-1 border-b bg-background/95 px-2">
        {(isWebProject || isMarkdownProject) && (
          <Button size="sm" variant={tab === 'preview' ? 'secondary' : 'ghost'} onClick={() => setTab('preview')}>
            Preview
          </Button>
        )}
        <Button size="sm" variant={tab === 'console' ? 'secondary' : 'ghost'} onClick={() => setTab('console')}>
          <TerminalSquare className="mr-1 size-3.5" />Console
        </Button>
        {supportsStdin && (
          <Button size="sm" variant={tab === 'input' ? 'secondary' : 'ghost'} onClick={() => setTab('input')} data-testid="project-input-tab">
            <Keyboard className="mr-1 size-3.5" />Input
          </Button>
        )}
        <span className="ml-auto inline-flex items-center gap-1 text-xs text-muted-foreground" aria-live="polite">
          {busy ? <Clock3 className="size-3.5 animate-pulse" /> : failed ? <AlertCircle className="size-3.5 text-destructive" /> : relevant.phase === 'success' ? <CheckCircle2 className="size-3.5 text-emerald-600" /> : null}
          {relevant.phase === 'saving' ? 'Saving…' : relevant.phase === 'queued' ? 'Queued…' : relevant.phase === 'preparing' ? 'Preparing…' : relevant.phase === 'compiling' ? 'Compiling…' : relevant.phase === 'running' ? 'Running…' : relevant.phase === 'success' ? 'Success' : relevant.phase === 'error' ? 'Error' : relevant.phase === 'cancelled' ? 'Cancelled' : ''}
          {typeof duration === 'number' && duration >= 0 ? ` · ${duration < 1000 ? `${Math.round(duration)} ms` : `${(duration / 1000).toFixed(2)} s`}` : ''}
        </span>
        <Button className="ml-1 h-8 px-2" size="sm" variant="ghost" onClick={() => projectCompileOrchestrator.closeOutput()} aria-label="Close result">
          <X className="mr-1 size-4" /><span className="lg:hidden">Close</span>
        </Button>
      </header>

      {tab === 'input' && supportsStdin ? (
        <div className="min-h-0 flex-1 overflow-auto bg-background p-3" data-testid="project-stdin-pane">
          <label className="flex h-full min-h-64 flex-col gap-2">
            <span className="text-sm font-medium">Standard input</span>
            <span className="text-xs text-muted-foreground">Text entered here is passed to stdin on the next Run. It is kept for this editor session and is not inserted into your source code.</span>
            <textarea
              className="mt-1 min-h-52 flex-1 resize-y rounded-md border bg-muted/20 p-3 font-mono text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
              value={stdin}
              onChange={(event) => {
                const next = event.currentTarget.value.slice(0, 1_000_000);
                setStdin(next);
                projectExecution.setStdin(wsName, next);
              }}
              placeholder="Input passed to the program on the next Run"
              spellCheck={false}
              aria-label="Standard input for next execution"
              data-testid="project-stdin-input"
            />
          </label>
        </div>
      ) : tab === 'preview' && (isWebProject || isMarkdownProject) ? (
        <div className="min-h-0 flex-1 overflow-auto bg-muted/30 p-3" data-testid={isMarkdownProject ? 'markdown-preview-pane' : 'web-preview-pane'}>
          {latestPreviewError ? <div className="mx-auto mb-3 flex w-full items-start justify-between gap-3 rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive" role="alert" data-testid="web-preview-runtime-error">
            <div><div className="font-medium">Web Preview runtime error</div><div className="mt-1 break-words font-mono text-xs">{latestPreviewError.text}</div></div>
            <Button size="sm" variant="outline" onClick={() => setTab('console')}>Console</Button>
          </div> : null}
          {livePreviewStatus === 'starting' ? <div className="mx-auto mb-2 w-full rounded-md border bg-background px-3 py-2 text-xs text-muted-foreground">Starting isolated live Preview… dependencies are restored inside your disposable sandbox when needed.</div> : null}
          {livePreviewStatus === 'error' && previewAvailable ? <div className="mx-auto mb-2 w-full rounded-md border bg-background px-3 py-2 text-xs text-muted-foreground">Live server Preview is unavailable. Showing the verified static build instead.</div> : null}
          {isMarkdownProject && previewAvailable ? <div className="mx-auto h-full min-h-72 w-full overflow-hidden rounded-md border bg-white shadow-sm">
            <iframe
              className="h-full min-h-72 w-full bg-white"
              sandbox="allow-same-origin"
              srcDoc={relevant.previewHtml}
              title="Markdown preview"
            />
          </div> : (livePreviewUrl || previewAvailable) ? <div className="mx-auto h-full min-h-72 w-full overflow-hidden rounded-md border bg-white shadow-sm">
            <iframe
              ref={frameRef}
              className="h-full min-h-72 w-full bg-white"
              sandbox="allow-scripts allow-forms allow-modals allow-downloads"
              {...(livePreviewUrl ? { src: livePreviewUrl } : { srcDoc: relevant.previewHtml })}
              title="Learnitive preview"
            />
          </div> : <div className="flex min-h-72 items-center justify-center rounded-md border border-dashed bg-background p-6 text-center text-sm text-muted-foreground">
            {busy ? (isMarkdownProject ? 'Rendering Markdown Preview…' : 'Building Web Preview…') : failed ? (isMarkdownProject ? 'Markdown Preview is unavailable until rendering succeeds.' : 'Web Preview is unavailable until the build succeeds.') : (isMarkdownProject ? 'Markdown Preview is ready. Use Preview to render this document.' : 'Web Preview is ready. Use the top execution action to build or refresh this project.')}
          </div>}
        </div>
      ) : (
        <div className="min-h-0 flex-1 overflow-auto bg-[color-mix(in_oklab,var(--foreground)_5%,var(--background))] p-3 font-mono text-[12px] leading-5" data-testid="project-console-output">
          {relevant.error && (
            <div className="mb-3 rounded-md border border-destructive/30 bg-destructive/5 p-2 font-sans text-sm text-destructive" role="alert">
              <div className="font-medium">{relevant.error}</div>
              {(relevant.errorCode || relevant.executionId) && (
                <div className="mt-1 text-xs opacity-80">
                  {relevant.errorCode ? `${relevant.errorCode}` : ''}{relevant.executionId ? `${relevant.errorCode ? ' · ' : ''}Reference ${relevant.executionId}` : ''}
                </div>
              )}
            </div>
          )}
          {stdout && <pre className="whitespace-pre-wrap break-words text-foreground">{stdout}</pre>}
          {stderr && <pre className="mt-2 whitespace-pre-wrap break-words text-destructive">{stderr}</pre>}
          {previewConsole.map((entry, index) => (
            <div key={`${index}:${entry.text}`} className={entry.level === 'error' ? 'text-destructive' : entry.level === 'warn' ? 'text-amber-700 dark:text-amber-400' : 'text-foreground'}>
              <span className="mr-2 select-none opacity-50">{entry.level}</span>{entry.text}
            </div>
          ))}
          {diagnostics.length > 0 && (
            <div className="mt-4 border-t pt-3 font-sans">
              <div className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">Diagnostics</div>
              <div className="space-y-1.5">
                {diagnostics.map((diagnostic, index) => {
                  const location = diagnosticLocation(diagnostic);
                  return (
                    <button
                      key={`${index}:${diagnostic.message}`}
                      className="block w-full rounded border bg-background/70 px-2 py-1.5 text-left text-xs hover:bg-muted disabled:cursor-default"
                      disabled={!diagnostic.file}
                      onClick={() => openDiagnostic(diagnostic)}
                      type="button"
                    >
                      <span className={diagnostic.severity === 'error' ? 'font-medium text-destructive' : diagnostic.severity === 'warning' ? 'font-medium text-amber-700 dark:text-amber-400' : 'font-medium'}>{diagnostic.severity}</span>
                      {location && <span className="ml-2 font-mono text-primary underline-offset-2 group-hover:underline">{location}</span>}
                      <span className="ml-2 text-foreground">{diagnostic.message}</span>
                    </button>
                  );
                })}
              </div>
            </div>
          )}
          {!busy && !relevant.error && !stdout && !stderr && previewConsole.length === 0 && diagnostics.length === 0 && (
            <div className="font-sans text-sm text-muted-foreground">No console output.</div>
          )}
        </div>
      )}
    </section>
  );
}
