package main

import (
	"encoding/json"
	"fmt"
	"go/format"
	"os"
	"path/filepath"
	"strings"
)

// MetaData represents the parsed meta.json structure.
type MetaData struct {
	AgentMethods  map[string]string `json:"agentMethods"`
	ClientMethods map[string]string `json:"clientMethods"`
	Version       any               `json:"version"`
}

// GenerateMeta generates Go code from meta.json.
func GenerateMeta(metaPath, outputDir string, dryRun bool) error {
	data, err := os.ReadFile(metaPath)
	if err != nil {
		return fmt.Errorf("read meta.json: %w", err)
	}

	var meta MetaData
	if err := json.Unmarshal(data, &meta); err != nil {
		return fmt.Errorf("parse meta.json: %w", err)
	}

	var buf strings.Builder
	buf.WriteString("// Code generated by internal/schemagen. DO NOT EDIT.\n\n")
	buf.WriteString("package schema\n\n")
	buf.WriteString("// ProtocolVersion is the current stable ACP protocol version.\n")
	buf.WriteString("type ProtocolVersion = int\n\n")

	// Agent method constants
	buf.WriteString("// Agent method names (client → agent).\n")
	buf.WriteString("const (\n")
	agentConsts := []string{
		"initialize",
		"authenticate",
		"session/new",
		"session/load",
		"session/list",
		"session/prompt",
		"session/cancel",
		"session/set_mode",
		"session/set_config_option",
	}
	for _, m := range agentConsts {
		name := toGoConstName("Method", m)
		buf.WriteString(fmt.Sprintf("\t%s = %q\n", name, m))
	}
	buf.WriteString(")\n\n")

	// Client method constants
	buf.WriteString("// Client method names (agent → client).\n")
	buf.WriteString("const (\n")
	clientConsts := []string{
		"session/update",
		"session/request_permission",
		"fs/read_text_file",
		"fs/write_text_file",
		"terminal/create",
		"terminal/output",
		"terminal/release",
		"terminal/wait_for_exit",
		"terminal/kill",
	}
	for _, m := range clientConsts {
		name := toGoConstName("Method", m)
		buf.WriteString(fmt.Sprintf("\t%s = %q\n", name, m))
	}
	buf.WriteString(")\n\n")

	// AgentMethods map
	buf.WriteString("// AgentMethods is the set of all agent-side method names.\n")
	buf.WriteString("var AgentMethods = map[string]string{\n")
	for _, m := range agentConsts {
		key := strings.ReplaceAll(m, "/", "_")
		name := toGoConstName("Method", m)
		buf.WriteString(fmt.Sprintf("\t%q: %s,\n", key, name))
	}
	buf.WriteString("}\n\n")

	// ClientMethods map
	buf.WriteString("// ClientMethods is the set of all client-side method names.\n")
	buf.WriteString("var ClientMethods = map[string]string{\n")
	for _, m := range clientConsts {
		key := strings.ReplaceAll(m, "/", "_")
		name := toGoConstName("Method", m)
		buf.WriteString(fmt.Sprintf("\t%q: %s,\n", key, name))
	}
	buf.WriteString("}\n")

	content := buf.String()

	if dryRun {
		fmt.Print(content)
		return nil
	}

	formatted, err := format.Source([]byte(content))
	if err != nil {
		formatted = []byte(content)
	}

	outPath := filepath.Join(outputDir, "meta.go")
	return os.WriteFile(outPath, formatted, 0644)
}

func toGoConstName(prefix, method string) string {
	// e.g. "session/new" -> "MethodSessionNew"
	parts := strings.Split(method, "/")
	var name strings.Builder
	name.WriteString(prefix)
	for _, p := range parts {
		if len(p) > 0 {
			name.WriteString(strings.ToUpper(p[:1]) + p[1:])
		}
	}
	return name.String()
}
