import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Box, CircularProgress, MenuItem, Select, Typography } from '@mui/material';
import { SelectChangeEvent } from '@mui/material/Select';
import { useAuth } from '../../../contexts/AuthContext';

type Manifest = {
  bucket: string;
  entries: Array<{ 
    // Old format
    name?: string; 
    path?: string; 
    size: number; 
    content_type?: string;
    // New docling_bundle format
    filename?: string;
    rel_path?: string;
    mime_type?: string;
  }>;
  markdown_full?: string;
  markdown_pages?: Array<{ page: number; path: string }>;
  html_full?: string;
  text_full?: string;
  json_full?: string;
  doctags_full?: string;
  // New docling_bundle format
  file_paths?: {
    md?: string;
    html?: string;
    text?: string;
    json?: string;
    doctags?: string;
  };
  bundle_type?: string;
};

type Mode = 'markdown_full'|'markdown_pages'|'html_full'|'text_full'|'json_full'|'doctags_full';

export const CCBundleViewer: React.FC<{
  fileId: string;
  bundleId: string | undefined;
  currentPage?: number;
  combinedBundles?: Array<{ id: string }>;
}> = ({ fileId, bundleId, currentPage, combinedBundles }) => {
  const { accessToken } = useAuth();
  const [manifest, setManifest] = useState<Manifest | null>(null);
  const [combinedManifests, setCombinedManifests] = useState<Manifest[] | null>(null);
  const [mode, setMode] = useState<Mode>('markdown_full');
  const [content, setContent] = useState<string>('');
  const [renderHtml, setRenderHtml] = useState<string>('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const API_BASE = useMemo(() => import.meta.env.VITE_API_BASE || (location.port.startsWith('517') ? 'http://127.0.0.1:8080' : '/api'), []);
  const API_BASE_FALLBACK = 'http://127.0.0.1:8080';

  const proxyUrl = useCallback(async (bucket: string, relPath: string) => {
    const token = accessToken || '';
    return `${API_BASE}/database/files/proxy_signed?bucket=${encodeURIComponent(bucket)}&path=${encodeURIComponent(relPath)}&token=${encodeURIComponent(token)}`;
  }, [API_BASE, accessToken]);

  const replaceAllSafe = useCallback((s: string, search: string, replacement: string) => {
    if (!s || typeof s !== 'string') return s || '';
    return s.split(search).join(replacement);
  }, []);

  // Normalize manifest format - convert new docling_bundle format to expected format
  const normalizeManifest = useCallback((manifest: Manifest): Manifest => {
    // If this is a new docling_bundle format with file_paths, convert to expected format
    if (manifest.file_paths && manifest.bundle_type === 'docling_bundle') {
      return {
        ...manifest,
        markdown_full: manifest.file_paths.md,
        html_full: manifest.file_paths.html,
        text_full: manifest.file_paths.text,
        json_full: manifest.file_paths.json,
        doctags_full: manifest.file_paths.doctags,
        // Keep original file_paths for reference
        file_paths: manifest.file_paths
      };
    }
    return manifest;
  }, []);

  const buildNameToPath = (m: Manifest | null): Record<string, string> => {
    const map: Record<string, string> = {};
    if (!m) return map;
    
    console.log('🖼️ Building name-to-path map. Manifest entries:', m.entries?.length || 0);
    
    for (const e of (m.entries || [])) {
      if (!e) continue;
      
      // Handle both old format (name/path) and new docling_bundle format (filename/rel_path)
      const entryName = e.name || e.filename;
      const entryPath = e.path || e.rel_path;
      
      if (!entryName || !entryPath) {
        console.log('🖼️ Skipping entry with missing name/path:', e);
        continue;
      }
      
      // Map filename only (e.g., "image_000000_...png")
      const filename = entryName.split('/').pop() || entryName;
      map[filename] = entryPath;
      
      // Map full relative path (e.g., "artifacts/image_000000_...png") 
      map[entryName] = entryPath;
      
      // Map relative path with "./" prefix (e.g., "./artifacts/image_000000_...png")
      map[`./${entryName}`] = entryPath;
      
      // For debugging - map any path component variations
      if (entryName.includes('/')) {
        // Map path without leading directory (e.g., if name is "artifacts/image.png", also map "image.png")
        const pathParts = entryName.split('/');
        for (let i = 1; i < pathParts.length; i++) {
          const partialPath = pathParts.slice(i).join('/');
          map[partialPath] = entryPath;
        }
      }
    }
    
    // Debug: log the first few mappings for images
    const imageKeys = Object.keys(map).filter(k => k.includes('image_')).slice(0, 3);
    console.log('🖼️ Total mappings created:', Object.keys(map).length);
    if (imageKeys.length > 0) {
      console.log('🖼️ Image path mappings:', imageKeys.map(k => `${k} → ${map[k]}`));
    } else {
      console.log('🖼️ No image mappings found. All keys:', Object.keys(map).slice(0, 10));
    }
    
    return map;
  };

  const rewriteHtmlImageSrcs = useCallback((html: string, m: Manifest): string => {
    if (!html || typeof html !== 'string') return html || '';
    const nameToPath = buildNameToPath(m);
    return html.replace(/<img\s+([^>]*?)src=("|')([^"']+)(\2)([^>]*?)>/gi, (_match, pre, q, src, _q2, post) => {
      const s = (src || '').trim();
      if (s.startsWith('http') || s.startsWith('data:')) return _match; // leave
      
      // Try multiple path resolution strategies
      const normalizedKey = s.replace(/^\.\//, '').replace(/^\//, '');
      let rel = nameToPath[s] || nameToPath[normalizedKey] || nameToPath[`./${s}`] || nameToPath[`./${normalizedKey}`];
      
      // If still not found, try finding by filename only
      if (!rel) {
        const filename = normalizedKey.split('/').pop() || normalizedKey;
        rel = nameToPath[filename];
      }
      
      // If still not found, try partial path matching
      if (!rel) {
        const matchingKey = Object.keys(nameToPath).find(k => 
          k.endsWith(normalizedKey) || k.endsWith(`/${normalizedKey}`) || normalizedKey.endsWith(k)
        );
        if (matchingKey) {
          rel = nameToPath[matchingKey];
        }
      }
      
      // Debug logging for failed image resolution (less verbose)
      if (!rel && s.includes('image_')) {
        console.log(`🖼️ HTML: Failed to resolve image: "${s}"`);
      }
      
      if (!rel) return _match;
      // token added at runtime later; leave placeholder and replace after
      const url = `__PROXY__::${rel}`;
      return `<img ${pre || ''}src="${url}"${post || ''}>`;
    });
  }, []);

  const markdownToHtmlWithImages = useCallback((md: string, m: Manifest): string => {
    if (!md || typeof md !== 'string') return '';
    // Replace images ![alt](path) with img tags that proxy to storage
    const nameToPath = buildNameToPath(m);
    let html = md.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_m, alt, url) => {
      const s = String(url || '').trim();
      const altText = String(alt || '');
      if (s.startsWith('http') || s.startsWith('data:')) return `<img alt="${altText}" src="${s}">`;
      
      // Try multiple path resolution strategies (same as HTML rewriting)
      const normalizedKey = s.replace(/^\.\//, '').replace(/^\//, '');
      let rel = nameToPath[s] || nameToPath[normalizedKey] || nameToPath[`./${s}`] || nameToPath[`./${normalizedKey}`];
      
      // If still not found, try finding by filename only
      if (!rel) {
        const filename = normalizedKey.split('/').pop() || normalizedKey;
        rel = nameToPath[filename];
      }
      
      // If still not found, try partial path matching
      if (!rel) {
        const matchingKey = Object.keys(nameToPath).find(k => 
          k.endsWith(normalizedKey) || k.endsWith(`/${normalizedKey}`) || normalizedKey.endsWith(k)
        );
        if (matchingKey) {
          rel = nameToPath[matchingKey];
        }
      }
      
      // Debug logging for failed image resolution (less verbose)
      if (!rel && s.includes('image_')) {
        console.log(`🖼️ Markdown: Failed to resolve image: "${s}"`);
      }
      
      const prox = rel ? `__PROXY__::${rel}` : s; // Use original path as fallback
      return `<img alt="${altText}" src="${prox}">`;
    });
    // Minimal paragraph handling
    if (html && typeof html === 'string') {
      html = html
        .split(/\n{2,}/).map(p => `<p>${p.replace(/\n/g, '<br/>')}</p>`).join('\n');
    }
    return html || '';
  }, []);

  useEffect(() => {
    const load = async () => {
      setError(null);
      setCombinedManifests(null);
      setManifest(null);
      if (combinedBundles && combinedBundles.length > 0) {
        try {
          const token = accessToken || '';
          const ms: Manifest[] = [];
          for (const b of combinedBundles) {
            const res = await fetch(`${API_BASE}/database/files/${encodeURIComponent(fileId)}/artefacts/${encodeURIComponent(b.id)}/manifest`, { headers: { Authorization: `Bearer ${token}` } });
            if (!res.ok) continue;
            const rawManifest = await res.json();
            ms.push(normalizeManifest(rawManifest));
          }
          setCombinedManifests(ms);
        } catch (e: unknown) {
          setCombinedManifests(null);
          setError(e instanceof Error ? e.message : 'Failed to load combined manifests');
        }
        return;
      }
      if (!bundleId) return;
      try {
        const token = accessToken || '';
        const res = await fetch(`${API_BASE}/database/files/${encodeURIComponent(fileId)}/artefacts/${encodeURIComponent(bundleId)}/manifest`, { headers: { Authorization: `Bearer ${token}` } });
        if (!res.ok) throw new Error(await res.text());
        const rawManifest: Manifest = await res.json();
        const normalizedManifest = normalizeManifest(rawManifest);
        setManifest(normalizedManifest);
      } catch (e: unknown) {
        setManifest(null);
        setError(e instanceof Error ? e.message : 'Failed to load bundle manifest');
      }
    };
    load();
  }, [fileId, bundleId, API_BASE, combinedBundles, normalizeManifest]);

  useEffect(() => {
    const loadContent = async () => {
      // Combined mode
      if (combinedManifests && combinedManifests.length > 0) {
        setLoading(true); setError(null);
        try {
          const bucket = combinedManifests[0]?.bucket || '';
          // Build combined output depending on selected mode. If selected mode
          // is not available for a part, fall back: markdown_full → html_full → text_full → json_full
          let htmlParts: string[] = [];
          let textParts: string[] = [];
          let jsonParts: string[] = [];
          for (const m of combinedManifests) {
            const token = accessToken || '';
            let rel: string | undefined;
            if (mode === 'markdown_full') rel = m.markdown_full || m.html_full || m.text_full || m.json_full;
            else if (mode === 'html_full') rel = m.html_full || m.markdown_full || m.text_full || m.json_full;
            else if (mode === 'text_full') rel = m.text_full || m.markdown_full || m.html_full || m.json_full;
            else if (mode === 'json_full') rel = m.json_full || m.text_full || m.markdown_full || m.html_full;
            else if (mode === 'doctags_full') rel = m.doctags_full || m.json_full || m.text_full || m.markdown_full;
            else if (mode === 'markdown_pages') rel = m.markdown_full || m.html_full || m.text_full || m.json_full;
            if (!rel) continue;
            const url = await proxyUrl(m.bucket || bucket, rel);
            const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
            if (!res.ok) continue;
            const ct = res.headers.get('Content-Type') || '';
            if ((mode === 'markdown_full' && m.markdown_full && rel === m.markdown_full) || (!m.html_full && !ct.includes('text/html') && !ct.includes('application/json'))) {
              // Treat as markdown
              const md = await res.text();
              if (!md || typeof md !== 'string') continue;
              let h = markdownToHtmlWithImages(md, m);
              // Replace placeholders with signed proxy URLs
              const matches = [...h.matchAll(/__PROXY__::([^"'>\s]+)/g)].map((mm: RegExpMatchArray) => mm[1]);
              const unique = Array.from(new Set(matches));
              for (const r of unique) {
                const p = await proxyUrl(m.bucket || bucket, r);
                h = replaceAllSafe(h, `__PROXY__::${r}`, p);
              }
              htmlParts.push(h);
              textParts.push(md);
            } else if ((mode === 'html_full' && m.html_full && rel === m.html_full) || ct.includes('text/html')) {
              let htxt = await res.text();
              if (!htxt || typeof htxt !== 'string') continue;
              let h = rewriteHtmlImageSrcs(htxt, m);
              const matches = [...h.matchAll(/__PROXY__::([^"'>\s]+)/g)].map((mm: RegExpMatchArray) => mm[1]);
              const unique = Array.from(new Set(matches));
              for (const r of unique) {
                const p = await proxyUrl(m.bucket || bucket, r);
                h = replaceAllSafe(h, `__PROXY__::${r}`, p);
              }
              htmlParts.push(h);
              textParts.push(htxt);
            } else if (ct.includes('application/json') || mode === 'doctags_full' || (mode === 'json_full' && rel === m.doctags_full)) {
              const js = await res.json();
              jsonParts.push(JSON.stringify(js, null, 2));
            } else {
              const t = await res.text();
              if (t && typeof t === 'string') {
                textParts.push(t);
              }
            }
          }
          if ((mode === 'json_full' || mode === 'doctags_full') && jsonParts.length > 0 && htmlParts.length === 0) {
            setRenderHtml('');
            setContent(jsonParts.join('\n\n'));
          } else if (htmlParts.length > 0) {
            setRenderHtml(htmlParts.join('<hr/>'));
            setContent('');
          } else {
            setContent(textParts.join('\n\n'));
            setRenderHtml('');
          }
        } catch (e: unknown) {
          setError(e instanceof Error ? e.message : 'Failed to load combined content');
        } finally {
          setLoading(false);
        }
        return;
      }
      if (!manifest) { setContent(''); return; }
      setLoading(true); setError(null);
      try {
        const bucket = manifest.bucket || '';
        let relPath: string | undefined = undefined;
        if (mode === 'markdown_full') relPath = manifest.markdown_full;
        else if (mode === 'html_full') relPath = manifest.html_full;
        else if (mode === 'text_full') relPath = manifest.text_full;
        else if (mode === 'json_full') relPath = manifest.json_full;
        else if (mode === 'doctags_full') relPath = manifest.doctags_full;
        else if (mode === 'markdown_pages') {
          const p = Math.max(1, (currentPage || 1));
          const rec = (manifest.markdown_pages || []).find(x => x.page === p) || (manifest.markdown_pages || [])[0];
          relPath = rec?.path;
        }
        if (!relPath) { setContent(''); setLoading(false); return; }
        const token = accessToken || '';
        const url = await proxyUrl(bucket, relPath);
        let res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
        if (!res.ok) throw new Error(await res.text());
        const ct = res.headers.get('Content-Type') || '';
        if (ct.includes('application/json')) {
          const js = await res.json();
          setContent(JSON.stringify(js, null, 2));
          setRenderHtml('');
        } else {
          let txt = await res.text();
          if (!txt || typeof txt !== 'string') {
            setContent('');
            setRenderHtml('');
            setLoading(false);
            return;
          }
          // Dev fallback: if we accidentally hit Vite index, refetch from fallback base
          if ((ct.includes('text/html') && txt.includes('/@vite/client')) && API_BASE !== API_BASE_FALLBACK) {
            const alt = url.replace(API_BASE, API_BASE_FALLBACK);
            res = await fetch(alt, { headers: { Authorization: `Bearer ${token}` } });
            if (res.ok) {
              txt = await res.text();
            }
          }
          setContent(txt);
          // Prepare renderable HTML if markdown or html modes
          if ((mode === 'markdown_full' || mode === 'markdown_pages') && txt && typeof txt === 'string') {
            let html = markdownToHtmlWithImages(txt, manifest);
            // Replace placeholders with signed proxy URLs
            if (html && typeof html === 'string') {
              const tokenUrl = async (rel: string) => await proxyUrl(bucket, rel);
              const matches = [...html.matchAll(/__PROXY__::([^"'>\s]+)/g)].map(m => m[1]);
              const unique = Array.from(new Set(matches));
              for (const rel of unique) {
                const p = await tokenUrl(rel);
                html = replaceAllSafe(html, `__PROXY__::${rel}`, p);
              }
              setRenderHtml(html);
            } else {
              setRenderHtml('');
            }
          } else if (mode === 'html_full' && txt && typeof txt === 'string') {
            let html = rewriteHtmlImageSrcs(txt, manifest);
            if (html && typeof html === 'string') {
              const matches = [...html.matchAll(/__PROXY__::([^"'>\s]+)/g)].map(m => m[1]);
              const unique = Array.from(new Set(matches));
              for (const rel of unique) {
                const p = await proxyUrl(bucket, rel);
                html = replaceAllSafe(html, `__PROXY__::${rel}`, p);
              }
              setRenderHtml(html);
            } else {
              setRenderHtml('');
            }
          } else {
            setRenderHtml('');
          }
        }
      } catch (e: unknown) {
        setError(e instanceof Error ? e.message : 'Failed to load bundle content');
      } finally { setLoading(false); }
    };
    loadContent();
  }, [manifest, mode, currentPage, API_BASE, fileId, combinedManifests, markdownToHtmlWithImages, rewriteHtmlImageSrcs, proxyUrl, replaceAllSafe]);

  const availableModes = useMemo(() => {
    const hasCombined = !!(combinedManifests && combinedManifests.length);
    const has = (field: keyof Manifest): boolean => {
      if (hasCombined) {
        return combinedManifests!.some((cm: Manifest) => {
          const v = cm[field as keyof Manifest] as unknown;
          return Array.isArray(v) ? v.length > 0 : Boolean(v);
        });
      }
      const v = manifest ? (manifest[field as keyof Manifest] as unknown) : undefined;
      return Array.isArray(v) ? (v as unknown[]).length > 0 : Boolean(v);
    };
    const m: Array<{ key: typeof mode; label: string; enabled: boolean }> = [
      { key: 'markdown_full', label: 'Markdown (full)', enabled: has('markdown_full') },
      { key: 'markdown_pages', label: 'Markdown (pages)', enabled: !hasCombined && !!manifest?.markdown_pages?.length },
      { key: 'html_full', label: 'HTML (full)', enabled: has('html_full') },
      { key: 'text_full', label: 'Text (full)', enabled: has('text_full') },
      { key: 'json_full', label: 'JSON (full)', enabled: has('json_full') },
      { key: 'doctags_full', label: 'DocTags (full)', enabled: has('doctags_full') },
    ];
    const first = m.find(x => x.enabled)?.key;
    if (first && !m.find(x => x.key === mode && x.enabled)) setMode(first);
    return m;
  }, [manifest, combinedManifests, mode]);

  return (
    <Box sx={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
      <Box sx={{ p: 1, borderBottom: '1px solid var(--color-divider)', display: 'flex', alignItems: 'center', gap: 1 }}>
        <Typography variant="body2" sx={{ color: 'var(--color-text-2)' }}>Docling artefact</Typography>
        <Select size="small" value={mode} onChange={(e: SelectChangeEvent<Mode>) => setMode(e.target.value as Mode)}>
          {availableModes.map(m => (
            <MenuItem key={m.key} value={m.key} disabled={!m.enabled}>{m.label}</MenuItem>
          ))}
        </Select>
      </Box>
      <Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
        {loading ? <CircularProgress size={18} /> : error ? <Box sx={{ color: 'var(--color-text-2)' }}>{error}</Box> : (
          (mode === 'markdown_full' || mode === 'markdown_pages' || mode === 'html_full') && renderHtml ? (
            <iframe
              title="docling-html"
              style={{ width: '100%', height: '100%', border: 'none' }}
              srcDoc={`<!doctype html><html><head><meta charset='utf-8'><style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Arial,sans-serif;padding:16px;color:#222} img{max-width:100%;height:auto} pre,code{white-space:pre-wrap;word-break:break-word}</style></head><body>${renderHtml}</body></html>`}
            />
          ) : (
            <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{content}</pre>
          )
        )}
      </Box>
    </Box>
  );
};

export default CCBundleViewer;


