// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

// Package osshare provides utilities for enabling/disabling Taildrop file
// sharing on Windows.
package osshare

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"

	"golang.org/x/sys/windows/registry"
	"tailscale.com/types/lazy"
	"tailscale.com/types/logger"
	"tailscale.com/util/winutil"
)

const (
	sendFileShellKey = `*\shell\tailscale`
)

var ipnExePath lazy.SyncValue[string] // absolute path of the GUI executable

func getIpnExePath(logf logger.Logf) string {
	exe, err := winutil.GUIPathFromReg()
	if err == nil {
		return exe
	}

	return findGUIInSameDirAsThisExe(logf)
}

func findGUIInSameDirAsThisExe(logf logger.Logf) string {
	// Find the absolute path of the GUI, assuming that it's in the same
	// directory as this executable (tailscaled.exe).
	p, err := os.Executable()
	if err != nil {
		logf("os.Executable error: %v", err)
		return ""
	}
	if p, err = filepath.EvalSymlinks(p); err != nil {
		logf("filepath.EvalSymlinks error: %v", err)
		return ""
	}
	if p, err = filepath.Abs(p); err != nil {
		logf("filepath.Abs error: %v", err)
		return ""
	}
	d := filepath.Dir(p)
	candidates := []string{"tailscale-ipn.exe"}
	if runtime.GOARCH == "arm64" {
		// This name may be used on Windows 10 ARM64.
		candidates = append(candidates, "tailscale-gui-386.exe")
	}
	for _, c := range candidates {
		testPath := filepath.Join(d, c)
		if _, err := os.Stat(testPath); err == nil {
			return testPath
		}
	}
	return ""
}

// SetFileSharingEnabled adds/removes "Send with Tailscale" from the Windows shell menu.
func SetFileSharingEnabled(enabled bool, logf logger.Logf) {
	logf = logger.WithPrefix(logf, fmt.Sprintf("SetFileSharingEnabled(%v) error: ", enabled))
	if enabled {
		enableFileSharing(logf)
	} else {
		disableFileSharing(logf)
	}
}

func enableFileSharing(logf logger.Logf) {
	path := ipnExePath.Get(func() string {
		return getIpnExePath(logf)
	})
	if path == "" {
		return
	}

	k, _, err := registry.CreateKey(registry.CLASSES_ROOT, sendFileShellKey, registry.WRITE)
	if err != nil {
		logf("failed to create HKEY_CLASSES_ROOT\\%s reg key: %v", sendFileShellKey, err)
		return
	}
	defer k.Close()
	if err := k.SetStringValue("", "Send with Tailscale..."); err != nil {
		logf("k.SetStringValue error: %v", err)
		return
	}
	if err := k.SetStringValue("Icon", path+",1"); err != nil {
		logf("k.SetStringValue error: %v", err)
		return
	}
	c, _, err := registry.CreateKey(k, "command", registry.WRITE)
	if err != nil {
		logf("failed to create HKEY_CLASSES_ROOT\\%s\\command reg key: %v", sendFileShellKey, err)
		return
	}
	defer c.Close()
	if err := c.SetStringValue("", "\""+path+"\" /push \"%1\""); err != nil {
		logf("c.SetStringValue error: %v", err)
	}
}

func disableFileSharing(logf logger.Logf) {
	if err := registry.DeleteKey(registry.CLASSES_ROOT, sendFileShellKey+"\\command"); err != nil &&
		err != registry.ErrNotExist {
		logf("registry.DeleteKey error: %v\n", err)
		return
	}
	if err := registry.DeleteKey(registry.CLASSES_ROOT, sendFileShellKey); err != nil && err != registry.ErrNotExist {
		logf("registry.DeleteKey error: %v\n", err)
	}
}
