package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// runGuided is what happens when somebody double-clicks the program instead of
// typing its name at a prompt.
//
// Without this, Explorer opens a console, main() finds no arguments, prints
// the usage text to stderr and exits — and Windows destroys the window in the
// same instant. From the other side of the screen that is indistinguishable
// from a crash. So when we know we were double-clicked, we ask the questions
// the program actually needs and stay on screen until the reader is done.
//
// The guided session shows the plan and stops there. FilePilot renames files in
// bulk, and a bulk rename is the kind of thing that has to be read before it
// happens; there is no arrangement of answers in this session that renames
// anything. Performing the plan stays on the command line.
//
// This path is entered ONLY when there are no arguments and both ends of the
// program are a real console. Any scripted or piped use takes exactly the same
// code path it always did.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  FilePilot")
	fmt.Println("  Plan a batch of renames and read them before they happen.")
	fmt.Println()
	fmt.Println("  Tell it a folder and a piece of text that appears in the file")
	fmt.Println("  names, and what that text should say instead. It works out the")
	fmt.Println("  new name for every file and shows you the whole list, including")
	fmt.Println("  any two files that would end up fighting over one name.")
	fmt.Println()
	fmt.Println("  This is a plan and only a plan. Not one file is renamed.")
	fmt.Println()

	folder := askFolder(in)
	if folder == "" {
		return
	}
	find, ok := askText(in, "  Which piece of the names shall I change?")
	if !ok {
		return
	}
	replace, ok := askReplacement(in, find)
	if !ok {
		return
	}

	fmt.Println()
	fmt.Printf("  Showing what changing %q to %q would look like.\n", find, replace)
	fmt.Println()

	// QuoteMeta on the search text and $$ on the replacement keep both literal:
	// the reader typed text, not a pattern and not a template.
	match := regexp.QuoteMeta(find)
	template := strings.ReplaceAll(replace, "$", "$$")
	cmdRename([]string{folder, "--match", match, "--replace", template})

	fmt.Println()
	fmt.Println("  Done. Every file is still called exactly what it was called.")
	fmt.Println("  The command-line version is the one that performs a plan, and it")
	fmt.Println("  can also renumber a whole folder in order: filepilot --help")
	pause(in)
}

// askFolder asks which folder to plan against and keeps asking until the answer
// is a real folder. It returns "" only when stdin closes, which means there is
// nobody left to ask.
func askFolder(in *bufio.Scanner) string {
	suggested := suggestedFolder()
	for {
		fmt.Println("  Which folder are the files in?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		fmt.Print("  > ")

		if !in.Scan() {
			return ""
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need a folder to look at. Try again, or close this window.")
			fmt.Println()
			continue
		}

		info, err := os.Stat(answer)
		switch {
		case err != nil:
			fmt.Println()
			fmt.Printf("  I cannot find %q.\n", answer)
			fmt.Println("  Tip: you can drag a folder from Explorer onto this window to")
			fmt.Println("  paste its location, then press Enter.")
			fmt.Println()
			continue
		case !info.IsDir():
			fmt.Println()
			fmt.Printf("  %q is a file, not a folder. Give me the folder it sits in.\n", answer)
			fmt.Println()
			continue
		}
		return answer
	}
}

// askText asks one plain-text question and insists on an answer. The second
// result is false only when stdin closes, which means there is nobody left to
// ask.
func askText(in *bufio.Scanner, prompt string) (string, bool) {
	for {
		fmt.Println()
		fmt.Println(prompt)
		fmt.Print("  > ")

		if !in.Scan() {
			return "", false
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need some text to work with. Try again, or close this")
			fmt.Println("  window.")
			continue
		}
		if strings.ContainsAny(answer, `/\`) {
			fmt.Println()
			fmt.Println("  FilePilot only changes names, never which folder a file is")
			fmt.Println("  in, so a slash cannot go in a name. Try again without it.")
			continue
		}
		return answer, true
	}
}

// askReplacement asks what the text should become. FilePilot refuses an empty
// replacement outright, and an answer identical to the text being replaced
// would plan a folder full of files renaming themselves to themselves, so both
// are sent back here rather than turned into an error the reader has to
// decode.
func askReplacement(in *bufio.Scanner, find string) (string, bool) {
	for {
		answer, ok := askText(in, "  What should it say instead?")
		if !ok {
			return "", false
		}
		if answer == find {
			fmt.Println()
			fmt.Println("  That is what they already say, so nothing would change.")
			fmt.Println("  Try something different, or close this window.")
			continue
		}
		return answer, true
	}
}

// suggestedFolder offers somewhere worth planning against that is certain to
// exist, so the reader can get a useful answer by pressing one key.
func suggestedFolder() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	// A home directory is the honest default, but a folder of photos straight
	// off a camera is the batch of names people actually want to fix.
	for _, name := range []string{"Pictures", "Documents", "Downloads"} {
		candidate := filepath.Join(home, name)
		if info, err := os.Stat(candidate); err == nil && info.IsDir() {
			return candidate
		}
	}
	return home
}

// pause keeps the console window open. Explorer closes it the moment the
// process exits, so without this the reader never sees the output.
func pause(in *bufio.Scanner) {
	fmt.Println()
	fmt.Print("  Press Enter to close this window. ")
	in.Scan()
}
