import { LEFT, RIGHT, SIDE } from '../const'
import { DirectionClass, type NodeObj, type TagObj } from '../types/index'

/**
 * Server-side compatible layout data structure
 */
export interface SSRLayoutNode {
  id: string
  topic: string
  direction?: typeof LEFT | typeof RIGHT
  style?: {
    fontSize?: string
    color?: string
    background?: string
    fontWeight?: string
  }
  children?: SSRLayoutNode[]
  tags?: (string | TagObj)[]
  icons?: string[]
  hyperLink?: string
  expanded?: boolean
  image?: {
    url: string
    width: number
    height: number
    fit?: 'fill' | 'contain' | 'cover'
  }
  branchColor?: string
  dangerouslySetInnerHTML?: string
  note?: string
}

/**
 * SSR Layout result structure
 */
export interface SSRLayoutResult {
  root: SSRLayoutNode
  leftNodes: SSRLayoutNode[]
  rightNodes: SSRLayoutNode[]
  direction: number
}

/**
 * SSR Layout options
 */
export interface SSRLayoutOptions {
  direction?: number
  newTopicName?: string
}

const nodesWrapper = (nodesString: string) => {
  // don't add class="map-canvas" to prevent 20000px height
  return `<div class="map-container"><div>${nodesString}</div></div>`
}

/**
 * Server-side compatible layout function for SSR
 * This function processes the mind map data structure without DOM manipulation
 *
 * @param nodeData - The root node data
 * @param options - Layout options including direction
 * @returns Structured layout data for server-side rendering
 */
export const layoutSSR = function (nodeData: NodeObj, options: SSRLayoutOptions = {}): SSRLayoutResult {
  const { direction = SIDE } = options

  // Convert NodeObj to SSRLayoutNode (removing parent references for serialization)
  const convertToSSRNode = (node: NodeObj): SSRLayoutNode => {
    const ssrNode: SSRLayoutNode = {
      id: node.id,
      topic: node.topic,
      direction: node.direction,
      style: node.style,
      tags: node.tags,
      icons: node.icons,
      hyperLink: node.hyperLink,
      expanded: node.expanded,
      image: node.image,
      branchColor: node.branchColor,
      dangerouslySetInnerHTML: node.dangerouslySetInnerHTML,
      note: node.note,
    }

    if (node.children && node.children.length > 0) {
      ssrNode.children = node.children.map(convertToSSRNode)
    }

    return ssrNode
  }

  // Create root node
  const root = convertToSSRNode(nodeData)

  // Process main nodes (children of root)
  const mainNodes = nodeData.children || []
  const leftNodes: SSRLayoutNode[] = []
  const rightNodes: SSRLayoutNode[] = []

  if (direction === SIDE) {
    // Distribute nodes between left and right sides
    let lcount = 0
    let rcount = 0

    mainNodes.forEach(node => {
      const ssrNode = convertToSSRNode(node)

      if (node.direction === LEFT) {
        ssrNode.direction = LEFT
        leftNodes.push(ssrNode)
        lcount += 1
      } else if (node.direction === RIGHT) {
        ssrNode.direction = RIGHT
        rightNodes.push(ssrNode)
        rcount += 1
      } else {
        // Auto-assign direction based on balance
        if (lcount <= rcount) {
          ssrNode.direction = LEFT
          leftNodes.push(ssrNode)
          lcount += 1
        } else {
          ssrNode.direction = RIGHT
          rightNodes.push(ssrNode)
          rcount += 1
        }
      }
    })
  } else if (direction === LEFT) {
    // All nodes go to left side
    mainNodes.forEach(node => {
      const ssrNode = convertToSSRNode(node)
      ssrNode.direction = LEFT
      leftNodes.push(ssrNode)
    })
  } else {
    // All nodes go to right side (RIGHT direction)
    mainNodes.forEach(node => {
      const ssrNode = convertToSSRNode(node)
      ssrNode.direction = RIGHT
      rightNodes.push(ssrNode)
    })
  }

  return {
    root,
    leftNodes,
    rightNodes,
    direction,
  }
}

/**
 * Generate HTML string for server-side rendering
 * This function creates the HTML structure that would be generated by the DOM-based layout
 *
 * @param layoutResult - The result from layoutSSR function
 * @param options - Additional rendering options
 * @returns HTML string for server-side rendering
 */
export const renderSSRHTML = function (
  layoutResult: SSRLayoutResult,
  options: { className?: string; imageProxy?: (url: string) => string } = {}
): string {
  const { className = '' } = options

  const renderNode = (node: SSRLayoutNode, isRoot = false): string => {
    const nodeId = `me${node.id}`
    const topicClass = isRoot ? 'me-tpc' : 'me-tpc'

    let styleAttr = ''
    if (node.style) {
      const styles: string[] = []
      if (node.style.color) styles.push(`color: ${node.style.color}`)
      if (node.style.background) styles.push(`background: ${node.style.background}`)
      if (node.style.fontSize) styles.push(`font-size: ${node.style.fontSize}px`)
      if (node.style.fontWeight) styles.push(`font-weight: ${node.style.fontWeight}`)
      if (styles.length > 0) {
        styleAttr = ` style="${styles.join('; ')}"`
      }
    }

    let topicContent = ''
    if (node.dangerouslySetInnerHTML) {
      topicContent = node.dangerouslySetInnerHTML
    } else {
      topicContent = escapeHtml(node.topic)

      // Add tags if present
      if (node.tags && node.tags.length > 0) {
        const tagsHtml = node.tags
          .map(tag => {
            if (typeof tag === 'string') {
              // Compatible with legacy string configuration
              return `<span class="me-tag">${escapeHtml(tag)}</span>`
            } else {
              // Support object configuration
              let classAttr = 'me-tag'
              if (tag.className) {
                classAttr += ` ${tag.className}`
              }

              let styleAttr = ''
              if (tag.style) {
                const styles = Object.entries(tag.style)
                  .filter(([_, value]) => value !== undefined && value !== null && value !== '')
                  .map(([key, value]) => {
                    // Convert camelCase to CSS property name
                    const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
                    return `${cssKey}: ${value}`
                  })

                if (styles.length > 0) {
                  styleAttr = ` style="${styles.join('; ')}"`
                }
              }

              return `<span class="${classAttr}"${styleAttr}>${escapeHtml(tag.text)}</span>`
            }
          })
          .join('')
        topicContent += tagsHtml
      }

      // Add icons if present
      if (node.icons && node.icons.length > 0) {
        const iconsHtml = node.icons.map(icon => `<span class="me-icon">${icon}</span>`).join('')
        topicContent += iconsHtml
      }

      // Add image if present
      if (node.image) {
        const { url, width, height, fit = 'cover' } = node.image
        // Use imageProxy function if provided, otherwise use original URL
        const processedUrl = options.imageProxy ? options.imageProxy(url) : url
        topicContent += `<img src="${escapeHtml(processedUrl)}" width="${width}" height="${height}" style="object-fit: ${fit}" alt="" />`
      }
    }

    const topicHtml = `<me-tpc class="${topicClass}" data-nodeid="${nodeId}"${styleAttr}>${topicContent}</me-tpc>`

    if (isRoot) {
      return `<me-root>${topicHtml}</me-root>`
    }

    let childrenHtml = ''
    if (node.children && node.children.length > 0 && node.expanded !== false) {
      const childWrappers = node.children.map(child => renderWrapper(child)).join('')
      childrenHtml = `<me-children>${childWrappers}</me-children>`
    }

    const parentHtml = `<me-parent>${topicHtml}</me-parent>`
    return `<me-wrapper>${parentHtml}${childrenHtml}</me-wrapper>`
  }

  const renderWrapper = (node: SSRLayoutNode): string => {
    return renderNode(node, false)
  }

  const rootHtml = renderNode(layoutResult.root, true)

  const leftWrappers = layoutResult.leftNodes.map(node => renderWrapper(node)).join('')
  const rightWrappers = layoutResult.rightNodes.map(node => renderWrapper(node)).join('')

  const leftPartHtml = `<me-main class="${DirectionClass.LHS}">${leftWrappers}</me-main>`
  const rightPartHtml = `<me-main class="${DirectionClass.RHS}">${rightWrappers}</me-main>`

  return nodesWrapper(`<div class="${className}">${leftPartHtml}${rootHtml}${rightPartHtml}</div>`)
}

/**
 * Utility function to escape HTML characters
 */
function escapeHtml(text: string): string {
  const div = typeof document !== 'undefined' ? document.createElement('div') : null
  if (div) {
    div.textContent = text
    return div.innerHTML
  }

  // Fallback for server-side
  return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')
}

/**
 * Generate JSON data structure for client-side hydration
 * This can be used to pass the layout data to the client for hydration
 *
 * @param layoutResult - The result from layoutSSR function
 * @returns JSON-serializable data structure
 */
export const getSSRData = function (layoutResult: SSRLayoutResult): string {
  return JSON.stringify(layoutResult, null, 2)
}

/**
 * Hydration data structure for client-side initialization
 */
export interface HydrationData {
  nodeData: NodeObj
  layoutResult: SSRLayoutResult
  options: {
    direction: number
    [key: string]: any
  }
  timestamp: number
}

/**
 * Generate complete hydration data including original nodeData
 */
export const getHydrationData = function (nodeData: NodeObj, layoutResult: SSRLayoutResult, options: any = {}): HydrationData {
  return {
    nodeData,
    layoutResult,
    options: {
      direction: layoutResult.direction,
      ...options,
    },
    timestamp: Date.now(),
  }
}
