import React, { useState, useRef, useEffect } from 'react'
import { BaseBoxShapeUtil, TLBaseShape, TLShapeId, createShapeId, IndexKey, TLParentId, HTMLContainer } from '@tldraw/tldraw'
import { ObsidianObsNote } from '@/lib/obsidianImporter'
import { QuartzSync, createQuartzNoteFromShape, QuartzSyncConfig } from '@/lib/quartzSync'
import { logGitHubSetupStatus } from '@/lib/githubSetupValidator'
import { getClientConfig } from '@/lib/clientConfig'
import { StandardizedToolWrapper } from '../components/StandardizedToolWrapper'
import { usePinnedToView } from '../hooks/usePinnedToView'

// Auto-resizing textarea component
const AutoResizeTextarea: React.FC<{
  value: string
  onChange: (value: string) => void
  onBlur: () => void
  onKeyDown: (e: React.KeyboardEvent) => void
  style: React.CSSProperties
  placeholder?: string
  onPointerDown?: (e: React.PointerEvent) => void
}> = ({ value, onChange, onBlur, onKeyDown, style, placeholder, onPointerDown }) => {
  const textareaRef = useRef<HTMLTextAreaElement>(null)

  const adjustHeight = () => {
    const textarea = textareaRef.current
    if (textarea) {
      textarea.style.height = 'auto'
      textarea.style.height = `${textarea.scrollHeight}px`
    }
  }

  useEffect(() => {
    adjustHeight()
    // Focus the textarea when it mounts
    if (textareaRef.current) {
      textareaRef.current.focus()
    }
  }, [value])

  return (
    <textarea
      ref={textareaRef}
      value={value}
      onChange={(e) => {
        onChange(e.target.value)
        adjustHeight()
      }}
      onBlur={onBlur}
      onKeyDown={onKeyDown}
      onPointerDown={onPointerDown}
      style={style}
      placeholder={placeholder}
      rows={1}
      autoFocus
    />
  )
}

// Main ObsNote component with editable content
const ObsNoteComponent: React.FC<{
  shape: IObsNoteShape
  shapeUtil: ObsNoteShape
}> = ({ shape, shapeUtil }) => {
  const isSelected = shapeUtil.editor.getSelectedShapeIds().includes(shape.id)
  const [isEditing, setIsEditing] = useState(shape.props.isEditing)
  const [editingContent, setEditingContent] = useState(shape.props.editingContent || shape.props.content)
  const [isEditingTitle, setIsEditingTitle] = useState(false)
  const [editingTitle, setEditingTitle] = useState(shape.props.title || 'Untitled')
  const [isSyncing, setIsSyncing] = useState(false)
  const [isRefreshing, setIsRefreshing] = useState(false)
  const [isMinimized, setIsMinimized] = useState(false)
  const [isCopying, setIsCopying] = useState(false)

  // Use the pinning hook to keep the shape fixed to viewport when pinned
  usePinnedToView(shapeUtil.editor, shape.id, shape.props.pinnedToView)
  // Store the content at the start of editing to revert to on cancel
  const [contentAtEditStart, setContentAtEditStart] = useState<string | null>(null)
  // Notification state for in-shape notifications
  const [notification, setNotification] = useState<{ message: string; type: 'success' | 'error' } | null>(null)

  // Sync editingContent with shape content when shape changes (but not when editing)
  // This ensures the component stays in sync with the shape's content
  // Note: We don't sync during editing or immediately after cancel (when contentAtEditStart might be set)
  useEffect(() => {
    if (!isEditing && contentAtEditStart === null && shape.props.content !== editingContent) {
      setEditingContent(shape.props.content)
    }
  }, [shape.props.content, isEditing, contentAtEditStart, editingContent])

  // Auto-hide notification after 3 seconds
  useEffect(() => {
    if (notification) {
      const timer = setTimeout(() => {
        setNotification(null)
      }, 3000)
      return () => clearTimeout(timer)
    }
  }, [notification])

  const titleStyle: React.CSSProperties = {
    fontSize: '14px',
    fontWeight: 'bold',
    color: shape.props.textColor,
    margin: 0,
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap',
    flex: 1,
    cursor: isEditingTitle ? 'text' : 'pointer',
    padding: '4px 8px',
    borderRadius: '4px',
    transition: 'background-color 0.2s ease',
  }

  const tagsStyle: React.CSSProperties = {
    display: 'flex',
    flexWrap: 'wrap',
    gap: '4px',
    marginTop: '4px',
  }

  const tagStyle: React.CSSProperties = {
    backgroundColor: '#007acc',
    color: 'white',
    padding: '2px 6px',
    borderRadius: '12px',
    fontSize: '10px',
    fontWeight: '500',
  }

  const contentStyle: React.CSSProperties = {
    padding: '12px',
    flex: 1,
    overflow: 'auto',
    color: shape.props.textColor,
    fontSize: '12px',
    lineHeight: '1.4',
    cursor: isEditing ? 'text' : 'pointer',
    transition: 'background-color 0.2s ease',
  }

  const previewStyle: React.CSSProperties = {
    height: '100%',
    overflow: 'auto',
  }

  const fullContentStyle: React.CSSProperties = {
    height: '100%',
    overflow: 'auto',
  }

  const emptyStyle: React.CSSProperties = {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    height: '100%',
    color: '#999',
    fontSize: '12px',
    fontStyle: 'italic',
    cursor: 'pointer',
    border: '2px dashed #ddd',
    borderRadius: '4px',
    padding: '8px',
    textAlign: 'center',
  }

  const textareaStyle: React.CSSProperties = {
    width: '100%',
    minHeight: '60px',
    border: 'none',
    outline: 'none',
    resize: 'none',
    fontFamily: 'inherit',
    fontSize: '12px',
    lineHeight: '1.4',
    color: shape.props.textColor,
    backgroundColor: 'transparent',
    padding: 0,
    margin: 0,
    position: 'relative',
    zIndex: 1000,
    pointerEvents: 'auto',
  }

  const editControlsStyle: React.CSSProperties = {
    display: 'flex',
    gap: '8px',
    padding: '8px 12px',
    backgroundColor: '#f8f9fa',
    borderTop: '1px solid #e0e0e0',
    position: 'relative',
    zIndex: 1000,
    pointerEvents: 'auto',
  }

  const buttonStyle: React.CSSProperties = {
    padding: '4px 8px',
    fontSize: '10px',
    border: '1px solid #ccc',
    borderRadius: '4px',
    backgroundColor: 'white',
    cursor: 'pointer',
  }

  const handleStartEdit = () => {
    try {
      // Capture the current content as the baseline for cancel
      // This is the content that exists BEFORE editing starts
      const currentContent = shape.props.content || ''
      setContentAtEditStart(currentContent)
      setIsEditing(true)
      setEditingContent(currentContent)
      const sanitizedProps = ObsNoteShape.sanitizeProps({
        ...shape.props,
        isEditing: true,
        editingContent: currentContent,
      })
      shapeUtil.editor.updateShape<IObsNoteShape>({
        id: shape.id,
        type: 'ObsNote',
        props: sanitizedProps
      })
    } catch (error) {
      console.error('❌ Error in handleStartEdit:', error)
    }
  }

  const handleStartTitleEdit = () => {
    setIsEditingTitle(true)
    setEditingTitle(shape.props.title || 'Untitled')
  }

  const handleSaveTitleEdit = () => {
    if (editingTitle.trim() !== shape.props.title) {
      const sanitizedProps = ObsNoteShape.sanitizeProps({
        ...shape.props,
        title: editingTitle.trim(),
        isModified: true
      })
      shapeUtil.editor.updateShape<IObsNoteShape>({
        id: shape.id,
        type: 'ObsNote',
        props: sanitizedProps
      })
    }
    setIsEditingTitle(false)
  }

  const handleCancelTitleEdit = () => {
    setEditingTitle(shape.props.title || 'Untitled')
    setIsEditingTitle(false)
  }

  const handleTitleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      e.preventDefault()
      handleSaveTitleEdit()
    } else if (e.key === 'Escape') {
      e.preventDefault()
      handleCancelTitleEdit()
    }
  }

  const handleSaveEdit = () => {
    try {
      const hasChanged = editingContent !== shape.props.originalContent
      
      setIsEditing(false)
      setContentAtEditStart(null) // Clear the stored baseline
      const sanitizedProps = ObsNoteShape.sanitizeProps({
        ...shape.props,
        content: editingContent, // Save the edited content
        isEditing: false,
        editingContent: '',
        isModified: hasChanged,
      })
      shapeUtil.editor.updateShape<IObsNoteShape>({
        id: shape.id,
        type: 'ObsNote',
        props: sanitizedProps
      })
    } catch (error) {
      console.error('❌ Error in handleSaveEdit:', error)
    }
  }

  const handleCancelEdit = () => {
    try {
      // Revert to the content that was there when editing started
      // Priority: contentAtEditStart > originalContent > current content
      const contentToRevert = contentAtEditStart !== null 
        ? contentAtEditStart 
        : (shape.props.originalContent || shape.props.content || '')
      
      // Update state first to exit editing mode
      setIsEditing(false)
      setEditingContent(contentToRevert)
      
      // Update the shape with the reverted content
      const sanitizedProps = ObsNoteShape.sanitizeProps({
        ...shape.props,
        content: contentToRevert, // Revert content to what it was when editing started
        isEditing: false,
        editingContent: '',
        // Reset isModified only if we're reverting all the way back to originalContent
        isModified: contentToRevert === shape.props.originalContent ? false : shape.props.isModified,
      })
      
      shapeUtil.editor.updateShape<IObsNoteShape>({
        id: shape.id,
        type: 'ObsNote',
        props: sanitizedProps
      })
      
      // Clear the stored baseline after reverting
      setContentAtEditStart(null)
    } catch (error) {
      console.error('❌ Error in handleCancelEdit:', error)
      // Fallback: just exit editing mode
      setIsEditing(false)
      setContentAtEditStart(null)
    }
  }

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && e.ctrlKey) {
      e.preventDefault()
      handleSaveEdit()
    } else if (e.key === 'Escape') {
      e.preventDefault()
      handleCancelEdit()
    }
  }

  const handleRefresh = async () => {
    if (isRefreshing) return
    
    setIsRefreshing(true)
    
    try {
      const success = await shapeUtil.refreshFromVault(shape.id)
      if (success) {
        setNotification({ message: '✅ Note restored from vault', type: 'success' })
      } else {
        setNotification({ message: '❌ Failed to restore note', type: 'error' })
      }
    } catch (error) {
      console.error('❌ Refresh failed:', error)
      setNotification({ 
        message: `Refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 
        type: 'error' 
      })
    } finally {
      setIsRefreshing(false)
    }
  }

  const handleCopy = async () => {
    if (isCopying) return
    
    if (!isSelected) {
      setNotification({ message: '⚠️ Please select this note to copy', type: 'error' })
      return
    }
    
    setIsCopying(true)
    
    try {
      const contentToCopy = shape.props.content || ''
      if (!contentToCopy.trim()) {
        setNotification({ message: '⚠️ No content to copy', type: 'error' })
        setIsCopying(false)
        return
      }
      
      await navigator.clipboard.writeText(contentToCopy)
      setNotification({ message: '✅ Content copied to clipboard', type: 'success' })
    } catch (error) {
      console.error('❌ Copy failed:', error)
      setNotification({ 
        message: `Copy failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 
        type: 'error' 
      })
    } finally {
      setIsCopying(false)
    }
  }

  const handleSync = async () => {
    if (isSyncing) return
    
    setIsSyncing(true)
    
    try {
      // Capture the content to sync BEFORE any state changes
      // This ensures we have the absolute latest content
      const contentToSync = isEditing ? editingContent : (shape.props.content || '')
      const titleToSync = isEditingTitle ? editingTitle : (shape.props.title || 'Untitled')
      
      // If we're editing, save the edit FIRST to ensure the shape has the latest content
      if (isEditing) {
        // Save the edit synchronously
        const hasChanged = editingContent !== shape.props.originalContent
        const sanitizedProps = ObsNoteShape.sanitizeProps({
          ...shape.props,
          content: editingContent, // Save the edited content
          isEditing: false,
          editingContent: '',
          isModified: hasChanged,
        })
        shapeUtil.editor.updateShape<IObsNoteShape>({
          id: shape.id,
          type: 'ObsNote',
          props: sanitizedProps
        })
        
        // Update local state
        setIsEditing(false)
        setContentAtEditStart(null)
      }
      
      // If we're editing title, save that too
      if (isEditingTitle) {
        const sanitizedProps = ObsNoteShape.sanitizeProps({
          ...shape.props,
          title: editingTitle,
        })
        shapeUtil.editor.updateShape<IObsNoteShape>({
          id: shape.id,
          type: 'ObsNote',
          props: sanitizedProps
        })
        setIsEditingTitle(false)
      }
      
      // Get fresh shape reference for vault info and other properties
      const currentShape = shapeUtil.editor.getShape(shape.id) as IObsNoteShape
      if (!currentShape) {
        throw new Error('Shape not found')
      }
      
      // Use the captured contentToSync and titleToSync (which are the latest)
      
      let vaultPath = currentShape.props.vaultPath
      let vaultName = currentShape.props.vaultName
      
      if (!vaultPath || !vaultName) {
        // Try to get vault info from session if not in shape props
        // This is a fallback for existing shapes that don't have vault info
        const sessionData = localStorage.getItem('canvas_auth_session')
        if (sessionData) {
          try {
            const session = JSON.parse(sessionData)
            
            if (session.obsidianVaultPath && session.obsidianVaultName) {
              // Update the shape with vault info for future syncs
              const sanitizedProps = ObsNoteShape.sanitizeProps({
                ...currentShape.props,
                vaultPath: session.obsidianVaultPath,
                vaultName: session.obsidianVaultName,
              })
              shapeUtil.editor.updateShape<IObsNoteShape>({
                id: currentShape.id,
                type: 'ObsNote',
                props: sanitizedProps
              })
              
              // Use the session vault info
              vaultPath = session.obsidianVaultPath
              vaultName = session.obsidianVaultName
            }
          } catch (e) {
            console.error('Failed to parse session data:', e)
          }
        }
        
        if (!vaultPath || !vaultName) {
          throw new Error('No vault configured for sync. Please configure a vault in settings first.')
        }
      }
      
      // Determine if this is a Quartz URL or local vault
      const isQuartzVault = vaultPath.startsWith('http') || vaultPath.includes('quartz') || vaultPath.includes('.xyz') || vaultPath.includes('.com')
      
      if (isQuartzVault) {
        // Use the new Quartz sync system
        try {
          // Validate GitHub setup first
          logGitHubSetupStatus()
          
          // Create Quartz note with the latest content
          // Create a temporary shape object with the latest content for sync
          const shapeForSync = {
            ...currentShape,
            props: {
              ...currentShape.props,
              content: contentToSync,
              title: titleToSync,
            }
          }
          const quartzNote = createQuartzNoteFromShape(shapeForSync)
          
          // Configure Quartz sync
          const config = getClientConfig()
          
          const syncConfig: QuartzSyncConfig = {
            githubToken: config.githubToken,
            githubRepo: config.quartzRepo,
            quartzUrl: vaultPath,
            cloudflareApiKey: config.cloudflareApiKey,
            cloudflareAccountId: config.cloudflareAccountId
          }
          
          const quartzSync = new QuartzSync(syncConfig)
          
          // Try smart sync (tries multiple approaches)
          const syncSuccess = await quartzSync.smartSync(quartzNote)
          
          if (syncSuccess) {
            alert('✅ Note synced to Quartz successfully! Check your GitHub repository for changes.')
          } else {
            throw new Error('All sync methods failed')
          }
        } catch (error) {
          console.error('❌ Quartz sync failed:', error)
          
          // Fallback to local storage
          const quartzStorageKey = `quartz_vault_${vaultName}_${currentShape.props.noteId || titleToSync}`
          const tags = currentShape.props.tags || []
          const title = titleToSync
          const content = contentToSync
          const frontmatter = `---
title: "${title}"
tags: [${tags.map(tag => `"${tag.replace('#', '')}"`).join(', ')}]
created: ${new Date().toISOString()}
modified: ${new Date().toISOString()}
quartz_url: "${vaultPath}"
---

${content}`
          
          localStorage.setItem(quartzStorageKey, frontmatter)
          alert(`Quartz sync: Stored locally as fallback. Check console for details.`)
        }
      } else {
        // For local vaults, try to write using File System Access API
        // Use stored filePath if available to maintain filename consistency
        // Otherwise, generate from title or noteId
        let fileName: string
        if (currentShape.props.filePath && currentShape.props.filePath.trim() !== '') {
          // Extract just the filename from the full path
          const pathParts = currentShape.props.filePath.split('/')
          fileName = pathParts[pathParts.length - 1]
          // Ensure it ends with .md
          if (!fileName.endsWith('.md')) {
            fileName = `${fileName}.md`
          }
        } else {
          // Generate from title or noteId
          fileName = `${titleToSync.replace(/[^a-zA-Z0-9]/g, '_')}.md`
        }
        
        // Create the markdown content with frontmatter using the latest content
        const tags = currentShape.props.tags || []
        const title = titleToSync
        const content = contentToSync
        const frontmatter = `---
title: "${title}"
tags: [${tags.map(tag => `"${tag.replace('#', '')}"`).join(', ')}]
created: ${new Date().toISOString()}
modified: ${new Date().toISOString()}
---

${content}`
        
        try {
          
          // Try to write using File System Access API
          if ('showSaveFilePicker' in window) {
            const fileHandle = await (window as any).showSaveFilePicker({
              suggestedName: fileName,
              types: [{
                description: 'Markdown files',
                accept: { 'text/markdown': ['.md'] }
              }]
            })
            
            const writable = await fileHandle.createWritable()
            await writable.write(frontmatter)
            await writable.close()
            
            alert(`Local vault sync: File saved successfully as ${fileName}`)
          } else {
            // Fallback: download the file
            const blob = new Blob([frontmatter], { type: 'text/markdown' })
            const url = URL.createObjectURL(blob)
            const a = document.createElement('a')
            a.href = url
            a.download = fileName // Use the calculated fileName (preserves original filePath if available)
            document.body.appendChild(a)
            a.click()
            document.body.removeChild(a)
            URL.revokeObjectURL(url)
            
            alert('Local vault sync: File downloaded! Please save it to your vault folder.')
          }
        } catch (error) {
          console.error('❌ Failed to write local vault file:', error)
          
          // Fallback: store locally and show instructions
          const localStorageKey = `local_vault_${vaultName}_${currentShape.props.noteId || titleToSync}`
          localStorage.setItem(localStorageKey, frontmatter)
          
          alert(`Local vault sync: Failed to write directly. Content stored locally and logged to console. Key: ${localStorageKey}`)
        }
      }
      
      // Mark as synced - get fresh shape to ensure we have the latest
      const finalShape = shapeUtil.editor.getShape(shape.id) as IObsNoteShape
      if (finalShape) {
        const sanitizedProps = ObsNoteShape.sanitizeProps({
          ...finalShape.props,
          isModified: false,
          originalContent: finalShape.props.content, // Update originalContent to current content after successful sync
        })
        shapeUtil.editor.updateShape<IObsNoteShape>({
          id: finalShape.id,
          type: 'ObsNote',
          props: sanitizedProps
        })
      }
      
    } catch (error) {
      console.error('❌ Sync failed:', error)
      alert(`Sync failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
    } finally {
      setIsSyncing(false)
    }
  }

  const handleMinimize = () => {
    setIsMinimized(!isMinimized)
  }

  const handleClose = () => {
    shapeUtil.editor.deleteShape(shape.id)
  }

  const handlePinToggle = () => {
    shapeUtil.editor.updateShape<IObsNoteShape>({
      id: shape.id,
      type: shape.type,
      props: {
        ...shape.props,
        pinnedToView: !shape.props.pinnedToView,
      },
    })
  }

  // Custom header content with editable title and action buttons
  const headerContent = (
    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', gap: '8px' }}>
      {isEditingTitle ? (
        <input
          type="text"
          value={editingTitle}
          onChange={(e) => setEditingTitle(e.target.value)}
          onBlur={handleSaveTitleEdit}
          onKeyDown={handleTitleKeyDown}
          onPointerDown={(e) => e.stopPropagation()}
          style={{
            ...titleStyle,
            border: '1px solid #007acc',
            borderRadius: '4px',
            padding: '4px 8px',
            backgroundColor: 'white',
            outline: 'none',
            fontSize: '14px',
            fontWeight: 'bold',
            color: shape.props.textColor,
            flex: 1,
            minWidth: 0,
          }}
          autoFocus
        />
      ) : (
        <h3 
          style={{...titleStyle, margin: 0, padding: 0}} 
          title={shape.props.title}
          onClick={(e) => {
            e.stopPropagation()
            handleStartTitleEdit()
          }}
          onPointerDown={(e) => e.stopPropagation()}
          onMouseEnter={(e) => {
            if (!isEditingTitle) {
              e.currentTarget.style.backgroundColor = '#f0f0f0'
            }
          }}
          onMouseLeave={(e) => {
            if (!isEditingTitle) {
              e.currentTarget.style.backgroundColor = 'transparent'
            }
          }}
        >
          {shape.props.title}
        </h3>
      )}
    </div>
  )

  return (
    <HTMLContainer style={{ width: shape.props.w, height: shape.props.h }}>
      <StandardizedToolWrapper
        title="Obsidian Note"
        primaryColor={shape.props.primaryColor || ObsNoteShape.PRIMARY_COLOR}
        isSelected={isSelected}
        width={shape.props.w}
        height={shape.props.h}
        onClose={handleClose}
        onMinimize={handleMinimize}
        isMinimized={isMinimized}
        headerContent={headerContent}
        editor={shapeUtil.editor}
        shapeId={shape.id}
        isPinnedToView={shape.props.pinnedToView}
        onPinToggle={handlePinToggle}
        tags={shape.props.tags}
        onTagsChange={(newTags) => {
          const sanitizedProps = ObsNoteShape.sanitizeProps({
            ...shape.props,
            tags: newTags,
          })
          shapeUtil.editor.updateShape<IObsNoteShape>({
            id: shape.id,
            type: 'ObsNote',
            props: sanitizedProps
          })
        }}
        tagsEditable={true}
      >

      <div 
        style={{
          ...contentStyle,
          backgroundColor: isEditing ? 'transparent' : 'transparent',
          position: 'relative',
          zIndex: isEditing ? 1000 : 'auto',
          pointerEvents: isEditing ? 'auto' : 'auto',
        }}
        onMouseEnter={(e) => {
          if (!isEditing) {
            e.currentTarget.style.backgroundColor = '#f8f9fa'
          }
        }}
        onMouseLeave={(e) => {
          if (!isEditing) {
            e.currentTarget.style.backgroundColor = 'transparent'
          }
        }}
        onClick={(e) => {
          if (!isEditing) {
            e.stopPropagation()
            handleStartEdit()
          }
        }}
        onPointerDown={(e) => {
          if (!isEditing) {
            e.stopPropagation()
          }
        }}
        onWheel={(e) => {
          // Allow mouse wheel scrolling within the obsnote content
          e.stopPropagation()
        }}
        onTouchStart={(e) => {
          // Allow touch scrolling within the obsnote content
          e.stopPropagation()
        }}
        onTouchMove={(e) => {
          // Allow touch scrolling within the obsnote content
          e.stopPropagation()
        }}
        onTouchEnd={(e) => {
          // Allow touch scrolling within the obsnote content
          e.stopPropagation()
        }}
      >
        {isEditing ? (
          <AutoResizeTextarea
            value={editingContent}
            onChange={setEditingContent}
            onBlur={handleSaveEdit}
            onKeyDown={handleKeyDown}
            style={textareaStyle}
            placeholder="Enter your note content..."
            onPointerDown={(e) => e.stopPropagation()}
          />
        ) : shape.props.content ? (
          <div 
            style={shape.props.showPreview ? previewStyle : fullContentStyle}
            onWheel={(e) => {
              // Allow mouse wheel scrolling within the content area
              e.stopPropagation()
            }}
            onTouchStart={(e) => {
              // Allow touch scrolling within the content area
              e.stopPropagation()
            }}
            onTouchMove={(e) => {
              // Allow touch scrolling within the content area
              e.stopPropagation()
            }}
            onTouchEnd={(e) => {
              // Allow touch scrolling within the content area
              e.stopPropagation()
            }}
          >
            {shape.props.showPreview ? (
              <div 
                dangerouslySetInnerHTML={{ 
                  __html: shapeUtil.formatMarkdownPreview(shape.props.content) 
                }}
                onWheel={(e) => {
                  // Allow mouse wheel scrolling within the preview content
                  e.stopPropagation()
                }}
                onTouchStart={(e) => {
                  // Allow touch scrolling within the preview content
                  e.stopPropagation()
                }}
                onTouchMove={(e) => {
                  // Allow touch scrolling within the preview content
                  e.stopPropagation()
                }}
                onTouchEnd={(e) => {
                  // Allow touch scrolling within the preview content
                  e.stopPropagation()
                }}
              />
            ) : (
              <pre 
                style={{ 
                  margin: 0, 
                  whiteSpace: 'pre-wrap', 
                  fontFamily: 'inherit',
                  fontSize: 'inherit',
                  lineHeight: 'inherit'
                }}
                onWheel={(e) => {
                  // Allow mouse wheel scrolling within the pre content
                  e.stopPropagation()
                }}
                onTouchStart={(e) => {
                  // Allow touch scrolling within the pre content
                  e.stopPropagation()
                }}
                onTouchMove={(e) => {
                  // Allow touch scrolling within the pre content
                  e.stopPropagation()
                }}
                onTouchEnd={(e) => {
                  // Allow touch scrolling within the pre content
                  e.stopPropagation()
                }}
              >
                {shape.props.content}
              </pre>
            )}
          </div>
        ) : (
          <div style={emptyStyle}>
            Click to add content
          </div>
        )}
        
        {/* Notification display - positioned at top of content area */}
        {notification && (
          <>
            <style>{`
              @keyframes obsNoteNotificationFade {
                0% { opacity: 0; transform: translateX(-50%) translateY(-8px); }
                10% { opacity: 1; transform: translateX(-50%) translateY(0); }
                90% { opacity: 1; transform: translateX(-50%) translateY(0); }
                100% { opacity: 0; transform: translateX(-50%) translateY(-8px); }
              }
            `}</style>
            <div
              style={{
                position: 'absolute',
                top: '12px',
                left: '50%',
                zIndex: 10001,
                padding: '8px 16px',
                borderRadius: '6px',
                fontSize: '12px',
                fontWeight: '500',
                color: 'white',
                backgroundColor: notification.type === 'success' ? '#22c55e' : '#ef4444',
                boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
                pointerEvents: 'none',
                whiteSpace: 'nowrap',
                maxWidth: 'calc(100% - 24px)',
                overflow: 'hidden',
                textOverflow: 'ellipsis',
                animation: 'obsNoteNotificationFade 3s ease-in-out forwards',
              }}
            >
              {notification.message}
            </div>
          </>
        )}
      </div>

      {/* Bottom action buttons - always visible */}
      <div style={{
        padding: '8px 12px',
        borderTop: '1px solid #e0e0e0',
        display: 'flex',
        justifyContent: 'center',
        gap: '8px',
        marginTop: 'auto',
      }}>
        {/* Copy button - always clickable */}
        <button
          onClick={(e) => {
            e.stopPropagation()
            handleCopy()
          }}
          onPointerDown={(e) => e.stopPropagation()}
          onMouseDown={(e) => e.stopPropagation()}
          disabled={isCopying}
          style={{
            ...buttonStyle,
            fontSize: '11px',
            padding: '6px 12px',
            backgroundColor: isCopying ? '#6c757d' : isSelected ? '#004d85' : '#005a9e',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: isCopying ? 'not-allowed' : 'pointer',
            display: 'flex',
            alignItems: 'center',
            gap: '4px',
            pointerEvents: 'auto',
            opacity: isCopying ? 0.7 : 1,
            transition: 'background-color 0.2s ease',
          }}
          title={!isSelected ? "Select this note to copy" : "Copy transcript content to clipboard"}
        >
          {isCopying ? '⏳ Copying...' : '📋 Copy'}
        </button>
        
        {/* Save changes button - shown when there are modifications or when editing with changes */}
        {(() => {
          // Check if there are changes: either already modified, or currently editing with different content
          const hasChanges = shape.props.isModified || 
            (isEditing && editingContent !== (contentAtEditStart !== null ? contentAtEditStart : shape.props.originalContent))
          return hasChanges
        })() && (
          <button
            onClick={(e) => {
              e.stopPropagation()
              // Sync will handle saving if editing
              handleSync()
            }}
            onPointerDown={(e) => e.stopPropagation()}
            onMouseDown={(e) => e.stopPropagation()}
            disabled={isSyncing}
            style={{
              ...buttonStyle,
              fontSize: '11px',
              padding: '6px 12px',
              backgroundColor: isSyncing ? '#ccc' : '#22c55e',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: isSyncing ? 'not-allowed' : 'pointer',
              display: 'flex',
              alignItems: 'center',
              gap: '4px',
              pointerEvents: 'auto',
              opacity: isSyncing ? 0.7 : 1,
            }}
            title="save new content to vault"
          >
            {isSyncing ? '⏳ Saving...' : '💾 Save changes'}
          </button>
        )}
      </div>
      </StandardizedToolWrapper>
    </HTMLContainer>
  )
}

export type IObsNoteShape = TLBaseShape<
  'ObsNote',
  {
    w: number
    h: number
    color: string
    size: string
    font: string
    textAlign: string
    scale: number
    noteId: string
    title: string
    content: string
    tags: string[]
    showPreview: boolean
    backgroundColor: string
    textColor: string
    isEditing: boolean
    editingContent: string
    isModified: boolean
    originalContent: string
    vaultPath?: string
    vaultName?: string
    filePath?: string // Original file path from vault - used to maintain filename consistency
    pinnedToView: boolean
    primaryColor?: string // Optional custom primary color for the header
  }
>

export class ObsNoteShape extends BaseBoxShapeUtil<IObsNoteShape> {
  static override type = 'ObsNote'

  // Obsidian Note theme color: Indigo (similar to ObsidianBrowser)
  static readonly PRIMARY_COLOR = "#9333ea"

  /**
   * Sanitize props to ensure all values are JSON serializable
   */
  public static sanitizeProps(props: Partial<IObsNoteShape['props']>): IObsNoteShape['props'] {
    // Ensure tags is a proper string array
    const tags = Array.isArray(props.tags)
      ? props.tags.filter(tag => typeof tag === 'string').map(tag => String(tag))
      : []
    
    // Build sanitized props object
    const sanitized: IObsNoteShape['props'] = {
      w: typeof props.w === 'number' ? props.w : 300,
      h: typeof props.h === 'number' ? props.h : 200,
      color: typeof props.color === 'string' ? props.color : 'black',
      size: typeof props.size === 'string' ? props.size : 'm',
      font: typeof props.font === 'string' ? props.font : 'sans',
      textAlign: typeof props.textAlign === 'string' ? props.textAlign : 'start',
      scale: typeof props.scale === 'number' ? props.scale : 1,
      noteId: typeof props.noteId === 'string' ? props.noteId : '',
      title: typeof props.title === 'string' ? props.title : 'Untitled ObsNote',
      content: typeof props.content === 'string' ? props.content : '',
      tags,
      showPreview: typeof props.showPreview === 'boolean' ? props.showPreview : true,
      backgroundColor: typeof props.backgroundColor === 'string' ? props.backgroundColor : '#ffffff',
      textColor: typeof props.textColor === 'string' ? props.textColor : '#000000',
      isEditing: typeof props.isEditing === 'boolean' ? props.isEditing : false,
      editingContent: typeof props.editingContent === 'string' ? props.editingContent : '',
      isModified: typeof props.isModified === 'boolean' ? props.isModified : false,
      originalContent: typeof props.originalContent === 'string' ? props.originalContent : '',
      pinnedToView: typeof props.pinnedToView === 'boolean' ? props.pinnedToView : false,
    }
    
    // Only add optional properties if they're defined and are strings
    if (props.vaultPath !== undefined && typeof props.vaultPath === 'string') {
      sanitized.vaultPath = props.vaultPath
    }
    if (props.vaultName !== undefined && typeof props.vaultName === 'string') {
      sanitized.vaultName = props.vaultName
    }
    if (props.filePath !== undefined && typeof props.filePath === 'string') {
      sanitized.filePath = props.filePath
    }
    if (props.primaryColor !== undefined && typeof props.primaryColor === 'string') {
      sanitized.primaryColor = props.primaryColor
    }
    
    return sanitized
  }

  getDefaultProps(): IObsNoteShape['props'] {
    return ObsNoteShape.sanitizeProps({})
  }

  component(shape: IObsNoteShape) {
    return <ObsNoteComponent shape={shape} shapeUtil={this} />
  }

  indicator(shape: IObsNoteShape) {
    return <rect width={shape.props.w} height={shape.props.h} />
  }

  /**
   * Format markdown content for preview
   * Simplified conversion that avoids extra characters
   */
  formatMarkdownPreview(content: string): string {
    if (!content) return ''
    
    // Escape HTML first to prevent injection
    const escapeHtml = (text: string) => {
      const div = document.createElement('div')
      div.textContent = text
      return div.innerHTML
    }
    
    // Split into lines for line-based processing
    const lines = content.split('\n')
    const processedLines: string[] = []
    
    for (const line of lines) {
      let processed = escapeHtml(line)
      
      // Headers (must be at start of line)
      if (processed.match(/^### /)) {
        processed = processed.replace(/^### (.*)$/, '<h3 style="font-size: 13px; margin: 0 0 4px 0; font-weight: bold;">$1</h3>')
      } else if (processed.match(/^## /)) {
        processed = processed.replace(/^## (.*)$/, '<h2 style="font-size: 14px; margin: 0 0 6px 0; font-weight: bold;">$1</h2>')
      } else if (processed.match(/^# /)) {
        processed = processed.replace(/^# (.*)$/, '<h1 style="font-size: 16px; margin: 0 0 8px 0; font-weight: bold;">$1</h1>')
      }
      // Lists (only if not already a header)
      else if (processed.match(/^- /) && !processed.startsWith('<h')) {
        processed = processed.replace(/^- (.*)$/, '<div style="margin: 2px 0;">• $1</div>')
      } else if (processed.match(/^\d+\. /) && !processed.startsWith('<h')) {
        processed = processed.replace(/^(\d+)\. (.*)$/, '<div style="margin: 2px 0;">$1. $2</div>')
      }
      // Regular line - apply inline formatting
      else {
        // Process bold first (before italic to avoid conflicts)
        processed = processed.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
        // Then italic (single asterisks that aren't part of bold - use a simple pattern)
        // Match *text* but not **text** by checking it's not preceded or followed by *
        processed = processed.replace(/\b\*([^*]+?)\*\b/g, '<em>$1</em>')
        // Code blocks
        processed = processed.replace(/`([^`]+?)`/g, '<code style="background: #f0f0f0; padding: 1px 3px; border-radius: 3px; font-family: monospace;">$1</code>')
        // Wikilinks
        processed = processed.replace(/\[\[([^\]]+)\]\]/g, '<span style="color: #007acc; text-decoration: underline;">$1</span>')
      }
      
      processedLines.push(processed)
    }
    
    return processedLines.join('<br>')
  }

  /**
   * Create an obs_note shape from an ObsidianObsNote
   */
  static createFromObsidianObsNote(obs_note: ObsidianObsNote, x: number = 0, y: number = 0, id?: TLShapeId, vaultPath?: string, vaultName?: string): IObsNoteShape {
    // Use sanitizeProps to ensure all values are JSON serializable
    const props = ObsNoteShape.sanitizeProps({
      w: 300,
      h: 200,
      color: 'black',
      size: 'm',
      font: 'sans',
      textAlign: 'start',
      scale: 1,
      noteId: obs_note.id || '',
      title: obs_note.title || 'Untitled',
      content: obs_note.content || '',
      tags: obs_note.tags || [],
      showPreview: true,
      backgroundColor: '#ffffff',
      textColor: '#000000',
      isEditing: false,
      editingContent: '',
      isModified: false,
      originalContent: obs_note.content || '',
      vaultPath: vaultPath,
      vaultName: vaultName,
      filePath: obs_note.filePath,
    })
    
    return {
      id: id || createShapeId(),
      type: 'ObsNote',
      x,
      y,
      rotation: 0,
      index: 'a1' as IndexKey,
      parentId: 'page:page' as TLParentId,
      isLocked: false,
      opacity: 1,
      meta: {},
      typeName: 'shape',
      props
    }
  }

  /**
   * Update obs_note content
   */
  updateObsNoteContent(shapeId: string, content: string) {
    const shape = this.editor.getShape(shapeId as TLShapeId) as IObsNoteShape
    if (!shape) return
    
    const sanitizedProps = ObsNoteShape.sanitizeProps({
      ...shape.props,
      content,
    })
    this.editor.updateShape<IObsNoteShape>({
      id: shapeId as TLShapeId,
      type: 'ObsNote',
      props: sanitizedProps
    })
  }

  /**
   * Toggle preview mode
   */
  togglePreview(shapeId: string) {
    const shape = this.editor.getShape(shapeId as TLShapeId) as IObsNoteShape
    if (shape) {
      const sanitizedProps = ObsNoteShape.sanitizeProps({
        ...shape.props,
        showPreview: !shape.props.showPreview,
      })
      this.editor.updateShape<IObsNoteShape>({
        id: shapeId as TLShapeId,
        type: 'ObsNote',
        props: sanitizedProps
      })
    }
  }

  /**
   * Update obs_note styling
   */
  updateStyling(shapeId: string, backgroundColor: string, textColor: string) {
    const shape = this.editor.getShape(shapeId as TLShapeId) as IObsNoteShape
    if (!shape) return
    
    const sanitizedProps = ObsNoteShape.sanitizeProps({
      ...shape.props,
      backgroundColor,
      textColor,
    })
    this.editor.updateShape<IObsNoteShape>({
      id: shapeId as TLShapeId,
      type: 'ObsNote',
      props: sanitizedProps
    })
  }

  /**
   * Refresh obs_note content from vault
   */
  async refreshFromVault(shapeId: string): Promise<boolean> {
    const shape = this.editor.getShape(shapeId as TLShapeId) as IObsNoteShape
    if (!shape) return false

    try {
      const { ObsidianImporter } = await import('@/lib/obsidianImporter')
      const importer = new ObsidianImporter()
      
      // Get vault info from shape or session
      let vaultPath = shape.props.vaultPath
      let vaultName = shape.props.vaultName
      
      if (!vaultPath || !vaultName) {
        const sessionData = localStorage.getItem('canvas_auth_session')
        if (sessionData) {
          const session = JSON.parse(sessionData)
          vaultPath = session.obsidianVaultPath
          vaultName = session.obsidianVaultName
        }
      }
      
      if (!vaultPath || !vaultName) {
        console.error('No vault configured for refresh')
        return false
      }

      // Load latest content from vault
      let vault: any
      if (vaultPath.startsWith('http') || vaultPath.includes('quartz') || vaultPath.includes('.xyz') || vaultPath.includes('.com')) {
        vault = await importer.importFromQuartzUrl(vaultPath)
      } else {
        vault = await importer.importFromDirectory(vaultPath)
      }

      // Find the updated note by ID, preferring notes without quotes in filename
      const matchingNotes = vault.obs_notes.filter((note: any) => note.id === shape.props.noteId)
      if (matchingNotes.length === 0) {
        return false
      }
      
      // If there are multiple notes with the same ID (duplicates), prefer the one without quotes
      let updatedNote = matchingNotes[0]
      if (matchingNotes.length > 1) {
        const withoutQuotes = matchingNotes.find((note: any) => !note.filePath?.includes('"'))
        if (withoutQuotes) {
          updatedNote = withoutQuotes
        } else {
          // If all have quotes, pick the one with the most content
          updatedNote = matchingNotes.reduce((best: any, current: any) => 
            current.content?.length > best.content?.length ? current : best
          )
        }
      }

      // Sanitize and update the shape with latest content
      const sanitizedProps = ObsNoteShape.sanitizeProps({
        ...shape.props,
        title: updatedNote.title,
        content: updatedNote.content,
        tags: updatedNote.tags,
        originalContent: updatedNote.content,
        isModified: false, // Reset modified flag since we're updating from source
        // Preserve filePath from updated note if available, otherwise keep existing
        filePath: updatedNote.filePath || shape.props.filePath,
      })
      
      this.editor.updateShape<IObsNoteShape>({
        id: shapeId as TLShapeId,
        type: 'ObsNote',
        props: sanitizedProps
      })

      return true
    } catch (error) {
      console.error('❌ Failed to refresh ObsNote from vault:', error)
      return false
    }
  }

  /**
   * Refresh all ObsNote shapes on the current page
   */
  async refreshAllFromVault(): Promise<{ success: number; failed: number }> {
    const allShapes = this.editor.getCurrentPageShapes()
    const obsNoteShapes = allShapes.filter(shape => shape.type === 'ObsNote')
    
    let success = 0
    let failed = 0
    
    for (const shape of obsNoteShapes) {
      const result = await this.refreshFromVault(shape.id)
      if (result) {
        success++
      } else {
        failed++
      }
    }
    
    return { success, failed }
  }
}
