package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/bwmarrin/discordgo"
	"golang.design/x/hotkey"
	"golang.design/x/mainthread"
)

type Config struct {
	Token     string `json:"token"`
	GuildID   string `json:"guild_id"`
	ChannelID string `json:"channel_id"`
	Keybind   string `json:"keybind"`
}

func loadConfig() (*Config, error) {
	exe, _ := os.Executable()
	for _, dir := range []string{filepath.Dir(exe), "."} {
		data, err := os.ReadFile(filepath.Join(dir, "config.json"))
		if err != nil {
			continue
		}
		var cfg Config
		if err := json.Unmarshal(data, &cfg); err != nil {
			return nil, fmt.Errorf("parsing config.json: %w", err)
		}
		return &cfg, nil
	}
	return nil, fmt.Errorf("config.json not found next to executable or in working directory")
}

var modMap = map[string]hotkey.Modifier{
	"ctrl":  hotkey.ModCtrl,
	"shift": hotkey.ModShift,
	"alt":   hotkey.ModAlt,
	"win":   hotkey.ModWin,
	"super": hotkey.ModWin,
}

var keyMap = map[string]hotkey.Key{
	"a": hotkey.KeyA, "b": hotkey.KeyB, "c": hotkey.KeyC, "d": hotkey.KeyD,
	"e": hotkey.KeyE, "f": hotkey.KeyF, "g": hotkey.KeyG, "h": hotkey.KeyH,
	"i": hotkey.KeyI, "j": hotkey.KeyJ, "k": hotkey.KeyK, "l": hotkey.KeyL,
	"m": hotkey.KeyM, "n": hotkey.KeyN, "o": hotkey.KeyO, "p": hotkey.KeyP,
	"q": hotkey.KeyQ, "r": hotkey.KeyR, "s": hotkey.KeyS, "t": hotkey.KeyT,
	"u": hotkey.KeyU, "v": hotkey.KeyV, "w": hotkey.KeyW, "x": hotkey.KeyX,
	"y": hotkey.KeyY, "z": hotkey.KeyZ,
	"0": hotkey.Key0, "1": hotkey.Key1, "2": hotkey.Key2, "3": hotkey.Key3,
	"4": hotkey.Key4, "5": hotkey.Key5, "6": hotkey.Key6, "7": hotkey.Key7,
	"8": hotkey.Key8, "9": hotkey.Key9,
	"f1": hotkey.KeyF1, "f2": hotkey.KeyF2, "f3": hotkey.KeyF3,
	"f4": hotkey.KeyF4, "f5": hotkey.KeyF5, "f6": hotkey.KeyF6,
	"f7": hotkey.KeyF7, "f8": hotkey.KeyF8, "f9": hotkey.KeyF9,
	"f10": hotkey.KeyF10, "f11": hotkey.KeyF11, "f12": hotkey.KeyF12,
	"f13": hotkey.KeyF13, "f14": hotkey.KeyF14, "f15": hotkey.KeyF15,
	"f16": hotkey.KeyF16, "f17": hotkey.KeyF17, "f18": hotkey.KeyF18,
	"f19": hotkey.KeyF19, "f20": hotkey.KeyF20,
	"space":  hotkey.KeySpace,
	"return": hotkey.KeyReturn,
	"enter":  hotkey.KeyReturn,
	"escape": hotkey.KeyEscape,
	"esc":    hotkey.KeyEscape,
	"tab":    hotkey.KeyTab,
	"delete": hotkey.KeyDelete,
	"left":   hotkey.KeyLeft,
	"right":  hotkey.KeyRight,
	"up":     hotkey.KeyUp,
	"down":   hotkey.KeyDown,
}

func parseKeybind(s string) ([]hotkey.Modifier, hotkey.Key, error) {
	parts := strings.Split(strings.ToLower(strings.TrimSpace(s)), "+")
	if len(parts) == 0 {
		return nil, 0, fmt.Errorf("empty keybind")
	}
	var mods []hotkey.Modifier
	for _, p := range parts[:len(parts)-1] {
		p = strings.TrimSpace(p)
		m, ok := modMap[p]
		if !ok {
			return nil, 0, fmt.Errorf("unknown modifier %q — valid: ctrl, shift, alt, win", p)
		}
		mods = append(mods, m)
	}
	keyStr := strings.TrimSpace(parts[len(parts)-1])
	k, ok := keyMap[keyStr]
	if !ok {
		return nil, 0, fmt.Errorf("unknown key %q — valid: a-z, 0-9, f1-f20, space, enter, escape, tab, delete, left, right, up, down", keyStr)
	}
	return mods, k, nil
}

func toggleMute(dg *discordgo.Session, cfg *Config, muted bool) {
	guild, err := dg.State.Guild(cfg.GuildID)
	if err != nil {
		log.Printf("guild not in state cache (is the bot in the server?): %v", err)
		return
	}
	count := 0
	for _, vs := range guild.VoiceStates {
		if vs.ChannelID == cfg.ChannelID {
			if err := dg.GuildMemberMute(cfg.GuildID, vs.UserID, muted); err != nil {
				log.Printf("  warning: user %s: %v", vs.UserID, err)
			}
			count++
		}
	}
	if count == 0 {
		fmt.Println("  (no users found in channel)")
	} else {
		fmt.Printf("  %d user(s) affected\n", count)
	}
}

func main() { mainthread.Init(run) }

func run() {
	cfg, err := loadConfig()
	if err != nil {
		log.Fatal(err)
	}

	dg, err := discordgo.New("Bot " + cfg.Token)
	if err != nil {
		log.Fatal(err)
	}
	dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildVoiceStates

	ready := make(chan struct{})
	dg.AddHandler(func(_ *discordgo.Session, _ *discordgo.Ready) {
		close(ready)
	})

	if err := dg.Open(); err != nil {
		log.Fatalf("connect to Discord: %v", err)
	}
	defer dg.Close()

	<-ready
	fmt.Println("Connected to Discord.")

	mods, key, err := parseKeybind(cfg.Keybind)
	if err != nil {
		log.Fatalf("keybind: %v", err)
	}

	hk := hotkey.New(mods, key)
	if err := hk.Register(); err != nil {
		log.Fatalf("register hotkey %q: %v", cfg.Keybind, err)
	}
	defer hk.Unregister()

	fmt.Printf("Listening on %s — toggles server mute for channel %s\n", cfg.Keybind, cfg.ChannelID)

	muted := false
	for range hk.Keydown() {
		muted = !muted
		if muted {
			fmt.Print("Muting all users... ")
		} else {
			fmt.Print("Unmuting all users... ")
		}
		toggleMute(dg, cfg, muted)
	}
}
