//go:build darwin

package platform

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

// launchAgentLabel is the reverse-DNS label + plist filename for the per-user
// LaunchAgent that starts cdrop at login.
const launchAgentLabel = "net.commilitia.cdrop"

func launchAgentPath() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(home, "Library", "LaunchAgents", launchAgentLabel+".plist"), nil
}

// launchTarget returns the program the LaunchAgent should run. For a packaged
// .app it launches the bundle via `open` (proper LaunchServices activation);
// in a bare dev binary it runs the executable directly. Either way it appends
// --hidden so a login launch comes up to the menu bar with no window (see
// launchedHidden in main); `open --args` forwards the rest as the app's argv.
func launchTarget() ([]string, error) {
	exe, err := os.Executable()
	if err != nil {
		return nil, err
	}
	if resolved, err := filepath.EvalSymlinks(exe); err == nil {
		exe = resolved
	}
	if bundle := appBundlePath(exe); bundle != "" {
		return []string{"/usr/bin/open", bundle, "--args", "--hidden"}, nil
	}
	return []string{exe, "--hidden"}, nil
}

// appBundlePath returns the enclosing .app path if exe lives inside one
// (…/Foo.app/Contents/MacOS/binary), else "".
func appBundlePath(exe string) string {
	const marker = ".app/Contents/MacOS/"
	if i := strings.LastIndex(exe, marker); i != -1 {
		return exe[:i+len(".app")]
	}
	return ""
}

// SetLaunchAtLogin installs or removes the LaunchAgent plist. Removing a missing
// agent is a no-op success.
func SetLaunchAtLogin(enabled bool) error {
	p, err := launchAgentPath()
	if err != nil {
		return err
	}
	if !enabled {
		if err := os.Remove(p); err != nil && !errors.Is(err, os.ErrNotExist) {
			return err
		}
		return nil
	}
	args, err := launchTarget()
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
		return err
	}
	return os.WriteFile(p, []byte(buildLaunchAgentPlist(launchAgentLabel, args)), 0o644)
}

// IsLaunchAtLoginEnabled reports whether the LaunchAgent plist is present.
func IsLaunchAtLoginEnabled() bool {
	p, err := launchAgentPath()
	if err != nil {
		return false
	}
	_, err = os.Stat(p)
	return err == nil
}

func buildLaunchAgentPlist(label string, programArgs []string) string {
	var args strings.Builder
	for _, a := range programArgs {
		fmt.Fprintf(&args, "\n        <string>%s</string>", xmlEscape(a))
	}
	return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>%s</string>
    <key>ProgramArguments</key>
    <array>%s
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>
`, xmlEscape(label), args.String())
}

func xmlEscape(s string) string {
	r := strings.NewReplacer(
		"&", "&amp;",
		"<", "&lt;",
		">", "&gt;",
		`"`, "&quot;",
		"'", "&apos;",
	)
	return r.Replace(s)
}
