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 runs "preview", which is the command FileOps documents as
// permanently read-only: it ignores the flag that would make it write, so there
// is no arrangement of answers here that can rename a file. Renaming a folder
// full of files is exactly the kind of thing somebody wants to see spelled out
// before it happens, and this shows them the list. Doing it 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("  FileOps")
	fmt.Println("  See what a bulk rename would do, before it does it.")
	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 lists every")
	fmt.Println("  file it would touch and the name each one would end up with.")
	fmt.Println()
	fmt.Println("  This is a preview and only a preview. 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?", true)
	if !ok {
		return
	}
	replace, ok := askReplacement(in, find)
	if !ok {
		return
	}

	fmt.Println()
	if replace == "" {
		fmt.Printf("  Showing what removing %q from the names would look like.\n", find)
	} else {
		fmt.Printf("  Showing what changing %q to %q would look like.\n", find, replace)
	}
	fmt.Println()

	// FindStringSubmatch is unhelpfully happy to match anywhere, so the two
	// capture groups pin down "everything before" and "everything after" the
	// first occurrence, and the template puts the replacement between them.
	// QuoteMeta keeps punctuation in the reader's answer literal: they typed
	// text, not a pattern.
	match := "^(.*?)" + regexp.QuoteMeta(find) + "(.*)$"
	template := "{1}" + replace + "{2}"
	cmdRename("preview", []string{folder, "--match", match, "--to", 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 renames, and it writes a")
	fmt.Println("  journal that puts every name back: fileops --help")
	pause(in)
}

// askFolder asks which folder to preview 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. Answers go into a name template where
// curly brackets mean something, so an answer containing them is sent back
// rather than turned into an error message about template syntax.
//
// The second result is false only when stdin closes, which keeps an empty
// answer — a legitimate "take that text out altogether" — distinct from having
// nobody left to ask.
func askText(in *bufio.Scanner, prompt string, required bool) (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 == "" && required {
			fmt.Println()
			fmt.Println("  I need some text to look for. Try again, or close this window.")
			continue
		}
		if strings.ContainsAny(answer, "{}") {
			fmt.Println()
			fmt.Println("  Curly brackets mean something special here, so I cannot use")
			fmt.Println("  them in a name. Try again without { or }.")
			continue
		}
		if strings.ContainsAny(answer, `/\`) {
			fmt.Println()
			fmt.Println("  FileOps only changes names, never which folder a file is in,")
			fmt.Println("  so a slash cannot go in a name. Try again without it.")
			continue
		}
		return answer, true
	}
}

// askReplacement asks what the text should become. An answer identical to the
// text being replaced would preview a folder full of files renaming themselves
// to themselves, which tells the reader nothing.
func askReplacement(in *bufio.Scanner, find string) (string, bool) {
	for {
		answer, ok := askText(in, "  What should it say instead? (leave empty to drop it)", false)
		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 previewing 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()
}
