'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 { CodePanelHeader } from '../../../../common/CodePanelHeader';

// Surface palette — driven by the ui-core `--code*` semantic tokens so the
// code panel matches the rest of the theme system (neutral, preset-aware)
// instead of a hard-coded GitHub blue. The tokens are already tuned per
// theme (`light.css` / `dark.css`): a near-white gray in light, a near-black
// gray in dark — so the surface stays a calm neutral in either mode and never
// reads blue. Syntax colors still come from Prism's vsDark/github palettes.
const CODE_SURFACE_BG = 'var(--code)';
const CODE_SURFACE_BORDER = 'var(--code-border)';
const CODE_INLINE_BG = 'var(--code-inline)';

// 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;
  /**
   * Force a specific Prism palette + surface. **Defaults to the resolved
   * host theme** (light → light surface + GitHub palette, dark → dark
   * surface + vsDark palette), so the panel matches the rest of the UI and
   * its `--code*` tokens. Pass `mode` explicitly only to pin a palette
   * regardless of theme — e.g. printing a PDF on a light page.
   */
  mode?: 'dark' | 'light';
  inline?: boolean;
  customBg?: string; // Custom background class
  isCompact?: boolean; // Compact mode for smaller font sizes
  /** @deprecated No-op since the toolbar moved into a persistent
   *  `CodePanelHeader` (no scroll-capturing overlay). Kept for API
   *  back-compat; will be removed in a future major. */
  scrollIsolation?: boolean;
  /**
   * 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;
  /**
   * When set, an expand (⤢) button appears in the header — but ONLY while
   * the content is actually height-capped (i.e. exceeds `maxLines`). Lets a
   * host open the full code full-screen. No-op when the code fits.
   */
  onExpand?: () => void;
  /**
   * 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, maxLines, onExpand, variant = 'card' }: PrettyCodeProps) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const t = useAppT();

  // Default to the host theme so the surface (`--code*` tokens) and the Prism
  // palette move together; an explicit `mode` prop still pins a palette.
  const resolvedTheme = useResolvedTheme();
  const effectiveMode = mode ?? (resolvedTheme === 'light' ? 'light' : 'dark');

  // 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

  const isDarkMode = effectiveMode !== 'light';
  const prismTheme = isDarkMode ? themes.vsDark : themes.github;
  // The surface now reads the `--code*` tokens in BOTH modes — they already
  // carry the right neutral per theme, so the panel matches the UI and never
  // reads blue. (Previously light mode fell back to `bg-card` and dark mode
  // hard-coded GitHub's #0d1117.)
  const surfaceStyle = { backgroundColor: CODE_SURFACE_BG, borderColor: CODE_SURFACE_BORDER };
  const inlineSurfaceStyle = { backgroundColor: CODE_INLINE_BG };

  // 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 || '';
    return (
      <div
        className={`relative h-full ${emptyBgClass} rounded-sm border ${className || ''}`}
        style={customBg ? undefined : surfaceStyle}
      >
        <div className="h-full overflow-auto p-4">
          <p
            className="text-sm italic text-muted-foreground"
            role="status"
          >
            {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) {
    // Inline chip surface also comes from `--code-inline` via inlineSurfaceStyle.
    const inlineBgClass = customBg || '';
    return (
      <Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
        {({ className: prismClassName, style, tokens, getTokenProps }) => (
          <code
            className={`${prismClassName} ${inlineBgClass} px-2 py-1 rounded ${isCompact ? 'text-xs' : 'text-sm'} font-mono inline-block ${className || ''}`}
            style={{
              ...style,
              ...(customBg ? undefined : inlineSurfaceStyle),
              fontSize,
              fontFamily: 'monospace',
            }}
          >
            {tokens.map((line, i) =>
              line.map((token, key) => (
                <span key={`${i}-${key}`} {...getTokenProps({ token })} />
              )),
            )}
          </code>
        )}
      </Highlight>
    );
  }

  // Code surface is fixed (dark by default). Falls back to semantic
  // Surface comes from the `--code*` tokens via `surfaceStyle` (both modes).
  const bgClass = customBg || '';

  return (
    <div
      ref={containerRef}
      className={`group relative overflow-hidden ${bgClass} rounded-lg border ${className || ''}`}
      style={customBg ? undefined : surfaceStyle}
    >
      {/* Persistent header bar (GitHub / ChatGPT style): language label
          left, copy button right, in flow — never floats over the code. */}
      <CodePanelHeader
        language={displayLanguage}
        copyValue={contentJson}
        copyLabel={labels.copyCode}
        dark={isDarkMode}
        // Expand only matters when the code is actually height-capped.
        onExpand={onExpand && shouldScroll ? onExpand : undefined}
      />

      <div
        className={shouldScroll ? 'h-full overflow-auto' : ''}
        style={shouldScroll ? { maxHeight: maxHeightPx } : undefined}
        // When the viewer scrolls internally, make it keyboard-reachable
        // so users without a wheel/trackpad can scroll with arrow keys.
        {...(shouldScroll
          ? { tabIndex: 0, role: 'region', 'aria-label': displayLanguage }
          : {})}
      >
        <Highlight theme={prismTheme} code={contentJson} language={normalizedLanguage as Language}>
          {({ className: prismClassName, style, tokens, getLineProps, getTokenProps }) => {
            // Remove background from Prism theme - we use our own via CSS
            const { backgroundColor: _bg, ...restStyle } = style;
            return (
            <pre
              className={prismClassName}
              style={{
                ...restStyle,
                margin: 0,
                // The header now occupies the top; the code body just needs
                // even padding (no extra top room for an overlay anymore).
                padding: '0.875rem 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; 