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 thing
// the program needs and stay on screen until the reader is done.
//
// Recording is the half of SessionForge that runs a command and writes a file,
// and it needs to be told which command — neither of which belongs in a window
// somebody opened by accident. The guided session does the reading half: it
// opens a recording that already exists and describes what is in it, changing
// nothing.
//
// 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("  SessionForge")
	fmt.Println("  Keep a command's output along with the exact timing of every line.")
	fmt.Println()
	fmt.Println("  A recording made by SessionForge can be played back later at real")
	fmt.Println("  speed, pauses and all — so you can show somebody exactly what happened")
	fmt.Println("  on that machine, including the minute where nothing moved.")
	fmt.Println()
	fmt.Println("  This window opens a recording and tells you what is inside it. Nothing")
	fmt.Println("  is recorded, replayed or changed while you are in here.")
	fmt.Println()

	path, ok := askRecording(in, suggestedRecording())
	if !ok {
		return
	}

	fmt.Println()
	cmdInfo([]string{path})

	fmt.Println()
	fmt.Println("  Nothing was changed. The recording was only read.")
	fmt.Println("  To watch it play back at its original speed, to dump the plain text out")
	fmt.Println("  of it, or to record something new, run SessionForge from a command")
	fmt.Println("  prompt: sessionforge --help")
	pause(in)
}

// askRecording asks for the recording and keeps asking until it gets one that
// exists and can really be read as a recording.
func askRecording(in *bufio.Scanner, suggested string) (string, bool) {
	for {
		fmt.Println("  Which recording shall I open?")
		fmt.Println("  It is the file SessionForge wrote when it recorded the session. You")
		fmt.Println("  can drag it, or the folder holding it, from Explorer onto this")
		fmt.Println("  window to paste the location.")
		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 "", false
		}
		answer := strings.Trim(strings.TrimSpace(in.Text()), `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  There is no recording here to open yet. Making one means telling")
			fmt.Println("  SessionForge which command to run, so it is a job for the command")
			fmt.Println("  prompt: sessionforge --help shows how. Try again, or close this")
			fmt.Println("  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("  Check the location and try again, or close this window.")
			fmt.Println()
			continue
		case info.IsDir():
			// A dragged folder is a near miss worth rescuing rather than
			// scolding: look inside it for the recording they meant.
			if found := firstRecordingIn(answer); found != "" {
				fmt.Println()
				fmt.Printf("  That is a folder, so I will open the recording inside it:\n  %s\n", found)
				return found, true
			}
			fmt.Println()
			fmt.Printf("  %q is a folder and I found no recording in it. Give me the\n", answer)
			fmt.Println("  recording file itself.")
			fmt.Println()
			continue
		}

		if _, err := loadCast(answer); err != nil {
			fmt.Println()
			fmt.Printf("  %q is not a recording I can read.\n", answer)
			fmt.Println("  It has to be a file SessionForge wrote itself — an ordinary log")
			fmt.Println("  will not do, because it has no timing in it. Try again, or close")
			fmt.Println("  this window.")
			fmt.Println()
			continue
		}
		return answer, true
	}
}

// suggestedRecording offers a recording the reader is likely to have just
// made, so the common case is one keypress. It returns "" when there is
// nothing to offer, and the prompt says how a recording gets made.
func suggestedRecording() string {
	var dirs []string
	if wd, err := os.Getwd(); err == nil {
		dirs = append(dirs, wd)
	}
	if exe, err := os.Executable(); err == nil {
		dirs = append(dirs, filepath.Dir(exe))
	}
	if home, err := os.UserHomeDir(); err == nil {
		dirs = append(dirs, filepath.Join(home, "Downloads"),
			filepath.Join(home, "Documents"), home)
	}
	for _, dir := range dirs {
		if found := firstRecordingIn(dir); found != "" {
			return found
		}
	}
	return ""
}

// firstRecordingIn returns the first file in dir that SessionForge can
// actually read as a recording, or "" if there is none. Candidates are checked
// by opening them rather than by trusting the name, so a file that merely ends
// in .jsonl is never offered as a default that then fails.
func firstRecordingIn(dir string) string {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return ""
	}
	var names []string
	for _, e := range entries {
		if e.IsDir() {
			continue
		}
		if ext := strings.ToLower(filepath.Ext(e.Name())); ext == ".jsonl" || ext == ".cast" {
			names = append(names, e.Name())
		}
	}
	sort.Strings(names)
	for _, name := range names {
		candidate := filepath.Join(dir, name)
		if _, err := loadCast(candidate); err == nil {
			return candidate
		}
	}
	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()
}
