package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"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.
//
// Two things this session deliberately does NOT do.
//
// It does not start the server. "syncledger serve" blocks until it is
// interrupted, and a listener that never returns would leave the reader
// staring at a window with no way out but the close button — and a port open
// on their machine that they did not knowingly ask for. So this runs one
// self-contained pass instead: it builds exactly the file list a server rooted
// at that folder would publish at /manifest, prints it, and returns.
//
// It does not transfer anything, in either direction. Sending or receiving
// files stays a command-line act, typed on purpose.
//
// 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("  SyncLedger")
	fmt.Println("  Share a folder with another machine, and keep a record of every")
	fmt.Println("  file that moves.")
	fmt.Println()
	fmt.Println("  Before you share a folder it is worth knowing exactly what is in it.")
	fmt.Println("  Point me at one and I will list every file the other machine would")
	fmt.Println("  be able to see, with its size and its fingerprint.")
	fmt.Println()
	fmt.Println("  It only reads. Nothing is sent, received, changed or deleted, and")
	fmt.Println("  no server is started.")
	fmt.Println()

	suggested := suggestedFolder()
	var root string
	for {
		fmt.Println("  Which folder shall I look at?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		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 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
		}
		root = answer
		break
	}

	fmt.Println()
	fmt.Println("  Working. Every file is read and fingerprinted, so on a large folder")
	fmt.Println("  this can take a minute.")
	fmt.Println()

	printSharedFileList(root)

	fmt.Println()
	fmt.Println("  Done. Nothing was shared and nothing was changed.")
	fmt.Println("  The command-line version runs the server and the transfers:")
	fmt.Println("  syncledger help")
	pause(in)
}

// printSharedFileList prints the file list a server rooted at this folder
// would publish, using the very same scan the /manifest endpoint uses — so
// what the reader sees here is what the other machine would see.
func printSharedFileList(root string) {
	files, err := scanLocalDir(root, "")
	if err != nil {
		fmt.Printf("  I could not read all of %s: %v\n", root, err)
		fmt.Println("  That usually means a file in there is locked or needs permission")
		fmt.Println("  you do not have. Try a folder of your own documents instead.")
		return
	}

	entries := make([]FileEntry, 0, len(files))
	for _, fe := range files {
		entries = append(entries, fe)
	}
	sort.Slice(entries, func(i, j int) bool { return entries[i].Path < entries[j].Path })

	var total int64
	for _, fe := range entries {
		total += fe.Size
	}

	fmt.Printf("  Folder : %s\n", root)
	fmt.Printf("  Files  : %d\n", len(entries))
	fmt.Printf("  Size   : %s\n\n", humanBytes(total))

	if len(entries) == 0 {
		fmt.Println("  That folder is empty, so there would be nothing to share.")
		return
	}

	const preview = 40
	shown := entries
	if len(shown) > preview {
		shown = shown[:preview]
	}
	for _, fe := range shown {
		fmt.Printf("  %10s  %s  %s\n", humanBytes(fe.Size), fe.SHA256[:12], fe.Path)
	}
	if len(entries) > len(shown) {
		fmt.Printf("\n  ... and %d more file(s).\n", len(entries)-len(shown))
	}
}

// humanBytes renders a byte count the way a person reads one.
func humanBytes(n int64) string {
	const unit = 1024
	if n < unit {
		return fmt.Sprintf("%d B", n)
	}
	div, exp := int64(unit), 0
	for x := n / unit; x >= unit; x /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %ciB", float64(n)/float64(div), "KMGTPE"[exp])
}

// suggestedFolder offers somewhere 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 the folders people actually
	// sync between two machines are the ones worth offering first.
	for _, name := range []string{"Documents", "Pictures"} {
		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()
}
