// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

package main

// Note, main.go needs to be in the root of the project for the go:embed directive to work.

import (
	"context"
	"embed"
	"fmt"
	"log"
	"net/http"
	"runtime"
	"strings"
	"time"

	"github.com/wavetermdev/thenextwave/pkg/blockstore"
	"github.com/wavetermdev/thenextwave/pkg/eventbus"
	"github.com/wavetermdev/thenextwave/pkg/service/blockservice"
	"github.com/wavetermdev/thenextwave/pkg/service/clientservice"
	"github.com/wavetermdev/thenextwave/pkg/service/fileservice"
	"github.com/wavetermdev/thenextwave/pkg/service/objectservice"
	"github.com/wavetermdev/thenextwave/pkg/wavebase"
	"github.com/wavetermdev/thenextwave/pkg/wstore"

	"github.com/wailsapp/wails/v3/pkg/application"
	"github.com/wailsapp/wails/v3/pkg/events"
)

//go:embed dist
var assets embed.FS

//go:embed build/icons.icns
var appIcon []byte

func createAppMenu(app *application.App) *application.Menu {
	menu := application.NewMenu()
	menu.AddRole(application.AppMenu)
	fileMenu := menu.AddSubmenu("File")
	// newWindow := fileMenu.Add("New Window")
	// newWindow.OnClick(func(appContext *application.Context) {
	// 	createWindow(app)
	// })
	closeWindow := fileMenu.Add("Close Window")
	closeWindow.OnClick(func(appContext *application.Context) {
		app.CurrentWindow().Close()
	})
	menu.AddRole(application.EditMenu)
	menu.AddRole(application.ViewMenu)
	menu.AddRole(application.WindowMenu)
	menu.AddRole(application.HelpMenu)
	return menu
}

func createWindow(windowData *wstore.Window, app *application.App) {
	client, err := wstore.DBGetSingleton[*wstore.Client](context.Background())
	if err != nil {
		panic(fmt.Errorf("error getting client data: %w", err))
	}
	window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
		Title: "Wave Terminal",
		Mac: application.MacWindow{
			InvisibleTitleBarHeight: 50,
			Backdrop:                application.MacBackdropTranslucent,
			TitleBar:                application.MacTitleBarHiddenInset,
		},
		BackgroundColour: application.NewRGB(0, 0, 0),
		URL:              "/public/index.html?windowid=" + windowData.OID + "&clientid=" + client.OID,
		X:                windowData.Pos.X,
		Y:                windowData.Pos.Y,
		Width:            windowData.WinSize.Width,
		Height:           windowData.WinSize.Height,
	})
	eventbus.RegisterWailsWindow(window, windowData.OID)
	window.On(events.Common.WindowClosing, func(event *application.WindowEvent) {
		eventbus.UnregisterWailsWindow(window.ID())
	})
	window.Show()
}

type waveAssetHandler struct {
	AssetHandler http.Handler
}

func serveWaveUrls(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/wave/stream-file" {
		fileName := r.URL.Query().Get("path")
		fileName = wavebase.ExpandHomeDir(fileName)
		http.ServeFile(w, r, fileName)
		return
	}
	http.NotFound(w, r)
}

func (wah waveAssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" && strings.HasPrefix(r.URL.Path, "/wave/") {
		serveWaveUrls(w, r)
		return
	}
	wah.AssetHandler.ServeHTTP(w, r)
}

func main() {
	err := wavebase.EnsureWaveHomeDir()
	if err != nil {
		log.Printf("error ensuring wave home dir: %v\n", err)
		return
	}
	waveLock, err := wavebase.AcquireWaveLock()
	if err != nil {
		log.Printf("error acquiring wave lock (another instance of Wave is likely running): %v\n", err)
		return
	}

	log.Printf("wave home dir: %s\n", wavebase.GetWaveHomeDir())
	err = blockstore.InitBlockstore()
	if err != nil {
		log.Printf("error initializing blockstore: %v\n", err)
		return
	}
	err = wstore.InitWStore()
	if err != nil {
		log.Printf("error initializing wstore: %v\n", err)
		return
	}
	err = wstore.EnsureInitialData()
	if err != nil {
		log.Printf("error ensuring initial data: %v\n", err)
		return
	}

	app := application.New(application.Options{
		Name:        "NextWave",
		Description: "The Next Wave Terminal",
		Services: []application.Service{
			application.NewService(&fileservice.FileService{}),
			application.NewService(&blockservice.BlockService{}),
			application.NewService(&clientservice.ClientService{}),
			application.NewService(&objectservice.ObjectService{}),
		},
		Icon: appIcon,
		Assets: application.AssetOptions{
			Handler: waveAssetHandler{AssetHandler: application.AssetFileServerFS(assets)},
		},
		Mac: application.MacOptions{
			ApplicationShouldTerminateAfterLastWindowClosed: true,
		},
	})
	menu := createAppMenu(app)
	app.SetMenu(menu)
	eventbus.RegisterWailsApp(app)

	setupCtx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancelFn()
	client, err := wstore.DBGetSingleton[*wstore.Client](setupCtx)
	if err != nil {
		log.Printf("error getting client data: %v\n", err)
		return
	}
	mainWindow, err := wstore.DBGet[*wstore.Window](setupCtx, client.MainWindowId)
	if err != nil {
		log.Printf("error getting main window: %v\n", err)
		return
	}
	if mainWindow == nil {
		log.Printf("no main window data\n")
		return
	}
	createWindow(mainWindow, app)

	eventbus.Start()
	defer eventbus.Shutdown()

	// blocking
	err = app.Run()

	// If an error occurred while running the application, log it and exit.
	if err != nil {
		log.Printf("run error: %v\n", err)
	}
	runtime.KeepAlive(waveLock)
}