'use client';

import { Highlight, Language, themes } from 'prism-react-renderer';
import React, { useMemo, useRef } from 'react';

import { useAppT } from '@djangocfg/i18n';
import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
import { FloatingToolbar } from '../../components/FloatingToolbar';
import { CopyAction } from '../../components/FloatingToolbar/actions';

// Load extra Prism grammars (``bash``, ``ruby``, ``java``, ``php``)
// that aren't in ``prism-react-renderer``'s default bundle. The hook
// below subscribes to its ready state and re-renders this component
// once loading completes, so the first tab click gets highlighted
// correctly even if it happened before the dynamic import resolved.
import { useEnsurePrismLanguages } from './registerPrismLanguages';

interface PrettyCodeProps {
  data: string | object;
  language: Language;
  className?: string;
  mode?: 'dark' | 'light';
  inline?: boolean;
  customBg?: string; // Custom background class
  isCompact?: boolean; // Compact mode for smaller font sizes
  scrollIsolation?: boolean; // Block scroll capture until user clicks (default: true)
  /**
   * Line count at which the viewer starts to scroll instead of growing.
   * ``undefined`` (default) = always grows to fit content, no scroll.
   * Set e.g. ``50`` to cap short snippets inline and scroll long ones.
   */
  maxLines?: number;
  /**
   * Visual variant. ``"card"`` (default) ships full chrome (border,
   * background, hover toolbar, optional internal scroll). ``"plain"``
   * is chrome-less — use when embedding inside another scroll
   * container so the surface manages its own chrome and scroll. */
  variant?: 'card' | 'plain';
}

const PrettyCode = ({ data, language, className, mode, inline = false, customBg, isCompact = false, scrollIsolation, maxLines, variant = 'card' }: PrettyCodeProps) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const t = useAppT();
  const detectedTheme = useResolvedTheme();

  // Subscribe to the extra-grammars ready state. When ``bash`` /
  // ``ruby`` / ``java`` / ``php`` finish loading, this hook triggers a
  // re-render so the code block picks up the new grammar.
  useEnsurePrismLanguages();

  const labels = useMemo(() => ({
    copyCode: t('tools.code.copyCode'),
    noContent: t('tools.code.noContent'),
  }), [t]);

  // Font size based on compact mode
  const fontSize = isCompact ? '0.75rem' : '0.875rem'; // 12px vs 14px

  // Use provided mode or fall back to detected theme
  const currentTheme = mode || detectedTheme;
  const isDarkMode = currentTheme === 'dark';

  // Select the Prism theme based on the current theme
  const prismTheme = isDarkMode ? themes.vsDark : themes.vsLight;

  // Convert form object to JSON string with proper formatting
  const contentJson = typeof data === 'string' ? data : JSON.stringify(data || {}, null, 2);

  // Enable scroll only when content exceeds maxLines. Otherwise the block
  // grows to fit — short snippets feel natural, long ones get a cap.
  const lineCount = contentJson ? contentJson.split('\n').length : 0;
  const lineHeightRatio = isCompact ? 1.4 : 1.5;
  const fontSizePx = isCompact ? 12 : 14;
  // Vertical padding of the <pre> (top + bottom, in px) — keep in sync
  // with the padding string below.
  const verticalPadPx = 16 + 12; // 1rem top + 0.75rem bottom (≈)
  const shouldScroll = maxLines !== undefined && lineCount > maxLines;
  const maxHeightPx = maxLines !== undefined
    ? maxLines * fontSizePx * lineHeightRatio + verticalPadPx
    : undefined;
  
  // Handle empty content
  if (!contentJson || contentJson.trim() === '') {
    const emptyBgClass = customBg || (isDarkMode ? 'bg-zinc-900' : 'bg-white');
    const emptyBorderClass = isDarkMode ? 'border-zinc-700' : 'border-border';
    return (
      <div className={`relative h-full ${emptyBgClass} rounded-sm border ${emptyBorderClass} ${className || ''}`}>
        <div className="h-full overflow-auto p-4">
          <p className="text-muted-foreground text-sm italic">{labels.noContent}</p>
        </div>
      </div>
    );
  }

  // Get display name for language badge
  const getLanguageDisplayName = (lang: string): string => {
    switch (lang.toLowerCase()) {
      case 'bash':
      case 'shell':
        return 'Bash';
      case 'python':
      case 'py':
        return 'Python';
      case 'javascript':
      case 'js':
        return 'JavaScript';
      case 'typescript':
      case 'ts':
        return 'TypeScript';
      case 'json':
        return 'JSON';
      case 'yaml':
      case 'yml':
        return 'YAML';
      case 'html':
        return 'HTML';
      case 'css':
        return 'CSS';
      case 'sql':
        return 'SQL';
      case 'xml':
        return 'XML';
      case 'markdown':
      case 'md':
        return 'Markdown';
      case 'plaintext':
      case 'text':
        return 'Text';
      case 'mermaid':
        return 'Mermaid';
      case 'ruby':
      case 'rb':
        return 'Ruby';
      case 'java':
        return 'Java';
      case 'php':
        return 'PHP';
      case 'go':
      case 'golang':
        return 'Go';
      default:
        return lang.charAt(0).toUpperCase() + lang.slice(1);
    }
  };

  // Normalize language for Prism - use only basic supported languages
  const normalizedLanguage = (() => {
    const lang = language.toLowerCase();
    
    // Try basic languages that are definitely supported
    switch (lang) {
      case 'javascript':
      case 'js':
        return 'javascript';
      case 'typescript':
      case 'ts':
        return 'typescript'; // Try TypeScript first
      case 'python':
      case 'py':
        return 'python';
      case 'json':
        return 'json';
      case 'css':
        return 'css';
      case 'html':
        return 'markup';
      case 'xml':
        return 'markup';
      case 'bash':
      case 'shell':
      case 'sh':
        return 'bash';
      case 'ruby':
      case 'rb':
        return 'ruby';
      case 'java':
        return 'java';
      case 'php':
        return 'php';
      case 'go':
      case 'golang':
        return 'go';
      case 'sql':
        return 'sql';
      case 'yaml':
      case 'yml':
        return 'yaml';
      case 'markdown':
      case 'md':
        return 'markdown';
      case 'mermaid':
        return 'text'; // Mermaid is handled separately in MarkdownMessage
      default:
        // For unknown languages, try to use the original name first
        // If it doesn't work, Prism will fallback to plain text
        return lang || 'text';
    }
  })();

  const displayLanguage = getLanguageDisplayName(language);

  // Plain variant — chrome-less render for embedding inside other
  // scroll containers. No border, no background, no hover toolbar, no
  // internal scroll. The caller's ScrollArea/panel owns the chrome
  // and scroll responsibilities.
  if (variant === 'plain') {
    return (
      <Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
        {({ className: prismClassName, style, tokens, getLineProps, getTokenProps }) => {
          const { backgroundColor: _bg, ...restStyle } = style;
          return (
            <pre
              className={`${prismClassName} ${className || ''}`}
              style={{
                ...restStyle,
                background: 'transparent',
                margin: 0,
                padding: isCompact ? '0.75rem 1rem' : '1rem',
                fontSize,
                lineHeight: lineHeightRatio,
                fontFamily: 'monospace',
                // ``break-all`` (not ``break-word``) so long unbroken
                // strings without whitespace — typical of escaped
                // JSON bodies in generated code samples — wrap at any
                // character rather than overflowing the container.
                whiteSpace: 'pre-wrap',
                wordBreak: 'break-all',
                overflowWrap: 'anywhere',
                // Hard cap on width — parents (grid cells, panels)
                // should constrain us but some callers render inside
                // ``flex: 1 1 auto`` which lets the pre grow past
                // intended bounds. ``max-width: 100%`` pins to parent.
                maxWidth: '100%',
              }}
            >
              {tokens.map((line, i) => (
                <div key={i} {...getLineProps({ line })}>
                  {line.map((token, key) => (
                    <span key={key} {...getTokenProps({ token })} />
                  ))}
                </div>
              ))}
            </pre>
          );
        }}
      </Highlight>
    );
  }

  if (inline) {
    const inlineBgClass = customBg || (isDarkMode ? 'bg-zinc-800' : 'bg-zinc-100');
    return (
      <Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
        {({ className, style, tokens, getTokenProps }) => (
          <code
            className={`${className} ${inlineBgClass} px-2 py-1 rounded ${isCompact ? 'text-xs' : 'text-sm'} font-mono inline-block`}
            style={{
              ...style,
              fontSize,
              fontFamily: 'monospace',
            }}
          >
            {tokens.map((line) => (
              line.map((token, key) => (
                <span key={key} {...getTokenProps({ token })} />
              ))
            ))}
          </code>
        )}
      </Highlight>
    );
  }

  // Different backgrounds for dark/light - light mode uses white for better contrast with vsLight theme
  const bgClass = customBg || (isDarkMode ? 'bg-zinc-900' : 'bg-white');
  const borderClass = isDarkMode ? 'border-zinc-700' : 'border-border';

  return (
    <div
      ref={containerRef}
      className={`group relative ${bgClass} rounded-lg border ${borderClass} ${className || ''}`}
      style={
        // maxHeight caps growth at ``maxLines`` rows; without maxLines we
        // let the block grow to fit its content (no scroll).
        maxHeightPx ? { maxHeight: `${maxHeightPx}px` } : undefined
      }
    >
      {/* Toolbar: hidden by default, appears on hover. Absolute overlay so it doesn't shift layout.
          scrollIsolation is force-disabled when content fits without scrolling —
          otherwise the "Click to scroll" prompt shows on a block that has
          nothing to scroll, which reads as a bug. */}
      <div className="absolute inset-x-0 top-0 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-150 z-30">
        <div className="pointer-events-auto">
          <FloatingToolbar
            containerRef={containerRef}
            scrollIsolation={shouldScroll ? scrollIsolation : false}
            label={
              <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-muted/80 text-muted-foreground border border-border/50 backdrop-blur-sm">
                {displayLanguage}
              </span>
            }
          >
            <CopyAction value={contentJson} title={labels.copyCode} />
          </FloatingToolbar>
        </div>
      </div>

      <div className={shouldScroll ? 'h-full overflow-auto' : ''} style={shouldScroll ? { maxHeight: maxHeightPx } : undefined}>
        <Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
          {({ className, style, tokens, getLineProps, getTokenProps }) => {
            // Remove background from Prism theme - we use our own via CSS
            const { backgroundColor: _bg, ...restStyle } = style;
            return (
            <pre
              className={`${className} rounded-lg`}
              style={{
                ...restStyle,
                margin: 0,
                // Top padding gives the hover toolbar room to sit over
                // the first line without covering it. Before: 2.5rem —
                // too much empty space on short snippets. Now: 1rem,
                // toolbar overlays with translucent bg on hover only.
                padding: '1rem 1rem 0.75rem 1rem',
                fontSize,
                lineHeight: lineHeightRatio,
                fontFamily: 'monospace',
                whiteSpace: 'pre-wrap',
                wordBreak: 'break-word',
                overflowWrap: 'break-word',
              }}
            >
              {tokens.map((line, i) => (
                <div key={i} {...getLineProps({ line })}>
                  {line.map((token, key) => (
                    <span key={key} {...getTokenProps({ token })} />
                  ))}
                </div>
              ))}
            </pre>
          )}}
        </Highlight>
      </div>
    </div>
  );
};

export default PrettyCode; 