import { assert, describe, it } from "@effect/vitest"
import { Duration, Effect, Schema } from "effect"
import { AiError } from "effect/unstable/ai"

describe("AiError", () => {
  describe("reason types", () => {
    describe("RateLimitError", () => {
      it("should be retryable", () => {
        const error = new AiError.RateLimitError({})
        assert.isTrue(error.isRetryable)
      })

      it("should format message with retryAfter", () => {
        const error = new AiError.RateLimitError({
          retryAfter: Duration.seconds(60)
        })
        assert.match(error.message, /Retry after/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.RateLimitError({})
        assert.strictEqual(error._tag, "RateLimitError")
      })
    })

    describe("QuotaExhaustedError", () => {
      it("should not be retryable", () => {
        const error = new AiError.QuotaExhaustedError({})
        assert.isFalse(error.isRetryable)
      })

      it("should format message correctly", () => {
        const error = new AiError.QuotaExhaustedError({})
        assert.match(error.message, /Quota exhausted/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.QuotaExhaustedError({})
        assert.strictEqual(error._tag, "QuotaExhaustedError")
      })
    })

    describe("AuthenticationError", () => {
      it("should not be retryable", () => {
        const error = new AiError.AuthenticationError({ kind: "InvalidKey" })
        assert.isFalse(error.isRetryable)
      })

      it("should format message based on kind", () => {
        const invalidKey = new AiError.AuthenticationError({ kind: "InvalidKey" })
        assert.match(invalidKey.message, /InvalidKey/)

        const expiredKey = new AiError.AuthenticationError({ kind: "ExpiredKey" })
        assert.match(expiredKey.message, /ExpiredKey/)

        const missingKey = new AiError.AuthenticationError({ kind: "MissingKey" })
        assert.match(missingKey.message, /MissingKey/)

        const insufficientPermissions = new AiError.AuthenticationError({ kind: "InsufficientPermissions" })
        assert.match(insufficientPermissions.message, /InsufficientPermissions/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.AuthenticationError({ kind: "Unknown" })
        assert.strictEqual(error._tag, "AuthenticationError")
      })
    })

    describe("ContentPolicyError", () => {
      it("should not be retryable", () => {
        const error = new AiError.ContentPolicyError({ description: "hate speech detected" })
        assert.isFalse(error.isRetryable)
      })

      it("should format message with description", () => {
        const error = new AiError.ContentPolicyError({
          description: "Input contains prohibited content"
        })
        assert.match(error.message, /Content policy violation/)
        assert.match(error.message, /Input contains prohibited content/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.ContentPolicyError({ description: "violation" })
        assert.strictEqual(error._tag, "ContentPolicyError")
      })
    })

    describe("InvalidRequestError", () => {
      it("should not be retryable", () => {
        const error = new AiError.InvalidRequestError({
          parameter: "temperature",
          constraint: "must be between 0 and 2"
        })
        assert.isFalse(error.isRetryable)
      })

      it("should format message with parameter details", () => {
        const error = new AiError.InvalidRequestError({
          parameter: "temperature",
          constraint: "must be between 0 and 2",
          description: "Value 5 is invalid"
        })
        assert.match(error.message, /parameter 'temperature'/)
        assert.match(error.message, /must be between 0 and 2/)
        assert.match(error.message, /Value 5 is invalid/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.InvalidRequestError({})
        assert.strictEqual(error._tag, "InvalidRequestError")
      })
    })

    describe("InternalProviderError", () => {
      it("should be retryable", () => {
        const error = new AiError.InternalProviderError({
          description: "Server error"
        })
        assert.isTrue(error.isRetryable)
      })

      it("should format message with description", () => {
        const error = new AiError.InternalProviderError({
          description: "Unexpected server failure"
        })
        assert.match(error.message, /Internal provider error/)
        assert.match(error.message, /Unexpected server failure/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.InternalProviderError({
          description: "Server error"
        })
        assert.strictEqual(error._tag, "InternalProviderError")
      })
    })

    describe("InvalidOutputError", () => {
      it("should be retryable", () => {
        const error = new AiError.InvalidOutputError({
          description: "Invalid JSON structure"
        })
        assert.isTrue(error.isRetryable)
      })

      it("should format message with description", () => {
        const error = new AiError.InvalidOutputError({
          description: "Expected string but got number"
        })
        assert.match(error.message, /Invalid output/)
        assert.match(error.message, /Expected string but got number/)
      })

      it.effect("should create from SchemaError", () =>
        Effect.gen(function*() {
          const TestSchema = Schema.Struct({ name: Schema.String })
          const result = yield* Effect.exit(
            Schema.decodeUnknownEffect(TestSchema)({ name: 123 })
          )

          if (result._tag === "Failure") {
            const cause = result.cause
            if ("error" in cause && Schema.isSchemaError(cause.error)) {
              const parseError = AiError.InvalidOutputError.fromSchemaError(cause.error)
              assert.strictEqual(parseError._tag, "InvalidOutputError")
              assert.isString(parseError.description)
            }
          }
        }))

      it("should have _tag set correctly", () => {
        const error = new AiError.InvalidOutputError({
          description: "Test error"
        })
        assert.strictEqual(error._tag, "InvalidOutputError")
      })
    })

    describe("UnknownError", () => {
      it("should not be retryable", () => {
        const error = new AiError.UnknownError({})
        assert.isFalse(error.isRetryable)
      })

      it("should format message with description", () => {
        const error = new AiError.UnknownError({
          description: "Something unexpected happened"
        })
        assert.strictEqual(error.message, "Something unexpected happened")
      })

      it("should use default message without description", () => {
        const error = new AiError.UnknownError({})
        assert.strictEqual(error.message, "Unknown error")
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.UnknownError({})
        assert.strictEqual(error._tag, "UnknownError")
      })
    })

    describe("ToolNotFoundError", () => {
      it("should be retryable", () => {
        const error = new AiError.ToolNotFoundError({
          toolName: "UnknownTool",
          availableTools: ["GetWeather", "GetTime"]
        })
        assert.isTrue(error.isRetryable)
      })

      it("should format message with available tools", () => {
        const error = new AiError.ToolNotFoundError({
          toolName: "UnknownTool",
          availableTools: ["GetWeather", "GetTime"]
        })
        assert.match(error.message, /Tool 'UnknownTool' not found/)
        assert.match(error.message, /Available tools: GetWeather, GetTime/)
      })

      it("should format message with no available tools", () => {
        const error = new AiError.ToolNotFoundError({
          toolName: "UnknownTool",
          availableTools: []
        })
        assert.match(error.message, /Available tools: none/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.ToolNotFoundError({
          toolName: "Test",
          availableTools: []
        })
        assert.strictEqual(error._tag, "ToolNotFoundError")
      })
    })

    describe("ToolParameterValidationError", () => {
      it("should be retryable", () => {
        const error = new AiError.ToolParameterValidationError({
          toolName: "GetWeather",
          toolParams: { location: "NYC" },
          description: "Expected string"
        })
        assert.isTrue(error.isRetryable)
      })

      it("should format message with tool name", () => {
        const error = new AiError.ToolParameterValidationError({
          toolName: "GetWeather",
          toolParams: { location: "NYC" },
          description: "Expected string"
        })
        assert.match(error.message, /Invalid parameters for tool 'GetWeather'/)
      })

      it("should format message with validation message", () => {
        const error = new AiError.ToolParameterValidationError({
          toolName: "GetWeather",
          toolParams: { location: 123 },
          description: "Expected string, got number"
        })
        assert.match(error.message, /Expected string, got number/)
      })

      it("should store tool params", () => {
        const params = { location: 123 }
        const error = new AiError.ToolParameterValidationError({
          toolName: "GetWeather",
          toolParams: params,
          description: "Expected string"
        })
        assert.deepStrictEqual(error.toolParams, params)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.ToolParameterValidationError({
          toolName: "Test",
          toolParams: {},
          description: "Error"
        })
        assert.strictEqual(error._tag, "ToolParameterValidationError")
      })
    })

    describe("InvalidToolResultError", () => {
      it("should not be retryable", () => {
        const error = new AiError.InvalidToolResultError({
          toolName: "GetWeather",
          description: "missing required field"
        })
        assert.isFalse(error.isRetryable)
      })

      it("should format message with tool name", () => {
        const error = new AiError.InvalidToolResultError({
          toolName: "GetWeather",
          description: "missing required field"
        })
        assert.match(error.message, /Tool 'GetWeather' returned invalid result/)
      })

      it("should format message with description", () => {
        const error = new AiError.InvalidToolResultError({
          toolName: "GetWeather",
          description: "missing 'temperature' field"
        })
        assert.match(error.message, /missing 'temperature' field/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.InvalidToolResultError({
          toolName: "Test",
          description: "invalid result"
        })
        assert.strictEqual(error._tag, "InvalidToolResultError")
      })
    })

    describe("ToolResultEncodingError", () => {
      it("should not be retryable", () => {
        const error = new AiError.ToolResultEncodingError({
          toolName: "GetWeather",
          toolResult: { temp: 72 },
          description: "Cannot encode"
        })
        assert.isFalse(error.isRetryable)
      })

      it("should format message with tool name", () => {
        const error = new AiError.ToolResultEncodingError({
          toolName: "GetWeather",
          toolResult: { temp: 72 },
          description: "Cannot encode"
        })
        assert.match(error.message, /Failed to encode result for tool 'GetWeather'/)
      })

      it("should format message with validation message", () => {
        const error = new AiError.ToolResultEncodingError({
          toolName: "GetWeather",
          toolResult: { circular: "ref" },
          description: "Cannot encode circular reference"
        })
        assert.match(error.message, /Cannot encode circular reference/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.ToolResultEncodingError({
          toolName: "Test",
          toolResult: {},
          description: "Error"
        })
        assert.strictEqual(error._tag, "ToolResultEncodingError")
      })
    })

    describe("ToolConfigurationError", () => {
      it("should not be retryable", () => {
        const error = new AiError.ToolConfigurationError({
          toolName: "OpenAiCodeInterpreter",
          description: "Invalid container ID format"
        })
        assert.isFalse(error.isRetryable)
      })

      it("should format message with tool name", () => {
        const error = new AiError.ToolConfigurationError({
          toolName: "OpenAiCodeInterpreter",
          description: "Invalid container ID format"
        })
        assert.match(error.message, /Invalid configuration for tool 'OpenAiCodeInterpreter'/)
      })

      it("should format message with description", () => {
        const error = new AiError.ToolConfigurationError({
          toolName: "OpenAiWebSearch",
          description: "search_context_size must be between 1 and 10"
        })
        assert.match(error.message, /search_context_size must be between 1 and 10/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.ToolConfigurationError({
          toolName: "Test",
          description: "Error"
        })
        assert.strictEqual(error._tag, "ToolConfigurationError")
      })
    })

    describe("StructuredOutputError", () => {
      it("should be retryable", () => {
        const error = new AiError.StructuredOutputError({
          description: "Invalid JSON structure",
          responseText: "{\"invalid\":}"
        })
        assert.isTrue(error.isRetryable)
      })

      it("should format message with description", () => {
        const error = new AiError.StructuredOutputError({
          description: "Expected a valid JSON object",
          responseText: "{\"test\":true}"
        })
        assert.match(error.message, /Structured output validation failed/)
        assert.match(error.message, /Expected a valid JSON object/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.StructuredOutputError({
          description: "Test error",
          responseText: "{\"foo\":\"bar\"}"
        })
        assert.strictEqual(error._tag, "StructuredOutputError")
      })

      it.effect("should create from SchemaError", () =>
        Effect.gen(function*() {
          const TestSchema = Schema.Struct({ name: Schema.String })
          const result = yield* Effect.exit(
            Schema.decodeUnknownEffect(TestSchema)({ name: 123 })
          )

          if (result._tag === "Failure") {
            const cause = result.cause
            if ("error" in cause && Schema.isSchemaError(cause.error)) {
              const parseError = AiError.StructuredOutputError.fromSchemaError(cause.error, "{\"name\":123}")
              assert.strictEqual(parseError._tag, "StructuredOutputError")
              assert.isString(parseError.description)
              assert.strictEqual(parseError.responseText, "{\"name\":123}")
            }
          }
        }))
    })

    describe("UnsupportedSchemaError", () => {
      it("should not be retryable", () => {
        const error = new AiError.UnsupportedSchemaError({
          description: "Unions are not supported"
        })
        assert.isFalse(error.isRetryable)
      })

      it("should format message with description", () => {
        const error = new AiError.UnsupportedSchemaError({
          description: "Unions are not supported in Anthropic structured output"
        })
        assert.match(error.message, /Unsupported schema/)
        assert.match(error.message, /Unions are not supported in Anthropic structured output/)
      })

      it("should have _tag set correctly", () => {
        const error = new AiError.UnsupportedSchemaError({
          description: "Test error"
        })
        assert.strictEqual(error._tag, "UnsupportedSchemaError")
      })
    })
  })

  describe("delegation", () => {
    it("should delegate isRetryable to reason", () => {
      const retryableError = new AiError.AiError({
        module: "OpenAI",
        method: "completion",
        reason: new AiError.RateLimitError({})
      })
      assert.isTrue(retryableError.isRetryable)

      const nonRetryableError = new AiError.AiError({
        module: "OpenAI",
        method: "completion",
        reason: new AiError.AuthenticationError({ kind: "InvalidKey" })
      })
      assert.isFalse(nonRetryableError.isRetryable)
    })

    it("should delegate retryAfter to reason", () => {
      const errorWithRetryAfter = new AiError.AiError({
        module: "OpenAI",
        method: "completion",
        reason: new AiError.RateLimitError({
          retryAfter: Duration.seconds(60)
        })
      })
      assert.isDefined(errorWithRetryAfter.retryAfter)

      const errorWithoutRetryAfter = new AiError.AiError({
        module: "OpenAI",
        method: "completion",
        reason: new AiError.AuthenticationError({ kind: "InvalidKey" })
      })
      assert.isUndefined(errorWithoutRetryAfter.retryAfter)
    })

    it("should format message with module, method, and reason", () => {
      const error = new AiError.AiError({
        module: "OpenAI",
        method: "completion",
        reason: new AiError.RateLimitError({})
      })
      assert.match(error.message, /OpenAI\.completion:/)
      assert.match(error.message, /Rate limit exceeded/)
    })

    it("should have _tag set correctly", () => {
      const error = new AiError.AiError({
        module: "Test",
        method: "test",
        reason: new AiError.UnknownError({})
      })
      assert.strictEqual(error._tag, "AiError")
    })
  })

  describe("constructors", () => {
    describe("make", () => {
      it("should create AiError", () => {
        const error = AiError.make({
          module: "OpenAI",
          method: "completion",
          reason: new AiError.RateLimitError({})
        })
        assert.strictEqual(error._tag, "AiError")
        assert.strictEqual(error.module, "OpenAI")
        assert.strictEqual(error.method, "completion")
        assert.strictEqual(error.reason._tag, "RateLimitError")
      })
    })

    describe("reasonFromHttpStatus", () => {
      it("should map 400 to InvalidRequestError", () => {
        const reason = AiError.reasonFromHttpStatus({ status: 400 })
        assert.strictEqual(reason._tag, "InvalidRequestError")
      })

      it("should map 401 to AuthenticationError with InvalidKey", () => {
        const reason = AiError.reasonFromHttpStatus({ status: 401 })
        assert.strictEqual(reason._tag, "AuthenticationError")
        if (reason._tag === "AuthenticationError") {
          assert.strictEqual(reason.kind, "InvalidKey")
        }
      })

      it("should map 403 to AuthenticationError with InsufficientPermissions", () => {
        const reason = AiError.reasonFromHttpStatus({ status: 403 })
        assert.strictEqual(reason._tag, "AuthenticationError")
        if (reason._tag === "AuthenticationError") {
          assert.strictEqual(reason.kind, "InsufficientPermissions")
        }
      })

      it("should map 429 to RateLimitError", () => {
        const reason = AiError.reasonFromHttpStatus({ status: 429 })
        assert.strictEqual(reason._tag, "RateLimitError")
      })

      it("should map 5xx to InternalProviderError", () => {
        assert.strictEqual(
          AiError.reasonFromHttpStatus({ status: 500 })._tag,
          "InternalProviderError"
        )
        assert.strictEqual(
          AiError.reasonFromHttpStatus({ status: 502 })._tag,
          "InternalProviderError"
        )
        assert.strictEqual(
          AiError.reasonFromHttpStatus({ status: 503 })._tag,
          "InternalProviderError"
        )
      })

      it("should map unknown status to UnknownError", () => {
        const reason = AiError.reasonFromHttpStatus({ status: 418 })
        assert.strictEqual(reason._tag, "UnknownError")
      })
    })
  })

  describe("type guards", () => {
    describe("isAiError", () => {
      it("should return true for AiError", () => {
        const error = new AiError.AiError({
          module: "Test",
          method: "test",
          reason: new AiError.UnknownError({})
        })
        assert.isTrue(AiError.isAiError(error))
      })

      it("should return false for non-AiError values", () => {
        assert.isFalse(AiError.isAiError(new Error("regular error")))
        assert.isFalse(AiError.isAiError(null))
        assert.isFalse(AiError.isAiError(undefined))
        assert.isFalse(AiError.isAiError({ _tag: "FakeError" }))
      })

      it("should return false for reason types", () => {
        const networkError = new AiError.NetworkError({
          reason: "TransportError",
          request: {
            method: "GET",
            url: "https://example.com",
            urlParams: [],
            hash: undefined,
            headers: {}
          }
        })
        assert.isFalse(AiError.isAiError(networkError))
      })
    })
  })

  describe("supporting schemas", () => {
    describe("ProviderMetadata", () => {
      it.effect("should encode and decode roundtrip", () =>
        Effect.gen(function*() {
          const metadata = {
            name: "OpenAI",
            errorCode: "rate_limit_exceeded",
            errorType: "rate_limit",
            requestId: "req_123abc"
          }
          const encoded = yield* Schema.encodeEffect(AiError.ProviderMetadata)(metadata)
          const decoded = yield* Schema.decodeEffect(AiError.ProviderMetadata)(encoded)
          assert.deepStrictEqual(decoded, metadata)
        }))
    })

    describe("UsageInfo", () => {
      it.effect("should encode and decode roundtrip", () =>
        Effect.gen(function*() {
          const usage = {
            promptTokens: 100,
            completionTokens: 50,
            totalTokens: 150
          }
          const encoded = yield* Schema.encodeEffect(AiError.UsageInfo)(usage)
          const decoded = yield* Schema.decodeEffect(AiError.UsageInfo)(encoded)
          assert.deepStrictEqual(decoded, usage)
        }))
    })

    describe("HttpContext", () => {
      it.effect("should encode and decode roundtrip", () =>
        Effect.gen(function*() {
          const context = {
            request: {
              method: "POST" as const,
              url: "https://api.example.com/v1/chat",
              urlParams: [["model", "gpt-4"]] as Array<[string, string]>,
              hash: undefined,
              headers: { "Content-Type": "application/json" }
            },
            response: {
              status: 200,
              headers: { "Content-Type": "application/json" }
            },
            body: "{\"result\": \"success\"}"
          }
          const encoded = yield* Schema.encodeEffect(AiError.HttpContext)(context)
          const decoded = yield* Schema.decodeEffect(AiError.HttpContext)(encoded)
          assert.deepStrictEqual(decoded, context)
        }))
    })
  })

  describe("schema roundtrip", () => {
    it.effect("RateLimitError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.RateLimitError({
          retryAfter: Duration.seconds(60)
        })
        const encoded = yield* Schema.encodeEffect(AiError.RateLimitError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.RateLimitError)(encoded)
        assert.strictEqual(decoded._tag, "RateLimitError")
        assert.isDefined(decoded.retryAfter)
      }))

    it.effect("AiErrorReason union roundtrip", () =>
      Effect.gen(function*() {
        const rateLimitError: AiError.AiErrorReason = new AiError.RateLimitError({})
        const encoded = yield* Schema.encodeEffect(AiError.AiErrorReason)(rateLimitError)
        const decoded = yield* Schema.decodeEffect(AiError.AiErrorReason)(encoded)
        assert.strictEqual(decoded._tag, "RateLimitError")

        const authError: AiError.AiErrorReason = new AiError.AuthenticationError({
          kind: "ExpiredKey"
        })
        const authEncoded = yield* Schema.encodeEffect(AiError.AiErrorReason)(authError)
        const authDecoded = yield* Schema.decodeEffect(AiError.AiErrorReason)(authEncoded)
        assert.strictEqual(authDecoded._tag, "AuthenticationError")
      }))

    it.effect("AiError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.AiError({
          module: "OpenAI",
          method: "completion",
          reason: new AiError.RateLimitError({})
        })
        const encoded = yield* Schema.encodeEffect(AiError.AiError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.AiError)(encoded)
        assert.strictEqual(decoded._tag, "AiError")
        assert.strictEqual(decoded.module, "OpenAI")
        assert.strictEqual(decoded.method, "completion")
        assert.strictEqual(decoded.reason._tag, "RateLimitError")
      }))

    it.effect("ToolNotFoundError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.ToolNotFoundError({
          toolName: "UnknownTool",
          availableTools: ["GetWeather", "GetTime"]
        })
        const encoded = yield* Schema.encodeEffect(AiError.ToolNotFoundError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.ToolNotFoundError)(encoded)
        assert.strictEqual(decoded._tag, "ToolNotFoundError")
        assert.strictEqual(decoded.toolName, "UnknownTool")
        assert.deepStrictEqual(decoded.availableTools, ["GetWeather", "GetTime"])
      }))

    it.effect("ToolParameterValidationError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.ToolParameterValidationError({
          toolName: "GetWeather",
          toolParams: { location: 123 },
          description: "Expected string"
        })
        const encoded = yield* Schema.encodeEffect(AiError.ToolParameterValidationError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.ToolParameterValidationError)(encoded)
        assert.strictEqual(decoded._tag, "ToolParameterValidationError")
        assert.strictEqual(decoded.toolName, "GetWeather")
        assert.strictEqual(decoded.description, "Expected string")
      }))

    it.effect("InvalidToolResultError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.InvalidToolResultError({
          toolName: "GetWeather",
          description: "missing required field"
        })
        const encoded = yield* Schema.encodeEffect(AiError.InvalidToolResultError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.InvalidToolResultError)(encoded)
        assert.strictEqual(decoded._tag, "InvalidToolResultError")
        assert.strictEqual(decoded.toolName, "GetWeather")
        assert.strictEqual(decoded.description, "missing required field")
      }))

    it.effect("ToolResultEncodingError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.ToolResultEncodingError({
          toolName: "GetWeather",
          toolResult: { temp: 72 },
          description: "Circular reference"
        })
        const encoded = yield* Schema.encodeEffect(AiError.ToolResultEncodingError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.ToolResultEncodingError)(encoded)
        assert.strictEqual(decoded._tag, "ToolResultEncodingError")
        assert.strictEqual(decoded.toolName, "GetWeather")
        assert.strictEqual(decoded.description, "Circular reference")
      }))

    it.effect("ToolConfigurationError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.ToolConfigurationError({
          toolName: "OpenAiCodeInterpreter",
          description: "Invalid container ID format"
        })
        const encoded = yield* Schema.encodeEffect(AiError.ToolConfigurationError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.ToolConfigurationError)(encoded)
        assert.strictEqual(decoded._tag, "ToolConfigurationError")
        assert.strictEqual(decoded.toolName, "OpenAiCodeInterpreter")
        assert.strictEqual(decoded.description, "Invalid container ID format")
      }))

    it.effect("StructuredOutputError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.StructuredOutputError({
          description: "Invalid JSON structure",
          responseText: "{\"invalid\":}"
        })
        const encoded = yield* Schema.encodeEffect(AiError.StructuredOutputError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.StructuredOutputError)(encoded)
        assert.strictEqual(decoded._tag, "StructuredOutputError")
        assert.strictEqual(decoded.description, "Invalid JSON structure")
        assert.strictEqual(decoded.responseText, "{\"invalid\":}")
      }))

    it.effect("UnsupportedSchemaError roundtrip", () =>
      Effect.gen(function*() {
        const error = new AiError.UnsupportedSchemaError({
          description: "Unions are not supported"
        })
        const encoded = yield* Schema.encodeEffect(AiError.UnsupportedSchemaError)(error)
        const decoded = yield* Schema.decodeEffect(AiError.UnsupportedSchemaError)(encoded)
        assert.strictEqual(decoded._tag, "UnsupportedSchemaError")
        assert.strictEqual(decoded.description, "Unions are not supported")
      }))

    it.effect("AiErrorReason union with tool errors roundtrip", () =>
      Effect.gen(function*() {
        const toolNotFound: AiError.AiErrorReason = new AiError.ToolNotFoundError({
          toolName: "Unknown",
          availableTools: ["A", "B"]
        })
        const encoded = yield* Schema.encodeEffect(AiError.AiErrorReason)(toolNotFound)
        const decoded = yield* Schema.decodeEffect(AiError.AiErrorReason)(encoded)
        assert.strictEqual(decoded._tag, "ToolNotFoundError")

        const paramError: AiError.AiErrorReason = new AiError.ToolParameterValidationError({
          toolName: "Test",
          toolParams: {},
          description: "Error"
        })
        const paramEncoded = yield* Schema.encodeEffect(AiError.AiErrorReason)(paramError)
        const paramDecoded = yield* Schema.decodeEffect(AiError.AiErrorReason)(paramEncoded)
        assert.strictEqual(paramDecoded._tag, "ToolParameterValidationError")
      }))
  })
})
