import React, { createContext, useContext, useState, useMemo, useCallback } from 'react'

const XRDContext = createContext()

export const useXRD = () => {
  const context = useContext(XRDContext)
  if (!context) {
    throw new Error('useXRD must be used within XRDProvider')
  }
  return context
}

export const XRDProvider = ({ children }) => {
  // Model training specifications (from simulator.yaml)
  const MODEL_INPUT_SIZE = 8192
  const MODEL_WAVELENGTH = 0.6199  // Ångströms (synchrotron)
  const MODEL_MIN_2THETA = 5.0     // degrees
  const MODEL_MAX_2THETA = 20.0    // degrees
  
  // Raw data from file upload
  const [rawData, setRawData] = useState(null)
  const [filename, setFilename] = useState(null)
  
  // Wavelength management
  const [detectedWavelength, setDetectedWavelength] = useState(null)
  const [userWavelength, setUserWavelength] = useState(MODEL_WAVELENGTH)
  const [wavelengthSource, setWavelengthSource] = useState('default') // 'detected', 'user', 'default'
  
  // Processing parameters
  const [baselineCorrection, setBaselineCorrection] = useState(false)
  const [interpolationEnabled, setInterpolationEnabled] = useState(true)
  const [scalingEnabled, setScalingEnabled] = useState(true)
  const [interpolationStrategy, setInterpolationStrategy] = useState('linear') // 'linear' or 'cubic'
  
  // Warnings and metadata
  const [dataWarnings, setDataWarnings] = useState([])
  
  // Model results from API
  const [modelResults, setModelResults] = useState(null)
  const [isLoading, setIsLoading] = useState(false)
  const [analysisStatus, setAnalysisStatus] = useState('IDLE') // IDLE, PROCESSING, COMPLETE
  
  // UI state
  const [isLogitDrawerOpen, setIsLogitDrawerOpen] = useState(false)
  
  // Request tracking - ensure every click creates a new request
  const [analysisCount, setAnalysisCount] = useState(0)
  
  // Convert wavelength using Bragg's law: λ = 2d·sin(θ)
  // For same d-spacing: sin(θ₂) = (λ₂/λ₁)·sin(θ₁)
  const convertWavelength = (theta_deg, sourceWavelength, targetWavelength) => {
    if (Math.abs(sourceWavelength - targetWavelength) < 0.0001) {
      return theta_deg // No conversion needed
    }
    
    const theta_rad = (theta_deg * Math.PI) / 180
    const sin_theta2 = (targetWavelength / sourceWavelength) * Math.sin(theta_rad)
    
    // Check if conversion is physically possible
    if (Math.abs(sin_theta2) > 1) {
      return null // Peak not observable at target wavelength
    }
    
    const theta2_rad = Math.asin(sin_theta2)
    return (theta2_rad * 180) / Math.PI
  }
  
  // Interpolate data to fixed size for model input
  const interpolateData = (x, y, targetSize, xMin, xMax, strategy = 'linear') => {
    if (x.length === targetSize && xMin === undefined) {
      return { x, y }
    }
    
    const minX = xMin !== undefined ? xMin : Math.min(...x)
    const maxX = xMax !== undefined ? xMax : Math.max(...x)
    const step = (maxX - minX) / (targetSize - 1)
    
    const newX = Array.from({ length: targetSize }, (_, i) => minX + i * step)
    const newY = new Array(targetSize)
    
    // Get data range bounds
    const dataMinX = Math.min(...x)
    const dataMaxX = Math.max(...x)
    
    if (strategy === 'linear') {
      // Linear interpolation
      for (let i = 0; i < targetSize; i++) {
        const targetX = newX[i]
        
        // Check if out of range - set to 0 instead of extrapolating
        if (targetX < dataMinX || targetX > dataMaxX) {
          newY[i] = 0
          continue
        }
        
        // Find surrounding points
        let idx = x.findIndex(val => val >= targetX)
        if (idx === -1) idx = x.length - 1
        if (idx === 0) idx = 1
        
        const x0 = x[idx - 1]
        const x1 = x[idx]
        const y0 = y[idx - 1]
        const y1 = y[idx]
        
        // Linear interpolation
        newY[i] = y0 + ((targetX - x0) * (y1 - y0)) / (x1 - x0)
      }
    } else if (strategy === 'cubic') {
      // Cubic spline interpolation (simplified Catmull-Rom)
      for (let i = 0; i < targetSize; i++) {
        const targetX = newX[i]
        
        // Check if out of range - set to 0 instead of extrapolating
        if (targetX < dataMinX || targetX > dataMaxX) {
          newY[i] = 0
          continue
        }
        
        // Find surrounding points
        let idx = x.findIndex(val => val >= targetX)
        if (idx === -1) idx = x.length - 1
        if (idx === 0) idx = 1
        
        // Get 4 points for cubic interpolation
        const i0 = Math.max(0, idx - 2)
        const i1 = Math.max(0, idx - 1)
        const i2 = Math.min(x.length - 1, idx)
        const i3 = Math.min(x.length - 1, idx + 1)
        
        // Use linear interpolation if we don't have enough points
        if (i2 === i1) {
          newY[i] = y[i1]
        } else {
          const t = (targetX - x[i1]) / (x[i2] - x[i1])
          const t2 = t * t
          const t3 = t2 * t
          
          // Catmull-Rom spline coefficients
          const v0 = y[i0]
          const v1 = y[i1]
          const v2 = y[i2]
          const v3 = y[i3]
          
          newY[i] = 0.5 * (
            2 * v1 +
            (-v0 + v2) * t +
            (2 * v0 - 5 * v1 + 4 * v2 - v3) * t2 +
            (-v0 + 3 * v1 - 3 * v2 + v3) * t3
          )
        }
      }
    }
    
    return { x: newX, y: newY }
  }
  
  // Process data with optional interpolation
  const processedData = useMemo(() => {
    if (!rawData) return null
    
    try {
      const warnings = []
      let processedY = [...rawData.y]
      let processedX = [...rawData.x]
      
      // Step 1: Wavelength conversion (if needed)
      const sourceWavelength = userWavelength
      if (sourceWavelength && Math.abs(sourceWavelength - MODEL_WAVELENGTH) > 0.0001) {
        const convertedData = []
        for (let i = 0; i < processedX.length; i++) {
          const convertedTheta = convertWavelength(processedX[i], sourceWavelength, MODEL_WAVELENGTH)
          if (convertedTheta !== null) {
            convertedData.push({ x: convertedTheta, y: processedY[i] })
          }
        }
        
        if (convertedData.length < processedX.length) {
          warnings.push(`${processedX.length - convertedData.length} points outside physical range after wavelength conversion`)
        }
        
        processedX = convertedData.map(d => d.x)
        processedY = convertedData.map(d => d.y)
        
        warnings.push(`Converted from ${sourceWavelength.toFixed(4)} Å to ${MODEL_WAVELENGTH} Å`)
      }
      
      // Step 2: Apply baseline correction if enabled
      if (baselineCorrection) {
        const baseline = Math.min(...processedY)
        processedY = processedY.map(val => val - baseline)
      }
      
      // Step 3: Crop to model's 2θ range (5-20°)
      const inRangeData = []
      for (let i = 0; i < processedX.length; i++) {
        if (processedX[i] >= MODEL_MIN_2THETA && processedX[i] <= MODEL_MAX_2THETA) {
          inRangeData.push({ x: processedX[i], y: processedY[i] })
        }
      }
      
      if (inRangeData.length === 0) {
        warnings.push(`⚠️ No data points in model range (${MODEL_MIN_2THETA}-${MODEL_MAX_2THETA}°)`)
        // Use original data but warn
        inRangeData.push(...processedX.map((x, i) => ({ x, y: processedY[i] })))
      } else if (inRangeData.length < processedX.length) {
        const coverage = (inRangeData.length / processedX.length * 100).toFixed(1)
        warnings.push(`${coverage}% of data in model range (${MODEL_MIN_2THETA}-${MODEL_MAX_2THETA}°)`)
      }
      
      let croppedX = inRangeData.map(d => d.x)
      let croppedY = inRangeData.map(d => d.y)
      
      // Step 4: Apply 0-100 scaling if enabled (matching training data)
      // NOTE: Scaling happens AFTER cropping so the max peak in the visible range = 100
      if (scalingEnabled) {
        const minY = Math.min(...croppedY)
        const maxY = Math.max(...croppedY)
        if (maxY - minY > 0) {
          croppedY = croppedY.map(val => ((val - minY) / (maxY - minY)) * 100)
        }
      }
      
      // Step 5: Interpolate to model input size with fixed range
      const interpolated = interpolateData(
        croppedX, 
        croppedY, 
        MODEL_INPUT_SIZE,
        MODEL_MIN_2THETA,
        MODEL_MAX_2THETA,
        interpolationStrategy
      )
      
      // Update warnings
      setDataWarnings(warnings)
      
      return {
        x: interpolated.x,
        y: interpolated.y
      }
    } catch (error) {
      console.error('Error processing data:', error)
      setDataWarnings([`Error: ${error.message}`])
      return rawData
    }
  }, [rawData, baselineCorrection, userWavelength, interpolationStrategy, scalingEnabled])
  
  // Extract metadata from CIF/DIF files
  const extractMetadata = (text) => {
    const metadata = {
      wavelength: null,
      cellParams: null,
      spaceGroup: null,
      crystalSystem: null
    }
    
    const lines = text.split('\n')
    
    // Common wavelength patterns in headers
    const wavelengthPatterns = [
      /wavelength[:\s=]+([0-9.]+)/i,
      /lambda[:\s=]+([0-9.]+)/i,
      /wave[:\s=]+([0-9.]+)/i,
      /_pd_wavelength[:\s]+([0-9.]+)/i,  // CIF format
      /_diffrn_radiation_wavelength[:\s]+([0-9.]+)/i,  // CIF format
      /radiation.*?([0-9.]+)\s*[AÅ]/i,
    ]
    
    for (const line of lines) {
      // Extract wavelength
      if (!metadata.wavelength) {
        for (const pattern of wavelengthPatterns) {
          const match = line.match(pattern)
          if (match && match[1]) {
            const wavelength = parseFloat(match[1])
            if (wavelength > 0.1 && wavelength < 3.0) {  // Reasonable X-ray range
              metadata.wavelength = wavelength
              break
            }
          }
        }
        
        // Check for common radiation types
        if (/Cu\s*K[αa]/i.test(line)) metadata.wavelength = 1.5406  // Cu Kα
        else if (/Mo\s*K[αa]/i.test(line)) metadata.wavelength = 0.7107  // Mo Kα
        else if (/Co\s*K[αa]/i.test(line)) metadata.wavelength = 1.7889  // Co Kα
        else if (/Cr\s*K[αa]/i.test(line)) metadata.wavelength = 2.2897  // Cr Kα
      }
      
      // Extract cell parameters (DIF format)
      if (/CELL PARAMETERS:/i.test(line)) {
        const match = line.match(/CELL PARAMETERS:\s*([\d.\s]+)/)
        if (match) {
          metadata.cellParams = match[1].trim()
        }
      }
      
      // Extract space group
      if (/SPACE GROUP:/i.test(line) || /_symmetry_Int_Tables_number/i.test(line)) {
        const match = line.match(/(?:SPACE GROUP:|_symmetry_Int_Tables_number)[:\s]+(\d+)/)
        if (match) {
          metadata.spaceGroup = match[1]
        }
      }
      
      // Extract crystal system
      if (/Crystal System:/i.test(line)) {
        const match = line.match(/Crystal System:\s*(\d+)/)
        if (match) {
          metadata.crystalSystem = match[1]
        }
      }
    }
    
    return metadata
  }
  
  // Parse CIF format data
  const parseCIF = (text) => {
    const lines = text.split('\n')
    const x = []
    const y = []
    let inDataLoop = false
    let dataColumns = []
    let thetaIndex = -1
    let intensityIndex = -1
    
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i].trim()
      
      // Detect start of data loop
      if (line === 'loop_') {
        inDataLoop = true
        dataColumns = []
        continue
      }
      
      // Collect column names in loop
      if (inDataLoop && line.startsWith('_')) {
        dataColumns.push(line)
        
        // Identify 2theta column
        if (/_pd_meas_angle_2theta/i.test(line) || /_pd_calc_angle_2theta/i.test(line)) {
          thetaIndex = dataColumns.length - 1
        }
        
        // Identify intensity column
        if (/_pd_proc_intensity/i.test(line) || /_pd_calc_intensity/i.test(line) || /_pd_meas_counts/i.test(line)) {
          intensityIndex = dataColumns.length - 1
        }
        continue
      }
      
      // Parse data lines
      if (inDataLoop && !line.startsWith('_') && !line.startsWith('loop_') && line.length > 0 && !line.startsWith('#')) {
        // Check if we've found the data section
        if (thetaIndex >= 0 && intensityIndex >= 0) {
          const parts = line.split(/\s+/)
          
          if (parts.length >= Math.max(thetaIndex, intensityIndex) + 1) {
            const xVal = parseFloat(parts[thetaIndex])
            const yVal = parseFloat(parts[intensityIndex])
            
            if (!isNaN(xVal) && !isNaN(yVal)) {
              x.push(xVal)
              y.push(yVal)
            }
          }
        } else {
          // End of loop, no data found
          inDataLoop = false
          dataColumns = []
          thetaIndex = -1
          intensityIndex = -1
        }
      }
      
      // Reset if we hit another loop_ or data block
      if (inDataLoop && (line.startsWith('data_') || (line === 'loop_' && dataColumns.length > 0))) {
        inDataLoop = false
      }
    }
    
    return { x, y }
  }
  
  // Parse DIF or XY format (space-separated 2theta intensity)
  const parseDIF = (text) => {
    const lines = text.split('\n')
    const x = []
    const y = []
    
    for (const line of lines) {
      const trimmed = line.trim()
      
      // Skip comment lines, headers, and metadata
      if (!trimmed || 
          trimmed.startsWith('#') || 
          trimmed.startsWith('_') || 
          trimmed.startsWith('CELL') ||
          trimmed.startsWith('SPACE') ||
          /^[a-zA-Z]/.test(trimmed)) {  // Skip lines starting with letters (metadata)
        continue
      }
      
      // Split by whitespace
      const parts = trimmed.split(/\s+/)
      if (parts.length >= 2) {
        const xVal = parseFloat(parts[0])
        const yVal = parseFloat(parts[1])
        
        if (!isNaN(xVal) && !isNaN(yVal)) {
          x.push(xVal)
          y.push(yVal)
        }
      }
    }
    
    return { x, y }
  }
  
  // Parse uploaded file
  const parseFile = (file) => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader()
      
      reader.onload = (e) => {
        try {
          const text = e.target.result
          
          // Extract metadata (including wavelength)
          const metadata = extractMetadata(text)
          if (metadata.wavelength) {
            setDetectedWavelength(metadata.wavelength)
            setUserWavelength(metadata.wavelength)
            setWavelengthSource('detected')
          } else {
            setDetectedWavelength(null)
            setWavelengthSource('default')
          }
          
          // Determine file format and parse accordingly
          const fileName = file.name.toLowerCase()
          let data = { x: [], y: [] }
          
          if (fileName.endsWith('.cif')) {
            // CIF format - look for loop_ structures
            data = parseCIF(text)
            
            // Fallback to simple parsing if CIF parsing didn't find data
            if (data.x.length === 0) {
              console.log('CIF loop parsing failed, falling back to simple parser')
              data = parseDIF(text)
            }
          } else {
            // DIF, XY, CSV, TXT - simple space/comma separated
            data = parseDIF(text)
          }
          
          if (data.x.length === 0 || data.y.length === 0) {
            reject(new Error('No valid data points found in file'))
            return
          }
          
          console.log(`Parsed ${data.x.length} data points from ${fileName}`)
          resolve(data)
        } catch (error) {
          reject(error)
        }
      }
      
      reader.onerror = () => reject(new Error('Failed to read file'))
      reader.readAsText(file)
    })
  }
  
  // Upload and parse file
  const handleFileUpload = async (file) => {
    try {
      const data = await parseFile(file)
      
      setRawData(data)
      setFilename(file.name)
      setModelResults(null) // Clear previous results
      setAnalysisStatus('IDLE')
      setIsLogitDrawerOpen(false) // Close logit drawer if open
      
      return true
    } catch (error) {
      console.error('Error uploading file:', error)
      alert(`Error loading file: ${error.message}`)
      return false
    }
  }
  
  // Send processed data to API for inference
  const runInference = useCallback(async () => {
    if (!processedData) {
      alert('No data to analyze')
      return
    }
    
    // Increment analysis counter - tracks button clicks
    const currentCount = analysisCount + 1
    setAnalysisCount(currentCount)
    
    setIsLoading(true)
    setAnalysisStatus('PROCESSING')
    
    try {
      const requestTimestamp = Date.now()
      
      const response = await fetch('/api/predict', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          // Anti-caching headers
          'Cache-Control': 'no-cache, no-store, must-revalidate',
          'Pragma': 'no-cache',
          'Expires': '0',
          // Request tracking
          'X-Request-ID': String(requestTimestamp),
          'X-Filename': filename || 'unknown',
        },
        // Explicitly disable caching for this request
        cache: 'no-store',
        body: JSON.stringify({
          x: processedData.x,
          y: processedData.y,
          // Include metadata to help track requests
          metadata: {
            timestamp: requestTimestamp,
            filename: filename,
            analysisCount: currentCount,
          }
        }),
      })
      
      if (!response.ok) {
        throw new Error(`API error: ${response.status}`)
      }
      
      const results = await response.json()
      setModelResults(results)
      setAnalysisStatus('COMPLETE')
    } catch (error) {
      console.error('Error running inference:', error)
      alert(`Inference failed: ${error.message}`)
      setAnalysisStatus('IDLE')
    } finally {
      setIsLoading(false)
    }
  }, [processedData, analysisCount, filename])
  
  // Load an example data file from the API
  const loadExampleFile = useCallback(async (filename) => {
    try {
      const response = await fetch(`/api/examples/${encodeURIComponent(filename)}`)
      if (!response.ok) {
        throw new Error(`Failed to fetch example: ${response.status}`)
      }
      const text = await response.text()

      // Extract metadata (including wavelength) — same as normal file upload
      const metadata = extractMetadata(text)
      if (metadata.wavelength) {
        setDetectedWavelength(metadata.wavelength)
        setUserWavelength(metadata.wavelength)
        setWavelengthSource('detected')
      } else {
        setDetectedWavelength(null)
        setWavelengthSource('default')
      }

      // Parse using the DIF parser (all examples are .dif)
      const data = parseDIF(text)
      if (data.x.length === 0 || data.y.length === 0) {
        throw new Error('No valid data points found in example file')
      }

      setRawData(data)
      setFilename(filename)
      setModelResults(null)
      setAnalysisStatus('IDLE')
      setIsLogitDrawerOpen(false)

      return true
    } catch (error) {
      console.error('Error loading example file:', error)
      alert(`Error loading example: ${error.message}`)
      return false
    }
  }, [])

  // Reset application state
  const handleReset = () => {
    setRawData(null)
    setFilename(null)
    setModelResults(null)
    setAnalysisStatus('IDLE')
    setIsLogitDrawerOpen(false)
  }
  
  const value = {
    rawData,
    processedData,
    modelResults,
    isLoading,
    filename,
    analysisStatus,
    detectedWavelength,
    userWavelength,
    setUserWavelength,
    wavelengthSource,
    dataWarnings,
    baselineCorrection,
    setBaselineCorrection,
    interpolationEnabled,
    setInterpolationEnabled,
    scalingEnabled,
    setScalingEnabled,
    interpolationStrategy,
    setInterpolationStrategy,
    isLogitDrawerOpen,
    setIsLogitDrawerOpen,
    handleFileUpload,
    loadExampleFile,
    runInference,
    handleReset,
    MODEL_WAVELENGTH,
    MODEL_MIN_2THETA,
    MODEL_MAX_2THETA,
    MODEL_INPUT_SIZE,
  }
  
  return <XRDContext.Provider value={value}>{children}</XRDContext.Provider>
}
