// genprotocol generates the WebSocket protocol message-type constant files
// for both the Go server and the TypeScript client from the single source of
// truth at docs/protocol-schema.json.
//
// Usage (from the Server/ directory):
//
//	go run ./scripts/genprotocol
//
// or via make:
//
//	make protocol-generate   # regenerate both outputs
//	make protocol-verify     # fail if committed outputs are stale (CI)
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"go/format"
	"log"
	"os"
	"strings"
)

type message struct {
	Wire              string `json:"wire"`
	Go                string `json:"go"`
	TS                string `json:"ts"`
	Note              string `json:"note,omitempty"`
	GoTrailingComment string `json:"go_trailing_comment,omitempty"`
}

type schema struct {
	Comment        string    `json:"$comment"`
	Version        int       `json:"version"`
	ClientToServer []message `json:"client_to_server"`
	ServerToClient []message `json:"server_to_client"`
}

func main() {
	schemaPath := flag.String("schema", "../docs/protocol-schema.json", "path to protocol-schema.json")
	goOut := flag.String("go-out", "ws/message_types.go", "path to the generated Go file")
	tsOut := flag.String("ts-out", "../Client/tauri-client/src/lib/protocolTypes.ts", "path to the generated TypeScript file")
	flag.Parse()

	raw, err := os.ReadFile(*schemaPath)
	if err != nil {
		log.Fatalf("read schema: %v", err)
	}
	var s schema
	dec := json.NewDecoder(strings.NewReader(string(raw)))
	dec.DisallowUnknownFields()
	if err := dec.Decode(&s); err != nil {
		log.Fatalf("parse schema: %v", err)
	}
	if err := validate(s); err != nil {
		log.Fatalf("invalid schema: %v", err)
	}

	goSrc, err := renderGo(s)
	if err != nil {
		log.Fatalf("render Go: %v", err)
	}
	if err := os.WriteFile(*goOut, goSrc, 0o644); err != nil {
		log.Fatalf("write %s: %v", *goOut, err)
	}
	if err := os.WriteFile(*tsOut, []byte(renderTS(s)), 0o644); err != nil {
		log.Fatalf("write %s: %v", *tsOut, err)
	}
	fmt.Printf("generated %s (%d client→server, %d server→client) and %s\n",
		*goOut, len(s.ClientToServer), len(s.ServerToClient), *tsOut)
}

// validate rejects duplicate identifiers and empty fields early so a bad
// schema edit fails the generator instead of producing broken output.
func validate(s schema) error {
	goNames := map[string]bool{}
	for _, list := range [][]message{s.ClientToServer, s.ServerToClient} {
		tsNames := map[string]bool{}
		for _, m := range list {
			if m.Wire == "" || m.Go == "" || m.TS == "" {
				return fmt.Errorf("entry %+v: wire, go, and ts are all required", m)
			}
			if goNames[m.Go] {
				return fmt.Errorf("duplicate Go constant %q", m.Go)
			}
			goNames[m.Go] = true
			if tsNames[m.TS] {
				return fmt.Errorf("duplicate TS key %q within one direction", m.TS)
			}
			tsNames[m.TS] = true
		}
	}
	return nil
}

func header(comment string) string {
	return "// Code generated by scripts/genprotocol from docs/protocol-schema.json; DO NOT EDIT.\n" +
		comment + "\n"
}

func renderGo(s schema) ([]byte, error) {
	var b strings.Builder
	b.WriteString(header("//\n// WebSocket protocol message type constants — single source of truth for\n" +
		"// both Server (Go) and Client (TypeScript). Edit docs/protocol-schema.json\n" +
		"// and run `make protocol-generate` (see Server/Makefile)."))
	b.WriteString("\npackage ws\n\n")

	writeBlock := func(title string, msgs []message) {
		b.WriteString("// " + title + "\nconst (\n")
		for _, m := range msgs {
			b.WriteString("\t" + m.Go + " = " + fmt.Sprintf("%q", m.Wire))
			switch {
			case m.GoTrailingComment != "":
				b.WriteString(" " + m.GoTrailingComment)
			case m.Note != "":
				b.WriteString(" // " + m.Note)
			}
			b.WriteString("\n")
		}
		b.WriteString(")\n")
	}

	writeBlock("Client → Server message types (received by handlers).", s.ClientToServer)
	b.WriteString("\n")
	writeBlock("Server → Client message types (sent in broadcasts/responses).", s.ServerToClient)

	return format.Source([]byte(b.String()))
}

func renderTS(s schema) string {
	var b strings.Builder
	b.WriteString(header("//\n// Shared WebSocket protocol message type constants — single source of truth\n" +
		"// for both Server (Go) and Client (TypeScript). Edit docs/protocol-schema.json\n" +
		"// and run `make protocol-generate` in Server/.\n//\n" +
		"// Usage:  import { MessageType } from \"@lib/protocolTypes\";\n" +
		"//         ws.send({ type: MessageType.CHAT_SEND, payload: { ... } });"))

	writeBlock := func(title, name string, msgs []message) {
		b.WriteString("\n// " + strings.Repeat("-", 75) + "\n")
		b.WriteString("// " + title + "\n")
		b.WriteString("// " + strings.Repeat("-", 75) + "\n\n")
		b.WriteString("export const " + name + " = {\n")
		for _, m := range msgs {
			b.WriteString("  " + m.TS + ": " + fmt.Sprintf("%q", m.Wire) + ",")
			if m.Note != "" {
				b.WriteString(" // " + m.Note)
			}
			b.WriteString("\n")
		}
		b.WriteString("} as const;\n\n")
		b.WriteString("export type " + name + "Value = (typeof " + name + ")[keyof typeof " + name + "];\n")
	}

	writeBlock("Server → Client message types", "ServerMessageType", s.ServerToClient)
	writeBlock("Client → Server message types", "ClientMessageType", s.ClientToServer)

	b.WriteString("\n// " + strings.Repeat("-", 75) + "\n")
	b.WriteString("// Unified MessageType — all message types in one object for convenience\n")
	b.WriteString("// " + strings.Repeat("-", 75) + "\n\n")
	b.WriteString("export const MessageType = {\n  ...ServerMessageType,\n  ...ClientMessageType,\n} as const;\n\n")
	b.WriteString("export type MessageTypeValue = (typeof MessageType)[keyof typeof MessageType];\n")
	return b.String()
}
