'use client';

import {
  useState,
  useEffect,
  useMemo,
  useRef,
  useCallback,
  type PointerEvent as ReactPointerEvent,
  type MouseEvent as ReactMouseEvent,
} from 'react';
import { Group, Text, Box, ActionIcon, Tooltip, Badge, Loader } from '@tidbcloud/uikit';
import { message } from 'antd';
import dynamic from 'next/dynamic';
import { useWorkspace } from '@/lib/workspace';
import { fetchWithAuth } from '@/lib/auth';
import { useTranslation } from '@/lib/i18n';

// Dynamically import Monaco to avoid SSR issues
const Editor = dynamic(() => import('@monaco-editor/react'), { ssr: false });

const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? '';

/** Previewable vs binary is decided by the API (415 FILE_NOT_PREVIEWABLE); we only branch on Content-Type. */

/** pprof / browsers may emit XML declaration, DOCTYPE, then <svg> — do not require ^<svg. */
function looksLikeSvgMarkup(s: string): boolean {
  if (!s) return false;
  return /<svg\b/i.test(s.slice(0, 65536));
}

function getLanguage(path: string): string {
  if (path.endsWith('.json')) return 'json';
  if (path.endsWith('.ts') || path.endsWith('.tsx')) return 'typescript';
  if (path.endsWith('.js') || path.endsWith('.jsx')) return 'javascript';
  if (path.endsWith('.py')) return 'python';
  if (path.endsWith('.md')) return 'markdown';
  if (path.endsWith('.yaml') || path.endsWith('.yml')) return 'yaml';
  return 'plaintext';
}

function getFileName(path: string): string {
  return path.split('/').pop() || path;
}

function downloadBlobAsFile(blob: Blob, filename: string) {
  const objUrl = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = objUrl;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(objUrl);
}

function isFailedTextLoad(content: string): boolean {
  return content.startsWith('// Failed to load');
}

const MIN_ZOOM = 0.25;
const MAX_ZOOM = 8;

/**
 * Interactive SVG (pprof flame graphs, etc.): blob URL in an iframe so embedded scripts run.
 * React innerHTML does not execute &lt;script&gt;; iframe + blob does.
 */
function SvgIframeViewer({ blobUrl, fileName, label }: { blobUrl: string; fileName: string; label: string }) {
  return (
    <div
      style={{
        flex: 1,
        minHeight: 0,
        width: '100%',
        display: 'flex',
        flexDirection: 'column',
        position: 'relative',
        backgroundColor: '#ffffff',
      }}
    >
      <iframe
        title={fileName}
        src={blobUrl}
        // pprof flame graphs need JS; blob: origin — no sandbox so scripts run
        style={{
          flex: 1,
          minHeight: 0,
          width: '100%',
          border: 'none',
          display: 'block',
        }}
      />
      <Text
        size="xs"
        style={{
          position: 'absolute',
          right: 12,
          bottom: 8,
          color: '#6b7280',
          pointerEvents: 'none',
          fontSize: 10,
        }}
      >
        {label}
      </Text>
    </div>
  );
}

/** Raster images: blob URL + &lt;img&gt; with outer zoom/pan. */
function RasterZoomViewer({ imageUrl, fileName, hint }: { imageUrl: string; fileName: string; hint: string }) {
  const viewportRef = useRef<HTMLDivElement>(null);
  const panRef = useRef({ active: false, startX: 0, startY: 0, ox: 0, oy: 0 });
  const [scale, setScale] = useState(1);
  const [offset, setOffset] = useState({ x: 0, y: 0 });
  const offsetRef = useRef(offset);
  const [dragging, setDragging] = useState(false);

  useEffect(() => {
    offsetRef.current = offset;
  }, [offset]);

  useEffect(() => {
    const el = viewportRef.current;
    if (!el) return;

    const onWheel = (e: WheelEvent) => {
      e.preventDefault();
      const rect = el.getBoundingClientRect();
      const mouseX = e.clientX - rect.left;
      const mouseY = e.clientY - rect.top;
      const delta = -e.deltaY;
      const factor = Math.exp(delta * 0.0015);
      setScale((prevScale: number) => {
        const newScale = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, prevScale * factor));
        if (Math.abs(newScale - prevScale) < 1e-9) return prevScale;
        const ratio = newScale / prevScale;
        setOffset((prev: { x: number; y: number }) => ({
          x: mouseX - (mouseX - prev.x) * ratio,
          y: mouseY - (mouseY - prev.y) * ratio,
        }));
        return newScale;
      });
    };

    el.addEventListener('wheel', onWheel, { passive: false });
    return () => el.removeEventListener('wheel', onWheel);
  }, [imageUrl]);

  const onPointerDown = useCallback((e: ReactPointerEvent<HTMLDivElement>) => {
    if (e.button !== 0) return;
    e.currentTarget.setPointerCapture(e.pointerId);
    const o = offsetRef.current;
    panRef.current = {
      active: true,
      startX: e.clientX,
      startY: e.clientY,
      ox: o.x,
      oy: o.y,
    };
    setDragging(true);
  }, []);

  const onPointerMove = useCallback((e: ReactPointerEvent<HTMLDivElement>) => {
    if (!panRef.current.active) return;
    const { startX, startY, ox, oy } = panRef.current;
    setOffset({
      x: ox + e.clientX - startX,
      y: oy + e.clientY - startY,
    });
  }, []);

  const endPan = useCallback(() => {
    panRef.current.active = false;
    setDragging(false);
  }, []);

  const resetView = useCallback(() => {
    setScale(1);
    setOffset({ x: 0, y: 0 });
  }, []);

  return (
    <div
      ref={viewportRef}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={endPan}
      onPointerCancel={endPan}
      onDoubleClick={(e: ReactMouseEvent<HTMLDivElement>) => {
        e.preventDefault();
        resetView();
      }}
      style={{
        height: '100%',
        minHeight: 160,
        width: '100%',
        overflow: 'hidden',
        position: 'relative',
        backgroundColor: '#e5e7eb',
        touchAction: 'none',
        userSelect: 'none',
        cursor: dragging ? 'grabbing' : 'grab',
      }}
    >
      <Box
        style={{
          width: '100%',
          height: '100%',
          minHeight: 0,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})`,
          transformOrigin: 'center center',
          willChange: 'transform',
        }}
      >
        {/* eslint-disable-next-line @next/next/no-img-element -- blob: URLs from authenticated API */}
        <img
          src={imageUrl}
          alt={fileName}
          draggable={false}
          style={{
            maxWidth: '100%',
            maxHeight: '100%',
            width: 'auto',
            height: 'auto',
            objectFit: 'contain',
            pointerEvents: 'none',
          }}
        />
      </Box>
      <Text
        size="xs"
        style={{
          position: 'absolute',
          right: 12,
          bottom: 8,
          color: '#6b7280',
          pointerEvents: 'none',
          fontSize: 10,
        }}
      >
        {hint}
      </Text>
    </div>
  );
}

interface FileViewerProps {
  filePath: string;
  onClose: () => void;
}

const FILE_SIZE_LIMIT = 100 * 1024; // 100 KB

type PreviewKind = 'image' | 'text' | 'binary' | 'too_large';

export default function FileViewer({ filePath, onClose }: FileViewerProps) {
  const { workspaceId, openFileSizeBytes, filesRefreshTrigger } = useWorkspace();
  const { t } = useTranslation();
  const [content, setContent] = useState<string>('');
  const [loading, setLoading] = useState(true);
  const [kind, setKind] = useState<PreviewKind | null>(null);
  const [imageUrl, setImageUrl] = useState<string | null>(null);
  const [svgBlobUrl, setSvgBlobUrl] = useState<string | null>(null);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [downloading, setDownloading] = useState(false);
  const imageBlobRef = useRef<string | null>(null);

  const language = useMemo(() => getLanguage(filePath), [filePath]);
  const fileName = useMemo(() => getFileName(filePath), [filePath]);

  const badgeLabel = useMemo(() => {
    if (kind === 'image') return svgBlobUrl ? t('fileViewer.badgeSvg') : t('fileViewer.badgeImage');
    if (kind === 'binary') return t('fileViewer.badgeBinary');
    if (kind === 'too_large') return t('fileViewer.badgeTooLarge');
    if (kind === 'text') return language;
    return '—';
  }, [kind, svgBlobUrl, language, t]);

  /**
   * Prefer in-memory data (image blob URL → fetch blob without API; text → Blob from string).
   * Otherwise GET file with download=true (always raw bytes).
   */
  const handleDownload = useCallback(async () => {
    if (!workspaceId) return;

    const displayUrl = svgBlobUrl || imageUrl;
    if (kind === 'image' && displayUrl && !loadError) {
      try {
        const r = await fetch(displayUrl);
        const blob = await r.blob();
        downloadBlobAsFile(blob, fileName);
        return;
      } catch {
        /* fall through to API */
      }
    }

    if (kind === 'text' && !isFailedTextLoad(content)) {
      const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
      downloadBlobAsFile(blob, fileName);
      return;
    }

    try {
      setDownloading(true);
      const url = `${API_BASE_URL}/api/workspace/${encodeURIComponent(workspaceId)}/file?path=${encodeURIComponent(filePath)}&download=true`;
      const res = await fetchWithAuth(url, { cache: 'no-store' });
      if (!res.ok) {
        message.error(`下载失败：无法下载文件（HTTP ${res.status}）`);
        return;
      }
      const blob = await res.blob();
      downloadBlobAsFile(blob, fileName);
    } catch (e) {
      message.error(`下载失败：${e instanceof Error ? e.message : '网络错误，请稍后重试'}`);
    } finally {
      setDownloading(false);
    }
  }, [workspaceId, filePath, fileName, kind, svgBlobUrl, imageUrl, loadError, content]);

  /* Reset preview state when `filePath` / `workspaceId` change, then fetch; sync setState is intentional. */
  /* eslint-disable react-hooks/set-state-in-effect -- file switch */
  useEffect(() => {
    if (imageBlobRef.current) {
      URL.revokeObjectURL(imageBlobRef.current);
      imageBlobRef.current = null;
    }
    setImageUrl(null);
    setSvgBlobUrl(null);
    setLoadError(null);
    setContent('');
    setKind(null);

    if (!workspaceId) {
      setLoading(false);
      setLoadError(t('fileViewer.workspaceNotLoadedText'));
      return;
    }

    // Skip loading for large files (> 100 KB)
    if (openFileSizeBytes != null && openFileSizeBytes > FILE_SIZE_LIMIT) {
      setKind('too_large');
      setLoading(false);
      return;
    }

    setLoading(true);
    const ac = new AbortController();
    const url = `${API_BASE_URL}/api/workspace/${encodeURIComponent(workspaceId)}/file?path=${encodeURIComponent(filePath)}`;

    fetchWithAuth(url, { signal: ac.signal, cache: 'no-store' })
      .then(async (r) => {
        if (ac.signal.aborted) return;
        if (r.status === 415) {
          setKind('binary');
          return;
        }
        if (!r.ok) {
          setLoadError(`HTTP ${r.status}`);
          return;
        }
        const rawCt = r.headers.get('Content-Type') || '';
        const ct = rawCt.split(';')[0].trim().toLowerCase();
        if (ct.startsWith('image/')) {
          const isSvg = ct.includes('svg');
          if (isSvg) {
            const text = await r.text();
            if (!text.trim()) throw new Error('Empty SVG file');
            if (!looksLikeSvgMarkup(text)) {
              throw new Error('Response is not SVG (check login or API URL)');
            }
            const blob = new Blob([text], { type: 'image/svg+xml;charset=utf-8' });
            const u = URL.createObjectURL(blob);
            imageBlobRef.current = u;
            setSvgBlobUrl(u);
          } else {
            const blob = await r.blob();
            const u = URL.createObjectURL(blob);
            imageBlobRef.current = u;
            setImageUrl(u);
          }
          setKind('image');
          return;
        }
        const text = await r.text();
        setContent(text);
        setKind('text');
      })
      .catch((err: unknown) => {
        if (ac.signal.aborted) return;
        const msg = err instanceof Error ? err.message : String(err);
        setLoadError(msg);
      })
      .finally(() => {
        if (!ac.signal.aborted) setLoading(false);
      });

    return () => {
      ac.abort();
      if (imageBlobRef.current) {
        URL.revokeObjectURL(imageBlobRef.current);
        imageBlobRef.current = null;
      }
    };
  }, [filePath, workspaceId, openFileSizeBytes, filesRefreshTrigger]);
  /* eslint-enable react-hooks/set-state-in-effect */

  return (
    <Box
      style={{
        display: 'flex',
        flexDirection: 'column',
        height: '100%',
        minHeight: 0,
        borderTop: '2px solid #3b82f6',
        backgroundColor: '#ffffff',
      }}
    >
      {/* File tab bar */}
      <Box
        px="sm"
        py={4}
        style={{
          backgroundColor: '#f3f4f6',
          borderBottom: '1px solid #e5e7eb',
          flexShrink: 0,
        }}
      >
        <Group justify="space-between" align="center">
          <Group gap={8} align="center">
            <Text size="xs" style={{ color: '#1f2937', fontFamily: 'monospace', fontSize: '12px' }}>
              {fileName}
            </Text>
            <Badge
              size="xs"
              variant="light"
              color="blue"
              style={{ fontSize: '9px', textTransform: 'uppercase' }}
            >
              {badgeLabel}
            </Badge>
            <Text size="xs" style={{ color: '#9ca3af', fontFamily: 'monospace', fontSize: '10px' }}>
              {filePath}
            </Text>
          </Group>
          <Group gap={4}>
            <Tooltip label={t('fileViewer.downloadFileTip')}>
              {downloading ? (
                <Loader size={14} color="blue" />
              ) : (
                <ActionIcon
                  variant="subtle"
                  size="xs"
                  onClick={() => void handleDownload()}
                  style={{ color: '#6b7280' }}
                  aria-label={t('fileViewer.downloadFileTip')}
                >
                  <span style={{ fontSize: '14px' }}>⬇</span>
                </ActionIcon>
              )}
            </Tooltip>
            <Tooltip label={t('fileViewer.closeFile')}>
              <ActionIcon
                variant="subtle"
                size="xs"
                onClick={onClose}
                style={{ color: '#6b7280' }}
                aria-label={t('fileViewer.closeFile')}
              >
                <span style={{ fontSize: '14px' }}>✕</span>
              </ActionIcon>
            </Tooltip>
          </Group>
        </Group>
      </Box>

      {/* Editor or image preview */}
      <Box style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
        {loading ? (
          <Box p="md" style={{ textAlign: 'center' }}>
            <Text size="sm" c="carbon.5">{t('fileViewer.loadingFileText')}</Text>
          </Box>
        ) : loadError ? (
          <Box p="md">
            <Text size="sm" c="red.7">{loadError}</Text>
          </Box>
        ) : kind === 'image' ? (
          svgBlobUrl ? (
            <Box style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
              <SvgIframeViewer blobUrl={svgBlobUrl} fileName={fileName} label={t('fileViewer.svgInteractiveLabel')} />
            </Box>
          ) : imageUrl ? (
            <RasterZoomViewer key={imageUrl} imageUrl={imageUrl} fileName={fileName} hint={t('fileViewer.zoomHintLabel')} />
          ) : null
        ) : kind === 'too_large' ? (
          <Box
            p="xl"
            style={{
              flex: 1,
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'center',
              justifyContent: 'center',
              gap: 16,
              textAlign: 'center',
            }}
          >
            <Text size="sm" c="carbon.6" style={{ maxWidth: 360 }}>
              {openFileSizeBytes != null
                ? t('fileViewer.tooLargeWithSize', { size: `${Math.round(openFileSizeBytes / 1024)} KB` })
                : t('fileViewer.tooLargeFallback')}
            </Text>
            {downloading ? (
              <Loader size={28} color="blue" />
            ) : (
              <ActionIcon
                variant="light"
                color="blue"
                size="lg"
                onClick={() => void handleDownload()}
                aria-label={t('fileViewer.downloadFile')}
              >
                <span style={{ fontSize: '18px' }}>⬇</span>
              </ActionIcon>
            )}
            <Text size="xs" c="carbon.5">{downloading ? t('fileViewer.downloading') : t('fileViewer.downloadHint')}</Text>
          </Box>
        ) : kind === 'binary' ? (
          <Box
            p="xl"
            style={{
              flex: 1,
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'center',
              justifyContent: 'center',
              gap: 16,
              textAlign: 'center',
            }}
          >
            <Text size="sm" c="carbon.6" style={{ maxWidth: 360 }}>
              {t('fileViewer.binaryUnsupported')}
            </Text>
          </Box>
        ) : kind === 'text' ? (
          <Editor
            height="100%"
            language={language}
            value={content}
            theme="light"
            options={{
              readOnly: true,
              minimap: { enabled: false },
              scrollBeyondLastLine: false,
              fontSize: 12,
              lineNumbers: 'on',
              folding: true,
              wordWrap: 'on',
              automaticLayout: true,
              domReadOnly: true,
              renderLineHighlight: 'line',
              scrollbar: {
                verticalScrollbarSize: 8,
                horizontalScrollbarSize: 8,
              },
              padding: { top: 8 },
            }}
          />
        ) : null}
      </Box>
    </Box>
  );
}
