/**
 * Collection Indexer
 * Builds a searchable index of all API endpoints for the AI agent
 */

import type { BrainfishCollection, BrainfishRESTRequest, FormDataKeyValue } from '../types'
import type { EndpointIndex } from './types'

/**
 * Find a request by ID in the collection (recursive)
 */
export function findRequestById(
  collection: BrainfishCollection, 
  id: string
): BrainfishRESTRequest | null {
  // Check direct requests
  const found = collection.requests.find(r => r.id === id)
  if (found) return found
  
  // Check folders recursively
  for (const folder of collection.folders) {
    const request = findRequestById(folder, id)
    if (request) return request
  }
  return null
}

/**
 * Format full request details for AI context
 * Returns a structured object with all the information the AI needs
 */
export function formatRequestForAI(request: BrainfishRESTRequest): {
  id: string
  name: string
  method: string
  path: string
  description: string | null
  parameters: Array<{ name: string; description: string; required: boolean }>
  headers: Array<{ name: string; description: string; required: boolean }>
  auth: { type: string; details?: Record<string, unknown> }
  body: {
    required: boolean
    contentType: string | null
    schema: string | Array<{ key: string; isFile: boolean }> | null
    example?: string
  }
  responses: Array<{
    code: number
    status: string
    description?: string
    example?: string
  }>
} {
  // Format parameters
  const parameters = request.params.map(p => ({
    name: p.key,
    description: p.description || '',
    required: p.active,
  }))

  // Format headers
  const headers = request.headers
    .filter(h => !['Content-Type', 'Authorization'].includes(h.key)) // Filter out auto-managed headers
    .map(h => ({
      name: h.key,
      description: h.description || '',
      required: h.active,
    }))

  // Format auth
  const auth: { type: string; details?: Record<string, unknown> } = {
    type: request.auth.authType,
  }
  if (request.auth.authType === 'api-key') {
    auth.details = {
      keyName: request.auth.key,
      location: request.auth.addTo === 'HEADERS' ? 'header' : 'query',
    }
  } else if (request.auth.authType === 'bearer') {
    auth.details = { headerFormat: 'Bearer <token>' }
  } else if (request.auth.authType === 'basic') {
    auth.details = { format: 'Basic base64(username:password)' }
  }

  // Format body
  const body: {
    required: boolean
    contentType: string | null
    schema: string | Array<{ key: string; isFile: boolean }> | null
    example?: string
  } = {
    required: request.body.body !== null,
    contentType: request.body.contentType,
    schema: null,
  }

  if (request.body.body !== null) {
    if (Array.isArray(request.body.body)) {
      // Form data
      body.schema = (request.body.body as FormDataKeyValue[]).map(item => ({
        key: item.key,
        isFile: item.isFile,
      }))
    } else if (typeof request.body.body === 'string') {
      // JSON or raw body - try to parse and extract schema info
      try {
        const parsed = JSON.parse(request.body.body)
        body.example = request.body.body
        // Extract field names and types from example
        body.schema = JSON.stringify(
          extractSchemaFromExample(parsed),
          null,
          2
        )
      } catch {
        // Raw string body
        body.example = request.body.body.slice(0, 500)
        body.schema = 'raw text'
      }
    }
  }

  // Format responses
  const responses = Object.entries(request.responses).map(([name, resp]) => ({
    code: resp.code,
    status: resp.status,
    description: name !== resp.status ? name : undefined,
    example: resp.body ? resp.body.slice(0, 500) : undefined,
  }))

  return {
    id: request.id,
    name: request.name,
    method: request.method,
    path: request.endpoint,
    description: request.description,
    parameters,
    headers,
    auth,
    body,
    responses,
  }
}

/**
 * Extract a simple schema from a JSON example
 */
function extractSchemaFromExample(obj: unknown, depth = 0): unknown {
  if (depth > 3) return '...' // Prevent deep recursion
  
  if (obj === null) return 'null'
  if (typeof obj === 'string') return 'string'
  if (typeof obj === 'number') return 'number'
  if (typeof obj === 'boolean') return 'boolean'
  
  if (Array.isArray(obj)) {
    if (obj.length === 0) return '[]'
    return [extractSchemaFromExample(obj[0], depth + 1)]
  }
  
  if (typeof obj === 'object') {
    const result: Record<string, unknown> = {}
    for (const [key, value] of Object.entries(obj)) {
      result[key] = extractSchemaFromExample(value, depth + 1)
    }
    return result
  }
  
  return 'unknown'
}

/**
 * Recursively extracts all requests from a collection and its nested folders
 */
function extractRequests(
  collection: BrainfishCollection,
  parentTags: string[] = []
): { request: BrainfishRESTRequest; tags: string[] }[] {
  const results: { request: BrainfishRESTRequest; tags: string[] }[] = []
  
  // Add requests from this level
  for (const request of collection.requests) {
    results.push({
      request,
      tags: [...parentTags, collection.name].filter(t => t !== 'root'),
    })
  }
  
  // Recursively process folders
  for (const folder of collection.folders) {
    const folderResults = extractRequests(folder, [...parentTags, collection.name])
    results.push(...folderResults)
  }
  
  return results
}

/**
 * Builds an endpoint index for AI context
 */
export function buildEndpointIndex(collection: BrainfishCollection): EndpointIndex[] {
  const allRequests = extractRequests(collection)
  
  return allRequests.map(({ request, tags }) => ({
    id: request.id,
    name: request.name,
    method: request.method,
    path: request.endpoint,
    description: request.description,
    parameters: request.params.map(p => p.key),
    hasBody: request.body.body !== null,
    tags: [...new Set([...tags, ...request.tags])].filter(Boolean),
  }))
}

/**
 * Formats the endpoint index as a string for the AI system prompt
 */
export function formatEndpointsForContext(endpoints: EndpointIndex[]): string {
  return endpoints
    .map((ep, i) => {
      const params = ep.parameters.length > 0 
        ? `\n   Parameters: ${ep.parameters.join(', ')}` 
        : ''
      const tags = ep.tags.length > 0 
        ? `\n   Tags: ${ep.tags.join(', ')}` 
        : ''
      
      return `${i + 1}. [${ep.method}] ${ep.path}
   ID: ${ep.id}
   Name: ${ep.name}${ep.description ? `\n   Description: ${ep.description.slice(0, 200)}${ep.description.length > 200 ? '...' : ''}` : ''}${params}${tags}`
    })
    .join('\n\n')
}

/**
 * Fuzzy/BM25-like search over endpoints
 */
export function searchEndpoints(
  endpoints: EndpointIndex[],
  query: string,
  method?: string
): EndpointIndex[] {
  // Tokenize query
  const queryTokens = query.toLowerCase().split(/\s+/).filter(t => t.length > 1)
  const queryLower = query.toLowerCase()
  
  // Score each endpoint
  const scoredEndpoints = endpoints
    .filter(ep => !method || ep.method === method)
    .map(ep => {
      let score = 0
      const nameLower = ep.name.toLowerCase()
      const pathLower = ep.path.toLowerCase()
      const descLower = (ep.description || '').toLowerCase()
      const tagsLower = ep.tags.map(t => t.toLowerCase())
      const paramsLower = ep.parameters.map(p => p.toLowerCase())
      
      // Exact phrase match (highest priority)
      if (nameLower.includes(queryLower)) score += 100
      if (pathLower.includes(queryLower)) score += 80
      if (descLower.includes(queryLower)) score += 60
      
      // Token matching
      for (const token of queryTokens) {
        // Name matches (most important)
        if (nameLower === token) score += 50
        else if (nameLower.startsWith(token)) score += 30
        else if (nameLower.includes(token)) score += 20
        
        // Path segment matches
        const pathParts = pathLower.split('/').filter(Boolean)
        if (pathParts.some(part => part === token)) score += 40
        if (pathParts.some(part => part.startsWith(token))) score += 25
        if (pathParts.some(part => part.includes(token))) score += 15
        
        // Description matches
        if (descLower.includes(token)) score += 15
        
        // Tag matches
        if (tagsLower.some(tag => tag === token)) score += 30
        if (tagsLower.some(tag => tag.includes(token))) score += 15
        
        // Parameter matches
        if (paramsLower.some(param => param === token)) score += 25
        if (paramsLower.some(param => param.includes(token))) score += 10
        
        // HTTP method relevance
        if (ep.method.toLowerCase() === token) score += 20
        
        // Fuzzy matching for similar words
        const allWords = [nameLower, ...pathParts, ...tagsLower, ...paramsLower]
        for (const word of allWords) {
          if (word.length > 2 && token.length > 2) {
            const minLen = Math.min(word.length, token.length)
            let matches = 0
            for (let i = 0; i < minLen; i++) {
              if (word[i] === token[i]) matches++
            }
            if (matches / minLen > 0.7) score += 5
          }
        }
      }
      
      return { ...ep, score }
    })
  
  // Filter and sort by score
  return scoredEndpoints
    .filter(ep => ep.score > 0)
    .sort((a, b) => b.score - a.score)
    .slice(0, 10)
    .map(ep => ({
      id: ep.id,
      name: ep.name,
      method: ep.method,
      path: ep.path,
      description: ep.description,
      parameters: ep.parameters,
      hasBody: ep.hasBody,
      tags: ep.tags,
    }))
}
