/**
 * Rehype plugin to handle spoiler text
 * 
 * This plugin processes text between ::spoiler:: markers and replaces them
 * with structured HTML that will be enhanced client-side with blur effect.
 */

import { visit } from 'unist-util-visit'
import type { Root, Element, Text } from 'hast'
import type { Plugin } from 'unified'

/**
 * Default values for spoiler plugin configuration
 */
const DEFAULT_MAX_SPOILER_LENGTH = 1000; // Maximum length of each spoiler's content
const DEFAULT_MAX_SPOILERS_PER_DOC = 50;  // Maximum number of spoilers per document
const DEFAULT_PERFORMANCE_THRESHOLD = 1000; // Maximum processing time in milliseconds

interface SpoilerOptions {
  /**
   * Maximum length of text content within a spoiler tag.
   * Text exceeding this length will be truncated.
   * @default 1000
   */
  maxSpoilerLength?: number;

  /**
   * Maximum number of spoilers allowed in a single document.
   * Additional spoilers will be ignored.
   * @default 50
   */
  maxSpoilersPerDoc?: number;

  /**
   * Maximum time (in milliseconds) allowed for processing spoilers.
   * @default 1000
   */
  performanceThreshold?: number;
}

const rehypeSpoiler: Plugin<[SpoilerOptions?], Root> = (options = {}) => {
  const {
    maxSpoilerLength = DEFAULT_MAX_SPOILER_LENGTH,
    maxSpoilersPerDoc = DEFAULT_MAX_SPOILERS_PER_DOC,
    performanceThreshold = DEFAULT_PERFORMANCE_THRESHOLD
  } = options

  return (tree: Root) => {
    // Track the number of spoilers processed
    let spoilerCount = 0
    // Track processing start time
    const startTime = Date.now()
    visit(tree, 'text', (node: Text, index: number | undefined, parent: Root | Element | undefined) => {
      if (!parent || index === undefined) return

      const content = node.value
      // Use a more specific pattern that prevents catastrophic backtracking
      // [^] matches any character including newlines, (?=::spoiler::) is a positive lookahead
      // that ensures we're matching up to the next ::spoiler:: marker
      const spoilerRegex = /::spoiler::([^]*?(?=::spoiler::))::spoiler::/g
      let match
      const parts: (string | Element)[] = []
      let lastIndex = 0

      while ((match = spoilerRegex.exec(content)) !== null) {
        // Add text before the match
        if (match.index > lastIndex) {
          parts.push(content.slice(lastIndex, match.index))
        }

      // Check processing time
        if (Date.now() - startTime > performanceThreshold) {
          console.warn('Spoiler processing exceeded time threshold')
          break
        }

        // Check spoiler count
        if (spoilerCount >= maxSpoilersPerDoc) {
          console.warn('Maximum number of spoilers exceeded')
          break
        }

        // Add the spoiler element
        const spoilerText = match[1]
        // Truncate if exceeds max length
        const trimmedText = spoilerText.slice(0, maxSpoilerLength)
        if (trimmedText.trim()) {
          spoilerCount++
          parts.push({
            type: 'element',
            tagName: 'span',
            properties: {
              className: ['spoiler-text', 'blur'],
              dataSpoilerId: `spoiler-${Math.random().toString(36).substr(2, 9)}`,
              role: 'button',
              tabIndex: 0
            },
            children: [{ type: 'text', value: trimmedText }]
          })
        }

        lastIndex = match.index + match[0].length
      }

      // Add remaining text after last match
      if (lastIndex < content.length) {
        parts.push(content.slice(lastIndex))
      }

      // Only replace if we found spoiler markers
      if (parts.length > 0) {
      const newNodes = parts.map(part => {
        if (typeof part === 'string') {
          return { type: 'text', value: part } as Text
        }
        return part
      }) as Array<Text | Element>

        parent.children.splice(index, 1, ...newNodes)
      }
    })
  }
}

export default rehypeSpoiler