package cmd

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/fs/cache"
	"github.com/rclone/rclone/fs/config"
	"github.com/rclone/rclone/fs/fspath"
	"github.com/spf13/cobra"
)

// Make a debug message while doing the completion.
//
// These end up in the file specified by BASH_COMP_DEBUG_FILE
func compLogf(format string, a ...any) {
	cobra.CompDebugln(fmt.Sprintf(format, a...), true)
}

// Add remotes to the completions being built up
func addRemotes(toComplete string, completions []string) []string {
	remotes := config.FileSections()
	for _, remote := range remotes {
		remote += ":"
		if strings.HasPrefix(remote, toComplete) {
			completions = append(completions, remote)
		}
	}
	return completions
}

// Add local files to the completions being built up
func addLocalFiles(toComplete string, result cobra.ShellCompDirective, completions []string) (cobra.ShellCompDirective, []string) {
	path := filepath.Clean(toComplete)
	dir, file := filepath.Split(path)
	if dir == "" {
		dir = "."
	}
	if len(dir) > 0 && dir[0] != filepath.Separator && dir[0] != '/' {
		dir = strings.TrimRight(dir, string(filepath.Separator))
		dir = strings.TrimRight(dir, "/")
	}
	fi, err := os.Stat(toComplete)
	if err == nil {
		if fi.IsDir() {
			dir = toComplete
			file = ""
		}
	}
	fis, err := os.ReadDir(dir)
	if err != nil {
		compLogf("Failed to read directory %q: %v", dir, err)
		return result, completions
	}
	for _, fi := range fis {
		name := fi.Name()
		if strings.HasPrefix(name, file) {
			path := filepath.Join(dir, name)
			if fi.IsDir() {
				path += string(filepath.Separator)
				result |= cobra.ShellCompDirectiveNoSpace
			}
			completions = append(completions, path)
		}
	}
	return result, completions
}

// Add remote files to the completions being built up
func addRemoteFiles(toComplete string, result cobra.ShellCompDirective, completions []string) (cobra.ShellCompDirective, []string) {
	ctx := context.Background()
	parent, _, err := fspath.Split(toComplete)
	if err != nil {
		compLogf("Failed to split path %q: %v", toComplete, err)
		return result, completions
	}
	f, err := cache.Get(ctx, parent)
	if err == fs.ErrorIsFile {
		completions = append(completions, toComplete)
		return result, completions
	} else if err != nil {
		compLogf("Failed to make Fs %q: %v", parent, err)
		return result, completions
	}
	fis, err := f.List(ctx, "")
	if err != nil {
		compLogf("Failed to list Fs %q: %v", parent, err)
		return result, completions
	}
	for _, fi := range fis {
		remote := fi.Remote()
		path := parent + remote
		if strings.HasPrefix(path, toComplete) {
			if _, ok := fi.(fs.Directory); ok {
				path += "/"
				result |= cobra.ShellCompDirectiveNoSpace
			}
			completions = append(completions, path)
		}
	}
	return result, completions
}

// Workaround doesn't seem to be needed for BashCompletionV2
const useColonWorkaround = false

// do command completion
//
// This is called by the command completion scripts using a hidden __complete or __completeNoDesc commands.
func validArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
	compLogf("ValidArgsFunction called with args=%q toComplete=%q", args, toComplete)

	fixBug := -1
	if useColonWorkaround {
		// Work around what I think is a bug in cobra's bash
		// completion which seems to be splitting the arguments on :
		// Or there is something I don't understand - ncw
		args = append(args, toComplete)
		colonArg := -1
		for i, arg := range args {
			if arg == ":" {
				colonArg = i
			}
		}
		if colonArg > 0 {
			newToComplete := strings.Join(args[colonArg-1:], "")
			fixBug = len(newToComplete) - len(toComplete)
			toComplete = newToComplete
		}
		compLogf("...shuffled args=%q toComplete=%q", args, toComplete)
	}

	result := cobra.ShellCompDirectiveDefault
	completions := []string{}

	// See whether we have a valid remote yet
	_, err := fspath.Parse(toComplete)
	parseOK := err == nil
	hasColon := strings.ContainsRune(toComplete, ':')
	validRemote := parseOK && hasColon
	compLogf("valid remote = %v", validRemote)

	// Add remotes for completion
	if !validRemote {
		completions = addRemotes(toComplete, completions)
	}

	// Add local files for completion
	if !validRemote {
		result, completions = addLocalFiles(toComplete, result, completions)
	}

	// Add remote files for completion
	if validRemote {
		result, completions = addRemoteFiles(toComplete, result, completions)
	}

	// If using bug workaround, adjust completions to start with :
	if useColonWorkaround && fixBug >= 0 {
		for i := range completions {
			if len(completions[i]) >= fixBug {
				completions[i] = completions[i][fixBug:]
			}
		}
	}

	return completions, result
}