🏡 index : quine-global/quintodrome.git

package internal

import (
	"go/format"
	"os"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("Generator", func() {
	Describe("GenerateHost", func() {
		It("should generate valid Go code for a simple service with strings", func() {
			// All methods use JSON request/response types
			svc := Service{
				Name:       "SubsonicAPI",
				Permission: "subsonicapi",
				Interface:  "SubsonicAPIService",
				Methods: []Method{
					{
						Name:     "Call",
						HasError: true,
						Params:   []Param{NewParam("uri", "string")},
						Returns:  []Param{NewParam("response", "string")},
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			// Verify the code is valid Go
			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check for generated header
			Expect(codeStr).To(ContainSubstring("Code generated by ndpgen. DO NOT EDIT."))

			// Check for package declaration
			Expect(codeStr).To(ContainSubstring("package host"))

			// All methods now use request type for JSON protocol
			Expect(codeStr).To(ContainSubstring("type SubsonicAPICallRequest struct"))
			Expect(codeStr).To(ContainSubstring(`Uri string `))

			// Response type with error handling
			Expect(codeStr).To(ContainSubstring("type SubsonicAPICallResponse struct"))
			Expect(codeStr).To(ContainSubstring(`Response string `))
			Expect(codeStr).To(ContainSubstring(`Error string `))

			// Check for registration function
			Expect(codeStr).To(ContainSubstring("func RegisterSubsonicAPIHostFunctions(service SubsonicAPIService)"))

			// Check for host function name
			Expect(codeStr).To(ContainSubstring(`"subsonicapi_call"`))

			// Check for JSON unmarshal (all methods use JSON now)
			Expect(codeStr).To(ContainSubstring("json.Unmarshal"))
		})

		It("should generate code for methods without parameters", func() {
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:     "NoParams",
						HasError: true,
						Returns:  []Param{NewParam("result", "string")},
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			// Methods without params don't need a request type - no params to serialize
			Expect(codeStr).NotTo(ContainSubstring("type TestNoParamsRequest struct"))
			// But still uses PTR input/output for consistency
			Expect(codeStr).To(MatchRegexp(`\[\]extism\.ValueType\{extism\.ValueTypePTR\},\s*\[\]extism\.ValueType\{extism\.ValueTypePTR\}`))
		})

		It("should generate code for methods without return values", func() {
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:     "NoReturn",
						HasError: true,
						Params:   []Param{NewParam("input", "string")},
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())
		})

		It("should generate code for multiple methods", func() {
			svc := Service{
				Name:       "Scheduler",
				Permission: "scheduler",
				Interface:  "SchedulerService",
				Methods: []Method{
					{
						Name:     "ScheduleRecurring",
						HasError: true,
						Params:   []Param{NewParam("cronExpression", "string")},
						Returns:  []Param{NewParam("scheduleID", "string")},
					},
					{
						Name:     "ScheduleOneTime",
						HasError: true,
						Params:   []Param{NewParam("delaySeconds", "int32")},
						Returns:  []Param{NewParam("scheduleID", "string")},
					},
					{
						Name:     "CancelSchedule",
						HasError: true,
						Params:   []Param{NewParam("scheduleID", "string")},
						Returns:  []Param{NewParam("canceled", "bool")},
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			Expect(codeStr).To(ContainSubstring("scheduler_schedulerecurring"))
			Expect(codeStr).To(ContainSubstring("scheduler_scheduleonetime"))
			Expect(codeStr).To(ContainSubstring("scheduler_cancelschedule"))
		})

		It("should handle multiple simple parameters with JSON", func() {
			// All params use JSON - single PTR input
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:     "MultiParam",
						HasError: true,
						Params: []Param{
							NewParam("name", "string"),
							NewParam("count", "int32"),
							NewParam("enabled", "bool"),
						},
						Returns: []Param{NewParam("result", "string")},
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			// All methods use request type with JSON protocol
			Expect(codeStr).To(ContainSubstring("type TestMultiParamRequest struct"))
			// Check for JSON unmarshal (all methods use JSON now)
			Expect(codeStr).To(ContainSubstring("json.Unmarshal"))
			// Check that input/output ValueType both use PTR (JSON)
			Expect(codeStr).To(MatchRegexp(`\[\]extism\.ValueType\{extism\.ValueTypePTR\},\s*\[\]extism\.ValueType\{extism\.ValueTypePTR\}`))
		})

		It("should use single PTR for mixed simple and complex params", func() {
			// When any param needs JSON, all are bundled into one request struct
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:     "MixedParam",
						HasError: true,
						Params: []Param{
							NewParam("id", "string"),     // simple (PTR for string)
							NewParam("tags", "[]string"), // complex - needs JSON
						},
						Returns: []Param{NewParam("count", "int32")}, // simple
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			// Request type IS needed because of complex param
			Expect(codeStr).To(ContainSubstring("type TestMixedParamRequest struct"))
			// When using request type, only ONE PTR for input (the JSON request)
			Expect(codeStr).To(MatchRegexp(`\[\]extism\.ValueType\{extism\.ValueTypePTR\},\s*\[\]extism\.ValueType\{extism\.ValueTypePTR\}`))
		})

		It("should generate proper JSON tags for complex types", func() {
			// Complex types (structs, slices, maps) need JSON serialization
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:     "Method",
						HasError: true,
						Params:   []Param{NewParam("inputValue", "[]string")},           // slice needs JSON
						Returns:  []Param{NewParam("outputValue", "map[string]string")}, // map needs JSON
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			// Complex params need request type with JSON tags
			Expect(codeStr).To(ContainSubstring(`json:"inputValue"`))
			// Complex returns need response type with JSON tags
			Expect(codeStr).To(ContainSubstring(`json:"outputValue,omitempty"`))
		})

		It("should include required imports", func() {
			// Service with complex types needs JSON import
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:     "Method",
						HasError: true,
						Params:   []Param{NewParam("data", "MyStruct")}, // struct needs JSON
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			Expect(codeStr).To(ContainSubstring(`"context"`))
			Expect(codeStr).To(ContainSubstring(`"encoding/json"`))
			Expect(codeStr).To(ContainSubstring(`extism "github.com/extism/go-sdk"`))
		})

		It("should always include json import for JSON protocol", func() {
			// All services use JSON protocol, so json import is always needed
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:    "Method",
						Params:  []Param{NewParam("count", "int32")},
						Returns: []Param{NewParam("result", "int64")},
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			Expect(codeStr).To(ContainSubstring(`"context"`))
			Expect(codeStr).To(ContainSubstring(`"encoding/json"`))
			Expect(codeStr).To(ContainSubstring(`extism "github.com/extism/go-sdk"`))
		})

		It("imports the shared types package when a method references types directly", func() {
			svc := Service{
				Name:      "Matcher",
				Interface: "MatcherService",
				Methods: []Method{
					{
						Name:     "MatchSongs",
						HasError: true,
						Params:   []Param{NewParam("songs", "[]types.SongRef")},
						Returns:  []Param{NewParam("results", "[]*types.Track")},
					},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			Expect(codeStr).To(ContainSubstring(`"github.com/navidrome/navidrome/plugins/types"`))
			Expect(codeStr).To(ContainSubstring("Songs []types.SongRef"))
		})

		It("does not import the shared types package when no method references types", func() {
			svc := Service{
				Name:      "Test",
				Interface: "TestService",
				Methods: []Method{
					{Name: "Method", Params: []Param{NewParam("count", "int32")}},
				},
			}

			code, err := GenerateHost(svc, "host")
			Expect(err).NotTo(HaveOccurred())
			Expect(string(code)).NotTo(ContainSubstring(`"github.com/navidrome/navidrome/plugins/types"`))
		})
	})

	Describe("toJSONName", func() {
		It("should convert to camelCase matching Rust serde behavior", func() {
			Expect(toJSONName("InputValue")).To(Equal("inputValue"))
			Expect(toJSONName("URI")).To(Equal("uri"))
			Expect(toJSONName("id")).To(Equal("id"))
			Expect(toJSONName("ID")).To(Equal("id"))
			Expect(toJSONName("ConnectionID")).To(Equal("connectionId"))
			Expect(toJSONName("NewConnectionID")).To(Equal("newConnectionId"))
			Expect(toJSONName("XMLHTTPRequest")).To(Equal("xmlhttpRequest"))
			Expect(toJSONName("APIKey")).To(Equal("apiKey"))
		})

		It("should handle empty string", func() {
			Expect(toJSONName("")).To(Equal(""))
		})
	})

	Describe("NewParam", func() {
		It("should create param with auto-generated JSON name", func() {
			p := NewParam("MyParam", "string")
			Expect(p.Name).To(Equal("MyParam"))
			Expect(p.Type).To(Equal("string"))
			Expect(p.JSONName).To(Equal("myParam"))
		})
	})

	Describe("Method.IsOptionPattern", func() {
		It("should return true for (value, exists bool) pattern", func() {
			m := Method{
				Returns: []Param{
					{Name: "value", Type: "string"},
					{Name: "exists", Type: "bool"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeTrue())
		})

		It("should return true for (value, ok bool) pattern", func() {
			m := Method{
				Returns: []Param{
					{Name: "value", Type: "int64"},
					{Name: "ok", Type: "bool"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeTrue())
		})

		It("should return true for (value, found bool) pattern", func() {
			m := Method{
				Returns: []Param{
					{Name: "data", Type: "[]byte"},
					{Name: "found", Type: "bool"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeTrue())
		})

		It("should be case insensitive for bool name", func() {
			m := Method{
				Returns: []Param{
					{Name: "value", Type: "string"},
					{Name: "EXISTS", Type: "bool"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeTrue())
		})

		It("should return false for single return", func() {
			m := Method{
				Returns: []Param{
					{Name: "value", Type: "string"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeFalse())
		})

		It("should return false for more than two returns", func() {
			m := Method{
				Returns: []Param{
					{Name: "value", Type: "string"},
					{Name: "count", Type: "int"},
					{Name: "exists", Type: "bool"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeFalse())
		})

		It("should return false when second return is not bool", func() {
			m := Method{
				Returns: []Param{
					{Name: "value", Type: "string"},
					{Name: "count", Type: "int"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeFalse())
		})

		It("should return false when bool is not named exists/ok/found", func() {
			m := Method{
				Returns: []Param{
					{Name: "value", Type: "string"},
					{Name: "success", Type: "bool"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeFalse())
		})

		It("should return false for Has() pattern where first return is bool", func() {
			// Has(key) -> (exists bool) should NOT be treated as Option pattern
			m := Method{
				Returns: []Param{
					{Name: "exists", Type: "bool"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeFalse())
		})

		It("should return false when first return is bool (preserves Has-like methods)", func() {
			// Even with two returns, if first is bool, don't convert to Option<bool>
			m := Method{
				Returns: []Param{
					{Name: "result", Type: "bool"},
					{Name: "exists", Type: "bool"},
				},
			}
			Expect(m.IsOptionPattern()).To(BeFalse())
		})
	})

	Describe("ToSnakeCase", func() {
		It("should convert PascalCase to snake_case", func() {
			Expect(ToSnakeCase("ScheduleRecurring")).To(Equal("schedule_recurring"))
			Expect(ToSnakeCase("GetString")).To(Equal("get_string"))
			Expect(ToSnakeCase("simple")).To(Equal("simple"))
		})

		It("should handle acronyms correctly", func() {
			Expect(ToSnakeCase("ID")).To(Equal("id"))
			Expect(ToSnakeCase("ScheduleID")).To(Equal("schedule_id"))
			Expect(ToSnakeCase("NewScheduleID")).To(Equal("new_schedule_id"))
			Expect(ToSnakeCase("XMLParser")).To(Equal("xml_parser"))
			Expect(ToSnakeCase("GetHTTPResponse")).To(Equal("get_http_response"))
		})
	})

	Describe("GenerateGoDoc", func() {
		It("should generate valid doc.go content for multiple services", func() {
			services := []Service{
				{
					Name:       "Cache",
					Permission: "cache",
					Interface:  "CacheService",
					Doc:        "CacheService provides temporary key-value storage with TTL.",
				},
				{
					Name:       "Scheduler",
					Permission: "scheduler",
					Interface:  "SchedulerService",
					Doc:        "SchedulerService manages scheduled tasks.",
				},
			}

			code, err := GenerateGoDoc(services, "ndpdk")
			Expect(err).NotTo(HaveOccurred())

			// Verify it's valid Go code
			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check for generated header
			Expect(codeStr).To(ContainSubstring("Code generated by ndpgen. DO NOT EDIT."))

			// Check for package declaration
			Expect(codeStr).To(ContainSubstring("package ndpdk"))

			// Check for package documentation
			Expect(codeStr).To(ContainSubstring("Package ndpdk provides Navidrome Plugin Development Kit wrappers"))

			// Check that services are listed
			Expect(codeStr).To(ContainSubstring("Cache:"))
			Expect(codeStr).To(ContainSubstring("Scheduler:"))
		})
	})

	Describe("GenerateGoMod", func() {
		It("should generate valid go.mod content", func() {
			code, err := GenerateGoMod()
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check for module declaration (consolidated PDK path at pdk/go level)
			Expect(codeStr).To(ContainSubstring("module github.com/navidrome/navidrome/plugins/pdk/go"))
			// Ensure it's not the old host-specific path
			Expect(codeStr).NotTo(ContainSubstring("module github.com/navidrome/navidrome/plugins/pdk/go/host"))

			// Check for Go version
			Expect(codeStr).To(ContainSubstring("go 1.25"))

			// Check for extism-go-pdk dependency
			Expect(codeStr).To(ContainSubstring("github.com/extism/go-pdk"))
		})
	})

	Describe("GenerateClientGo", func() {
		It("should include errors import when service has methods with errors", func() {
			svc := Service{
				Name:       "Cache",
				Permission: "cache",
				Interface:  "CacheService",
				Methods: []Method{
					{
						Name:     "Get",
						HasError: true,
						Params:   []Param{NewParam("key", "string")},
						Returns:  []Param{NewParam("value", "string")},
					},
				},
			}

			code, err := GenerateClientGo(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			// Verify the code is valid Go (can't actually compile without wasip1)
			codeStr := string(code)

			// Check for errors import when methods have errors
			Expect(codeStr).To(ContainSubstring(`"errors"`))
			Expect(codeStr).To(ContainSubstring("errors.New"))
		})

		It("should not include errors import when service has no methods with errors", func() {
			svc := Service{
				Name:       "Config",
				Permission: "config",
				Interface:  "ConfigService",
				Methods: []Method{
					{
						Name:     "Get",
						HasError: false,
						Params:   []Param{NewParam("key", "string")},
						Returns:  []Param{NewParam("value", "string"), NewParam("exists", "bool")},
					},
					{
						Name:     "List",
						HasError: false,
						Params:   []Param{NewParam("prefix", "string")},
						Returns:  []Param{NewParam("keys", "[]string")},
					},
				},
			}

			code, err := GenerateClientGo(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check that errors is NOT imported when no methods have errors
			Expect(codeStr).NotTo(ContainSubstring(`"errors"`))
			Expect(codeStr).NotTo(ContainSubstring("errors.New"))
		})

		It("should generate valid Go code structure", func() {
			svc := Service{
				Name:       "SubsonicAPI",
				Permission: "subsonicapi",
				Interface:  "SubsonicAPIService",
				Methods: []Method{
					{
						Name:     "Call",
						HasError: true,
						Params:   []Param{NewParam("uri", "string")},
						Returns:  []Param{NewParam("response", "string")},
					},
				},
			}

			code, err := GenerateClientGo(svc, "host")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check for generated header
			Expect(codeStr).To(ContainSubstring("Code generated by ndpgen. DO NOT EDIT."))

			// Check for build tag
			Expect(codeStr).To(ContainSubstring("//go:build wasip1"))

			// Check for package declaration
			Expect(codeStr).To(ContainSubstring("package host"))

			// Check for wasmimport directive
			Expect(codeStr).To(ContainSubstring("//go:wasmimport extism:host/user"))

			// Check for PDK import
			Expect(codeStr).To(ContainSubstring("github.com/navidrome/navidrome/plugins/pdk/go/pdk"))
		})

	})

	Describe("GenerateClientGoStub", func() {
		It("should generate valid mock code with testify/mock", func() {
			svc := Service{
				Name:       "Cache",
				Permission: "cache",
				Interface:  "CacheService",
				Doc:        "CacheService provides caching capabilities.",
				Methods: []Method{
					{
						Name: "Get",
						Doc:  "Get retrieves a value from the cache.",
						Params: []Param{
							{Name: "key", Type: "string"},
						},
						Returns: []Param{
							{Name: "value", Type: "string"},
							{Name: "exists", Type: "bool"},
						},
					},
				},
			}

			code, err := GenerateClientGoStub(svc, "ndpdk")
			Expect(err).NotTo(HaveOccurred())

			// Verify it's valid Go code
			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check for build tag (non-WASM)
			Expect(codeStr).To(ContainSubstring("//go:build !wasip1"))

			// Check for package declaration
			Expect(codeStr).To(ContainSubstring("package ndpdk"))

			// Check for mock comment
			Expect(codeStr).To(ContainSubstring("mock implementations for non-WASM builds"))

			// Check for testify/mock import
			Expect(codeStr).To(ContainSubstring(`"github.com/stretchr/testify/mock"`))

			// Check for private mock struct
			Expect(codeStr).To(ContainSubstring("type mockCacheService struct"))
			Expect(codeStr).To(ContainSubstring("mock.Mock"))

			// Check for exported mock instance
			Expect(codeStr).To(ContainSubstring("var CacheMock = &mockCacheService{}"))

			// Check for mock method
			Expect(codeStr).To(ContainSubstring("func (m *mockCacheService) Get(key string)"))
			Expect(codeStr).To(ContainSubstring("m.Called(key)"))

			// Check for wrapper function delegating to mock
			Expect(codeStr).To(ContainSubstring("func CacheGet(key string)"))
			Expect(codeStr).To(ContainSubstring("return CacheMock.Get(key)"))

			// Stub files should NOT have request/response types (they're not needed)
			Expect(codeStr).NotTo(ContainSubstring("Request struct"))
			Expect(codeStr).NotTo(ContainSubstring("Response struct"))
		})

		It("should generate correct mock return values for different types", func() {
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name: "GetString",
						Params: []Param{
							{Name: "key", Type: "string"},
						},
						Returns: []Param{
							{Name: "value", Type: "string"},
						},
						HasError: true,
					},
					{
						Name: "GetInt64",
						Params: []Param{
							{Name: "key", Type: "string"},
						},
						Returns: []Param{
							{Name: "value", Type: "int64"},
							{Name: "exists", Type: "bool"},
						},
						HasError: true,
					},
					{
						Name: "GetBytes",
						Params: []Param{
							{Name: "key", Type: "string"},
						},
						Returns: []Param{
							{Name: "value", Type: "[]byte"},
						},
						HasError: true,
					},
				},
			}

			code, err := GenerateClientGoStub(svc, "ndpdk")
			Expect(err).NotTo(HaveOccurred())

			// Verify it's valid Go code
			_, err = format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check string return uses args.String(0)
			Expect(codeStr).To(ContainSubstring("args.String(0)"))

			// Check int64 return uses args.Get(0).(int64)
			Expect(codeStr).To(ContainSubstring("args.Get(0).(int64)"))

			// Check bool return uses args.Bool(1)
			Expect(codeStr).To(ContainSubstring("args.Bool(1)"))

			// []byte is nil-able, so it gets a guarded extraction
			Expect(codeStr).To(ContainSubstring("var r0 []byte"))
			Expect(codeStr).To(ContainSubstring("r0 = v.([]byte)"))

			// Check error returns use args.Error(N)
			Expect(codeStr).To(ContainSubstring("args.Error("))
		})
	})

	Describe("Integration", func() {
		It("should generate compilable code from parsed source", func() {
			// This is an integration test that verifies the full pipeline
			src := `package host

import "context"

// TestService is a test service.
//nd:hostservice name=Test permission=test
type TestService interface {
	// DoSomething does something.
	//nd:hostfunc
	DoSomething(ctx context.Context, input string) (output string, err error)
}
`
			// Create temporary directory
			tmpDir := GinkgoT().TempDir()
			path := tmpDir + "/test.go"
			err := writeFile(path, src)
			Expect(err).NotTo(HaveOccurred())

			// Parse
			services, err := ParseDirectory(tmpDir)
			Expect(err).NotTo(HaveOccurred())
			Expect(services).To(HaveLen(1))

			// Generate
			code, err := GenerateHost(services[0], "host")
			Expect(err).NotTo(HaveOccurred())

			// Format (validates syntax)
			formatted, err := format.Source(code)
			Expect(err).NotTo(HaveOccurred())

			// Verify key elements
			codeStr := string(formatted)
			Expect(codeStr).To(ContainSubstring("RegisterTestHostFunctions"))
			Expect(codeStr).To(ContainSubstring(`"test_dosomething"`))
		})
	})

	Describe("GenerateCapabilityGo", func() {
		It("should generate valid Go code for a non-required capability", func() {
			cap := Capability{
				Name:      "metadata",
				Interface: "MetadataAgent",
				Required:  false,
				Doc:       "MetadataAgent provides metadata retrieval.",
				Methods: []Export{
					{
						Name:       "GetArtistBiography",
						ExportName: "nd_get_artist_biography",
						Input:      Param{Type: "ArtistInput"},
						Output:     Param{Type: "ArtistBiographyOutput"},
						Doc:        "Returns artist biography",
					},
					{
						Name:       "GetArtistImages",
						ExportName: "nd_get_artist_images",
						Input:      Param{Type: "ArtistInput"},
						Output:     Param{Type: "ArtistImagesOutput"},
						Doc:        "Returns artist images",
					},
				},
				Structs: []StructDef{
					{
						Name: "ArtistInput",
						Fields: []FieldDef{
							{Name: "ID", Type: "string", JSONTag: "id"},
							{Name: "Name", Type: "string", JSONTag: "name"},
						},
					},
					{
						Name: "ArtistBiographyOutput",
						Fields: []FieldDef{
							{Name: "Biography", Type: "string", JSONTag: "biography"},
						},
					},
					{
						Name: "ArtistImagesOutput",
						Fields: []FieldDef{
							{Name: "Images", Type: "[]ImageInfo", JSONTag: "images"},
						},
					},
					{
						Name: "ImageInfo",
						Fields: []FieldDef{
							{Name: "URL", Type: "string", JSONTag: "url"},
							{Name: "Size", Type: "int32", JSONTag: "size"},
						},
					},
				},
			}

			code, err := GenerateCapabilityGo(cap, "metadata")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check for build tag
			Expect(codeStr).To(ContainSubstring("//go:build wasip1"))

			// Check for package declaration
			Expect(codeStr).To(ContainSubstring("package metadata"))

			// Check for marker interface (non-required)
			Expect(codeStr).To(ContainSubstring("type Metadata interface{}"))

			// Check for provider interfaces
			Expect(codeStr).To(ContainSubstring("type ArtistBiographyProvider interface"))
			Expect(codeStr).To(ContainSubstring("type ArtistImagesProvider interface"))

			// Check for Register function with type assertions
			Expect(codeStr).To(ContainSubstring("func Register(impl Metadata)"))
			Expect(codeStr).To(ContainSubstring("impl.(ArtistBiographyProvider)"))

			// Check for export wrappers
			Expect(codeStr).To(ContainSubstring("//go:wasmexport nd_get_artist_biography"))
			Expect(codeStr).To(ContainSubstring("func _NdGetArtistBiography()"))

			// Check for NotImplementedCode handling
			Expect(codeStr).To(ContainSubstring("NotImplementedCode"))
			Expect(codeStr).To(ContainSubstring("return NotImplementedCode"))

			// Check struct definitions
			Expect(codeStr).To(ContainSubstring("type ArtistInput struct"))
			Expect(codeStr).To(ContainSubstring("type ImageInfo struct"))
		})

		It("should generate valid Go code for a required capability", func() {
			cap := Capability{
				Name:      "scrobbler",
				Interface: "Scrobbler",
				Required:  true,
				Methods: []Export{
					{
						Name:       "IsAuthorized",
						ExportName: "nd_scrobbler_is_authorized",
						Input:      Param{Type: "AuthInput"},
						Output:     Param{Type: "AuthOutput"},
					},
					{
						Name:       "Scrobble",
						ExportName: "nd_scrobbler_scrobble",
						Input:      Param{Type: "ScrobbleInput"},
						Output:     Param{Type: "ScrobblerOutput"},
					},
				},
				Structs: []StructDef{
					{Name: "AuthInput", Fields: []FieldDef{{Name: "UserID", Type: "string", JSONTag: "userId"}}},
					{Name: "AuthOutput", Fields: []FieldDef{{Name: "Authorized", Type: "bool", JSONTag: "authorized"}}},
					{Name: "ScrobbleInput", Fields: []FieldDef{{Name: "UserID", Type: "string", JSONTag: "userId"}}},
					{Name: "ScrobblerOutput", Fields: []FieldDef{{Name: "Error", Type: "*string", JSONTag: "error", OmitEmpty: true}}},
				},
			}

			code, err := GenerateCapabilityGo(cap, "scrobbler")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check for full interface (required capability)
			Expect(codeStr).To(ContainSubstring("type Scrobbler interface {"))
			Expect(codeStr).To(ContainSubstring("IsAuthorized(AuthInput) (AuthOutput, error)"))
			Expect(codeStr).To(ContainSubstring("Scrobble(ScrobbleInput) (ScrobblerOutput, error)"))

			// Should NOT have provider interfaces for required capability
			Expect(codeStr).NotTo(ContainSubstring("AuthProvider interface"))

			// Register should directly assign methods
			Expect(codeStr).To(ContainSubstring("func Register(impl Scrobbler)"))
			Expect(codeStr).To(ContainSubstring("impl.IsAuthorized"))
		})

		It("should include type aliases and consts", func() {
			cap := Capability{
				Name:      "scrobbler",
				Interface: "Scrobbler",
				Required:  true,
				Methods: []Export{
					{
						Name:       "Scrobble",
						ExportName: "nd_scrobble",
						Input:      Param{Type: "ScrobbleInput"},
						Output:     Param{Type: "ScrobblerOutput"},
					},
				},
				Structs: []StructDef{
					{Name: "ScrobbleInput", Fields: []FieldDef{{Name: "UserID", Type: "string", JSONTag: "userId"}}},
					{Name: "ScrobblerOutput", Fields: []FieldDef{{Name: "ErrorType", Type: "*ScrobblerErrorType", JSONTag: "errorType", OmitEmpty: true}}},
				},
				TypeAliases: []TypeAlias{
					{Name: "ScrobblerErrorType", Type: "string", Doc: "ScrobblerErrorType indicates error handling."},
				},
				Consts: []ConstGroup{
					{
						Type: "ScrobblerErrorType",
						Values: []ConstDef{
							{Name: "ScrobblerErrorNone", Value: `"none"`, Doc: "No error"},
							{Name: "ScrobblerErrorRetry", Value: `"retry"`, Doc: "Retry later"},
						},
					},
				},
			}

			code, err := GenerateCapabilityGo(cap, "scrobbler")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check type alias
			Expect(codeStr).To(ContainSubstring("type ScrobblerErrorType string"))

			// Check consts - all consts should have type annotation
			Expect(codeStr).To(ContainSubstring("ScrobblerErrorNone ScrobblerErrorType ="))
			Expect(codeStr).To(ContainSubstring(`"none"`))
			Expect(codeStr).To(ContainSubstring("ScrobblerErrorRetry ScrobblerErrorType ="))
			Expect(codeStr).To(ContainSubstring(`"retry"`))
		})

		It("emits a deprecated alias and types import for a shared-aliased capability", func() {
			cap := Capability{
				Name:      "scrobbler",
				Interface: "Scrobbler",
				Required:  true,
				Methods: []Export{{
					Name: "NowPlaying", ExportName: "nd_scrobbler_now_playing",
					Input: Param{Name: "input", Type: "NowPlayingRequest"},
				}},
				Structs: []StructDef{{Name: "NowPlayingRequest", Fields: []FieldDef{
					{Name: "Track", Type: "TrackInfo", JSONTag: "track"},
				}}},
				SharedAliases: []SharedAlias{{
					Name: "TrackInfo", Target: "types.TrackInfo",
					Doc: "Deprecated: use types.TrackInfo.",
					Def: StructDef{Name: "TrackInfo", Fields: []FieldDef{{Name: "Title", Type: "string", JSONTag: "title"}}},
				}},
			}
			code, err := GenerateCapabilityGo(cap, "scrobbler")
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			Expect(out).To(ContainSubstring(`"github.com/navidrome/navidrome/plugins/pdk/go/types"`))
			Expect(out).To(ContainSubstring("// Deprecated: use types.TrackInfo."))
			Expect(out).To(ContainSubstring("type TrackInfo = types.TrackInfo"))
			Expect(out).NotTo(ContainSubstring("type TrackInfo struct"))
		})

		It("emits the types import for a direct types.X field with no deprecated alias", func() {
			cap := Capability{
				Name:      "scrobbler",
				Interface: "Scrobbler",
				Required:  true,
				Methods: []Export{{
					Name: "NowPlaying", ExportName: "nd_scrobbler_now_playing",
					Input: Param{Name: "input", Type: "NowPlayingRequest"},
				}},
				Structs: []StructDef{{Name: "NowPlayingRequest", Fields: []FieldDef{
					{Name: "Song", Type: "types.SongRef", JSONTag: "song"},
				}}},
				// No SharedAliases: the field references the canonical type directly.
			}
			code, err := GenerateCapabilityGo(cap, "scrobbler")
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			Expect(out).To(ContainSubstring(`"github.com/navidrome/navidrome/plugins/pdk/go/types"`))
			Expect(out).To(ContainSubstring("types.SongRef"))
		})

		It("emits the types import for a direct types.X method input/output", func() {
			cap := Capability{
				Name:      "demo",
				Interface: "Demo",
				Required:  true,
				Methods: []Export{{
					Name: "Lookup", ExportName: "nd_demo_lookup",
					Input:  Param{Name: "input", Type: "types.SongRef"},
					Output: Param{Name: "output", Type: "types.SongRef"},
				}},
				// No structs, no aliases: the method signature references the shared type directly.
			}
			code, err := GenerateCapabilityGo(cap, "demo")
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			Expect(out).To(ContainSubstring(`"github.com/navidrome/navidrome/plugins/pdk/go/types"`))
			Expect(out).To(ContainSubstring("types.SongRef"))
		})
	})

	Describe("GenerateCapabilityGoStub", func() {
		It("should generate valid stub code for non-WASM builds", func() {
			cap := Capability{
				Name:      "metadata",
				Interface: "MetadataAgent",
				Required:  false,
				Methods: []Export{
					{
						Name:       "GetArtistBiography",
						ExportName: "nd_get_artist_biography",
						Input:      Param{Type: "ArtistInput"},
						Output:     Param{Type: "ArtistBiographyOutput"},
					},
				},
				Structs: []StructDef{
					{Name: "ArtistInput", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
					{Name: "ArtistBiographyOutput", Fields: []FieldDef{{Name: "Biography", Type: "string", JSONTag: "biography"}}},
				},
			}

			code, err := GenerateCapabilityGoStub(cap, "metadata")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check for non-WASM build tag
			Expect(codeStr).To(ContainSubstring("//go:build !wasip1"))

			// Check for package declaration
			Expect(codeStr).To(ContainSubstring("package metadata"))

			// Check for no-op Register
			Expect(codeStr).To(ContainSubstring("func Register(_ Metadata) {}"))

			// Check struct definitions are present
			Expect(codeStr).To(ContainSubstring("type ArtistInput struct"))

			// Check there are no export wrappers
			Expect(codeStr).NotTo(ContainSubstring("//go:wasmexport"))
			Expect(codeStr).NotTo(ContainSubstring("pdk.InputJSON"))
		})
	})

	Describe("End-to-end capability generation", func() {
		It("should parse and generate capability code from source", func() {
			src := `package capabilities

// Lifecycle provides plugin lifecycle hooks.
//nd:capability name=lifecycle
type Lifecycle interface {
	// OnInit is called when the plugin is loaded.
	//nd:export name=nd_on_init
	OnInit(OnInitInput) (OnInitOutput, error)
}

// OnInitInput is the input for OnInit.
type OnInitInput struct {
}

// OnInitOutput is the output for OnInit.
type OnInitOutput struct {
	// Error is the error message if initialization failed.
	Error *string ` + "`json:\"error,omitempty\"`" + `
}
`
			// Create temporary directory
			tmpDir := GinkgoT().TempDir()
			path := tmpDir + "/lifecycle.go"
			err := writeFile(path, src)
			Expect(err).NotTo(HaveOccurred())

			// Parse
			capabilities, err := ParseCapabilities(tmpDir)
			Expect(err).NotTo(HaveOccurred())
			Expect(capabilities).To(HaveLen(1))

			cap := capabilities[0]
			Expect(cap.Name).To(Equal("lifecycle"))
			Expect(cap.Methods).To(HaveLen(1))

			// Generate WASM code
			code, err := GenerateCapabilityGo(cap, "lifecycle")
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)
			Expect(codeStr).To(ContainSubstring("//go:wasmexport nd_on_init"))
			Expect(codeStr).To(ContainSubstring("type InitProvider interface"))

			// Generate stub code
			stubCode, err := GenerateCapabilityGoStub(cap, "lifecycle")
			Expect(err).NotTo(HaveOccurred())

			stubStr := string(stubCode)
			Expect(stubStr).To(ContainSubstring("//go:build !wasip1"))
			Expect(stubStr).To(ContainSubstring("func Register(_ Lifecycle) {}"))
		})
	})
})

var _ = Describe("Shared Types Generation", func() {
	It("emits a Rust types crate root with serde derives", func() {
		structs := []StructDef{
			{Name: "ArtistRef", Doc: "ArtistRef references an artist.", Fields: []FieldDef{
				{Name: "ID", Type: "string", JSONTag: "id", OmitEmpty: true},
				{Name: "Name", Type: "string", JSONTag: "name"},
			}},
		}
		code, err := GenerateSharedTypesRust(structs)
		Expect(err).NotTo(HaveOccurred())
		out := string(code)
		Expect(out).To(ContainSubstring("use serde::{Deserialize, Serialize};"))
		Expect(out).To(ContainSubstring("pub struct ArtistRef {"))
		Expect(out).To(ContainSubstring(`#[serde(rename_all = "camelCase")]`))
		Expect(out).To(ContainSubstring("pub name: String,"))
	})

	It("emits a flat Go types package with no imports", func() {
		structs := []StructDef{
			{Name: "ArtistRef", Doc: "ArtistRef references an artist.", Fields: []FieldDef{
				{Name: "ID", Type: "string", JSONTag: "id", OmitEmpty: true},
				{Name: "Name", Type: "string", JSONTag: "name"},
			}},
		}
		code, err := GenerateSharedTypesGo(structs, "types")
		Expect(err).NotTo(HaveOccurred())
		out := string(code)
		Expect(out).To(ContainSubstring("package types"))
		Expect(out).To(ContainSubstring("type ArtistRef struct {"))
		Expect(out).To(ContainSubstring("ID string `json:\"id,omitempty\"`"))
		Expect(out).To(ContainSubstring("Name string `json:\"name\"`"))
		Expect(out).NotTo(ContainSubstring("import"))
	})

	It("emits base64 serde for Vec<u8> fields in the Rust types crate", func() {
		structs := []StructDef{
			{Name: "Payload", Doc: "Payload carries raw bytes.", Fields: []FieldDef{
				{Name: "Data", Type: "[]byte", JSONTag: "data"},
			}},
		}
		code, err := GenerateSharedTypesRust(structs)
		Expect(err).NotTo(HaveOccurred())
		out := string(code)
		Expect(out).To(ContainSubstring("mod base64_bytes"))
		Expect(out).To(ContainSubstring("use base64::Engine as _"))
		Expect(out).To(ContainSubstring(`#[serde(with = "base64_bytes")]`))
	})
})

var _ = Describe("Rust Generation", func() {
	Describe("skipSerializingFunc", func() {
		It("should return Option::is_none for pointer types", func() {
			Expect(skipSerializingFunc("*string")).To(Equal("Option::is_none"))
			Expect(skipSerializingFunc("*MyStruct")).To(Equal("Option::is_none"))
		})

		It("should return the matching emptiness predicate for slice and map types", func() {
			// The predicate must match the rendered Rust type: []T -> Vec<T>, map[K]V -> HashMap<K,V>.
			Expect(skipSerializingFunc("[]string")).To(Equal("Vec::is_empty"))
			Expect(skipSerializingFunc("[]int32")).To(Equal("Vec::is_empty"))
			Expect(skipSerializingFunc("map[string]int")).To(Equal("HashMap::is_empty"))
		})

		It("should return String::is_empty for string type", func() {
			Expect(skipSerializingFunc("string")).To(Equal("String::is_empty"))
		})

		It("should return std::ops::Not::not for bool type", func() {
			Expect(skipSerializingFunc("bool")).To(Equal("std::ops::Not::not"))
		})

		It("should return is_zero_* functions for numeric types", func() {
			Expect(skipSerializingFunc("int32")).To(Equal("is_zero_i32"))
			Expect(skipSerializingFunc("uint32")).To(Equal("is_zero_u32"))
			Expect(skipSerializingFunc("int64")).To(Equal("is_zero_i64"))
			Expect(skipSerializingFunc("uint64")).To(Equal("is_zero_u64"))
			Expect(skipSerializingFunc("float32")).To(Equal("is_zero_f32"))
			Expect(skipSerializingFunc("float64")).To(Equal("is_zero_f64"))
		})

		It("should return Option::is_none for unknown types", func() {
			Expect(skipSerializingFunc("CustomType")).To(Equal("Option::is_none"))
		})
	})

	Describe("rustOutputType", func() {
		It("should convert Go primitives to Rust primitives", func() {
			Expect(rustOutputType("bool")).To(Equal("bool"))
			Expect(rustOutputType("string")).To(Equal("String"))
			Expect(rustOutputType("int")).To(Equal("i32"))
			Expect(rustOutputType("int32")).To(Equal("i32"))
			Expect(rustOutputType("int64")).To(Equal("i64"))
			Expect(rustOutputType("float32")).To(Equal("f32"))
			Expect(rustOutputType("float64")).To(Equal("f64"))
		})

		It("should strip pointer prefix", func() {
			// NOTE: This behavior is incorrect for pointer to primitives.
			// "*string" returns "string" instead of "String", which would generate
			// invalid Rust code. No current capability uses this pattern.
			// See TODO in rustOutputType function.
			Expect(rustOutputType("*string")).To(Equal("string"))
			Expect(rustOutputType("*MyStruct")).To(Equal("MyStruct"))
		})

		It("should pass through unknown types", func() {
			Expect(rustOutputType("CustomType")).To(Equal("CustomType"))
			Expect(rustOutputType("MyStruct")).To(Equal("MyStruct"))
		})
	})

	Describe("isPrimitiveRustType", func() {
		It("should return true for primitive Go types", func() {
			Expect(isPrimitiveRustType("bool")).To(BeTrue())
			Expect(isPrimitiveRustType("string")).To(BeTrue())
			Expect(isPrimitiveRustType("int")).To(BeTrue())
			Expect(isPrimitiveRustType("int32")).To(BeTrue())
			Expect(isPrimitiveRustType("int64")).To(BeTrue())
			Expect(isPrimitiveRustType("float32")).To(BeTrue())
			Expect(isPrimitiveRustType("float64")).To(BeTrue())
		})

		It("should return false for non-primitive types", func() {
			Expect(isPrimitiveRustType("MyStruct")).To(BeFalse())
			Expect(isPrimitiveRustType("CustomType")).To(BeFalse())
			Expect(isPrimitiveRustType("[]string")).To(BeFalse())
			Expect(isPrimitiveRustType("map[string]int")).To(BeFalse())
		})

		It("should handle pointer types by stripping prefix", func() {
			Expect(isPrimitiveRustType("*string")).To(BeTrue())
			Expect(isPrimitiveRustType("*int64")).To(BeTrue())
			Expect(isPrimitiveRustType("*MyStruct")).To(BeFalse())
		})
	})

	Describe("GenerateCapabilityRust", func() {
		It("should generate valid Rust code with primitive output types", func() {
			cap := Capability{
				Name:       "test",
				Interface:  "TestAgent",
				Required:   true,
				SourceFile: "test",
				Methods: []Export{
					{
						Name:       "GetBool",
						ExportName: "nd_get_bool",
						Input:      Param{Type: "BoolInput"},
						Output:     Param{Type: "bool"},
					},
					{
						Name:       "GetString",
						ExportName: "nd_get_string",
						Input:      Param{Type: "StrInput"},
						Output:     Param{Type: "string"},
					},
					{
						Name:       "GetInt",
						ExportName: "nd_get_int",
						Input:      Param{Type: "IntInput"},
						Output:     Param{Type: "int32"},
					},
				},
				Structs: []StructDef{
					{Name: "BoolInput", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
					{Name: "StrInput", Fields: []FieldDef{{Name: "Key", Type: "string", JSONTag: "key"}}},
					{Name: "IntInput", Fields: []FieldDef{{Name: "Index", Type: "int32", JSONTag: "index"}}},
				},
			}

			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Check that primitive output types are not prefixed with $crate::
			// The template should use isPrimitiveRust to determine this
			Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<bool>>"))
			Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<String>>"))
			Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<i32>>"))

			// Verify that primitive output types don't use $crate:: prefix in FnResult
			// The pattern "$crate::test::bool>" would indicate incorrect generation
			Expect(codeStr).NotTo(ContainSubstring("$crate::test::bool>"))
			Expect(codeStr).NotTo(ContainSubstring("$crate::test::String>"))
			Expect(codeStr).NotTo(ContainSubstring("$crate::test::i32>"))
		})

		It("should generate valid Rust code with struct output types", func() {
			cap := Capability{
				Name:       "metadata",
				Interface:  "MetadataAgent",
				Required:   true,
				SourceFile: "metadata",
				Methods: []Export{
					{
						Name:       "GetArtist",
						ExportName: "nd_get_artist",
						Input:      Param{Type: "ArtistInput"},
						Output:     Param{Type: "ArtistOutput"},
					},
				},
				Structs: []StructDef{
					{Name: "ArtistInput", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
					{Name: "ArtistOutput", Fields: []FieldDef{{Name: "Name", Type: "string", JSONTag: "name"}}},
				},
			}

			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Non-primitive struct types should use $crate:: prefix
			Expect(codeStr).To(ContainSubstring("$crate::metadata::ArtistOutput"))
		})

		It("should generate valid Rust code with pointer output types", func() {
			cap := Capability{
				Name:       "test",
				Interface:  "TestAgent",
				Required:   true,
				SourceFile: "test",
				Methods: []Export{
					{
						Name:       "GetOptionalStruct",
						ExportName: "nd_get_optional_struct",
						Input:      Param{Type: "Input"},
						Output:     Param{Type: "*Output"},
					},
				},
				Structs: []StructDef{
					{Name: "Input", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
					{Name: "Output", Fields: []FieldDef{{Name: "Value", Type: "string", JSONTag: "value"}}},
				},
			}

			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Pointer to struct should strip pointer and use struct type with $crate::
			Expect(codeStr).To(ContainSubstring("$crate::test::Output>"))
			// Pointer output types should NOT have Option<> wrapping - Result handles optionality
			Expect(codeStr).NotTo(ContainSubstring("Option<"))
		})

		It("translates a shared type used directly as a method input/output", func() {
			cap := Capability{
				Name: "demo", Interface: "Demo", Required: true,
				Methods: []Export{{Name: "Echo", ExportName: "nd_demo_echo",
					Input:  Param{Name: "input", Type: "types.SongRef"},
					Output: Param{Name: "output", Type: "types.SongRef"}}},
				// No structs, no aliases: the method signature references the shared type directly.
			}
			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			// The shared type must resolve to the canonical crate path, not pass through
			// as the invalid Go selector `types.SongRef`.
			Expect(out).To(ContainSubstring("nd_pdk_types::SongRef"))
			Expect(out).NotTo(ContainSubstring("types.SongRef"))
		})

		It("routes shared macro types through $crate so umbrella-crate plugins resolve them", func() {
			cap := Capability{
				Name: "demo", Interface: "Demo", Required: true,
				Methods: []Export{{Name: "Echo", ExportName: "nd_demo_echo",
					Input:  Param{Name: "input", Type: "types.SongRef"},
					Output: Param{Name: "output", Type: "types.SongRef"}}},
			}
			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			// Inside the export macro (expanded in the downstream plugin crate, which depends
			// on the umbrella nd-pdk only), the shared type must be reachable via $crate, not
			// by naming the transitive nd_pdk_types crate directly.
			Expect(out).To(ContainSubstring("extism_pdk::Json<$crate::types::SongRef>"))
			Expect(out).NotTo(ContainSubstring("extism_pdk::Json<nd_pdk_types::SongRef>"))
		})

		It("emits a deprecated Rust type alias for shared types", func() {
			cap := Capability{
				Name: "scrobbler", Interface: "Scrobbler", Required: true,
				Methods: []Export{{Name: "NowPlaying", ExportName: "nd_scrobbler_now_playing",
					Input: Param{Name: "input", Type: "NowPlayingRequest"}}},
				Structs: []StructDef{{Name: "NowPlayingRequest", Fields: []FieldDef{
					{Name: "Track", Type: "TrackInfo", JSONTag: "track"}}}},
				SharedAliases: []SharedAlias{{
					Name: "TrackInfo", Target: "types.TrackInfo",
					Doc: "Deprecated: use types.TrackInfo.",
				}},
			}
			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			// Note points authors at the umbrella path (nd-pdk re-exports nd_pdk_types as `types`);
			// the alias target stays the real crate path so it resolves inside nd-pdk-capabilities.
			Expect(out).To(ContainSubstring(`#[deprecated(note = "use nd_pdk::types::TrackInfo")]`))
			Expect(out).To(ContainSubstring("pub type TrackInfo = nd_pdk_types::TrackInfo;"))
		})

		It("keeps struct-field type when the type name is a shared alias (regression: was serde_json::Value)", func() {
			// Wrapper has a field whose type is only in SharedAliases, not Structs.
			// The field must render as `pub track: nd_pdk_types::TrackInfo` (canonical
			// path), not as the local deprecated alias and not as serde_json::Value.
			cap := Capability{
				Name: "test", Interface: "TestAgent", Required: true,
				Methods: []Export{{Name: "Submit", ExportName: "nd_test_submit",
					Input: Param{Name: "req", Type: "Wrapper"}}},
				Structs: []StructDef{{Name: "Wrapper", Fields: []FieldDef{
					{Name: "Track", Type: "TrackInfo", JSONTag: "track"}}}},
				SharedAliases: []SharedAlias{{
					Name: "TrackInfo", Target: "types.TrackInfo",
					Doc: "Deprecated: use types.TrackInfo.",
				}},
			}
			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			// Field must use the canonical nd_pdk_types:: path, not the local alias.
			Expect(out).To(ContainSubstring("nd_pdk_types::TrackInfo"))
			Expect(out).NotTo(ContainSubstring("pub track: serde_json::Value"))
			Expect(out).NotTo(ContainSubstring("pub track: TrackInfo,"))
		})

		It("renders a qualified types.X field as nd_pdk_types::X and keeps the renamed re-export", func() {
			// The capability references the shared type by its canonical qualified
			// name (types.Track) while the deprecated alias keeps the old name.
			cap := Capability{
				Name: "test", Interface: "TestAgent", Required: true,
				Methods: []Export{{Name: "Submit", ExportName: "nd_test_submit",
					Input: Param{Name: "req", Type: "Wrapper"}}},
				Structs: []StructDef{{Name: "Wrapper", Fields: []FieldDef{
					{Name: "Track", Type: "types.Track", JSONTag: "track"}}}},
				SharedAliases: []SharedAlias{{
					Name: "TrackInfo", Target: "types.Track",
					Doc: "Deprecated: use types.Track.",
				}},
			}
			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			// Field uses the canonical qualified path (resolved from the types. prefix).
			Expect(out).To(ContainSubstring("pub track: nd_pdk_types::Track,"))
			// The deprecated alias is still re-exported under its kept name.
			Expect(out).To(ContainSubstring("pub type TrackInfo = nd_pdk_types::Track;"))
			Expect(out).NotTo(ContainSubstring("pub track: serde_json::Value"))
		})

		It("should include all float types correctly", func() {
			cap := Capability{
				Name:       "test",
				Interface:  "TestAgent",
				Required:   true,
				SourceFile: "test",
				Methods: []Export{
					{
						Name:       "GetFloat32",
						ExportName: "nd_get_float32",
						Input:      Param{Type: "Input"},
						Output:     Param{Type: "float32"},
					},
					{
						Name:       "GetFloat64",
						ExportName: "nd_get_float64",
						Input:      Param{Type: "Input"},
						Output:     Param{Type: "float64"},
					},
				},
				Structs: []StructDef{
					{Name: "Input", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
				},
			}

			code, err := GenerateCapabilityRust(cap)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<f32>>"))
			Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<f64>>"))
		})
	})

	Describe("GenerateClientRust", func() {
		It("should generate Option<T> for (value, exists bool) pattern", func() {
			svc := Service{
				Name:       "Config",
				Permission: "config",
				Interface:  "ConfigService",
				Methods: []Method{
					{
						Name: "Get",
						Params: []Param{
							{Name: "key", Type: "string", JSONName: "key"},
						},
						Returns: []Param{
							{Name: "value", Type: "string", JSONName: "value"},
							{Name: "exists", Type: "bool", JSONName: "exists"},
						},
					},
				},
			}

			code, err := GenerateClientRust(svc)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Should generate Option<String> return type, not (String, bool)
			Expect(codeStr).To(ContainSubstring("Result<Option<String>, Error>"))
			Expect(codeStr).NotTo(ContainSubstring("Result<(String, bool), Error>"))

			// Should generate Some/None logic
			Expect(codeStr).To(ContainSubstring("Ok(Some("))
			Expect(codeStr).To(ContainSubstring("Ok(None)"))
		})

		It("should generate tuple for non-option multi-return", func() {
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name: "GetStats",
						Returns: []Param{
							{Name: "count", Type: "int64", JSONName: "count"},
							{Name: "size", Type: "int64", JSONName: "size"},
						},
					},
				},
			}

			code, err := GenerateClientRust(svc)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Should generate tuple return type
			Expect(codeStr).To(ContainSubstring("Result<(i64, i64), Error>"))
			Expect(codeStr).NotTo(ContainSubstring("Option<"))
		})

		It("should NOT generate Option for Has() pattern where first return is bool", func() {
			svc := Service{
				Name:       "Cache",
				Permission: "cache",
				Interface:  "CacheService",
				Methods: []Method{
					{
						Name: "Has",
						Params: []Param{
							{Name: "key", Type: "string", JSONName: "key"},
						},
						Returns: []Param{
							{Name: "exists", Type: "bool", JSONName: "exists"},
						},
					},
				},
			}

			code, err := GenerateClientRust(svc)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Should generate simple bool return, not Option
			Expect(codeStr).To(ContainSubstring("Result<bool, Error>"))
			Expect(codeStr).NotTo(ContainSubstring("Option<bool>"))
		})

		It("should generate base64 serde for Vec<u8> fields", func() {
			svc := Service{
				Name:       "Codec",
				Permission: "codec",
				Interface:  "CodecService",
				Methods: []Method{
					{
						Name:     "Encode",
						HasError: true,
						Params:   []Param{NewParam("data", "[]byte")},
						Returns:  []Param{NewParam("result", "[]byte")},
					},
				},
			}

			code, err := GenerateClientRust(svc)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Should generate base64_bytes serde module
			Expect(codeStr).To(ContainSubstring("mod base64_bytes"))
			Expect(codeStr).To(ContainSubstring("use base64::Engine as _"))

			// Should add serde(with = "base64_bytes") on Vec<u8> fields
			Expect(codeStr).To(ContainSubstring(`#[serde(with = "base64_bytes")]`))
		})

		It("resolves a shared alias used in a method param/return to its canonical crate path", func() {
			svc := Service{
				Name:       "Matcher",
				Permission: "matcher",
				Interface:  "MatcherService",
				Methods: []Method{
					{
						Name:     "MatchSongs",
						HasError: true,
						Params:   []Param{NewParam("query", "string")},
						// Return uses the deprecated alias name directly.
						Returns: []Param{NewParam("matches", "[]Track")},
					},
				},
				SharedAliases: []SharedAlias{{
					Name: "Track", Target: "types.Track",
					Def: StructDef{Name: "Track", Fields: []FieldDef{
						{Name: "Title", Type: "string", JSONTag: "title"}}},
				}},
			}

			code, err := GenerateClientRust(svc)
			Expect(err).NotTo(HaveOccurred())
			out := string(code)
			// The alias must resolve to the shared crate type; a bare `Track` is undefined
			// in nd-pdk-host and would not compile.
			Expect(out).To(ContainSubstring("nd_pdk_types::Track"))
			Expect(out).NotTo(ContainSubstring("Vec<Track>"))
		})

		It("should not generate base64 module when no byte fields", func() {
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:     "Call",
						HasError: true,
						Params:   []Param{NewParam("uri", "string")},
						Returns:  []Param{NewParam("response", "string")},
					},
				},
			}

			code, err := GenerateClientRust(svc)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			Expect(codeStr).NotTo(ContainSubstring("mod base64_bytes"))
			Expect(codeStr).NotTo(ContainSubstring("use base64"))
		})
	})

	Describe("nil-safe mock accessors", func() {
		Describe("GenerateClientGoStub", func() {
			It("guards pointer, slice and map returns so Return(nil, ...) does not panic", func() {
				svc := Service{
					Name:      "Paged",
					Interface: "PagedService",
					Methods: []Method{
						{
							Name:     "GetPage",
							HasError: true,
							Params:   []Param{NewParam("query", "string")},
							Returns:  []Param{NewParam("items", "[]Item"), NewParam("next", "*PageOptions")},
						},
						{
							Name:     "GetLabels",
							HasError: true,
							Params:   []Param{NewParam("id", "string")},
							Returns:  []Param{NewParam("labels", "map[string]string")},
						},
					},
				}

				code, err := GenerateClientGoStub(svc, "host")
				Expect(err).NotTo(HaveOccurred())
				_, err = format.Source(code)
				Expect(err).NotTo(HaveOccurred())

				codeStr := string(code)
				Expect(codeStr).To(ContainSubstring("var r0 []Item"))
				Expect(codeStr).To(ContainSubstring("var r1 *PageOptions"))
				Expect(codeStr).To(ContainSubstring("if v := args.Get(1); v != nil {"))
				Expect(codeStr).To(ContainSubstring("r1 = v.(*PageOptions)"))
				Expect(codeStr).To(ContainSubstring("return r0, r1, args.Error(2)"))
				Expect(codeStr).To(ContainSubstring("var r0 map[string]string"))
				Expect(codeStr).NotTo(ContainSubstring("args.Get(0).([]Item)"))
				Expect(codeStr).NotTo(ContainSubstring("args.Get(1).(*PageOptions)"))
			})

			It("keeps non-nilable returns as inline accessors", func() {
				svc := Service{
					Name:      "Counter",
					Interface: "CounterService",
					Methods: []Method{
						{
							Name:     "Count",
							HasError: true,
							Params:   []Param{NewParam("id", "string")},
							Returns:  []Param{NewParam("count", "int64"), NewParam("name", "string")},
						},
					},
				}

				code, err := GenerateClientGoStub(svc, "host")
				Expect(err).NotTo(HaveOccurred())

				codeStr := string(code)
				Expect(codeStr).To(ContainSubstring("return args.Get(0).(int64), args.String(1), args.Error(2)"))
				Expect(codeStr).NotTo(ContainSubstring("var r0"))
			})
		})

		Describe("GeneratePDKGoStub", func() {
			It("guards pointer and slice returns", func() {
				symbols := &PDKSymbols{
					Functions: []PDKFunc{
						{
							Name:    "NewHTTPRequest",
							Params:  []PDKParam{{Name: "method", Type: "HTTPMethod"}, {Name: "url", Type: "string"}},
							Returns: []PDKReturn{{Type: "*HTTPRequest"}},
						},
						{
							Name:    "Input",
							Returns: []PDKReturn{{Type: "[]byte"}},
						},
					},
				}

				code, err := GeneratePDKGoStub(symbols)
				Expect(err).NotTo(HaveOccurred())
				_, err = format.Source(code)
				Expect(err).NotTo(HaveOccurred())

				codeStr := string(code)
				Expect(codeStr).To(ContainSubstring("var r0 *HTTPRequest"))
				Expect(codeStr).To(ContainSubstring("r0 = v.(*HTTPRequest)"))
				Expect(codeStr).To(ContainSubstring("var r0 []byte"))
				Expect(codeStr).NotTo(ContainSubstring("return args.Get(0).(*HTTPRequest)"))
			})
		})

		Describe("Deprecated PDK functions", func() {
			symbols := &PDKSymbols{
				Functions: []PDKFunc{
					{
						Name:       "NewHTTPRequest",
						Doc:        "NewHTTPRequest returns a new `HTTPRequest`.",
						Params:     []PDKParam{{Name: "method", Type: "HTTPMethod"}, {Name: "url", Type: "string"}},
						Returns:    []PDKReturn{{Type: "*HTTPRequest"}},
						Deprecated: "Use host.HTTPSend instead.",
					},
				},
			}

			DescribeTable("emits a Deprecated paragraph after the doc line",
				func(generate func(*PDKSymbols) ([]byte, error)) {
					code, err := generate(symbols)
					Expect(err).NotTo(HaveOccurred())
					_, err = format.Source(code)
					Expect(err).NotTo(HaveOccurred())
					Expect(string(code)).To(ContainSubstring(
						"// NewHTTPRequest NewHTTPRequest returns a new `HTTPRequest`.\n//\n// Deprecated: Use host.HTTPSend instead.\nfunc NewHTTPRequest("))
				},
				Entry("WASM wrapper", GeneratePDKGo),
				Entry("native stub", GeneratePDKGoStub),
			)
		})
	})
})

func writeFile(path, content string) error {
	return os.WriteFile(path, []byte(content), 0600)
}