/**
 * rehype-functionplot
 * 
 * A rehype plugin for processing mathematical function plot code blocks.
 * This plugin finds code blocks with language "functionplot", extracts their content,
 * and replaces them with wrapper elements that can be enhanced client-side
 * using function-plot and mathjs for 2D mathematical function visualization.
 * 
 * Similar to rehype-abc, rehype-mermaid, and rehype-fountain, this plugin:
 * - Processes server-side for better performance and SEO
 * - Creates semantic wrapper elements with data attributes
 * - Preserves source code in collapsible details elements
 * - Enables client-side enhancement while maintaining SSR benefits
 * 
 * Expected input format:
 * ```functionplot
 * x^2
 * sin(x)
 * cos(x)
 * ```
 */

import { visit } from 'unist-util-visit'
import type { Element, Root } from 'hast'
import type { Plugin } from 'unified'
import { MAX_CODE_LENGTH, createErrorElement } from './security-constants'
import { logger } from '~/lib/logger'

interface FunctionPlotOptions {
  // Configuration options for function plot rendering
  width?: number
  height?: number
  xDomain?: [number, number]
  yDomain?: [number, number]
  grid?: boolean
}

// Generate a unique ID for each function plot
const generateFunctionPlotId = (): string => {
  return 'function-plot-' + Math.random().toString(36).substr(2, 9)
}

const rehypeFunctionPlot: Plugin<[FunctionPlotOptions?], Root> = (options = {}) => {
  return (tree: Root) => {
    visit(tree, 'element', (node: Element, index: number | undefined, parent: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
      // Look for code blocks with language-functionplot class
      if (
        node.tagName === 'pre' &&
        node.children.length === 1 &&
        node.children[0].type === 'element' &&
        node.children[0].tagName === 'code'
      ) {
        const codeElement = node.children[0] as Element
        const classNames = codeElement.properties?.className as string[] | undefined
        
        if (classNames?.includes('language-functionplot')) {
          // Extract the function plot code and frontmatter
          let fullCode = ''
          visit(codeElement, 'text', (textNode: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
            fullCode += textNode.value
          })

          const trimmedCode = fullCode.trim()
          
          // Skip empty blocks
          if (!trimmedCode) return
          
          // Validate code length
          if (trimmedCode.length > MAX_CODE_LENGTH) {
            if (parent && typeof index === 'number') {
              parent.children[index] = createErrorElement(
                'Function plot code exceeds maximum allowed length.',
                'function-plot-error'
              )
            }
            return
          }

          // Parse frontmatter and code
          let functionPlotCode = fullCode.trim()
          const frontmatterConfig: FunctionPlotOptions = {}
          
          // Check for frontmatter between --- markers
          // More precise regex that requires exact three dashes and handles various line endings
          const frontmatterMatch = fullCode.match(/^---[\r\n]+(.*?)[\r\n]+---[\r\n]+([\s\S]*)$/s)
          
          if (frontmatterMatch) {
            try {
              // Extract frontmatter and code parts
              const [, frontmatter, code] = frontmatterMatch
              
              // Only update functionPlotCode if we successfully parse the frontmatter
              let frontmatterValid = false
              
              // Parse frontmatter key-value pairs
              const frontmatterLines = frontmatter.split(/\r?\n/)
              for (const line of frontmatterLines) {
                const trimmedLine = line.trim()
                if (!trimmedLine) continue
                
                const separatorIndex = trimmedLine.indexOf(':')
                if (separatorIndex === -1) continue
                
                const key = trimmedLine.slice(0, separatorIndex).trim()
                const value = trimmedLine.slice(separatorIndex + 1).trim()
                
                if (!key || !value) continue
                
                switch (key) {
                  case 'xMin': {
                    const xMin = parseFloat(value)
                    if (!isNaN(xMin)) {
                      frontmatterConfig.xDomain = [xMin, frontmatterConfig.xDomain?.[1] ?? options.xDomain?.[1] ?? 10]
                      frontmatterValid = true
                    }
                    break
                  }
                  case 'xMax': {
                    const xMax = parseFloat(value)
                    if (!isNaN(xMax)) {
                      frontmatterConfig.xDomain = [frontmatterConfig.xDomain?.[0] ?? options.xDomain?.[0] ?? -10, xMax]
                      frontmatterValid = true
                    }
                    break
                  }
                  case 'yMin': {
                    const yMin = parseFloat(value)
                    if (!isNaN(yMin)) {
                      frontmatterConfig.yDomain = [yMin, frontmatterConfig.yDomain?.[1] ?? options.yDomain?.[1] ?? 10]
                      frontmatterValid = true
                    }
                    break
                  }
                  case 'yMax': {
                    const yMax = parseFloat(value)
                    if (!isNaN(yMax)) {
                      frontmatterConfig.yDomain = [frontmatterConfig.yDomain?.[0] ?? options.yDomain?.[0] ?? -10, yMax]
                      frontmatterValid = true
                    }
                    break
                  }
                  case 'width': {
                    const width = parseInt(value)
                    if (!isNaN(width) && width > 0) {
                      frontmatterConfig.width = width
                      frontmatterValid = true
                    }
                    break
                  }
                  case 'height': {
                    const height = parseInt(value)
                    if (!isNaN(height) && height > 0) {
                      frontmatterConfig.height = height
                      frontmatterValid = true
                    }
                    break
                  }
                  case 'grid': {
                    const gridValue = value.toLowerCase()
                    if (gridValue === 'true' || gridValue === 'false') {
                      frontmatterConfig.grid = gridValue === 'true'
                      frontmatterValid = true
                    }
                    break
                  }
                }
              }
              
              // Only use the extracted code if we successfully parsed at least one frontmatter value
              if (frontmatterValid) {
                functionPlotCode = code.trim()
              }
            } catch (error) {
              // If frontmatter parsing fails, keep the original code and use default options
              logger.error({ error }, 'Error parsing function plot frontmatter:')
            }
          }

          // Generate unique ID for this function plot
          const functionPlotId = generateFunctionPlotId()

          // Create wrapper with data attributes for client-side processing
          const wrapper: Element = {
            type: 'element',
            tagName: 'div',
            properties: {
              className: ['function-plot-wrapper'],
              dataFunctionPlotCode: functionPlotCode,
              dataFunctionPlotId: functionPlotId,
              // Store configuration options as data attributes
              // Ensure numerical values are properly validated
              dataFunctionPlotWidth: String(frontmatterConfig.width ?? options.width ?? 800),
              dataFunctionPlotHeight: String(frontmatterConfig.height ?? options.height ?? 400),
              dataFunctionPlotXMin: String(frontmatterConfig.xDomain?.[0] ?? options.xDomain?.[0] ?? -10),
              dataFunctionPlotXMax: String(frontmatterConfig.xDomain?.[1] ?? options.xDomain?.[1] ?? 10),
              dataFunctionPlotYMin: String(frontmatterConfig.yDomain?.[0] ?? options.yDomain?.[0] ?? -10),
              dataFunctionPlotYMax: String(frontmatterConfig.yDomain?.[1] ?? options.yDomain?.[1] ?? 10),
              dataFunctionPlotGrid: (frontmatterConfig.grid ?? options.grid ?? true) ? 'true' : 'false'
            },
            children: [
              // Container for the rendered function plot
              {
                type: 'element',
                tagName: 'div',
                properties: {
                  id: functionPlotId,
                  className: ['function-plot-container']
                },
                children: []
              },
              // Collapsible source code display
              {
                type: 'element',
                tagName: 'details',
                properties: {
                  className: ['function-plot-source']
                },
                children: [
                  {
                    type: 'element',
                    tagName: 'summary',
                    properties: {
                      className: ['text-sm', 'text-gray-600', 'cursor-pointer']
                    },
                    children: [
                      { type: 'text', value: 'Show function plot source' }
                    ]
                  },
                  {
                    type: 'element',
                    tagName: 'pre',
                    properties: {
                      className: ['mt-2', 'p-2', 'rounded', 'text-sm', 'overflow-auto']
                    },
                    children: [
                      {
                        type: 'element',
                        tagName: 'code',
                        properties: {},
                        children: [
                          { type: 'text', value: fullCode }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }

          // Replace the code block with our wrapper
          if (parent && typeof index === 'number') {
            parent.children[index] = wrapper
          }
        }
      }
    })
  }
}

export default rehypeFunctionPlot