package utils

import (
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"gobsidian/internal/config"
	"gobsidian/internal/models"
)

func BuildTreeStructure(rootDir string, hiddenDirs []string, cfg *config.Config) (*models.TreeNode, error) {
	root := &models.TreeNode{
		Name: filepath.Base(rootDir),
		Path: "",
		Type: models.FileTypeDirectory,
	}

	err := buildTreeRecursive(rootDir, root, hiddenDirs, cfg)
	return root, err
}

func buildTreeRecursive(currentPath string, node *models.TreeNode, hiddenDirs []string, cfg *config.Config) error {
	entries, err := os.ReadDir(currentPath)
	if err != nil {
		return err
	}

	// Sort entries: directories first, then files
	sort.Slice(entries, func(i, j int) bool {
		if entries[i].IsDir() != entries[j].IsDir() {
			return entries[i].IsDir()
		}
		return strings.ToLower(entries[i].Name()) < strings.ToLower(entries[j].Name())
	})

	for _, entry := range entries {
		// Skip hidden files
		if strings.HasPrefix(entry.Name(), ".") {
			continue
		}

		// Skip hidden directories
		if entry.IsDir() && contains(hiddenDirs, entry.Name()) {
			continue
		}

		childPath := filepath.Join(currentPath, entry.Name())
		relativePath := getRelativePath(childPath, cfg.NotesDir)

		child := &models.TreeNode{
			Name: entry.Name(),
			Path: relativePath,
		}

		if entry.IsDir() {
			child.Type = models.FileTypeDirectory
			// Recursively build children
			if err := buildTreeRecursive(childPath, child, hiddenDirs, cfg); err != nil {
				continue // Skip directories that can't be read
			}
		} else {
			child.Type = models.GetFileType(filepath.Ext(entry.Name()), cfg.AllowedImageExtensions, cfg.AllowedFileExtensions)
			// Apply visibility for tree (left navigation)
			switch child.Type {
			case models.FileTypeMarkdown:
				// always show
			case models.FileTypeImage:
				if !cfg.ShowImagesInTree {
					continue
				}
			case models.FileTypeText:
				if !cfg.ShowFilesInTree {
					continue
				}
			default:
				continue
			}
		}

		node.Children = append(node.Children, child)
	}

	return nil
}

func GetFolderContents(folderPath string, cfg *config.Config) ([]models.FileInfo, error) {
	fullPath := filepath.Join(cfg.NotesDir, folderPath)

	entries, err := os.ReadDir(fullPath)
	if err != nil {
		return nil, err
	}

	var contents []models.FileInfo

	// Sort entries: directories first, then files
	sort.Slice(entries, func(i, j int) bool {
		if entries[i].IsDir() != entries[j].IsDir() {
			return entries[i].IsDir()
		}
		return strings.ToLower(entries[i].Name()) < strings.ToLower(entries[j].Name())
	})

	for _, entry := range entries {
		// Skip hidden files
		if strings.HasPrefix(entry.Name(), ".") {
			continue
		}

		info, err := entry.Info()
		if err != nil {
			continue
		}

		relativePath := filepath.Join(folderPath, entry.Name())
		if folderPath == "" {
			relativePath = entry.Name()
		}

		fileInfo := models.FileInfo{
			Name:    entry.Name(),
			Path:    relativePath,
			Size:    info.Size(),
			ModTime: info.ModTime(),
		}

		if entry.IsDir() {
			fileInfo.Type = models.FileTypeDirectory
			fileInfo.DisplayName = entry.Name()
		} else {
			fileInfo.Type = models.GetFileType(filepath.Ext(entry.Name()), cfg.AllowedImageExtensions, cfg.AllowedFileExtensions)

			// Set display name based on file type
			if fileInfo.Type == models.FileTypeMarkdown {
				fileInfo.DisplayName = strings.TrimSuffix(entry.Name(), ".md")
			} else {
				fileInfo.DisplayName = entry.Name()
			}

			// Visibility for folder view
			if fileInfo.Type == models.FileTypeImage {
				// prefer new flag; fallback to legacy ImagesHide (handled by default in config load)
				if !cfg.ShowImagesInFolder {
					continue
				}
			}
			if fileInfo.Type == models.FileTypeText {
				if !cfg.ShowFilesInFolder {
					continue
				}
			}
			// Skip files that are not allowed
			if fileInfo.Type == models.FileTypeOther {
				continue
			}
		}

		contents = append(contents, fileInfo)
	}

	return contents, nil
}

func GenerateBreadcrumbs(path string) []models.Breadcrumb {
	var breadcrumbs []models.Breadcrumb

	// Add root
	breadcrumbs = append(breadcrumbs, models.Breadcrumb{
		Name: "/",
		URL:  "/",
	})

	if path == "" {
		return breadcrumbs
	}

	parts := strings.Split(path, "/")
	currentPath := ""

	for _, part := range parts {
		if part == "" {
			continue
		}

		if currentPath == "" {
			currentPath = part
		} else {
			currentPath = filepath.Join(currentPath, part)
		}

		breadcrumbs = append(breadcrumbs, models.Breadcrumb{
			Name: part,
			URL:  "/folder/" + currentPath,
		})
	}

	return breadcrumbs
}

func GetImageStorageInfo(notePath string, cfg *config.Config) models.ImageStorageInfo {
	var storageDir, markdownPath string

	switch cfg.ImageStorageMode {
	case 1: // Store directly in NOTES_DIR
		storageDir = cfg.NotesDir
		markdownPath = ""

	case 2: // Store in specific folder
		storageDir = cfg.ImageStoragePath
		markdownPath = ""

	case 3: // Store in same directory as note
		if notePath != "" {
			storageDir = filepath.Join(cfg.NotesDir, filepath.Dir(notePath))
		} else {
			storageDir = cfg.NotesDir
		}
		markdownPath = ""

	case 4: // Store in subfolder of note's directory
		if notePath != "" {
			storageDir = filepath.Join(cfg.NotesDir, filepath.Dir(notePath), cfg.ImageSubfolderName)
		} else {
			storageDir = filepath.Join(cfg.NotesDir, cfg.ImageSubfolderName)
		}
		markdownPath = cfg.ImageSubfolderName

	default:
		storageDir = cfg.NotesDir
		markdownPath = ""
	}

	return models.ImageStorageInfo{
		StorageDir:   storageDir,
		MarkdownPath: markdownPath,
	}
}

func EnsureDir(dirPath string) error {
	return os.MkdirAll(dirPath, 0755)
}

func IsPathInSkippedDirs(path string, skippedDirs []string) bool {
	parts := strings.Split(filepath.Clean(path), string(filepath.Separator))

	for _, part := range parts {
		if contains(skippedDirs, part) {
			return true
		}
	}

	return false
}

func GetActivePath(currentPath string) []string {
	if currentPath == "" {
		return []string{}
	}

	return strings.Split(strings.Trim(currentPath, "/"), "/")
}

// Helper functions
func contains(slice []string, item string) bool {
	for _, s := range slice {
		if strings.TrimSpace(s) == item {
			return true
		}
	}
	return false
}

func getRelativePath(fullPath, basePath string) string {
	rel, err := filepath.Rel(basePath, fullPath)
	if err != nil {
		return fullPath
	}
	return filepath.ToSlash(rel)
}

func 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])
}

func FormatTime(t time.Time) string {
	return t.Format("2006-01-02 15:04")
}
