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.
//
// The guided session checks the manifest and stops there. Building the
// document writes files — annotated copies of the screenshots, an HTML page, a
// .docx — and writing files is not something anyone should trigger by
// double-clicking an icon. So this reports what a build would find and leaves
// the writing to the command line, 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("  StepShot")
	fmt.Println("  Turn a folder of screenshots into a step-by-step guide.")
	fmt.Println()
	fmt.Println("  Your steps live in a manifest — a small text file listing each")
	fmt.Println("  screenshot, what to call that step, and where the arrows and boxes")
	fmt.Println("  go on the picture. Show me yours and I will check it against the")
	fmt.Println("  actual images: missing pictures, steps out of order, callouts that")
	fmt.Println("  fall off the edge, screenshots nobody used.")
	fmt.Println()
	fmt.Println("  It only reads. No document is written and no image is modified.")
	fmt.Println()

	suggested := suggestedManifest()
	var path string
	for {
		fmt.Println("  Which manifest file shall I check?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		} else {
			fmt.Println("  (the .json file that lists your steps)")
		}
		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 manifest file to check. Try again, or close this window.")
			fmt.Println()
			continue
		}

		info, err := os.Stat(answer)
		if 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
		}
		if info.IsDir() {
			// Dragging the project folder rather than the file inside it is an
			// easy mistake to make and an easy one to just fix for them.
			if found := manifestInFolder(answer); found != "" {
				fmt.Println()
				fmt.Printf("  That is a folder, so I will check %s inside it.\n", found)
				path = found
				break
			}
			fmt.Println()
			fmt.Printf("  %q is a folder, and I cannot see a manifest in it.\n", answer)
			fmt.Println("  Give me the .json file that lists your steps.")
			fmt.Println()
			continue
		}
		path = answer
		break
	}

	fmt.Println()
	reportManifest(path)

	fmt.Println()
	fmt.Println("  Done. Nothing was written.")
	fmt.Println("  The command-line version builds the finished document once the")
	fmt.Println("  manifest is clean: stepshot --help")
	pause(in)
}

// reportManifest runs the same load-and-validate the check command runs, and
// prints the result in the same shape — but as a plain report the reader can
// act on, rather than something that ends the process on a bad manifest.
func reportManifest(path string) {
	m, err := loadManifest(path)
	if err != nil {
		fmt.Printf("  I could not read %s.\n\n", path)
		fmt.Printf("  %v\n\n", err)
		fmt.Println("  A manifest is a JSON file listing the ordered steps. The smallest")
		fmt.Println("  one that works looks like this:")
		fmt.Println()
		fmt.Println(`    {"title":"How to X","steps":[{"number":1,"image":"01.png","title":"Open it"}]}`)
		return
	}

	problems, _ := validate(m)
	nErr, nWarn := countProblems(problems)

	fmt.Printf("  manifest : %s\n", path)
	fmt.Printf("  images   : %s\n", m.imagesDir)
	fmt.Printf("  title    : %s\n", displayOrNone(m.Title))
	fmt.Printf("  steps    : %d\n\n", len(m.Steps))

	if len(problems) == 0 {
		fmt.Println("  No problems found. This manifest is ready to build.")
		return
	}
	for _, p := range problems {
		fmt.Printf("  %-7s [%s] %s\n           %s\n", p.Severity, p.Code, p.Where, p.Message)
	}
	fmt.Println()
	fmt.Printf("  %d error(s), %d warning(s)\n", nErr, nWarn)
	if nErr > 0 {
		fmt.Println("  Fix the errors and run me again; warnings are safe to build with.")
	} else {
		fmt.Println("  Only warnings, so this will still build.")
	}
}

// suggestedManifest offers a manifest the reader almost certainly means: the
// one sitting beside the program. Explorer starts a double-clicked program in
// its own folder, so a manifest dropped next to it is the obvious candidate.
func suggestedManifest() string {
	cwd, err := os.Getwd()
	if err != nil {
		return ""
	}
	return manifestInFolder(cwd)
}

// manifestInFolder picks the one manifest in a folder, if there is an obvious
// one: a conventionally named file, or a lone .json with nothing to confuse it
// with. It returns "" rather than guessing between several.
func manifestInFolder(dir string) string {
	for _, name := range []string{"manifest.json", "stepshot.json", "steps.json", "guide.json"} {
		candidate := filepath.Join(dir, name)
		if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
			return candidate
		}
	}
	entries, err := os.ReadDir(dir)
	if err != nil {
		return ""
	}
	var jsons []string
	for _, e := range entries {
		if e.IsDir() || !strings.EqualFold(filepath.Ext(e.Name()), ".json") {
			continue
		}
		jsons = append(jsons, filepath.Join(dir, e.Name()))
	}
	if len(jsons) != 1 {
		return ""
	}
	return jsons[0]
}

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