/**
 * Lexical to HTML Converter
 * Converts Payload CMS Lexical editor JSON format to HTML
 *
 * This implementation uses a simple recursive renderer that works in
 * both browser and SSR environments without requiring heavy dependencies.
 */

/**
 * Serialized Lexical node types
 */
interface LexicalTextNode {
  type: 'text'
  text: string
  bold?: boolean
  italic?: boolean
  underline?: boolean
  strikethrough?: boolean
  code?: boolean
}

interface LexicalHeadingNode {
  type: 'heading'
  tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
  children: any[]
}

interface LexicalParagraphNode {
  type: 'paragraph'
  children: any[]
}

interface LexicalListNode {
  type: 'list'
  listType: 'bullet' | 'number'
  children: any[]
}

interface LexicalListItemNode {
  type: 'listitem'
  children: any[]
}

interface LexicalLinkNode {
  type: 'link'
  url: string
  title?: string
  children: any[]
}

interface LexicalQuoteNode {
  type: 'quote'
  children: any[]
}

interface LexicalLineBreakNode {
  type: 'linebreak'
}

interface LexicalUploadNode {
  type: 'upload'
  value?: {
    url: string
    alt?: string
    width?: number
    height?: number
  }
}

interface LexicalBlockNode {
  type: 'block'
  fields?: {
    [key: string]: any
  }
}

interface LexicalRootNode {
  type: 'root'
  children: any[]
}

interface SerializedEditorState {
  root?: LexicalRootNode
}

/**
 * Convert Lexical JSON to HTML string
 *
 * @param lexicalData - The Lexical editor state from Payload CMS (object or JSON string)
 * @returns HTML string
 */
export async function serializeLexical(lexicalData: unknown): Promise<string> {
  // Handle null, undefined, empty values
  if (lexicalData === null || lexicalData === undefined) {
    return ''
  }

  // Handle string content (already HTML or JSON string)
  if (typeof lexicalData === 'string') {
    const trimmed = lexicalData.trim()

    // Empty string
    if (trimmed === '') {
      return ''
    }

    // Check if it's a JSON object (Lexical format)
    if (trimmed.startsWith('{')) {
      try {
        const parsed = JSON.parse(trimmed) as SerializedEditorState | LexicalRootNode
        return renderLexical(parsed)
      } catch {
        // Not valid JSON, return as-is (might be HTML already)
        return trimmed
      }
    }

    // Already HTML or plain text
    return trimmed
  }

  // Handle object (Lexical editor state)
  if (typeof lexicalData === 'object') {
    // Ensure it's not an array
    if (Array.isArray(lexicalData)) {
      return ''
    }

    return renderLexical(lexicalData as SerializedEditorState | LexicalRootNode)
  }

  return ''
}

/**
 * Render Lexical editor state to HTML
 */
function renderLexical(state: SerializedEditorState | LexicalRootNode): string {
  if (!state) return ''

  // Get root children - handle both formats
  let children: any[] = []
  if ('root' in state && state.root?.children) {
    children = state.root.children
  } else if ('children' in state && Array.isArray(state.children)) {
    children = state.children
  }

  return children
    .map((child) => renderNode(child))
    .filter(Boolean)
    .join('')
}

/**
 * Render a single Lexical node to HTML
 */
function renderNode(node: any): string {
  if (!node || typeof node !== 'object') return ''

  const type = node.type

  switch (type) {
    case 'root':
      return (node.children || []).map((child: any) => renderNode(child)).join('')

    case 'text':
      return renderTextNode(node)

    case 'paragraph':
      return renderParagraphNode(node)

    case 'heading':
      return renderHeadingNode(node)

    case 'link':
      return renderLinkNode(node)

    case 'list':
      return renderListNode(node)

    case 'listitem':
      return renderListItemNode(node)

    case 'quote':
      return renderQuoteNode(node)

    case 'linebreak':
      return '<br>'

    case 'upload':
    case 'image':
      return renderUploadNode(node)

    case 'block':
      return renderBlockNode(node)

    default:
      // Unknown node type - try to render children if available
      if (node.children && Array.isArray(node.children)) {
        return node.children.map((child: any) => renderNode(child)).join('')
      }
      return ''
  }
}

/**
 * Render text node with formatting
 */
function renderTextNode(node: LexicalTextNode): string {
  let text = escapeHtml(node.text || '')

  // Apply formatting styles (order matters for nested tags)
  if (node.code) text = `<code>${text}</code>`
  if (node.strikethrough) text = `<s>${text}</s>`
  if (node.underline) text = `<u>${text}</u>`
  if (node.italic) text = `<em>${text}</em>`
  if (node.bold) text = `<strong>${text}</strong>`

  return text
}

/**
 * Render paragraph node
 */
function renderParagraphNode(node: LexicalParagraphNode): string {
  const children = (node.children || []).map((child) => renderNode(child)).join('')
  return children ? `<p>${children}</p>` : ''
}

/**
 * Render heading node
 */
function renderHeadingNode(node: LexicalHeadingNode): string {
  const tag = node.tag || 'h2'
  const children = (node.children || []).map((child) => renderNode(child)).join('')
  return children ? `<${tag}>${children}</${tag}>` : ''
}

/**
 * Render link node
 */
function renderLinkNode(node: LexicalLinkNode): string {
  const url = escapeHtml(node.url || '#')
  const title = node.title ? ` title="${escapeHtml(node.title)}"` : ''
  const children = (node.children || []).map((child) => renderNode(child)).join('')
  return children ? `<a href="${url}"${title}>${children}</a>` : ''
}

/**
 * Render list node
 */
function renderListNode(node: LexicalListNode): string {
  const listType = node.listType === 'number' ? 'ol' : 'ul'
  const children = (node.children || []).map((child) => renderNode(child)).join('')
  return children ? `<${listType}>${children}</${listType}>` : ''
}

/**
 * Render list item node
 */
function renderListItemNode(node: LexicalListItemNode): string {
  const children = (node.children || []).map((child) => renderNode(child)).join('')
  return children ? `<li>${children}</li>` : ''
}

/**
 * Render quote node
 */
function renderQuoteNode(node: LexicalQuoteNode): string {
  const children = (node.children || []).map((child) => renderNode(child)).join('')
  return children ? `<blockquote>${children}</blockquote>` : ''
}

/**
 * Render upload/image node
 */
function renderUploadNode(node: LexicalUploadNode): string {
  const url = node.value?.url || ''
  const alt = escapeHtml(node.value?.alt || '')
  const width = node.value?.width ? ` width="${node.value.width}"` : ''
  const height = node.value?.height ? ` height="${node.value.height}"` : ''

  if (!url) return ''

  return `<img src="${escapeHtml(url)}" alt="${alt}"${width}${height} style="max-width: 100%; height: auto;" loading="lazy" />`
}

/**
 * Render block node (for custom blocks like media, banners, etc.)
 */
function renderBlockNode(node: LexicalBlockNode): string {
  const fields = node.fields || {}

  // Handle media block
  if (fields.media) {
    const media = fields.media
    const url = media.url || ''
    const alt = escapeHtml(media.alt || '')

    if (url) {
      return `<figure><img src="${escapeHtml(url)}" alt="${alt}" style="max-width: 100%; height: auto; border-radius: 8px; margin: 2rem 0;" loading="lazy" /></figure>`
    }
  }

  // Handle other block types
  if (fields.blockType === 'banner') {
    const content = fields.content || ''
    return `<div class="banner">${renderNode({ type: 'root', children: content.root?.children || [] })}</div>`
  }

  if (fields.blockType === 'code') {
    const code = escapeHtml(fields.code || '')
    const language = escapeHtml(fields.language || '')
    return `<pre><code class="language-${language}">${code}</code></pre>`
  }

  // Fallback: try to render any nested content
  if (fields.content?.root?.children) {
    return fields.content.root.children.map((child: any) => renderNode(child)).join('')
  }

  return ''
}

/**
 * Escape HTML special characters
 */
function escapeHtml(text: string): string {
  const map: Record<string, string> = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;'
  }
  return text.replace(/[&<>"']/g, (m) => map[m] || m)
}

/**
 * Synchronous version for compatibility (not recommended for use with Lexical)
 * @deprecated Use serializeLexical instead
 */
export function serializeLexicalSync(lexicalData: unknown): string {
  // This is a stub - the actual implementation should be async
  // For simple cases, we'll try to return something useful
  if (!lexicalData || typeof lexicalData !== 'object') {
    return String(lexicalData ?? '')
  }

  if (typeof lexicalData === 'string') {
    return lexicalData
  }

  // For Lexical objects, return empty since we can't properly render synchronously
  return ''
}
