/**
 * Type Descriptor System for Better Type Safety
 * Replaces 'any' with structured type representations
 */

// Type descriptor for runtime type information
export type TypeDescriptor =
  | { type: 'string' }
  | { type: 'number' }
  | { type: 'boolean' }
  | { type: 'null' }
  | { type: 'undefined' }
  | { type: 'array'; items: TypeDescriptor }
  | { type: 'object'; properties: Record<string, TypeDescriptor>; required?: string[] }
  | { type: 'union'; types: TypeDescriptor[] }
  | { type: 'unknown' }

// Convert TypeDescriptor to TypeScript type string (for debugging/display)
export function typeDescriptorToString(descriptor: TypeDescriptor): string {
  switch (descriptor.type) {
    case 'string':
      return 'string'
    case 'number':
      return 'number'
    case 'boolean':
      return 'boolean'
    case 'null':
      return 'null'
    case 'undefined':
      return 'undefined'
    case 'array':
      return `${typeDescriptorToString(descriptor.items)}[]`
    case 'object':
      const props = Object.entries(descriptor.properties)
        .map(([key, value]) => {
          const optional = descriptor.required && !descriptor.required.includes(key) ? '?' : ''
          return `${key}${optional}: ${typeDescriptorToString(value)}`
        })
        .join('; ')
      return `{ ${props} }`
    case 'union':
      return descriptor.types.map(typeDescriptorToString).join(' | ')
    case 'unknown':
    default:
      return 'unknown'
  }
}

// Validate data against TypeDescriptor
export function validateType(data: unknown, descriptor: TypeDescriptor): boolean {
  switch (descriptor.type) {
    case 'string':
      return typeof data === 'string'
    case 'number':
      return typeof data === 'number'
    case 'boolean':
      return typeof data === 'boolean'
    case 'null':
      return data === null
    case 'undefined':
      return data === undefined
    case 'array':
      return Array.isArray(data) && data.every(item => validateType(item, descriptor.items))
    case 'object':
      if (typeof data !== 'object' || data === null || Array.isArray(data)) return false
      const obj = data as Record<string, unknown>
      
      // Check required properties
      if (descriptor.required) {
        for (const key of descriptor.required) {
          if (!(key in obj)) return false
        }
      }
      
      // Validate all properties
      for (const [key, value] of Object.entries(descriptor.properties)) {
        if (key in obj && !validateType(obj[key], value)) return false
      }
      return true
    case 'union':
      return descriptor.types.some(type => validateType(data, type))
    case 'unknown':
      return true
    default:
      return false
  }
}

// Infer TypeDescriptor from sample data
export function inferTypeDescriptor(data: unknown, samples?: unknown[]): TypeDescriptor {
  if (data === null) return { type: 'null' }
  if (data === undefined) return { type: 'undefined' }
  if (typeof data === 'string') return { type: 'string' }
  if (typeof data === 'number') return { type: 'number' }
  if (typeof data === 'boolean') return { type: 'boolean' }
  
  if (Array.isArray(data)) {
    if (data.length === 0) {
      // Try to infer from samples if available
      if (samples) {
        for (const sample of samples) {
          if (Array.isArray(sample) && sample.length > 0) {
            return { type: 'array', items: inferTypeDescriptor(sample[0]) }
          }
        }
      }
      return { type: 'array', items: { type: 'unknown' } }
    }
    
    // Infer item type from all array elements
    const itemTypes = data.map(item => inferTypeDescriptor(item))
    const uniqueTypes = deduplicateTypes(itemTypes)
    
    if (uniqueTypes.length === 0) {
      return { type: 'array', items: { type: 'unknown' } }
    } else if (uniqueTypes.length === 1) {
      return { type: 'array', items: uniqueTypes[0]! }
    } else {
      return { type: 'array', items: { type: 'union', types: uniqueTypes } }
    }
  }
  
  if (typeof data === 'object') {
    const properties: Record<string, TypeDescriptor> = {}
    const required: string[] = []
    
    // Collect all keys from current data and samples
    const allKeys = new Set<string>(Object.keys(data))
    if (samples) {
      for (const sample of samples) {
        if (sample && typeof sample === 'object' && !Array.isArray(sample)) {
          Object.keys(sample).forEach(key => allKeys.add(key))
        }
      }
    }
    
    // Infer type for each property
    for (const key of allKeys) {
      const currentValue = (data as any)[key]
      const sampleValues = samples?.map(s => (s as any)?.[key]).filter(v => v !== undefined)
      
      if (currentValue !== undefined) {
        properties[key] = inferTypeDescriptor(currentValue, sampleValues)
        required.push(key)
      } else if (sampleValues && sampleValues.length > 0) {
        properties[key] = inferTypeDescriptor(sampleValues[0], sampleValues)
      }
    }
    
    return { type: 'object', properties, required }
  }
  
  return { type: 'unknown' }
}

// Helper to deduplicate types for union types
function deduplicateTypes(types: TypeDescriptor[]): TypeDescriptor[] {
  const seen = new Set<string>()
  const result: TypeDescriptor[] = []
  
  for (const type of types) {
    const key = JSON.stringify(type)
    if (!seen.has(key)) {
      seen.add(key)
      result.push(type)
    }
  }
  
  return result
}