// Package hst exports stable shared types for interacting with hakurei.
package hst

import (
	"errors"
	"math"
	"net"
	"os"

	"hakurei.app/check"
	"hakurei.app/fhs"
)

// An AppError is returned while starting an app according to [hst.Config].
type AppError struct {
	// A user-facing description of where the error occurred.
	Step string `json:"step"`
	// The underlying error value.
	Err error `json:"err"`
	// An arbitrary error message, overriding the return value of Message if not empty.
	Msg string `json:"message,omitempty"`
}

func (e *AppError) Error() string { return e.Err.Error() }
func (e *AppError) Unwrap() error { return e.Err }
func (e *AppError) Message() string {
	if e.Msg != "" {
		return e.Msg
	}

	switch {
	case errors.As(e.Err, new(*os.PathError)),
		errors.As(e.Err, new(*os.LinkError)),
		errors.As(e.Err, new(*os.SyscallError)),
		errors.As(e.Err, new(*net.OpError)):
		return "cannot " + e.Error()

	default:
		return "cannot " + e.Step + ": " + e.Error()
	}
}

// Paths contains environment-dependent paths used by hakurei.
type Paths struct {
	// Temporary directory returned by [os.TempDir], usually equivalent to [fhs.AbsTmp].
	TempDir *check.Absolute `json:"temp_dir"`
	// Shared directory specific to the hsu userid, usually
	// (`/tmp/hakurei.%d`, [Info.User]).
	SharePath *check.Absolute `json:"share_path"`
	// Checked XDG_RUNTIME_DIR value, usually (`/run/user/%d`, uid).
	RuntimePath *check.Absolute `json:"runtime_path"`
	// Shared directory specific to the hsu userid located in RuntimePath,
	// usually (`/run/user/%d/hakurei`, uid).
	RunDirPath *check.Absolute `json:"run_dir_path"`
}

// Info holds basic system information collected from the implementation.
type Info struct {
	// WaylandVersion is the libwayland value of WAYLAND_VERSION.
	WaylandVersion string `json:"WAYLAND_VERSION"`

	// Version is a hardcoded version string.
	Version string `json:"version"`
	// User is the userid according to hsu.
	User int `json:"user"`

	Paths
}

// Template returns a fully populated instance of Config.
func Template() *Config {
	return &Config{
		ID: "org.chromium.Chromium",

		Enablements: new(EWayland | EDBus | EPipeWire),

		SessionBus: &BusConfig{
			See: nil,
			Talk: []string{
				"org.freedesktop.Notifications",
				"org.freedesktop.FileManager1",
				"org.freedesktop.ScreenSaver",
				"org.freedesktop.secrets",

				"org.kde.kwalletd5",
				"org.kde.kwalletd6",

				"org.gnome.SessionManager",
			},
			Own: []string{
				"org.chromium.Chromium.*",

				"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
				"org.mpris.MediaPlayer2.chromium.*",
			},
			Call:      map[string]string{"org.freedesktop.portal.*": "*"},
			Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
			Log:       false,
			Filter:    true,
		},
		SystemBus: &BusConfig{
			See:       nil,
			Talk:      []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
			Own:       nil,
			Call:      nil,
			Broadcast: nil,
			Log:       false,
			Filter:    true,
		},

		ExtraPerms: []ExtraPermConfig{
			{Path: fhs.AbsVarLib.Append("hakurei/u0"), Ensure: true, Execute: true},
			{Path: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"), Read: true, Write: true, Execute: true},
		},

		Identity: 9,
		Groups:   []string{"video", "dialout", "plugdev"},

		Container: &ContainerConfig{
			Hostname:  "localhost",
			WaitDelay: -1,
			// example API credentials pulled from Google Chrome
			// DO NOT USE THESE IN A REAL BROWSER
			Env: map[string]string{
				"GOOGLE_API_KEY":               "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
				"GOOGLE_DEFAULT_CLIENT_ID":     "77185425430.apps.googleusercontent.com",
				"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
			},
			Filesystem: []FilesystemConfigJSON{
				{&FSBind{
					Target:  fhs.AbsRoot,
					Source:  fhs.AbsVarLib.Append("hakurei/base/org.debian"),
					Write:   true,
					Special: true,
				}},
				{&FSBind{Target: fhs.AbsEtc, Source: fhs.AbsEtc, Special: true}},
				{&FSEphemeral{Target: fhs.AbsTmp, Write: true, Perm: 0755}},
				{&FSOverlay{
					Target: check.MustAbs("/nix/store"),
					Lower:  []*check.Absolute{fhs.AbsVarLib.Append("hakurei/base/org.nixos/ro-store")},
					Upper:  fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/upper"),
					Work:   fhs.AbsVarLib.Append("hakurei/nix/u0/org.chromium.Chromium/rw-store/work"),
				}},
				{&FSLink{
					Target:      fhs.AbsRun.Append("current-system"),
					Linkname:    "/run/current-system",
					Dereference: true,
				}},
				{&FSLink{
					Target:      fhs.AbsRun.Append("opengl-driver"),
					Linkname:    "/run/opengl-driver",
					Dereference: true,
				}},
				{&FSBind{
					Source: fhs.AbsVarLib.Append("hakurei/u0/org.chromium.Chromium"),
					Target: check.MustAbs("/data/data/org.chromium.Chromium"),
					Write:  true,
					Ensure: true,
				}},
				{&FSBind{
					Source:   fhs.AbsDev.Append("dri"),
					Device:   true,
					Optional: true,
				}},
			},

			Username: "chronos",
			Shell:    fhs.AbsRun.Append("current-system/sw/bin/zsh"),
			Home:     check.MustAbs("/data/data/org.chromium.Chromium"),

			Path: fhs.AbsRun.Append("current-system/sw/bin/chromium"),
			Args: []string{
				"chromium",
				"--ignore-gpu-blocklist",
				"--disable-smooth-scrolling",
				"--enable-features=UseOzonePlatform",
				"--ozone-platform=wayland",
			},

			// Set all bits here so new flags trip the template test.
			Flags: math.MaxUint,
		},
	}
}
