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"`))
		})
	})

	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("Python type and name helpers", func() {
		Describe("ToPythonType", func() {
			It("should map Go types to Python types", func() {
				Expect(ToPythonType("string")).To(Equal("str"))
				Expect(ToPythonType("int")).To(Equal("int"))
				Expect(ToPythonType("int32")).To(Equal("int"))
				Expect(ToPythonType("int64")).To(Equal("int"))
				Expect(ToPythonType("float32")).To(Equal("float"))
				Expect(ToPythonType("float64")).To(Equal("float"))
				Expect(ToPythonType("bool")).To(Equal("bool"))
				Expect(ToPythonType("[]byte")).To(Equal("bytes"))
				Expect(ToPythonType("unknown")).To(Equal("Any"))
			})
		})

		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("Method.PythonFunctionName", func() {
			It("should generate snake_case function name with service prefix", func() {
				m := Method{Name: "GetString"}
				Expect(m.PythonFunctionName("cache")).To(Equal("cache_get_string"))
			})
		})

		Describe("Param.PythonType", func() {
			It("should return Python type for parameter", func() {
				p := NewParam("value", "string")
				Expect(p.PythonType()).To(Equal("str"))
			})
		})

		Describe("Param.PythonName", func() {
			It("should return snake_case name for parameter", func() {
				p := NewParam("ttlSeconds", "int64")
				Expect(p.PythonName()).To(Equal("ttl_seconds"))
			})
		})
	})

	Describe("GenerateClientPython", func() {
		It("should generate valid Python code for a simple service", func() {
			svc := Service{
				Name:       "SubsonicAPI",
				Permission: "subsonicapi",
				Interface:  "SubsonicAPIService",
				Methods: []Method{
					{
						Name:     "Call",
						HasError: true,
						Params:   []Param{NewParam("uri", "string")},
						Returns:  []Param{NewParam("responseJSON", "string")},
					},
				},
			}

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

			codeStr := string(code)

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

			// Check for imports
			Expect(codeStr).To(ContainSubstring("from dataclasses import dataclass"))
			Expect(codeStr).To(ContainSubstring("import extism"))
			Expect(codeStr).To(ContainSubstring("import json"))

			// Check for exception class
			Expect(codeStr).To(ContainSubstring("class HostFunctionError(Exception):"))

			// Check for raw import function
			Expect(codeStr).To(ContainSubstring(`@extism.import_fn("extism:host/user", "subsonicapi_call")`))
			Expect(codeStr).To(ContainSubstring("def _subsonicapi_call(offset: int) -> int:"))

			// Check for wrapper function with type hints
			Expect(codeStr).To(ContainSubstring("def subsonicapi_call(uri: str) -> str:"))

			// Check for error handling
			Expect(codeStr).To(ContainSubstring("raise HostFunctionError(response["))
		})

		It("should generate dataclass for multi-value returns", func() {
			svc := Service{
				Name:       "Cache",
				Permission: "cache",
				Interface:  "CacheService",
				Methods: []Method{
					{
						Name:     "GetString",
						HasError: true,
						Params:   []Param{NewParam("key", "string")},
						Returns: []Param{
							NewParam("value", "string"),
							NewParam("exists", "bool"),
						},
					},
				},
			}

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

			codeStr := string(code)

			// Check for dataclass
			Expect(codeStr).To(ContainSubstring("@dataclass"))
			Expect(codeStr).To(ContainSubstring("class CacheGetStringResult:"))
			Expect(codeStr).To(ContainSubstring("value: str"))
			Expect(codeStr).To(ContainSubstring("exists: bool"))

			// Check that function returns dataclass
			Expect(codeStr).To(ContainSubstring("def cache_get_string(key: str) -> CacheGetStringResult:"))
			Expect(codeStr).To(ContainSubstring("return CacheGetStringResult("))
		})

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

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

			codeStr := string(code)

			// Function with no params
			Expect(codeStr).To(ContainSubstring("def test_no_params() -> str:"))
			// Empty request
			Expect(codeStr).To(ContainSubstring(`request_bytes = b"{}"`))
		})

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

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

			codeStr := string(code)

			// Function returns None
			Expect(codeStr).To(ContainSubstring("def test_no_return(input: str) -> None:"))
		})

		It("should generate correct Python defaults for different types", func() {
			svc := Service{
				Name:       "Test",
				Permission: "test",
				Interface:  "TestService",
				Methods: []Method{
					{
						Name:     "AllTypes",
						HasError: true,
						Returns: []Param{
							NewParam("strVal", "string"),
							NewParam("intVal", "int64"),
							NewParam("floatVal", "float64"),
							NewParam("boolVal", "bool"),
						},
					},
				},
			}

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

			codeStr := string(code)

			// Check defaults in response.get() calls
			Expect(codeStr).To(ContainSubstring(`response.get("strVal", "")`))
			Expect(codeStr).To(ContainSubstring(`response.get("intVal", 0)`))
			Expect(codeStr).To(ContainSubstring(`response.get("floatVal", 0.0)`))
			Expect(codeStr).To(ContainSubstring(`response.get("boolVal", False)`))
		})

		It("should not import base64 for non-byte services", 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 := GenerateClientPython(svc)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			Expect(codeStr).NotTo(ContainSubstring("import base64"))
		})

		It("should generate base64 encoding/decoding for byte 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 := GenerateClientPython(svc)
			Expect(err).NotTo(HaveOccurred())

			codeStr := string(code)

			// Should import base64
			Expect(codeStr).To(ContainSubstring("import base64"))

			// Should base64-encode byte params in request
			Expect(codeStr).To(ContainSubstring(`base64.b64encode(data).decode("ascii")`))

			// Should base64-decode byte returns in response
			Expect(codeStr).To(ContainSubstring(`base64.b64decode(response.get("result", ""))`))
		})
	})

	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)"))

			// Check []byte return uses args.Get(0).([]byte)
			Expect(codeStr).To(ContainSubstring("args.Get(0).([]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"`))
		})
	})

	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("Rust Generation", func() {
	Describe("skipSerializingFunc", func() {
		It("should return Option::is_none for pointer, slice, and map types", func() {
			Expect(skipSerializingFunc("*string")).To(Equal("Option::is_none"))
			Expect(skipSerializingFunc("*MyStruct")).To(Equal("Option::is_none"))
			Expect(skipSerializingFunc("[]string")).To(Equal("Option::is_none"))
			Expect(skipSerializingFunc("[]int32")).To(Equal("Option::is_none"))
			Expect(skipSerializingFunc("map[string]int")).To(Equal("Option::is_none"))
		})

		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("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("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"))
		})
	})
})

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