import { addEpisode } from "~utils/api"
import { isExtensionContextValid } from "~utils/context"
import { createRoot, type Root } from "react-dom/client"
import { createElement } from "react"
import TextSelectionPopup from "~components/TextSelectionPopup"
let selectionDot: HTMLDivElement | null = null
let selectionPopup: HTMLDivElement | null = null
let selectionRoot: Root | null = null
let autoHideTimer: NodeJS.Timeout | null = null
let isHoveringElements = false
// Logo dot for text selection
function createSelectionDot(rect: DOMRect) {
removeSelectionElements()
selectionDot = document.createElement("div")
selectionDot.style.cssText = `
position: absolute;
width: 54px;
height: 20px;
cursor: pointer;
z-index: 10001;
opacity: 0;
transition: opacity 0.2s ease;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
background: #c15e50;
border-radius: 6px;
padding: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
`
// Add the logo SVG with white color and Core text
selectionDot.innerHTML = `
`
const top = rect.bottom + window.scrollY + 5
const left = Math.min(
rect.right + window.scrollX - 16,
window.innerWidth - 40
)
selectionDot.style.top = `${top}px`
selectionDot.style.left = `${left}px`
selectionDot.addEventListener("click", showSelectionPopup)
selectionDot.addEventListener("mouseenter", showSelectionPopup)
document.body.appendChild(selectionDot)
setTimeout(() => {
if (selectionDot) {
selectionDot.style.opacity = "1"
}
}, 10)
// Start auto-hide timer
startAutoHideTimer(8000) // Longer initial timer
// Handle hover events
selectionDot.addEventListener("mouseenter", () => {
isHoveringElements = true
clearAutoHideTimer()
})
selectionDot.addEventListener("mouseleave", () => {
isHoveringElements = false
if (!selectionPopup) {
startAutoHideTimer(3000) // Shorter timer when leaving dot
}
})
}
// Timer management functions
function startAutoHideTimer(delay: number) {
clearAutoHideTimer()
autoHideTimer = setTimeout(() => {
if (!isHoveringElements) {
removeSelectionElements()
}
}, delay)
}
function clearAutoHideTimer() {
if (autoHideTimer) {
clearTimeout(autoHideTimer)
autoHideTimer = null
}
}
// Popup for text selection with React
function showSelectionPopup() {
const selectedText = window.getSelection()?.toString().trim()
if (!selectedText || !selectionDot) return
removeSelectionPopup()
// Create popup container
selectionPopup = document.createElement("div")
selectionPopup.style.cssText = `
position: fixed;
z-index: 10002;
opacity: 0;
transition: opacity 0.2s ease;
`
// Position popup
const dotRect = selectionDot.getBoundingClientRect()
let top = dotRect.bottom + 5
let left = dotRect.left
// Keep popup within viewport bounds
if (left + 350 > window.innerWidth) {
left = window.innerWidth - 360
}
if (left < 10) {
left = 10
}
// If popup would go below viewport, show above the dot
if (top + 250 > window.innerHeight) {
top = dotRect.top - 255
}
// Ensure popup doesn't go above viewport
if (top < 10) {
top = 10
}
selectionPopup.style.top = `${top}px`
selectionPopup.style.left = `${left}px`
document.body.appendChild(selectionPopup)
// Handle popup hover events
selectionPopup.addEventListener("mouseenter", () => {
isHoveringElements = true
clearAutoHideTimer()
})
selectionPopup.addEventListener("mouseleave", () => {
isHoveringElements = false
startAutoHideTimer(5000) // Auto-hide after leaving popup
})
// Prevent popup from closing when clicking inside it
selectionPopup.addEventListener("click", (e) => {
e.stopPropagation()
})
// Create React root
selectionRoot = createRoot(selectionPopup)
// State management for the component
let saveStatus: 'idle' | 'saving' | 'success' | 'error' | 'empty' = 'idle'
let isSaving = false
// Handle save
const handleSave = async (text: string) => {
if (!isExtensionContextValid()) {
saveStatus = 'error'
renderPopup()
return
}
if (!text.trim()) {
saveStatus = 'empty'
renderPopup()
setTimeout(() => {
saveStatus = 'idle'
renderPopup()
}, 2000)
return
}
isSaving = true
saveStatus = 'saving'
renderPopup()
try {
const success = await addEpisode(text)
if (success) {
saveStatus = 'success'
renderPopup()
setTimeout(() => removeSelectionElements(), 1500)
} else {
saveStatus = 'error'
renderPopup()
setTimeout(() => {
saveStatus = 'idle'
isSaving = false
renderPopup()
}, 2000)
}
} catch (error) {
saveStatus = 'error'
renderPopup()
setTimeout(() => {
saveStatus = 'idle'
isSaving = false
renderPopup()
}, 2000)
}
}
// Handle close
const handleClose = () => {
removeSelectionElements()
}
// Render function
const renderPopup = () => {
if (selectionRoot) {
selectionRoot.render(
createElement(TextSelectionPopup, {
selectedText,
onSave: handleSave,
onClose: handleClose,
isSaving,
saveStatus
})
)
}
}
// Initial render
renderPopup()
// Show popup
setTimeout(() => {
if (selectionPopup) {
selectionPopup.style.opacity = "1"
}
}, 10)
}
// Text selection handler
export function handleTextSelection() {
const selection = window.getSelection()
const selectedText = selection?.toString().trim()
if (
selectedText &&
selectedText.length > 0 &&
selection &&
selection.rangeCount > 0
) {
const range = selection.getRangeAt(0)
const rect = range.getBoundingClientRect()
createSelectionDot(rect)
} else {
removeSelectionElements()
}
}
// Cleanup functions
export function removeSelectionElements() {
clearAutoHideTimer()
isHoveringElements = false
if (selectionDot) {
selectionDot.remove()
selectionDot = null
}
removeSelectionPopup()
}
function removeSelectionPopup() {
// Clean up React root first
if (selectionRoot) {
selectionRoot.unmount()
selectionRoot = null
}
// Remove DOM element
if (selectionPopup) {
selectionPopup.remove()
selectionPopup = null
}
}
// Check if click is on selection elements
export function isSelectionElement(target: HTMLElement): boolean {
// Check if clicking on the dot
if (selectionDot && selectionDot.contains(target)) {
return true
}
// Check if clicking on the popup (including textarea and buttons)
if (selectionPopup && selectionPopup.contains(target)) {
return true
}
// Check if clicking on specific popup elements by ID
if (target.id === 'memory-text' || target.id === 'save-btn' || target.id === 'close-btn') {
return true
}
// Check if target is within any element with our popup z-index
if (target.closest('[style*="z-index: 10002"]')) {
return true
}
return false
}
// Close selection popup if clicking elsewhere
export function handleSelectionClickOutside(target: HTMLElement) {
// Only close if we're not clicking on any selection elements
if (selectionPopup && !isSelectionElement(target)) {
removeSelectionElements()
}
}