package nextcloud

import (
	"context"
	"sort"
	"strings"
)

const (
	searchSuggestLimit   = 8
	searchDefaultLimit   = 100
	searchSuggestMaxDirs = 400
	searchFullMaxDirs    = 2000
	searchMaxCollect     = 500
)

type SearchScope string

const (
	SearchScopeAll    SearchScope = "all"
	SearchScopeShared SearchScope = "shared"
	SearchScopeFolder SearchScope = "folder"
)

type SearchOptions struct {
	Query      string
	Scope      SearchScope
	BasePath   string
	Suggest    bool
	Limit      int
	MaxDirs    int
}

func (c *Client) SearchFiles(ctx context.Context, userID string, opts SearchOptions) ([]FileInfo, error) {
	q := strings.ToLower(strings.TrimSpace(opts.Query))
	if q == "" {
		return []FileInfo{}, nil
	}

	limit := opts.Limit
	if limit <= 0 {
		if opts.Suggest {
			limit = searchSuggestLimit
		} else {
			limit = searchDefaultLimit
		}
	}
	maxDirs := opts.MaxDirs
	if maxDirs <= 0 {
		if opts.Suggest {
			maxDirs = searchSuggestMaxDirs
		} else {
			maxDirs = searchFullMaxDirs
		}
	}

	scope := opts.Scope
	if scope == "" {
		scope = SearchScopeAll
	}

	var roots []string
	switch scope {
	case SearchScopeShared:
		shared, err := c.ListSharedWithMe(ctx, userID)
		if err != nil {
			return nil, err
		}
		roots = make([]string, 0, len(shared))
		for _, item := range shared {
			roots = append(roots, item.Path)
		}
	case SearchScopeFolder:
		base := normalizeSearchPath(opts.BasePath)
		if base == "" {
			base = "/"
		}
		roots = []string{base}
	default:
		roots = []string{"/"}
	}

	results, err := c.searchRecursive(ctx, userID, roots, q, limit, maxDirs)
	if err != nil {
		return nil, err
	}
	sortSearchResults(results, q)
	if len(results) > limit {
		results = results[:limit]
	}
	return results, nil
}

func normalizeSearchPath(path string) string {
	path = strings.TrimSpace(path)
	if path == "" || path == "/" {
		return "/"
	}
	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}
	return strings.TrimSuffix(path, "/")
}

func fileMatchesQuery(f FileInfo, q string) bool {
	return strings.Contains(strings.ToLower(f.Name), q) ||
		strings.Contains(strings.ToLower(f.Path), q)
}

func isDirectoryEntry(f FileInfo) bool {
	if f.Type == "directory" {
		return true
	}
	return strings.HasPrefix(strings.ToLower(f.MimeType), "httpd/unix-directory")
}

func (c *Client) searchRecursive(
	ctx context.Context,
	userID string,
	roots []string,
	q string,
	limit, maxDirs int,
) ([]FileInfo, error) {
	queue := make([]string, 0, len(roots))
	seen := make(map[string]struct{}, len(roots))
	for _, root := range roots {
		root = normalizeSearchPath(root)
		if root == "" {
			continue
		}
		if _, ok := seen[root]; ok {
			continue
		}
		seen[root] = struct{}{}
		queue = append(queue, root)
	}

	results := make([]FileInfo, 0, limit)
	visited := 0
	collectCap := searchMaxCollect
	if limit*5 > collectCap {
		collectCap = limit * 5
	}

	for len(queue) > 0 && visited < maxDirs && len(results) < collectCap {
		dir := queue[0]
		queue = queue[1:]
		visited++

		files, err := c.ListFiles(ctx, userID, dir)
		if err != nil {
			continue
		}
		for _, f := range files {
			if fileMatchesQuery(f, q) {
				results = append(results, f)
				if len(results) >= collectCap {
					break
				}
			}
			if !isDirectoryEntry(f) {
				continue
			}
			child := normalizeSearchPath(f.Path)
			if child == "" || child == "/" {
				continue
			}
			if _, ok := seen[child]; ok {
				continue
			}
			seen[child] = struct{}{}
			queue = append(queue, child)
		}
	}
	return results, nil
}

func searchMatchScore(f FileInfo, q string) int {
	name := strings.ToLower(f.Name)
	path := strings.ToLower(f.Path)
	switch {
	case name == q:
		return 1000
	case strings.HasPrefix(name, q):
		return 800
	case strings.Contains(name, q):
		return 600
	case strings.Contains(path, q):
		return 400
	default:
		return 0
	}
}

func sortSearchResults(files []FileInfo, q string) {
	sort.SliceStable(files, func(i, j int) bool {
		si := searchMatchScore(files[i], q)
		sj := searchMatchScore(files[j], q)
		if si != sj {
			return si > sj
		}
		if len(files[i].Path) != len(files[j].Path) {
			return len(files[i].Path) < len(files[j].Path)
		}
		return strings.ToLower(files[i].Name) < strings.ToLower(files[j].Name)
	})
}
