import { RecordsDiff, TLRecord } from "@tldraw/tldraw"

// Helper function to clean NaN values from richText content
// This prevents SVG export errors when TLDraw tries to render text with invalid coordinates
function cleanRichTextNaN(richText: any): any {
  if (!richText || typeof richText !== 'object') {
    return richText
  }
  
  // Deep clone to avoid mutating the original
  const cleaned = JSON.parse(JSON.stringify(richText))
  
  // Recursively clean content array
  if (Array.isArray(cleaned.content)) {
    cleaned.content = cleaned.content.map((item: any) => {
      if (typeof item === 'object' && item !== null) {
        // Remove any NaN values from the item
        const cleanedItem: any = {}
        for (const key in item) {
          const value = item[key]
          // Skip NaN values - they cause SVG export errors
          if (typeof value === 'number' && isNaN(value)) {
            // Skip NaN values
            continue
          }
          // Recursively clean nested objects
          if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
            cleanedItem[key] = cleanRichTextNaN(value)
          } else if (Array.isArray(value)) {
            cleanedItem[key] = value.map((v: any) => 
              typeof v === 'object' && v !== null ? cleanRichTextNaN(v) : v
            )
          } else {
            cleanedItem[key] = value
          }
        }
        return cleanedItem
      }
      return item
    })
  }
  
  return cleaned
}

function sanitizeRecord(record: TLRecord): TLRecord {
  const sanitized = { ...record }
  
  // CRITICAL FIXES ONLY - preserve all other properties
  // This function preserves ALL shape types (native and custom):
  // - Geo shapes (rectangles, ellipses, etc.) - handled below
  // - Arrow shapes - handled below
  // - Custom shapes (ObsNote, Holon, etc.) - all props preserved via deep copy
  // - All other native shapes (text, note, draw, line, group, image, video, etc.)
  
  // Ensure required top-level fields exist
  if (sanitized.typeName === 'shape') {
    // CRITICAL: Only set defaults if coordinates are truly missing or invalid
    // DO NOT overwrite valid coordinates (including 0, which is a valid position)
    // Only set to 0 if the value is undefined, null, or NaN
    if (sanitized.x === undefined || sanitized.x === null || (typeof sanitized.x === 'number' && isNaN(sanitized.x))) {
      sanitized.x = 0
    }
    if (sanitized.y === undefined || sanitized.y === null || (typeof sanitized.y === 'number' && isNaN(sanitized.y))) {
      sanitized.y = 0
    }
    if (typeof sanitized.rotation !== 'number') sanitized.rotation = 0
    if (typeof sanitized.isLocked !== 'boolean') sanitized.isLocked = false
    if (typeof sanitized.opacity !== 'number') sanitized.opacity = 1
    // CRITICAL: Preserve all existing meta properties - only create empty object if meta doesn't exist
    if (!sanitized.meta || typeof sanitized.meta !== 'object') {
      sanitized.meta = {}
    } else {
      // Ensure meta is a mutable copy to preserve all properties (including text for rectangles)
      sanitized.meta = { ...sanitized.meta }
    }
    if (!sanitized.props || typeof sanitized.props !== 'object') sanitized.props = {}
    
    // CRITICAL: Extract richText BEFORE deep copy to handle TLDraw RichText instances properly
    // TLDraw RichText objects may have methods/getters that don't serialize well
    let richTextValue: any = undefined
    try {
      // Safely check if richText exists using 'in' operator to avoid triggering getters
      const props = sanitized.props || {}
      if ('richText' in props) {
        try {
          // Use Object.getOwnPropertyDescriptor to safely check if it's a getter
          const descriptor = Object.getOwnPropertyDescriptor(props, 'richText')
          let rt: any = undefined
          
          if (descriptor && descriptor.get) {
            // It's a getter - try to call it safely
            try {
              rt = descriptor.get.call(props)
            } catch (getterError) {
              console.warn(`🔧 TLStoreToAutomerge: Error calling richText getter for shape ${sanitized.id}:`, getterError)
              rt = undefined
            }
          } else {
            // It's a regular property - access it directly
            rt = (props as any).richText
          }
          
          // Now process the value
          if (rt !== undefined && rt !== null) {
            // Check if it's a function (shouldn't happen, but be safe)
            if (typeof rt === 'function') {
              console.warn(`🔧 TLStoreToAutomerge: richText is a function for shape ${sanitized.id}, skipping`)
              richTextValue = { content: [], type: 'doc' }
            } 
            // Check if it's an array
            else if (Array.isArray(rt)) {
              richTextValue = { content: JSON.parse(JSON.stringify(rt)), type: 'doc' }
            } 
            // Check if it's an object
            else if (typeof rt === 'object') {
              // Extract plain object representation - use JSON to ensure it's serializable
              try {
                const serialized = JSON.parse(JSON.stringify(rt))
                richTextValue = {
                  type: serialized.type || 'doc',
                  content: serialized.content !== undefined ? serialized.content : []
                }
              } catch (serializeError) {
                // If serialization fails, try to extract manually
                richTextValue = {
                  type: (rt as any).type || 'doc',
                  content: (rt as any).content !== undefined ? (rt as any).content : []
                }
              }
            } 
            // Invalid type
            else {
              console.warn(`🔧 TLStoreToAutomerge: Invalid richText type for shape ${sanitized.id}:`, typeof rt)
              richTextValue = { content: [], type: 'doc' }
            }
          }
        } catch (e) {
          console.warn(`🔧 TLStoreToAutomerge: Error extracting richText for shape ${sanitized.id}:`, e)
          richTextValue = { content: [], type: 'doc' }
        }
      }
    } catch (e) {
      console.warn(`🔧 TLStoreToAutomerge: Error checking richText for shape ${sanitized.id}:`, e)
    }
    
    // CRITICAL: Extract arrow text BEFORE deep copy to handle RichText instances properly
    // Arrow text should be a string, but might be a RichText object in edge cases
    let arrowTextValue: any = undefined
    if (sanitized.type === 'arrow') {
      try {
        const props = sanitized.props || {}
        if ('text' in props) {
          try {
            // Use Object.getOwnPropertyDescriptor to safely check if it's a getter
            const descriptor = Object.getOwnPropertyDescriptor(props, 'text')
            let textValue: any = undefined
            
            if (descriptor && descriptor.get) {
              // It's a getter - try to call it safely
              try {
                textValue = descriptor.get.call(props)
              } catch (getterError) {
                console.warn(`🔧 TLStoreToAutomerge: Error calling text getter for arrow ${sanitized.id}:`, getterError)
                textValue = undefined
              }
            } else {
              // It's a regular property - access it directly
              textValue = (props as any).text
            }
            
            // Now process the value
            if (textValue !== undefined && textValue !== null) {
              // If it's a string, use it directly
              if (typeof textValue === 'string') {
                arrowTextValue = textValue
              }
              // If it's a RichText object, extract the text content
              else if (typeof textValue === 'object' && textValue !== null) {
                // Try to extract text from RichText object
                try {
                  const serialized = JSON.parse(JSON.stringify(textValue))
                  // If it has content array, extract text from it
                  if (Array.isArray(serialized.content)) {
                    // Extract text from RichText content
                    const extractText = (content: any[]): string => {
                      return content.map((item: any) => {
                        if (item.type === 'text' && item.text) {
                          return item.text
                        } else if (item.content && Array.isArray(item.content)) {
                          return extractText(item.content)
                        }
                        return ''
                      }).join('')
                    }
                    arrowTextValue = extractText(serialized.content)
                  } else {
                    // Fallback: try to get text property
                    arrowTextValue = serialized.text || ''
                  }
                } catch (serializeError) {
                  // If serialization fails, try to extract manually
                  if ((textValue as any).text && typeof (textValue as any).text === 'string') {
                    arrowTextValue = (textValue as any).text
                  } else {
                    arrowTextValue = String(textValue)
                  }
                }
              }
              // For other types, convert to string
              else {
                arrowTextValue = String(textValue)
              }
            }
          } catch (e) {
            console.warn(`🔧 TLStoreToAutomerge: Error extracting text for arrow ${sanitized.id}:`, e)
            arrowTextValue = undefined
          }
        }
      } catch (e) {
        console.warn(`🔧 TLStoreToAutomerge: Error checking text for arrow ${sanitized.id}:`, e)
      }
    }
    
    // CRITICAL: For all shapes, ensure props is a deep mutable copy to preserve all properties
    // This is essential for custom shapes like ObsNote and for preserving richText in geo shapes
    // Use JSON parse/stringify to create a deep copy of nested objects (like richText.content)
    // Remove richText and arrow text temporarily to avoid serialization issues
    try {
      const propsWithoutSpecial: any = {}
      // Copy all props except richText and arrow text (if extracted)
      for (const key in sanitized.props) {
        if (key !== 'richText' && !(sanitized.type === 'arrow' && key === 'text' && arrowTextValue !== undefined)) {
          propsWithoutSpecial[key] = (sanitized.props as any)[key]
        }
      }
      sanitized.props = JSON.parse(JSON.stringify(propsWithoutSpecial))
    } catch (e) {
      console.warn(`🔧 TLStoreToAutomerge: Error deep copying props for shape ${sanitized.id}:`, e)
      // Fallback: just copy props without deep copy
      sanitized.props = { ...sanitized.props }
      if (richTextValue !== undefined) {
        delete (sanitized.props as any).richText
      }
      if (arrowTextValue !== undefined) {
        delete (sanitized.props as any).text
      }
    }
    
    // CRITICAL: For geo shapes, move w/h/geo from top-level to props (required by TLDraw schema)
    if (sanitized.type === 'geo') {
      
      // Move w from top-level to props if needed
      if ('w' in sanitized && sanitized.w !== undefined) {
        if ((sanitized.props as any).w === undefined) {
          (sanitized.props as any).w = (sanitized as any).w
        }
        delete (sanitized as any).w
      }
      
      // Move h from top-level to props if needed
      if ('h' in sanitized && sanitized.h !== undefined) {
        if ((sanitized.props as any).h === undefined) {
          (sanitized.props as any).h = (sanitized as any).h
        }
        delete (sanitized as any).h
      }
      
      // Move geo from top-level to props if needed
      if ('geo' in sanitized && sanitized.geo !== undefined) {
        if ((sanitized.props as any).geo === undefined) {
          (sanitized.props as any).geo = (sanitized as any).geo
        }
        delete (sanitized as any).geo
      }
      
      // CRITICAL: Restore richText for geo shapes after deep copy
      // Fix richText structure if it exists (preserve content, ensure proper format)
      if (richTextValue !== undefined) {
        // Clean NaN values to prevent SVG export errors
        (sanitized.props as any).richText = cleanRichTextNaN(richTextValue)
      }
      // CRITICAL: Preserve meta.text for geo shapes - it's used by runLLMprompt for backwards compatibility
      // Ensure meta.text is preserved if it exists
      if ((sanitized.meta as any)?.text !== undefined) {
        // meta.text is already preserved since we copied meta above
        // Just ensure it's not accidentally deleted
      }
      // Note: We don't delete richText if it's missing - it's optional for geo shapes
    }
    
    // CRITICAL: For arrow shapes, preserve text property
    if (sanitized.type === 'arrow') {
      // CRITICAL: Restore extracted text value if available, otherwise preserve existing text
      if (arrowTextValue !== undefined) {
        // Use the extracted text value (handles RichText objects by extracting text content)
        (sanitized.props as any).text = arrowTextValue
      } else {
        // CRITICAL: Preserve text property - only set default if truly missing (preserve empty strings and all other values)
        if ((sanitized.props as any).text === undefined || (sanitized.props as any).text === null) {
          (sanitized.props as any).text = ''
        }
        // Note: We preserve text even if it's an empty string - that's a valid value
      }
    }
    
    // CRITICAL: For note shapes, preserve richText property (required for note shapes)
    if (sanitized.type === 'note') {
      // CRITICAL: Use the extracted richText value if available, otherwise create default
      if (richTextValue !== undefined) {
        // Clean NaN values to prevent SVG export errors
        (sanitized.props as any).richText = cleanRichTextNaN(richTextValue)
      } else {
        // Note shapes require richText - create default if missing
        (sanitized.props as any).richText = { content: [], type: 'doc' }
      }
    }

    // CRITICAL: For text shapes, preserve richText property (required for text shapes)
    // Text shapes store their content in props.richText, not props.text
    if (sanitized.type === 'text') {
      // CRITICAL: Use the extracted richText value if available, otherwise create default
      if (richTextValue !== undefined) {
        // Clean NaN values to prevent SVG export errors
        (sanitized.props as any).richText = cleanRichTextNaN(richTextValue)
      } else {
        // Text shapes require richText - create default if missing
        (sanitized.props as any).richText = { content: [], type: 'doc' }
      }
    }
    
    // CRITICAL: For ObsNote shapes, ensure all props are preserved (title, content, tags, etc.)
    if (sanitized.type === 'ObsNote') {
      // Props are already a mutable copy from above, so all properties are preserved
      // No special handling needed - just ensure props exists (which we did above)
    }
    
    // CRITICAL: For image/video shapes, fix crop structure if it exists
    if (sanitized.type === 'image' || sanitized.type === 'video') {
      const props = (sanitized.props as any)
      
      if (props.crop !== null && props.crop !== undefined) {
        // Fix crop structure if it has wrong format
        if (!props.crop.topLeft || !props.crop.bottomRight) {
          if (props.crop.x !== undefined && props.crop.y !== undefined) {
            // Convert old format { x, y, w, h } to new format
            props.crop = {
              topLeft: { x: props.crop.x || 0, y: props.crop.y || 0 },
              bottomRight: { 
                x: (props.crop.x || 0) + (props.crop.w || 1), 
                y: (props.crop.y || 0) + (props.crop.h || 1) 
              }
            }
          } else {
            // Invalid structure: set to default
            props.crop = {
              topLeft: { x: 0, y: 0 },
              bottomRight: { x: 1, y: 1 }
            }
          }
        }
      }
    }
    
    // CRITICAL: For group shapes, remove w/h from props (they cause validation errors)
    if (sanitized.type === 'group') {
      if ('w' in sanitized.props) delete (sanitized.props as any).w
      if ('h' in sanitized.props) delete (sanitized.props as any).h
    }
  } else if (sanitized.typeName === 'document') {
    // CRITICAL: Preserve all existing meta properties
    if (!sanitized.meta || typeof sanitized.meta !== 'object') {
      sanitized.meta = {}
    } else {
      sanitized.meta = { ...sanitized.meta }
    }
  } else if (sanitized.typeName === 'instance') {
    // CRITICAL: Preserve all existing meta properties
    if (!sanitized.meta || typeof sanitized.meta !== 'object') {
      sanitized.meta = {}
    } else {
      sanitized.meta = { ...sanitized.meta }
    }
    // Only fix critical instance fields that cause validation errors
    if ('brush' in sanitized && (sanitized.brush === null || sanitized.brush === undefined)) {
      (sanitized as any).brush = { x: 0, y: 0, w: 0, h: 0 }
    }
    if ('zoomBrush' in sanitized && (sanitized.zoomBrush === null || sanitized.zoomBrush === undefined)) {
      (sanitized as any).zoomBrush = { x: 0, y: 0, w: 0, h: 0 }
    }
    if ('insets' in sanitized && (sanitized.insets === undefined || !Array.isArray(sanitized.insets))) {
      (sanitized as any).insets = [false, false, false, false]
    }
    if ('scribbles' in sanitized && (sanitized.scribbles === undefined || !Array.isArray(sanitized.scribbles))) {
      (sanitized as any).scribbles = []
    }
    if ('duplicateProps' in sanitized && (sanitized.duplicateProps === undefined || typeof sanitized.duplicateProps !== 'object')) {
      (sanitized as any).duplicateProps = { 
        shapeIds: [],
        offset: { x: 0, y: 0 }
      }
    }
  }
  
  return sanitized
}

export function applyTLStoreChangesToAutomerge(
  doc: any,
  changes: RecordsDiff<TLRecord>
) {
  
  // Ensure doc.store exists
  if (!doc.store) {
    doc.store = {}
  }

  // Handle added records
  if (changes.added) {
    Object.values(changes.added).forEach((record) => {
      // CRITICAL: For shapes, preserve x and y coordinates before sanitization
      // This ensures coordinates aren't lost when saving to Automerge
      let originalX: number | undefined = undefined
      let originalY: number | undefined = undefined
      if (record.typeName === 'shape') {
        originalX = (record as any).x
        originalY = (record as any).y
      }
      
      // Sanitize record before saving to ensure all required fields are present
      const sanitizedRecord = sanitizeRecord(record)
      
      // CRITICAL: Restore original coordinates if they were valid
      // This prevents coordinates from being reset to 0,0 when saving to Automerge
      if (record.typeName === 'shape' && originalX !== undefined && originalY !== undefined) {
        if (typeof originalX === 'number' && !isNaN(originalX) && originalX !== null) {
          (sanitizedRecord as any).x = originalX
        }
        if (typeof originalY === 'number' && !isNaN(originalY) && originalY !== null) {
          (sanitizedRecord as any).y = originalY
        }
      }
      
      // CRITICAL: Create a deep copy to ensure all properties (including richText and text) are preserved
      // This prevents Automerge from treating the object as read-only
      const recordToSave = JSON.parse(JSON.stringify(sanitizedRecord))
      // Let Automerge handle the assignment - it will merge automatically
      doc.store[record.id] = recordToSave
    })
  }

  // Handle updated records
  // Simplified: Replace entire record and let Automerge handle merging
  // This is simpler than deep comparison and leverages Automerge's conflict resolution
  if (changes.updated) {
    Object.values(changes.updated).forEach(([_, record]) => {
      // CRITICAL: For shapes, preserve x and y coordinates before sanitization
      // This ensures coordinates aren't lost when updating records in Automerge
      let originalX: number | undefined = undefined
      let originalY: number | undefined = undefined
      if (record.typeName === 'shape') {
        originalX = (record as any).x
        originalY = (record as any).y
      }
      const sanitizedRecord = sanitizeRecord(record)
      
      // CRITICAL: Restore original coordinates if they were valid
      // This prevents coordinates from being reset to 0,0 when updating records in Automerge
      if (record.typeName === 'shape' && originalX !== undefined && originalY !== undefined) {
        if (typeof originalX === 'number' && !isNaN(originalX) && originalX !== null) {
          (sanitizedRecord as any).x = originalX
        }
        if (typeof originalY === 'number' && !isNaN(originalY) && originalY !== null) {
          (sanitizedRecord as any).y = originalY
        }
      }
      
      // CRITICAL: Create a deep copy to ensure all properties (including richText and text) are preserved
      // This prevents Automerge from treating the object as read-only
      // Note: sanitizedRecord.props is already a deep copy from sanitizeRecord, but we need to deep copy the entire record
      const recordToSave = JSON.parse(JSON.stringify(sanitizedRecord))
      
      // Replace the entire record - Automerge will handle merging with concurrent changes
      doc.store[record.id] = recordToSave
    })
  }

  // Handle removed records
  if (changes.removed) {
    Object.values(changes.removed).forEach((record) => {
      delete doc.store[record.id]
    })
  }

}

// Removed deepCompareAndUpdate - we now replace entire records and let Automerge handle merging
// This simplifies the code and leverages Automerge's built-in conflict resolution
