'use client';

import React from 'react';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import remarkGfm from 'remark-gfm';

// Allow-list HTML that OpenAPI descriptions commonly use — we want
// ``<b>``/``<code>``/``<br>`` to render, not to appear as literal text.
// Built on top of ``rehype-sanitize``'s default schema so we keep the
// XSS protection (no ``<script>``, no ``on*`` handlers, no ``javascript:``
// URLs); we just extend the tag allow-list with safe presentational
// elements that are in wide use in API docs.
const HTML_SCHEMA = {
  ...defaultSchema,
  tagNames: [
    ...(defaultSchema.tagNames ?? []),
    'br',
    'b',
    'i',
    'u',
    's',
    'sub',
    'sup',
    'small',
    'mark',
    'kbd',
    'code',
    'pre',
    'details',
    'summary',
  ],
};

import { CopyButton } from '@djangocfg/ui-core/components';
import { useResolvedTheme } from '@djangocfg/ui-core/hooks';

import Mermaid from '../../tools/Mermaid';
import PrettyCode from '../../tools/PrettyCode';
import { useCollapsibleContent } from './useCollapsibleContent';

import type { Components } from 'react-markdown';

// Helper function to extract text content from React children
const extractTextFromChildren = (children: React.ReactNode): string => {
  if (typeof children === 'string') {
    return children;
  }

  if (typeof children === 'number') {
    return String(children);
  }

  if (React.isValidElement(children)) {
    const props = children.props as { children?: React.ReactNode };
    return extractTextFromChildren(props.children);
  }

  if (Array.isArray(children)) {
    return children.map(extractTextFromChildren).join('');
  }

  return '';
};

export interface MarkdownMessageProps {
  /** Markdown content to render */
  content: string;
  /** Additional CSS classes */
  className?: string;
  /** Whether the message is from the user (affects styling) */
  isUser?: boolean;
  /** Use compact size (text-xs instead of text-sm) */
  isCompact?: boolean;
  /**
   * Enable collapsible "Read more..." functionality
   * When enabled, long content will be truncated with a toggle button
   * @default false
   */
  collapsible?: boolean;
  /**
   * Maximum character length before showing "Read more..."
   * Only applies when collapsible is true
   * If both maxLength and maxLines are set, the stricter limit applies
   */
  maxLength?: number;
  /**
   * Maximum number of lines before showing "Read more..."
   * Only applies when collapsible is true
   * If both maxLength and maxLines are set, the stricter limit applies
   */
  maxLines?: number;
  /**
   * Custom "Read more" button text
   * @default "Read more..."
   */
  readMoreLabel?: string;
  /**
   * Custom "Show less" button text
   * @default "Show less"
   */
  showLessLabel?: string;
  /**
   * Start expanded (only applies when collapsible is true)
   * @default false
   */
  defaultExpanded?: boolean;
  /**
   * Callback when collapsed state changes
   */
  onCollapseChange?: (isCollapsed: boolean) => void;
}

// Code block component with copy functionality
interface CodeBlockProps {
  code: string;
  language: string;
  isUser: boolean;
  isCompact?: boolean;
}

const CodeBlock: React.FC<CodeBlockProps> = ({ code, language, isUser, isCompact = false }) => {
  const theme = useResolvedTheme();

  return (
    <div className="relative group my-3">
      {/* Copy button */}
      <CopyButton
        value={code}
        variant="ghost"
        className={`
          absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity
          h-8 w-8
          ${isUser
            ? 'hover:bg-white/20 text-white'
            : 'hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground'
          }
        `}
        title="Copy code"
      />

      {/* Code content */}
      <PrettyCode
        data={code}
        language={language}
        className={isCompact ? 'text-xs' : 'text-sm'}
        customBg={isUser ? "bg-white/10" : "bg-muted dark:bg-muted"}
        mode={theme}
        isCompact={isCompact}
      />
    </div>
  );
};

// Custom components for markdown in chat
// Base size: text-sm (14px) for normal, text-xs (12px) for compact
const createMarkdownComponents = (isUser: boolean = false, isCompact: boolean = false): Components => {
  // Text size classes based on compact mode
  const textSize = isCompact ? 'text-xs' : 'text-sm';
  const headingBase = isCompact ? 'text-sm' : 'text-base';
  const headingSm = isCompact ? 'text-xs' : 'text-sm';

  return {
  // Headings - scaled for chat context
  h1: ({ children }) => (
    <h1 className={`${headingBase} font-bold mb-2 mt-3 first:mt-0`}>{children}</h1>
  ),
  h2: ({ children }) => (
    <h2 className={`${headingSm} font-bold mb-2 mt-3 first:mt-0`}>{children}</h2>
  ),
  h3: ({ children }) => (
    <h3 className={`${headingSm} font-semibold mb-1 mt-2 first:mt-0`}>{children}</h3>
  ),
  h4: ({ children }) => (
    <h4 className={`${headingSm} font-semibold mb-1 mt-2 first:mt-0`}>{children}</h4>
  ),
  h5: ({ children }) => (
    <h5 className={`${headingSm} font-medium mb-1 mt-2 first:mt-0`}>{children}</h5>
  ),
  h6: ({ children }) => (
    <h6 className={`${headingSm} font-medium mb-1 mt-2 first:mt-0`}>{children}</h6>
  ),

  // Paragraphs - optimized for chat readability
  p: ({ children }) => (
    <p className={`${textSize} mb-4 last:mb-0 leading-7 break-words font-light`}>{children}</p>
  ),

  // Lists - compact
  ul: ({ children }) => (
    <ul className={`list-disc list-inside mb-2 space-y-1 ${textSize}`}>{children}</ul>
  ),
  ol: ({ children }) => (
    <ol className={`list-decimal list-inside mb-2 space-y-1 ${textSize}`}>{children}</ol>
  ),
  li: ({ children }) => (
    <li className="break-words">{children}</li>
  ),

  // Links - appropriate for chat context
  a: ({ href, children }) => (
    <a
      href={href}
      className={`${textSize} ${
        isUser
          ? 'text-white/90 underline hover:text-white'
          : 'text-primary underline hover:text-primary/80'
      } transition-colors break-all`}
      target={href?.startsWith('http') ? '_blank' : undefined}
      rel={href?.startsWith('http') ? 'noopener noreferrer' : undefined}
    >
      {children}
    </a>
  ),

  // Code blocks - using CodeBlock component with copy functionality
  pre: ({ children }) => {
    // Extract code content and language
    let codeContent = '';
    let language = 'plaintext';

    if (React.isValidElement(children)) {
      const child = children;

      if (child.type === 'code' || (typeof child.type === 'function' && child.type.name === 'code')) {
        const codeProps = child.props as { className?: string; children?: React.ReactNode };
        const rawClassName = codeProps.className;
        language = rawClassName?.replace(/language-/, '').trim() || 'plaintext';
        codeContent = extractTextFromChildren(codeProps.children).trim();
      } else {
        codeContent = extractTextFromChildren(children).trim();
      }
    } else {
      codeContent = extractTextFromChildren(children).trim();
    }

    // If still no content, show placeholder
    if (!codeContent) {
      return (
        <div className="my-3 p-3 bg-muted rounded text-sm text-muted-foreground">
          No content available
        </div>
      );
    }

    // Handle Mermaid diagrams separately
    if (language === 'mermaid') {
      return (
        <div className="my-3 max-w-full overflow-x-auto">
          <Mermaid chart={codeContent} className="max-w-[600px] mx-auto" isCompact={isCompact} />
        </div>
      );
    }

    // Try to use CodeBlock component, fallback to simple pre if it fails
    try {
      return <CodeBlock code={codeContent} language={language} isUser={isUser} isCompact={isCompact} />;
    } catch (error) {
      // Fallback to simple pre element with copy button
      console.warn('CodeBlock failed, using fallback:', error);
      return (
        <div className="relative group my-3">
          <CopyButton
            value={codeContent}
            variant="ghost"
            className={`
              absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity
              h-8 w-8
              ${isUser
                ? 'hover:bg-white/20 text-white'
                : 'hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground'
              }
            `}
            title="Copy code"
          />
          <pre className={`
            p-3 rounded text-xs font-mono overflow-x-auto
            ${isUser
              ? 'bg-white/10 text-white'
              : 'bg-muted text-foreground'
            }
          `}>
            <code>{codeContent}</code>
          </pre>
        </div>
      );
    }
  },

  // Inline code
  code: ({ children, className }) => {
    // If it's inside a pre tag, let pre handle it
    if (className?.includes('language-')) {
      return <code className={className}>{children}</code>;
    }

    // Extract text content safely
    const codeContent = extractTextFromChildren(children);

    // Inline code styling
    return (
      <code className="px-1.5 py-0.5 rounded text-xs font-mono bg-muted text-foreground break-all">
        {codeContent}
      </code>
    );
  },

  // Blockquotes
  blockquote: ({ children }) => (
    <blockquote className={`${textSize} border-l-2 border-border pl-3 my-2 italic text-muted-foreground break-words`}>
      {children}
    </blockquote>
  ),

  // Tables - compact for chat
  table: ({ children }) => (
    <div className="overflow-x-auto my-3">
      <table className={`min-w-full ${textSize} border-collapse`}>
        {children}
      </table>
    </div>
  ),
  thead: ({ children }) => (
    <thead className="bg-muted/50">
      {children}
    </thead>
  ),
  tbody: ({ children }) => (
    <tbody>{children}</tbody>
  ),
  tr: ({ children }) => (
    <tr className="border-b border-border/50">{children}</tr>
  ),
  th: ({ children }) => (
    <th className="px-2 py-1 text-left font-medium break-words">{children}</th>
  ),
  td: ({ children }) => (
    <td className="px-2 py-1 break-words">{children}</td>
  ),

  // Horizontal rule
  hr: () => (
    <hr className="my-3 border-0 h-px bg-border" />
  ),

  // Strong and emphasis
  strong: ({ children }) => (
    <strong className="font-semibold">{children}</strong>
  ),
  em: ({ children }) => (
    <em className="italic">{children}</em>
  ),
};};

// Check if content contains markdown syntax or line breaks
const hasMarkdownSyntax = (text: string): boolean => {
  // If there are line breaks (after trim), treat as markdown for proper paragraph rendering
  if (text.trim().includes('\n')) {
    return true;
  }

  // Inline HTML tags (``<br>``, ``<b>``, ``<code>``, …) — used widely
  // in OpenAPI descriptions. Without this branch the plain-text fast
  // path would escape them and the user sees literal angle brackets.
  // The regex matches opening, closing, and self-closing tags without
  // being too clever about malformed HTML; it's an affordance test,
  // not a validator.
  if (/<\/?[a-zA-Z][a-zA-Z0-9-]*(\s[^>]*)?\/?>/.test(text)) {
    return true;
  }

  // Common markdown patterns
  const markdownPatterns = [
    /^#{1,6}\s/m,           // Headers
    /\*\*[^*]+\*\*/,        // Bold
    /\*[^*]+\*/,            // Italic
    /__[^_]+__/,            // Bold (underscore)
    /_[^_]+_/,              // Italic (underscore)
    /\[.+\]\(.+\)/,         // Links
    /!\[.*\]\(.+\)/,        // Images
    /```[\s\S]*```/,        // Code blocks
    /`[^`]+`/,              // Inline code
    /^\s*[-*+]\s/m,         // Unordered lists
    /^\s*\d+\.\s/m,         // Ordered lists
    /^\s*>/m,               // Blockquotes
    /\|.+\|/,               // Tables
    /^---+$/m,              // Horizontal rules
    /~~[^~]+~~/,            // Strikethrough
  ];

  return markdownPatterns.some(pattern => pattern.test(text));
};

/**
 * Read more / Show less toggle button
 */
interface CollapseToggleProps {
  isCollapsed: boolean;
  onClick: () => void;
  readMoreLabel: string;
  showLessLabel: string;
  isUser: boolean;
  isCompact: boolean;
}

const CollapseToggle: React.FC<CollapseToggleProps> = ({
  isCollapsed,
  onClick,
  readMoreLabel,
  showLessLabel,
  isUser,
  isCompact,
}) => {
  const textSize = isCompact ? 'text-xs' : 'text-sm';

  return (
    <button
      type="button"
      onClick={onClick}
      className={`
        ${textSize} font-medium cursor-pointer
        transition-colors duration-200
        ${isUser
          ? 'text-white/80 hover:text-white'
          : 'text-primary hover:text-primary/80'
        }
        inline-flex items-center gap-1
        mt-1
      `}
    >
      {isCollapsed ? (
        <>
          {readMoreLabel}
          <svg
            className="w-3 h-3"
            fill="none"
            stroke="currentColor"
            viewBox="0 0 24 24"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M19 9l-7 7-7-7"
            />
          </svg>
        </>
      ) : (
        <>
          {showLessLabel}
          <svg
            className="w-3 h-3"
            fill="none"
            stroke="currentColor"
            viewBox="0 0 24 24"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M5 15l7-7 7 7"
            />
          </svg>
        </>
      )}
    </button>
  );
};

/**
 * MarkdownMessage - Renders markdown content with syntax highlighting and GFM support
 *
 * Features:
 * - GitHub Flavored Markdown (GFM) support
 * - Syntax highlighted code blocks with copy button
 * - Mermaid diagram rendering
 * - Tables, lists, blockquotes
 * - User/assistant styling modes
 * - Plain text optimization (skips ReactMarkdown for simple text)
 * - Collapsible "Read more..." for long messages
 *
 * @example
 * ```tsx
 * <MarkdownMessage content="# Hello\n\nThis is **bold** text." />
 *
 * // User message styling
 * <MarkdownMessage content="Some content" isUser />
 *
 * // Collapsible long content (for chat apps)
 * <MarkdownMessage
 *   content={longText}
 *   collapsible
 *   maxLength={300}
 *   maxLines={5}
 * />
 * ```
 */
export const MarkdownMessage: React.FC<MarkdownMessageProps> = ({
  content,
  className = "",
  isUser = false,
  isCompact = false,
  collapsible = false,
  maxLength,
  maxLines,
  readMoreLabel = "Read more...",
  showLessLabel = "Show less",
  defaultExpanded = false,
  onCollapseChange,
}) => {
  // Trim content to remove leading/trailing whitespace and empty lines
  const trimmedContent = content.trim();

  // Collapsible content logic - use defaults when collapsible is enabled
  const collapsibleOptions = React.useMemo(() => {
    if (!collapsible) return {};
    // Default limits when collapsible is enabled but no limits specified
    const effectiveMaxLength = maxLength ?? 1000;
    const effectiveMaxLines = maxLines ?? 10;
    return { maxLength: effectiveMaxLength, maxLines: effectiveMaxLines, defaultExpanded };
  }, [collapsible, maxLength, maxLines, defaultExpanded]);

  const {
    isCollapsed,
    toggleCollapsed,
    displayContent,
    shouldCollapse,
  } = useCollapsibleContent(
    trimmedContent,
    collapsible ? collapsibleOptions : {}
  );

  // Call onCollapseChange when state changes
  React.useEffect(() => {
    if (collapsible && shouldCollapse && onCollapseChange) {
      onCollapseChange(isCollapsed);
    }
  }, [isCollapsed, collapsible, shouldCollapse, onCollapseChange]);

  const components = React.useMemo(() => createMarkdownComponents(isUser, isCompact), [isUser, isCompact]);

  const textSizeClass = isCompact ? 'text-xs' : 'text-sm';
  const proseClass = isCompact ? 'prose-xs' : 'prose-sm';

  // For plain text without markdown, render directly without ReactMarkdown
  const isPlainText = !hasMarkdownSyntax(displayContent);

  // Render plain text - use CSS white-space: pre-line to preserve newlines
  if (isPlainText) {
    return (
      <span className={`${textSizeClass} leading-7 break-words whitespace-pre-line font-light ${className}`}>
        {displayContent}
        {collapsible && shouldCollapse && (
          <>
            {isCollapsed && '... '}
            <CollapseToggle
              isCollapsed={isCollapsed}
              onClick={toggleCollapsed}
              readMoreLabel={readMoreLabel}
              showLessLabel={showLessLabel}
              isUser={isUser}
              isCompact={isCompact}
            />
          </>
        )}
      </span>
    );
  }

  // Render markdown
  return (
    <div className={className}>
      <div
        className={`
          prose ${proseClass} max-w-none break-words overflow-hidden ${textSizeClass}
          ${isUser ? 'prose-invert' : 'dark:prose-invert'}
          [&>*]:leading-7
        `}
        style={{
          // Inherit colors from parent - fixes issues with external CSS variables
          '--tw-prose-body': 'inherit',
          '--tw-prose-headings': 'inherit',
          '--tw-prose-bold': 'inherit',
          '--tw-prose-links': 'inherit',
          color: 'inherit',
        } as React.CSSProperties}
      >
        <ReactMarkdown
          remarkPlugins={[remarkGfm]}
          // ``rehype-raw`` parses inline HTML in the source (OpenAPI
          // ``description`` fields often contain ``<br>`` / ``<b>`` /
          // ``<code>``). ``rehype-sanitize`` with our extended schema
          // runs after it so the allowed tags pass through while
          // anything risky (scripts, event handlers, javascript: urls)
          // is stripped.
          rehypePlugins={[rehypeRaw, [rehypeSanitize, HTML_SCHEMA]]}
          components={components}
        >
          {displayContent}
        </ReactMarkdown>
      </div>
      {collapsible && shouldCollapse && (
        <CollapseToggle
          isCollapsed={isCollapsed}
          onClick={toggleCollapsed}
          readMoreLabel={readMoreLabel}
          showLessLabel={showLessLabel}
          isUser={isUser}
          isCompact={isCompact}
        />
      )}
    </div>
  );
};

export default MarkdownMessage;
