package main

import (
	"embed"
	"os"
	"strings"
	"time"

	"github.com/wailsapp/wails/v2"
	"github.com/wailsapp/wails/v2/pkg/options"
	"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)

//go:embed all:frontend/dist
var assets embed.FS

// profileArg extracts a profile name from the command line. Accepts
// "--profile NAME", "--profile=NAME", "-profile NAME", "-p NAME" so a desktop
// shortcut can launch OpsLog straight into a given profile (e.g. F4BPO / TM2Q).
func profileArg(args []string) string {
	for i := 0; i < len(args); i++ {
		a := args[i]
		switch {
		case a == "--profile" || a == "-profile" || a == "-p":
			if i+1 < len(args) {
				return strings.TrimSpace(args[i+1])
			}
		case strings.HasPrefix(a, "--profile="):
			return strings.TrimSpace(strings.TrimPrefix(a, "--profile="))
		case strings.HasPrefix(a, "-profile="):
			return strings.TrimSpace(strings.TrimPrefix(a, "-profile="))
		case strings.HasPrefix(a, "-p="):
			return strings.TrimSpace(strings.TrimPrefix(a, "-p="))
		}
	}
	return ""
}

// hasFlag reports whether flag is present in args.
func hasFlag(args []string, flag string) bool {
	for _, a := range args {
		if a == flag {
			return true
		}
	}
	return false
}

// acquireInstance grabs the single-instance mutex. On a normal launch it's a plain
// try (fail → another OpsLog is running, so exit). On a --post-update relaunch the
// previous instance may still be shutting down and holding the mutex, so retry for
// a few seconds until it frees.
func acquireInstance(postUpdate bool) bool {
	if acquireSingleInstance() {
		return true
	}
	if !postUpdate {
		return false
	}
	deadline := time.Now().Add(20 * time.Second)
	for time.Now().Before(deadline) {
		time.Sleep(300 * time.Millisecond)
		if acquireSingleInstance() {
			return true
		}
	}
	return false
}

// processStart is stamped on the very first instruction of main, before the
// single-instance guard and before anything else runs.
//
// It exists to split a slow launch in two, because the log alone could not: the
// first line applog writes already sits inside startup(), so everything spent
// loading the binary and creating the WebView2 environment happened before the
// log begins and was invisible. An operator reporting "nothing happens for three
// seconds" was impossible to answer from a file whose first timestamp is the
// moment the app was already running.
var processStart = time.Now()

func main() {
	// Single-instance guard: if OpsLog is already running, focus that window and
	// exit instead of spawning a duplicate. A second process would open its own
	// CAT (FlexRadio) connection and Ultrabeam follow loop, and the two would
	// fight over the rig/antenna frequency — the cause of "the antenna re-tunes on
	// its own" when a windowless zombie instance was left running.
	// A --post-update relaunch (from the auto-updater) may start while the previous
	// instance is still exiting and holding the single-instance mutex — wait for it
	// to free instead of bailing out. Then clear the old exe it left behind.
	postUpdate := hasFlag(os.Args[1:], "--post-update")
	if !acquireInstance(postUpdate) {
		return
	}
	if postUpdate {
		cleanupOldUpdateBinary()
	}

	// Create an instance of the app structure
	app := NewApp()
	app.startupProfile = profileArg(os.Args[1:])

	// Restore the window's SIZE and maximised state at CREATION, from the geometry
	// saved on last close. Doing it here (not after startup) is what makes the
	// window open already at the right size instead of maximising then snapping
	// smaller. Position can't be set through options, so it is applied while the
	// window is still hidden (domReady) — invisible, so no jump. First run, or a
	// window closed maximised, keeps the historical maximised default.
	width, height := 1400, 900
	startState := options.Maximised
	if dataDir, err := userDataDir(); err == nil {
		if ws, ok := readWindowState(dataDir); ok && !ws.Maximised &&
			ws.Width >= 1100 && ws.Height >= 700 && ws.Width <= 8000 && ws.Height <= 6000 {
			width, height = ws.Width, ws.Height
			startState = options.Normal
		}
	}

	// Create application with options
	err := wails.Run(&options.App{
		Title:            "OpsLog",
		Width:            width,
		Height:           height,
		MinWidth:         1100,
		MinHeight:        700,
		WindowStartState: startState,
		// No OS title bar: it was a dead 32-pixel band above a window that already
		// has its own title strip. The app header takes over — it carries the drag
		// region, the double-click-to-maximise, and the minimise/maximise/close
		// buttons. Windows still draws the resize borders and honours Aero Snap,
		// which is why the frame is dropped rather than the whole chrome.
		Frameless: true,
		// Start hidden and reveal only once the saved position has been applied and
		// the DOM has painted (OnDomReady → domReady) — so the window appears
		// already at its final size and position, with no post-launch jump.
		StartHidden: true,
		AssetServer: &assetserver.Options{
			Assets: assets,
		},
		// The colour the window is painted before the WebView draws. Matches the
		// graphite theme's background (#16181d), which is the default on a fresh
		// install — otherwise every launch flashes white first.
		BackgroundColour: &options.RGBA{R: 0x16, G: 0x18, B: 0x1d, A: 1},
		OnStartup:        app.startup,
		OnDomReady:       app.domReady,
		OnBeforeClose:    app.beforeClose,
		OnShutdown:       app.shutdown,
		Bind: []interface{}{
			app,
		},
	})

	if err != nil {
		println("Error:", err.Error())
	}
}
