package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"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 one
// question the program actually needs and stay on screen until the reader is
// done.
//
// 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.
//
// Guided mode checks a rules file and never starts watching anything, so no
// rule's command is ever executed here. Starting the daemon is a command-line
// job.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  ActionForge")
	fmt.Println("  Runs your \"when a file lands in this folder, do that\" rules.")
	fmt.Println()
	fmt.Println("  Your rules live in one file. This check reads that file and tells")
	fmt.Println("  you whether every rule is complete and correctly written, before you")
	fmt.Println("  trust it to run unattended.")
	fmt.Println()
	fmt.Println("  It only reads. No rule is started and no command is run.")
	fmt.Println()

	suggested := suggestedConfig()
	for {
		fmt.Println("  Which rules file shall I check?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		} else {
			fmt.Println("  Tip: you can drag the file, or the folder holding it, from")
			fmt.Println("  Explorer onto this window to paste its location.")
		}
		fmt.Print("  > ")

		if !in.Scan() {
			// stdin closed on us; there is nothing sensible left to ask.
			return
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need a rules file to check. Try again, or close this window.")
			fmt.Println()
			showExample()
			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 the file, or the folder holding it, from")
			fmt.Println("  Explorer onto this window to paste its location, then press Enter.")
			fmt.Println()
			continue
		case info.IsDir():
			// A dragged folder is a good guess at where the rules file lives.
			found := configInDir(answer)
			if found == "" {
				fmt.Println()
				fmt.Printf("  %q is a folder, and I could not spot a rules file inside it.\n", answer)
				fmt.Println("  Drag the rules file itself onto this window instead.")
				fmt.Println()
				continue
			}
			fmt.Println()
			fmt.Printf("  That is a folder. Using the rules file inside it: %s\n", found)
			answer = found
		}

		fmt.Println()
		fmt.Println("  Checking.")
		fmt.Println()
		reportConfig(answer)
		break
	}

	fmt.Println()
	fmt.Println("  Done. Once a rules file checks out, the command-line version can")
	fmt.Println("  watch those folders for you: actionforge --help")
	pause(in)
}

// reportConfig prints the same verdict the validate command prints, without
// calling os.Exit — the guided session has to stay alive to keep the window up.
func reportConfig(path string) {
	cfg, err := loadConfig(path)
	if err != nil {
		fmt.Printf("  I could not read that as a rules file: %v\n", err)
		fmt.Println()
		showExample()
		return
	}

	problems := validateConfig(cfg)
	if len(problems) == 0 {
		fmt.Printf("  OK: %s is valid (%d rule(s)).\n", path, len(cfg.Rules))
	} else {
		fmt.Printf("  %s has %d problem(s):\n", path, len(problems))
		for _, p := range problems {
			fmt.Printf("    - %s\n", p)
		}
	}

	if len(cfg.Rules) == 0 {
		return
	}
	fmt.Println()
	fmt.Println("  Rules in this file:")
	for _, r := range cfg.Rules {
		name := r.Name
		if strings.TrimSpace(name) == "" {
			name = "(unnamed)"
		}
		fmt.Printf("    %s\n", name)
		if strings.TrimSpace(r.WatchDir) != "" {
			note := ""
			if info, err := os.Stat(r.WatchDir); err != nil {
				note = "   <- this folder does not exist yet"
			} else if !info.IsDir() {
				note = "   <- this is a file, not a folder"
			}
			fmt.Printf("      watches: %s%s\n", r.WatchDir, note)
		}
		if len(r.Extensions) > 0 {
			fmt.Printf("      only files ending: %s\n", strings.Join(r.Extensions, " "))
		} else {
			fmt.Println("      every file in that folder")
		}
		if strings.TrimSpace(r.Run) != "" {
			fmt.Printf("      then runs: %s\n", r.Run)
		}
	}
}

// showExample prints a rules file the reader can copy, because "I need a rules
// file" is useless advice if you have never seen one.
func showExample() {
	fmt.Println("  A rules file is plain text and looks like this:")
	fmt.Println()
	fmt.Println(`    {`)
	fmt.Println(`      "rules": [`)
	fmt.Println(`        {`)
	fmt.Println(`          "name": "screenshots-to-archive",`)
	fmt.Println(`          "watch_dir": "C:\\Users\\me\\Pictures\\Screenshots",`)
	fmt.Println(`          "extensions": [".png"],`)
	fmt.Println(`          "run": "copy {file} D:\\Archive\\"`)
	fmt.Println(`        }`)
	fmt.Println(`      ]`)
	fmt.Println(`    }`)
	fmt.Println()
	fmt.Println("  Save that as rules.json next to this program and press Enter.")
	fmt.Println()
}

// configNames are the file names people actually give a rules file, in the
// order we would rather find them.
var configNames = []string{"rules.json", "actionforge.json", "actionforge-rules.json", "config.json"}

// configInDir returns the first plausible rules file inside dir, or "".
func configInDir(dir string) string {
	for _, name := range configNames {
		candidate := filepath.Join(dir, name)
		if info, err := os.Stat(candidate); err == nil && info.Mode().IsRegular() {
			return candidate
		}
	}
	return ""
}

// suggestedConfig offers a rules file that is certain to exist, so the reader
// can get an answer by pressing one key. It returns "" when there is nothing
// to suggest, and the prompt asks for a path instead.
func suggestedConfig() string {
	var dirs []string
	if wd, err := os.Getwd(); err == nil {
		dirs = append(dirs, wd)
	}
	// The folder the program itself was double-clicked in is the other place
	// people keep their rules file.
	if exe, err := os.Executable(); err == nil {
		dirs = append(dirs, filepath.Dir(exe))
	}
	if home, err := os.UserHomeDir(); err == nil {
		dirs = append(dirs, home, filepath.Join(home, "actionforge"))
	}
	for _, dir := range dirs {
		if found := configInDir(dir); found != "" {
			return found
		}
	}
	return ""
}

// 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()
}
