package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

// videoExtensions are the containers videomend can read. The guided session
// uses them to offer a sensible default and to spot an answer that is never
// going to work before it reaches the parser.
var videoExtensions = []string{".mp4", ".mov", ".m4v", ".3gp", ".avi"}

// 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.
//
// Everything videomend does is read-only — it opens a file, reads its
// structure and reports — so the guided session runs the most useful of its
// commands, "diagnose", with nothing held back.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  VideoMend")
	fmt.Println("  Explains why a video file will not play.")
	fmt.Println()
	fmt.Println("  It reads the file's internal structure — the index, the timing")
	fmt.Println("  header, where the picture and sound data starts and stops — and")
	fmt.Println("  tells you what is missing or damaged, which usually explains what")
	fmt.Println("  went wrong: a recording that was cut off, a copy that stopped part")
	fmt.Println("  way, a card pulled out too early.")
	fmt.Println()
	fmt.Println("  It only reads. Your file is not repaired, re-saved or changed.")
	fmt.Println()

	suggested := suggestedVideo()
	for {
		fmt.Println("  Which video file shall I look at?")
		fmt.Println("  (.mp4, .mov, .m4v, .3gp or .avi)")
		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 video file 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 the video — or the folder holding it — from")
			fmt.Println("  Explorer onto this window to paste its location, then press")
			fmt.Println("  Enter.")
			fmt.Println()
			continue
		case info.IsDir():
			// A dragged-in folder is a common answer; look inside it rather
			// than sending the reader away to find the file themselves.
			found := newestVideoIn(answer)
			if found == "" {
				fmt.Println()
				fmt.Printf("  %q is a folder, and I cannot see a video in it.\n", answer)
				fmt.Println("  Give me the video file itself.")
				fmt.Println()
				continue
			}
			fmt.Printf("\n  Using the video I found in that folder: %s\n", found)
			answer = found
		case info.Size() == 0:
			fmt.Println()
			fmt.Printf("  %q is empty — zero bytes. There is nothing in it to read,\n", answer)
			fmt.Println("  which is itself the answer: that recording never got written.")
			fmt.Println()
			continue
		}

		fmt.Println()
		fmt.Println("  Reading the file's structure. On a large video this takes a moment.")
		fmt.Println()
		if _, err := runDiagnose([]string{answer}); err != nil {
			fmt.Println()
			fmt.Printf("  I could not read %q as a video: %v\n", answer, err)
			fmt.Println("  I understand .mp4, .mov, .m4v, .3gp and .avi files.")
			fmt.Println()
			continue
		}
		break
	}

	fmt.Println()
	fmt.Println("  Done. Nothing was changed — your file is exactly as it was.")
	fmt.Println("  There is a command-line version too, which can print the full")
	fmt.Println("  structure and machine-readable output: videomend --help")
	pause(in)
}

// suggestedVideo offers a video the reader plausibly has in mind, so the common
// case is one keypress. It returns "" when there is nothing to offer, and the
// prompt copes with that.
func suggestedVideo() string {
	var dirs []string
	if wd, err := os.Getwd(); err == nil {
		dirs = append(dirs, wd)
	}
	if home, err := os.UserHomeDir(); err == nil {
		dirs = append(dirs,
			filepath.Join(home, "Videos"),
			filepath.Join(home, "Movies"),
			filepath.Join(home, "Downloads"),
			filepath.Join(home, "Desktop"),
			home)
	}
	for _, dir := range dirs {
		if found := newestVideoIn(dir); found != "" {
			return found
		}
	}
	return ""
}

// newestVideoIn returns the most recently modified video directly inside dir,
// or "" if there is none. Only the top level is searched: a guided default
// should be instant, not a disk crawl.
func newestVideoIn(dir string) string {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return ""
	}
	type candidate struct {
		path  string
		mtime int64
	}
	var found []candidate
	for _, e := range entries {
		if e.IsDir() || !isVideoName(e.Name()) {
			continue
		}
		info, err := e.Info()
		if err != nil || info.Size() == 0 {
			continue
		}
		found = append(found, candidate{filepath.Join(dir, e.Name()), info.ModTime().UnixNano()})
	}
	if len(found) == 0 {
		return ""
	}
	sort.Slice(found, func(i, j int) bool { return found[i].mtime > found[j].mtime })
	return found[0].path
}

func isVideoName(name string) bool {
	ext := strings.ToLower(filepath.Ext(name))
	for _, want := range videoExtensions {
		if ext == want {
			return true
		}
	}
	return false
}

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