/**
 * Utility Functions for Brainfish API Documentation
 */

import type { BrainfishRESTRequest } from './types'

/**
 * Checks if a value is a valid HTTP method
 */
export function isValidHTTPMethod(value: string): value is BrainfishRESTRequest['method'] {
  const validMethods: BrainfishRESTRequest['method'][] = [
    'GET',
    'POST',
    'PUT',
    'DELETE',
    'PATCH',
    'HEAD',
    'OPTIONS',
  ]
  return validMethods.includes(value as BrainfishRESTRequest['method'])
}

/**
 * Checks if a number is numeric
 */
export function isNumeric(value: unknown): boolean {
  if (typeof value === 'number') return !isNaN(value)
  if (typeof value === 'string') {
    const num = Number(value)
    return !isNaN(num) && isFinite(num)
  }
  return false
}

/**
 * Gets status code reason phrase
 */
export function getStatusCodeReasonPhrase(
  code: number,
  defaultPhrase?: string
): string {
  const statusCodes: Record<number, string> = {
    200: 'OK',
    201: 'Created',
    202: 'Accepted',
    204: 'No Content',
    400: 'Bad Request',
    401: 'Unauthorized',
    403: 'Forbidden',
    404: 'Not Found',
    500: 'Internal Server Error',
    502: 'Bad Gateway',
    503: 'Service Unavailable',
  }
  
  return statusCodes[code] || defaultPhrase || 'Unknown'
}

/**
 * Checks if an object has a specific property
 */
export function objectHasProperty<T extends string>(
  obj: unknown,
  propName: T
): obj is { [K in T]: unknown } {
  return (
    !!obj &&
    typeof obj === 'object' &&
    Object.prototype.hasOwnProperty.call(obj, propName)
  )
}

/**
 * Deep clone an object
 */
export function cloneDeep<T>(obj: T): T {
  if (obj === null || typeof obj !== 'object') {
    return obj
  }
  
  if (obj instanceof Date) {
    return new Date(obj.getTime()) as unknown as T
  }
  
  if (Array.isArray(obj)) {
    return obj.map(item => cloneDeep(item)) as unknown as T
  }
  
  const cloned = {} as T
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloned[key] = cloneDeep(obj[key])
    }
  }
  
  return cloned
}

/**
 * Strips markdown syntax from text and returns plain text
 * Useful for displaying markdown content in compact UI areas like sidebars
 */
export function stripMarkdown(markdown: string | null | undefined): string {
  if (!markdown) return ''
  
  let text = markdown
  
  // Remove code blocks (```...```)
  text = text.replace(/```[\s\S]*?```/g, '')
  
  // Remove inline code (`...`)
  text = text.replace(/`([^`]+)`/g, '$1')
  
  // Remove headers (# ## ###)
  text = text.replace(/^#{1,6}\s+(.+)$/gm, '$1')
  
  // Remove bold (**text** or __text__)
  text = text.replace(/\*\*([^*]+)\*\*/g, '$1')
  text = text.replace(/__([^_]+)__/g, '$1')
  
  // Remove italic (*text* or _text_)
  text = text.replace(/\*([^*]+)\*/g, '$1')
  text = text.replace(/_([^_]+)_/g, '$1')
  
  // Remove links [text](url)
  text = text.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
  
  // Remove images ![alt](url)
  text = text.replace(/!\[([^\]]*)\]\([^\)]+\)/g, '$1')
  
  // Remove horizontal rules (--- or ***)
  text = text.replace(/^[-*]{3,}$/gm, '')
  
  // Remove list markers (-, *, +, 1.)
  text = text.replace(/^[\s]*[-*+]\s+/gm, '')
  text = text.replace(/^[\s]*\d+\.\s+/gm, '')
  
  // Remove blockquotes (>)
  text = text.replace(/^>\s+/gm, '')
  
  // Clean up multiple newlines
  text = text.replace(/\n{3,}/g, '\n\n')
  
  // Trim whitespace
  text = text.trim()
  
  return text
}

/**
 * Gets a preview of text (strips markdown and truncates)
 */
export function getTextPreview(
  text: string | null | undefined,
  maxLength: number = 150
): string {
  if (!text) return ''
  
  const stripped = stripMarkdown(text)
  
  if (stripped.length <= maxLength) {
    return stripped
  }
  
  // Truncate at word boundary
  const truncated = stripped.substring(0, maxLength)
  const lastSpace = truncated.lastIndexOf(' ')
  
  if (lastSpace > maxLength * 0.7) {
    return truncated.substring(0, lastSpace) + '...'
  }
  
  return truncated + '...'
}

/**
 * Extracts markdown headings from text
 * Returns an array of { level, text, id, children? } objects
 * 
 * Following Scalar's approach:
 * - Only includes the two lowest heading levels (e.g., h2 and h3 if h2 is lowest)
 * - Creates hierarchical structure with children
 * - Adds "Introduction" if content doesn't start with a heading
 */
export interface MarkdownHeading {
  level: number
  text: string
  id: string
  children?: MarkdownHeading[]
}

/**
 * Get all headings from markdown text
 * Properly handles code blocks to avoid extracting code as headings
 */
function getAllHeadings(markdown: string): Array<{ level: number; text: string }> {
  const headings: Array<{ level: number; text: string }> = []
  const lines = markdown.split('\n')
  
  let inCodeBlock = false
  
  for (const line of lines) {
    // Check for code block delimiters (``` or ~~~)
    if (line.trim().startsWith('```') || line.trim().startsWith('~~~')) {
      inCodeBlock = !inCodeBlock
      continue
    }
    
    // Skip lines inside code blocks
    if (inCodeBlock) {
      continue
    }
    
    // Match markdown headings (# ## ### etc.)
    // Must start with # followed by space, then have actual text content
    const match = line.match(/^(#{1,6})\s+(.+)$/)
    if (match) {
      const text = match[2].trim()
      
      // Skip if text looks like JSON or code (starts with special chars)
      if (text.startsWith('{') || text.startsWith('"') || text.startsWith('[') || 
          text.startsWith('}') || text.startsWith(']') || text === '{' || text === '}') {
        continue
      }
      
      headings.push({
        level: match[1].length,
        text: text
      })
    }
  }
  
  return headings
}

/**
 * Get the lowest heading level in the document
 */
function getLowestHeadingLevel(headings: Array<{ level: number }>): number {
  if (headings.length === 0) return 1
  return Math.min(...headings.map(h => h.level))
}

/**
 * Generate a URL-friendly slug from text
 */
function generateSlug(text: string, idCounts: Record<string, number>): string {
  let baseId = text
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
  
  // Ensure uniqueness by appending a counter if needed
  if (idCounts[baseId] !== undefined) {
    idCounts[baseId]++
    baseId = `${baseId}-${idCounts[baseId]}`
  } else {
    idCounts[baseId] = 0
  }
  
  return baseId
}

export function extractMarkdownHeadings(
  markdown: string | null | undefined
): MarkdownHeading[] {
  if (!markdown?.trim()) return []
  
  const allHeadings = getAllHeadings(markdown)
  const lowestLevel = getLowestHeadingLevel(allHeadings)
  const idCounts: Record<string, number> = {}
  
  const entries: MarkdownHeading[] = []
  let currentParent: MarkdownHeading | null = null
  
  // Add "Introduction" if description doesn't start with a heading
  if (!markdown.trim().startsWith('#')) {
    entries.push({
      level: lowestLevel,
      text: 'Introduction',
      id: generateSlug('Introduction', idCounts),
      children: []
    })
  }
  
  // Process headings - only include lowest level and one level deeper
  for (const heading of allHeadings) {
    // Skip headings that are not at the lowest two levels
    if (heading.level !== lowestLevel && heading.level !== lowestLevel + 1) {
      continue
    }
    
    const entry: MarkdownHeading = {
      level: heading.level,
      text: heading.text,
      id: generateSlug(heading.text, idCounts)
    }
    
    if (heading.level === lowestLevel) {
      // This is a top-level heading
      entry.children = []
      entries.push(entry)
      currentParent = entry
    } else if (currentParent && currentParent.children) {
      // This is a child heading
      currentParent.children.push(entry)
    }
  }
  
  return entries
}
