package system

import (
	"context"
	"encoding/json"
	"fmt"
	"image"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
	"wox/common"
	"wox/plugin"
	"wox/plugin/system"
	"wox/setting/definition"
	"wox/util"
	"wox/util/clipboard"

	"github.com/cdfmlr/ellipsis"
	"github.com/disintegration/imaging"
	"github.com/google/uuid"
)

var clipboardIcon = plugin.PluginClipboardIcon
var isKeepTextHistorySettingKey = "is_keep_text_history"
var textHistoryDaysSettingKey = "text_history_days"
var isKeepImageHistorySettingKey = "is_keep_image_history"
var imageHistoryDaysSettingKey = "image_history_days"
var primaryActionSettingKey = "primary_action"
var primaryActionValueCopy = "copy"
var primaryActionValuePaste = "paste"
var favoritesSettingKey = "favorites"

func init() {
	plugin.AllSystemPlugin = append(plugin.AllSystemPlugin, &ClipboardPlugin{
		maxHistoryCount: 5000,
		imageCache:      make(map[string]*ImageCacheEntry),
	})
}

// ImageCacheEntry represents cached preview and icon images
type ImageCacheEntry struct {
	Preview common.WoxImage
	Icon    common.WoxImage
}

// FavoriteClipboardItem represents a favorite clipboard item stored in settings
type FavoriteClipboardItem struct {
	ID        string  `json:"id"`
	Type      string  `json:"type"`
	Content   string  `json:"content"`
	FilePath  string  `json:"filePath,omitempty"`
	IconData  *string `json:"iconData,omitempty"`
	Width     *int    `json:"width,omitempty"`
	Height    *int    `json:"height,omitempty"`
	FileSize  *int64  `json:"fileSize,omitempty"`
	Timestamp int64   `json:"timestamp"`
	CreatedAt int64   `json:"createdAt"`
}

// ClipboardDBInterface defines the interface for clipboard database operations
type ClipboardDBInterface interface {
	Insert(ctx context.Context, record ClipboardRecord) error
	Update(ctx context.Context, record ClipboardRecord) error
	UpdateTimestamp(ctx context.Context, id string, timestamp int64) error
	Delete(ctx context.Context, id string) error
	GetRecent(ctx context.Context, limit, offset int) ([]ClipboardRecord, error)
	SearchText(ctx context.Context, searchTerm string, limit int) ([]ClipboardRecord, error)
	GetByID(ctx context.Context, id string) (*ClipboardRecord, error)
	DeleteExpired(ctx context.Context, textDays, imageDays int) (int64, error)
	EnforceMaxCount(ctx context.Context, maxCount int) (int64, error)
	GetStats(ctx context.Context) (map[string]int, error)
	Close() error
}

type ClipboardPlugin struct {
	api             plugin.API
	db              ClipboardDBInterface
	maxHistoryCount int
	// Cache for generated preview and icon images to avoid regeneration
	imageCache map[string]*ImageCacheEntry
}

func (c *ClipboardPlugin) GetMetadata() plugin.Metadata {
	return plugin.Metadata{
		Id:            "5f815d98-27f5-488d-a756-c317ea39935b",
		Name:          "Clipboard History",
		Author:        "Wox Launcher",
		Website:       "https://github.com/Wox-launcher/Wox",
		Version:       "1.0.0",
		MinWoxVersion: "2.0.0",
		Runtime:       "Go",
		Description:   "Clipboard history for Wox",
		Icon:          clipboardIcon.String(),
		Entry:         "",
		TriggerKeywords: []string{
			"cb",
		},
		Features: []plugin.MetadataFeature{
			{
				Name: plugin.MetadataFeatureIgnoreAutoScore,
			},
		},
		Commands: []plugin.MetadataCommand{
			{
				Command:     "fav",
				Description: "List favorite clipboard history",
			},
		},
		SupportedOS: []string{
			"Windows",
			"Macos",
			"Linux",
		},
		SettingDefinitions: []definition.PluginSettingDefinitionItem{
			{
				Type: definition.PluginSettingDefinitionTypeCheckBox,
				Value: &definition.PluginSettingValueCheckBox{
					Key:          isKeepTextHistorySettingKey,
					DefaultValue: "true",
					Style: definition.PluginSettingValueStyle{
						PaddingRight: 10,
					},
				},
			},
			{
				Type: definition.PluginSettingDefinitionTypeTextBox,
				Value: &definition.PluginSettingValueTextBox{
					Key:          textHistoryDaysSettingKey,
					Label:        "i18n:plugin_clipboard_keep_text_history",
					Suffix:       "i18n:plugin_clipboard_days",
					DefaultValue: "90",
					Style: definition.PluginSettingValueStyle{
						Width: 50,
					},
				},
			},
			{
				Type: definition.PluginSettingDefinitionTypeNewLine,
			},
			{
				Type: definition.PluginSettingDefinitionTypeCheckBox,
				Value: &definition.PluginSettingValueCheckBox{
					Key:          isKeepImageHistorySettingKey,
					DefaultValue: "true",
					Style: definition.PluginSettingValueStyle{
						PaddingRight: 10,
					},
				},
			},
			{
				Type: definition.PluginSettingDefinitionTypeTextBox,
				Value: &definition.PluginSettingValueTextBox{
					Key:          imageHistoryDaysSettingKey,
					Label:        "i18n:plugin_clipboard_keep_image_history",
					Suffix:       "i18n:plugin_clipboard_days",
					DefaultValue: "3",
					Style: definition.PluginSettingValueStyle{
						Width: 50,
					},
				},
			},
			{
				Type: definition.PluginSettingDefinitionTypeNewLine,
			},
			{
				Type: definition.PluginSettingDefinitionTypeSelect,
				Value: &definition.PluginSettingValueSelect{
					Key:          primaryActionSettingKey,
					Label:        "i18n:plugin_clipboard_primary_action",
					DefaultValue: primaryActionValuePaste,
					Options: []definition.PluginSettingValueSelectOption{
						{Label: "i18n:plugin_clipboard_primary_action_copy_to_clipboard", Value: primaryActionValueCopy},
						{Label: "i18n:plugin_clipboard_primary_action_paste_to_active_app", Value: primaryActionValuePaste},
					},
				},
			},
		},
	}
}

func (c *ClipboardPlugin) Init(ctx context.Context, initParams plugin.InitParams) {
	c.api = initParams.API

	// Initialize database
	db, err := NewClipboardDB(ctx, c.GetMetadata().Id)
	if err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to initialize clipboard database: %s", err.Error()))
		return
	}
	c.db = db

	// Migration is now handled by the central migrator during app startup
	// No need for plugin-specific migration code here

	// Register unload callback to close database connection
	c.api.OnUnload(ctx, func() {
		if c.db != nil {
			c.db.Close()
		}
	})

	// Start periodic cleanup routine
	util.Go(ctx, "clipboard cleanup routine", func() {
		c.startCleanupRoutine(ctx)
	})

	// Log initial database statistics
	c.logDatabaseStats(ctx)

	clipboard.Watch(func(data clipboard.Data) {
		c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("clipboard data changed, type=%s", data.GetType()))

		// ignore file type
		if data.GetType() == clipboard.ClipboardTypeFile {
			return
		}

		if data.GetType() == clipboard.ClipboardTypeText && !c.isKeepTextHistory(ctx) {
			return
		}
		if data.GetType() == clipboard.ClipboardTypeImage && !c.isKeepImageHistory(ctx) {
			return
		}

		// Validate text data
		if data.GetType() == clipboard.ClipboardTypeText {
			textData := data.(*clipboard.TextData)
			if len(textData.Text) == 0 || strings.TrimSpace(textData.Text) == "" {
				return
			}
		}

		// Check for duplicate content by querying the most recent record
		if c.isDuplicateContent(ctx, data) {
			c.api.Log(ctx, plugin.LogLevelInfo, "duplicate clipboard content, skipping")
			return
		}

		// Create new record (always non-favorite initially)
		record := ClipboardRecord{
			ID:         uuid.NewString(),
			Type:       string(data.GetType()),
			Timestamp:  util.GetSystemTimestamp(),
			IsFavorite: false,
			CreatedAt:  time.Now(),
		}

		// Handle different data types
		if data.GetType() == clipboard.ClipboardTypeText {
			textData := data.(*clipboard.TextData)
			record.Content = textData.Text

			// Try to get active window icon for text clipboard
			if iconImage, iconErr := system.GetActiveWindowIcon(ctx); iconErr == nil {
				iconStr := iconImage.String()
				record.IconData = &iconStr
			}
		} else if data.GetType() == clipboard.ClipboardTypeImage {
			// Save image to disk
			imageData := data.(*clipboard.ImageData)
			imageFilePath := path.Join(util.GetLocation().GetImageCacheDirectory(), fmt.Sprintf("clipboard_%s.png", record.ID))

			if saveErr := imaging.Save(imageData.Image, imageFilePath); saveErr != nil {
				c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to save image to disk: %s", saveErr.Error()))
				return
			}

			// Get image dimensions
			width := imageData.Image.Bounds().Dx()
			height := imageData.Image.Bounds().Dy()

			// Get file size
			var fileSize int64
			if fileInfo, err := os.Stat(imageFilePath); err == nil {
				fileSize = fileInfo.Size()
			}

			record.FilePath = imageFilePath
			record.Width = &width
			record.Height = &height
			record.FileSize = &fileSize
			record.Content = fmt.Sprintf("Image (%d×%d) (%s)", width, height, c.formatFileSize(fileSize))
			c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("saved clipboard image to disk: %s", imageFilePath))
		}

		// Insert into database (non-favorite items only)
		if err := c.db.Insert(ctx, record); err != nil {
			c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to insert clipboard record: %s", err.Error()))
			return
		}

		// Enforce max count limit
		if deletedCount, err := c.db.EnforceMaxCount(ctx, c.maxHistoryCount); err != nil {
			c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to enforce max count: %s", err.Error()))
		} else if deletedCount > 0 {
			c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("enforced max count, deleted %d old records", deletedCount))
		}

		c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("saved clipboard %s to database", data.GetType()))
	})
}

func (c *ClipboardPlugin) Query(ctx context.Context, query plugin.Query) []plugin.QueryResult {
	var results []plugin.QueryResult

	if c.db == nil {
		c.api.Log(ctx, plugin.LogLevelError, "database not initialized")
		return results
	}

	if query.Command == "fav" {
		// Get favorite records from settings
		favorites, err := c.getFavoriteItems(ctx)
		if err != nil {
			c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to get favorites: %s", err.Error()))
			return results
		}

		for _, favoriteItem := range favorites {
			record := c.convertFavoriteToRecord(favoriteItem)
			results = append(results, c.convertRecordToResult(ctx, record, query))
		}
		return results
	}

	if query.Search == "" {
		// Get favorites first from settings
		favorites, err := c.getFavoriteItems(ctx)
		if err != nil {
			c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to get favorites: %s", err.Error()))
		} else {
			for _, favoriteItem := range favorites {
				record := c.convertFavoriteToRecord(favoriteItem)
				results = append(results, c.convertRecordToResult(ctx, record, query))
			}
		}

		// Get recent non-favorite records from database
		recent, err := c.db.GetRecent(ctx, 50, 0)
		if err != nil {
			c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to get recent records: %s", err.Error()))
		} else {
			for _, record := range recent {
				// All records in database are non-favorite now
				results = append(results, c.convertRecordToResult(ctx, record, query))
			}
		}

		return results
	}

	// Search in text content
	var allResults []ClipboardRecord

	// Search in favorites from settings
	favorites, err := c.getFavoriteItems(ctx)
	if err == nil {
		for _, favoriteItem := range favorites {
			if favoriteItem.Type == string(clipboard.ClipboardTypeText) &&
				strings.Contains(strings.ToLower(favoriteItem.Content), strings.ToLower(query.Search)) {
				record := c.convertFavoriteToRecord(favoriteItem)
				allResults = append(allResults, record)
			}
		}
	}

	// Search in database records
	searchResults, err := c.db.SearchText(ctx, query.Search, 100)
	if err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to search text: %s", err.Error()))
	} else {
		allResults = append(allResults, searchResults...)
	}

	for _, record := range allResults {
		results = append(results, c.convertRecordToResult(ctx, record, query))
	}

	return results
}

// isDuplicateContent checks if the content is duplicate by comparing with the most recent record
func (c *ClipboardPlugin) isDuplicateContent(ctx context.Context, data clipboard.Data) bool {
	// Check most recent record from database
	recent, err := c.db.GetRecent(ctx, 1, 0)
	var lastRecord *ClipboardRecord
	if err == nil && len(recent) > 0 {
		lastRecord = &recent[0]
	}

	// Check most recent favorite from settings
	favorites, err := c.getFavoriteItems(ctx)
	var lastFavorite *FavoriteClipboardItem
	if err == nil && len(favorites) > 0 {
		// Find the most recent favorite by timestamp
		for i := range favorites {
			if lastFavorite == nil || favorites[i].Timestamp > lastFavorite.Timestamp {
				lastFavorite = &favorites[i]
			}
		}
	}

	// Determine which is more recent
	var mostRecentRecord *ClipboardRecord
	if lastRecord != nil && lastFavorite != nil {
		if lastRecord.Timestamp > lastFavorite.Timestamp {
			mostRecentRecord = lastRecord
		} else {
			favoriteRecord := c.convertFavoriteToRecord(*lastFavorite)
			mostRecentRecord = &favoriteRecord
		}
	} else if lastRecord != nil {
		mostRecentRecord = lastRecord
	} else if lastFavorite != nil {
		favoriteRecord := c.convertFavoriteToRecord(*lastFavorite)
		mostRecentRecord = &favoriteRecord
	} else {
		return false
	}

	if mostRecentRecord.Type != string(data.GetType()) {
		return false
	}

	if data.GetType() == clipboard.ClipboardTypeText {
		textData := data.(*clipboard.TextData)
		if mostRecentRecord.Content == textData.Text {
			// Update timestamp of existing record
			c.updateRecordTimestamp(ctx, mostRecentRecord, util.GetSystemTimestamp())
			return true
		}
	}

	if data.GetType() == clipboard.ClipboardTypeImage {
		imageData := data.(*clipboard.ImageData)
		currentSize := fmt.Sprintf("image(%dx%d)", imageData.Image.Bounds().Dx(), imageData.Image.Bounds().Dy())
		if mostRecentRecord.Content == currentSize {
			// Update timestamp of existing record
			c.updateRecordTimestamp(ctx, mostRecentRecord, util.GetSystemTimestamp())
			return true
		}
	}

	return false
}

// convertRecordToResult converts a database record to a query result
func (c *ClipboardPlugin) convertRecordToResult(ctx context.Context, record ClipboardRecord, query plugin.Query) plugin.QueryResult {
	if record.Type == string(clipboard.ClipboardTypeText) {
		return c.convertTextRecord(ctx, record, query)
	} else if record.Type == string(clipboard.ClipboardTypeImage) {
		return c.convertImageRecord(ctx, record, query)
	}

	return plugin.QueryResult{
		Title: "ERR: Unknown record type",
	}
}

// convertTextRecord converts a text record to a query result
func (c *ClipboardPlugin) convertTextRecord(ctx context.Context, record ClipboardRecord, query plugin.Query) plugin.QueryResult {
	primaryActionCode := c.api.GetSetting(ctx, primaryActionSettingKey)

	actions := []plugin.QueryResultAction{
		{
			Name:      "Copy",
			Icon:      plugin.CopyIcon,
			IsDefault: primaryActionValueCopy == primaryActionCode,
			Action: func(ctx context.Context, actionContext plugin.ActionContext) {
				c.moveRecordToTop(ctx, record.ID)
				clipboard.WriteText(record.Content)
			},
		},
	}

	// paste to active window
	pasteToActiveWindowAction, pasteToActiveWindowErr := system.GetPasteToActiveWindowAction(ctx, c.api, func() {
		c.moveRecordToTop(ctx, record.ID)
		clipboard.WriteText(record.Content)
	})
	if pasteToActiveWindowErr == nil {
		actions = append(actions, pasteToActiveWindowAction)
	}

	if !record.IsFavorite {
		actions = append(actions, plugin.QueryResultAction{
			Name:                   "Mark as favorite",
			Icon:                   plugin.AddToFavIcon,
			PreventHideAfterAction: true,
			Action: func(ctx context.Context, actionContext plugin.ActionContext) {
				if err := c.markAsFavorite(ctx, record); err != nil {
					c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to set favorite: %s", err.Error()))
				} else {
					c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("marked record as favorite: %s", record.ID))
					system.RefreshQuery(ctx, c.api, query)
				}
			},
		})
	} else {
		actions = append(actions, plugin.QueryResultAction{
			Name:                   "Cancel favorite",
			Icon:                   plugin.RemoveFromFavIcon,
			PreventHideAfterAction: true,
			Action: func(ctx context.Context, actionContext plugin.ActionContext) {
				if err := c.cancelFavorite(ctx, record.ID); err != nil {
					c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to cancel favorite: %s", err.Error()))
				} else {
					c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("cancelled record favorite: %s", record.ID))
					system.RefreshQuery(ctx, c.api, query)
				}
			},
		})
	}

	group, groupScore := c.getResultGroup(ctx, record)

	// Use stored icon data if available, otherwise use default text icon
	icon := c.getDefaultTextIcon()
	if record.IconData != nil && *record.IconData != "" {
		if iconImage, err := common.ParseWoxImage(*record.IconData); err == nil {
			icon = iconImage
		}
	}

	return plugin.QueryResult{
		Title:      strings.TrimSpace(ellipsis.Centering(record.Content, 80)),
		Icon:       icon,
		Group:      group,
		GroupScore: groupScore,
		Preview: plugin.WoxPreview{
			PreviewType: plugin.WoxPreviewTypeText,
			PreviewData: record.Content,
			PreviewProperties: map[string]string{
				"i18n:plugin_clipboard_copy_date":       util.FormatTimestamp(record.Timestamp),
				"i18n:plugin_clipboard_copy_characters": fmt.Sprintf("%d", len(record.Content)),
			},
		},
		Score:   record.Timestamp,
		Actions: actions,
	}
}

// convertImageRecord converts an image record to a query result
func (c *ClipboardPlugin) convertImageRecord(ctx context.Context, record ClipboardRecord, query plugin.Query) plugin.QueryResult {
	previewWoxImage, iconWoxImage := c.generateImagePreviewAndIcon(ctx, record)

	group, groupScore := c.getResultGroup(ctx, record)

	// Build preview properties with available information
	previewProperties := map[string]string{
		"i18n:plugin_clipboard_copy_date": util.FormatTimestamp(record.Timestamp),
	}

	if record.Width != nil {
		previewProperties["i18n:plugin_clipboard_image_width"] = fmt.Sprintf("%d", *record.Width)
	}
	if record.Height != nil {
		previewProperties["i18n:plugin_clipboard_image_height"] = fmt.Sprintf("%d", *record.Height)
	}
	if record.FileSize != nil {
		previewProperties["i18n:plugin_clipboard_image_size"] = c.formatFileSize(*record.FileSize)
	}

	return plugin.QueryResult{
		Title:      record.Content, // Already formatted as "Image (WxH) (size)"
		Icon:       iconWoxImage,
		Group:      group,
		GroupScore: groupScore,
		Preview: plugin.WoxPreview{
			PreviewType:       plugin.WoxPreviewTypeImage,
			PreviewData:       previewWoxImage.String(),
			PreviewProperties: previewProperties,
		},
		Score: record.Timestamp,
		Actions: []plugin.QueryResultAction{
			{
				Name: "Copy to clipboard",
				Action: func(ctx context.Context, actionContext plugin.ActionContext) {
					c.moveRecordToTop(ctx, record.ID)
					// Load image from disk and copy to clipboard
					if record.FilePath != "" && util.IsFileExists(record.FilePath) {
						if img := c.loadImageFromFile(ctx, record.FilePath); img != nil {
							clipboard.Write(&clipboard.ImageData{Image: img})
						}
					}
				},
			},
		},
	}
}

// moveRecordToTop updates the timestamp of a record to move it to the top
func (c *ClipboardPlugin) moveRecordToTop(ctx context.Context, id string) {
	if err := c.db.UpdateTimestamp(ctx, id, util.GetSystemTimestamp()); err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to move record to top: %s", err.Error()))
	}
}

// getResultGroup returns the group and score for a result
func (c *ClipboardPlugin) getResultGroup(ctx context.Context, record ClipboardRecord) (string, int64) {
	if record.IsFavorite {
		return "Favorites", 100
	}

	if util.GetSystemTimestamp()-record.Timestamp < 1000*60*60*24 {
		return "Today", 90
	}
	if util.GetSystemTimestamp()-record.Timestamp < 1000*60*60*24*2 {
		return "Yesterday", 80
	}

	return "History", 10
}

// getDefaultTextIcon returns the default text icon
func (c *ClipboardPlugin) getDefaultTextIcon() common.WoxImage {
	return plugin.TextIcon
}

// generateImagePreviewAndIcon generates preview and icon for image records
func (c *ClipboardPlugin) generateImagePreviewAndIcon(ctx context.Context, record ClipboardRecord) (previewImg, iconImg common.WoxImage) {
	// Check memory cache first
	if cached, exists := c.imageCache[record.ID]; exists {
		return cached.Preview, cached.Icon
	}

	imagePreviewFile := path.Join(util.GetLocation().GetImageCacheDirectory(), fmt.Sprintf("clipboard_%s_preview.png", record.ID))
	imageIconFile := path.Join(util.GetLocation().GetImageCacheDirectory(), fmt.Sprintf("clipboard_%s_icon.png", record.ID))

	if util.IsFileExists(imagePreviewFile) && util.IsFileExists(imageIconFile) {
		previewImg = common.NewWoxImageAbsolutePath(imagePreviewFile)
		iconImg = common.NewWoxImageAbsolutePath(imageIconFile)

		// Cache the result in memory for faster access
		c.imageCache[record.ID] = &ImageCacheEntry{
			Preview: previewImg,
			Icon:    iconImg,
		}
		return
	}

	// Load original image and generate preview/icon
	sourceImage := c.loadImageFromFile(ctx, record.FilePath)
	if sourceImage == nil {
		// Return default icons if image is not available
		previewImage := c.getDefaultTextIcon()
		iconImage := plugin.PreviewIcon
		return previewImage, iconImage
	}

	compressedPreviewImg := imaging.Resize(sourceImage, 400, 0, imaging.Lanczos)
	compressedIconImg := imaging.Resize(sourceImage, 40, 0, imaging.Lanczos)

	// Save to disk cache first
	if saveErr := imaging.Save(compressedPreviewImg, imagePreviewFile); saveErr != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("save clipboard image preview cache failed, err=%s", saveErr.Error()))
		// Fallback to base64 if disk save fails
		previewImage, err := common.NewWoxImage(compressedPreviewImg)
		if err != nil {
			previewImage = c.getDefaultTextIcon()
		}
		iconImage, iconErr := common.NewWoxImage(compressedIconImg)
		if iconErr != nil {
			iconImage = plugin.PreviewIcon
		}
		return previewImage, iconImage
	}

	if saveErr := imaging.Save(compressedIconImg, imageIconFile); saveErr != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("save clipboard image icon cache failed, err=%s", saveErr.Error()))
		// Fallback to base64 if disk save fails
		previewImage, err := common.NewWoxImage(compressedPreviewImg)
		if err != nil {
			previewImage = c.getDefaultTextIcon()
		}
		iconImage, iconErr := common.NewWoxImage(compressedIconImg)
		if iconErr != nil {
			iconImage = plugin.PreviewIcon
		}
		return previewImage, iconImage
	}

	// Use file paths for better performance
	previewImage := common.NewWoxImageAbsolutePath(imagePreviewFile)
	iconImage := common.NewWoxImageAbsolutePath(imageIconFile)

	// Cache the generated images in memory for faster access
	c.imageCache[record.ID] = &ImageCacheEntry{
		Preview: previewImage,
		Icon:    iconImage,
	}

	c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("generated image preview and icon cache, id=%s", record.ID))
	return previewImage, iconImage
}

// loadImageFromFile loads an image from a file path
func (c *ClipboardPlugin) loadImageFromFile(ctx context.Context, filePath string) image.Image {
	if filePath == "" || !util.IsFileExists(filePath) {
		return nil
	}

	file, err := os.Open(filePath)
	if err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to open image file: %s", err.Error()))
		return nil
	}
	defer file.Close()

	img, _, err := image.Decode(file)
	if err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to decode image: %s", err.Error()))
		return nil
	}

	return img
}

// isKeepTextHistory checks if text history should be kept
func (c *ClipboardPlugin) isKeepTextHistory(ctx context.Context) bool {
	return c.api.GetSetting(ctx, isKeepTextHistorySettingKey) == "true"
}

// isKeepImageHistory checks if image history should be kept
func (c *ClipboardPlugin) isKeepImageHistory(ctx context.Context) bool {
	return c.api.GetSetting(ctx, isKeepImageHistorySettingKey) == "true"
}

// getTextHistoryDays returns the number of days to keep text history
func (c *ClipboardPlugin) getTextHistoryDays(ctx context.Context) int {
	textHistoryDaysStr := c.api.GetSetting(ctx, textHistoryDaysSettingKey)
	if textHistoryDaysStr == "" {
		return 90
	}

	if textHistoryDaysInt, err := strconv.Atoi(textHistoryDaysStr); err == nil {
		return textHistoryDaysInt
	}
	return 90
}

// getImageHistoryDays returns the number of days to keep image history
func (c *ClipboardPlugin) getImageHistoryDays(ctx context.Context) int {
	imageHistoryDaysStr := c.api.GetSetting(ctx, imageHistoryDaysSettingKey)
	if imageHistoryDaysStr == "" {
		return 3
	}

	if imageHistoryDaysInt, err := strconv.Atoi(imageHistoryDaysStr); err == nil {
		return imageHistoryDaysInt
	}
	return 3
}

// startCleanupRoutine starts a background routine to periodically clean up expired data
func (c *ClipboardPlugin) startCleanupRoutine(ctx context.Context) {
	ticker := time.NewTicker(30 * time.Minute) // Run cleanup every 30 minutes
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			c.performCleanup(ctx)
		}
	}
}

// performCleanup removes expired history entries and orphaned cache files
func (c *ClipboardPlugin) performCleanup(ctx context.Context) {
	c.api.Log(ctx, plugin.LogLevelInfo, "starting clipboard cleanup routine")

	if c.db == nil {
		return
	}

	// Clean up expired database records
	textDays := c.getTextHistoryDays(ctx)
	imageDays := c.getImageHistoryDays(ctx)

	deletedCount, err := c.db.DeleteExpired(ctx, textDays, imageDays)
	if err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to delete expired records: %s", err.Error()))
	} else if deletedCount > 0 {
		c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("deleted %d expired records", deletedCount))
	}

	// Clean up orphaned cache files
	c.cleanupOrphanedCacheFiles(ctx)

	// Clean up memory cache
	c.cleanupMemoryCache(ctx)

	// Log database statistics
	c.logDatabaseStats(ctx)

	c.api.Log(ctx, plugin.LogLevelInfo, "clipboard cleanup completed")
}

// cleanupOrphanedCacheFiles removes cache files that no longer have corresponding database records
func (c *ClipboardPlugin) cleanupOrphanedCacheFiles(ctx context.Context) {
	cacheDir := util.GetLocation().GetImageCacheDirectory()
	if !util.IsFileExists(cacheDir) {
		return
	}

	// Get all current record IDs from database
	recent, err := c.db.GetRecent(ctx, 10000, 0) // Get a large number to cover all records
	if err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to get records for cleanup: %s", err.Error()))
		return
	}

	validIds := make(map[string]bool)
	for _, record := range recent {
		validIds[record.ID] = true
	}

	// Scan cache directory for clipboard files
	files, err := os.ReadDir(cacheDir)
	if err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to read cache directory: %s", err.Error()))
		return
	}

	removedCount := 0
	for _, file := range files {
		if strings.HasPrefix(file.Name(), "clipboard_") {
			// Extract ID from filename (format: clipboard_{id}_{type}.png or clipboard_{id}.png)
			parts := strings.Split(file.Name(), "_")
			if len(parts) >= 2 {
				id := strings.TrimSuffix(parts[1], ".png")
				if len(parts) >= 3 {
					id = parts[1] // For files like clipboard_{id}_{type}.png
				}
				if !validIds[id] {
					filePath := path.Join(cacheDir, file.Name())
					if removeErr := os.Remove(filePath); removeErr == nil {
						removedCount++
					}
				}
			}
		}
	}

	if removedCount > 0 {
		c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("removed %d orphaned cache files", removedCount))
	}
}

// cleanupMemoryCache removes cache entries for records that no longer exist
func (c *ClipboardPlugin) cleanupMemoryCache(ctx context.Context) {
	if len(c.imageCache) == 0 {
		return
	}

	// Get current record IDs
	recent, err := c.db.GetRecent(ctx, 1000, 0)
	if err != nil {
		return
	}

	validIds := make(map[string]bool)
	for _, record := range recent {
		validIds[record.ID] = true
	}

	// Remove cache entries for non-existent records
	removedCount := 0
	for id := range c.imageCache {
		if !validIds[id] {
			delete(c.imageCache, id)
			removedCount++
		}
	}

	if removedCount > 0 {
		c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("cleaned up %d memory cache entries", removedCount))
	}
}

// logDatabaseStats logs current database statistics
func (c *ClipboardPlugin) logDatabaseStats(ctx context.Context) {
	if c.db == nil {
		return
	}

	stats, err := c.db.GetStats(ctx)
	if err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to get database stats: %s", err.Error()))
		return
	}

	c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf(
		"clipboard database stats - total: %d, favorites: %d, text: %d, images: %d",
		stats["total"], stats["favorites"], stats["text"], stats["images"]))
}

// formatFileSize formats file size in bytes to human readable format
func (c *ClipboardPlugin) formatFileSize(bytes int64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

// getFavoriteItems retrieves favorite items from settings
func (c *ClipboardPlugin) getFavoriteItems(ctx context.Context) ([]FavoriteClipboardItem, error) {
	favoritesJson := c.api.GetSetting(ctx, favoritesSettingKey)
	if favoritesJson == "" {
		return []FavoriteClipboardItem{}, nil
	}

	var favorites []FavoriteClipboardItem
	if err := json.Unmarshal([]byte(favoritesJson), &favorites); err != nil {
		c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to unmarshal favorites: %s", err.Error()))
		return []FavoriteClipboardItem{}, nil
	}

	return favorites, nil
}

// saveFavoriteItems saves favorite items to settings
func (c *ClipboardPlugin) saveFavoriteItems(ctx context.Context, favorites []FavoriteClipboardItem) error {
	favoritesJson, err := json.Marshal(favorites)
	if err != nil {
		return fmt.Errorf("failed to marshal favorites: %w", err)
	}

	c.api.SaveSetting(ctx, favoritesSettingKey, string(favoritesJson), false)
	return nil
}

// addToFavorites adds an item to favorites settings
func (c *ClipboardPlugin) addToFavorites(ctx context.Context, record ClipboardRecord) error {
	favorites, err := c.getFavoriteItems(ctx)
	if err != nil {
		return err
	}

	// Check if already exists
	for _, fav := range favorites {
		if fav.ID == record.ID {
			return nil // Already exists
		}
	}

	// Convert ClipboardRecord to FavoriteClipboardItem
	favoriteItem := FavoriteClipboardItem{
		ID:        record.ID,
		Type:      record.Type,
		Content:   record.Content,
		FilePath:  record.FilePath,
		IconData:  record.IconData,
		Width:     record.Width,
		Height:    record.Height,
		FileSize:  record.FileSize,
		Timestamp: record.Timestamp,
		CreatedAt: record.CreatedAt.Unix(),
	}

	favorites = append(favorites, favoriteItem)
	return c.saveFavoriteItems(ctx, favorites)
}

// removeFromFavorites removes an item from favorites settings
func (c *ClipboardPlugin) removeFromFavorites(ctx context.Context, id string) error {
	favorites, err := c.getFavoriteItems(ctx)
	if err != nil {
		return err
	}

	// Find and remove the item
	for i, fav := range favorites {
		if fav.ID == id {
			favorites = append(favorites[:i], favorites[i+1:]...)
			break
		}
	}

	return c.saveFavoriteItems(ctx, favorites)
}

// convertFavoriteToRecord converts FavoriteClipboardItem to ClipboardRecord
func (c *ClipboardPlugin) convertFavoriteToRecord(item FavoriteClipboardItem) ClipboardRecord {
	return ClipboardRecord{
		ID:         item.ID,
		Type:       item.Type,
		Content:    item.Content,
		FilePath:   item.FilePath,
		IconData:   item.IconData,
		Width:      item.Width,
		Height:     item.Height,
		FileSize:   item.FileSize,
		Timestamp:  item.Timestamp,
		IsFavorite: true,
		CreatedAt:  time.Unix(item.CreatedAt, 0),
	}
}

// markAsFavorite moves an item from database to favorites settings
func (c *ClipboardPlugin) markAsFavorite(ctx context.Context, record ClipboardRecord) error {
	// Add to favorites settings
	if err := c.addToFavorites(ctx, record); err != nil {
		return fmt.Errorf("failed to add to favorites: %w", err)
	}

	// Remove from database if it exists there
	if err := c.db.Delete(ctx, record.ID); err != nil {
		c.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("failed to remove from database (may not exist): %s", err.Error()))
	}

	return nil
}

// cancelFavorite moves an item from favorites settings to database
func (c *ClipboardPlugin) cancelFavorite(ctx context.Context, id string) error {
	// Get the favorite item first
	favorites, err := c.getFavoriteItems(ctx)
	if err != nil {
		return fmt.Errorf("failed to get favorites: %w", err)
	}

	var favoriteItem *FavoriteClipboardItem
	for _, fav := range favorites {
		if fav.ID == id {
			favoriteItem = &fav
			break
		}
	}

	if favoriteItem == nil {
		return fmt.Errorf("favorite item not found: %s", id)
	}

	// Convert to ClipboardRecord and add to database
	record := c.convertFavoriteToRecord(*favoriteItem)
	record.IsFavorite = false // Mark as non-favorite
	if err := c.db.Insert(ctx, record); err != nil {
		return fmt.Errorf("failed to insert to database: %w", err)
	}

	// Remove from favorites settings
	if err := c.removeFromFavorites(ctx, id); err != nil {
		return fmt.Errorf("failed to remove from favorites: %w", err)
	}

	return nil
}

// updateRecordTimestamp updates the timestamp of a record in the appropriate storage
func (c *ClipboardPlugin) updateRecordTimestamp(ctx context.Context, record *ClipboardRecord, timestamp int64) {
	if record.IsFavorite {
		// Update in favorites settings
		favorites, err := c.getFavoriteItems(ctx)
		if err != nil {
			c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to get favorites for timestamp update: %s", err.Error()))
			return
		}

		for i := range favorites {
			if favorites[i].ID == record.ID {
				favorites[i].Timestamp = timestamp
				if err := c.saveFavoriteItems(ctx, favorites); err != nil {
					c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to save favorites after timestamp update: %s", err.Error()))
				}
				return
			}
		}
	} else {
		// Update in database
		if err := c.db.UpdateTimestamp(ctx, record.ID, timestamp); err != nil {
			c.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("failed to update timestamp in database: %s", err.Error()))
		}
	}
}
