/** Error types + fromError + OutputFormat — extracted from message-v2.ts */

import { APICallError, LoadAPIKeyError } from "ai"
import { NamedError } from "@saeeol/core/util/error"
import * as ProviderError from "@/provider/error"
import { SessionNetwork } from "../core/network"
import { CodexAuthExpiredError } from "@/saeeol/provider/codex-refresh"
import { Effect, Schema } from "effect"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt } from "@/util/schema"
import { namedSchemaError } from "@/util/named-schema-error"
import { errorMessage } from "@/util/error"
import { ProviderID } from "@/provider/schema"
import type { Assistant } from "./message-types"

interface FetchDecompressionError extends Error { code: "ZlibError"; errno: number; path: string }

export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {})
export const AbortedError = namedSchemaError("MessageAbortedError", { message: Schema.String })
export const StructuredOutputError = namedSchemaError("StructuredOutputError", { message: Schema.String, retries: NonNegativeInt })
export const AuthError = namedSchemaError("ProviderAuthError", { providerID: Schema.String, message: Schema.String })
export const APIError = namedSchemaError("APIError", {
  message: Schema.String, statusCode: Schema.optional(NonNegativeInt), isRetryable: Schema.Boolean,
  responseHeaders: Schema.optional(Schema.Record(Schema.String, Schema.String)), responseBody: Schema.optional(Schema.String),
  metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)),
})
export type APIError = import("zod").infer<typeof APIError.Schema>
export const ContextOverflowError = namedSchemaError("ContextOverflowError", { message: Schema.String, responseBody: Schema.optional(Schema.String) })

export class OutputFormatText extends Schema.Class<OutputFormatText>("OutputFormatText")({ type: Schema.Literal("text") }) { static readonly zod = zod(this) }
export class OutputFormatJsonSchema extends Schema.Class<OutputFormatJsonSchema>("OutputFormatJsonSchema")({
  type: Schema.Literal("json_schema"), schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }),
  retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))),
}) { static readonly zod = zod(this) }

const _Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({ discriminator: "type", identifier: "OutputFormat" })
export { _Format }
export const Format = Object.assign(_Format, { zod: zod(_Format) })
export type OutputFormat = Schema.Schema.Type<typeof _Format>
import z from "zod"
const AssistantErrorZod = z.discriminatedUnion("name", [
  AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema, AbortedError.Schema,
  StructuredOutputError.Schema, ContextOverflowError.Schema, APIError.Schema,
])
export type AssistantError = z.infer<typeof AssistantErrorZod>
export { AssistantErrorZod }
export const AssistantErrorSchema = Schema.Union([
  AuthError.EffectSchema,
  Schema.Struct({ name: Schema.Literal("UnknownError"), data: Schema.Struct({ message: Schema.String }) }).annotate({ identifier: "UnknownError" }),
  OutputLengthError.EffectSchema, AbortedError.EffectSchema, StructuredOutputError.EffectSchema, ContextOverflowError.EffectSchema, APIError.EffectSchema,
]).annotate({ discriminator: "name" })

export function fromError(e: unknown, ctx: { providerID: ProviderID; aborted?: boolean }): NonNullable<Assistant["error"]> {
  switch (true) {
    case e instanceof DOMException && e.name === "AbortError": return new AbortedError({ message: e.message }, { cause: e }).toObject()
    case OutputLengthError.isInstance(e): return e
    case LoadAPIKeyError.isInstance(e): return new AuthError({ providerID: ctx.providerID, message: e.message }, { cause: e }).toObject()
    case e instanceof CodexAuthExpiredError: return new AuthError({ providerID: "openai", message: e.message }, { cause: e }).toObject()
    case SessionNetwork.disconnected(e):
      return new APIError({ message: SessionNetwork.message(e), isRetryable: true, metadata: { code: (e as import("bun").SystemError).code ?? "", syscall: (e as import("bun").SystemError).syscall ?? "", message: (e as import("bun").SystemError).message ?? "" } }, { cause: e }).toObject()
    case e instanceof Error && (e as FetchDecompressionError).code === "ZlibError":
      if (ctx.aborted) return new AbortedError({ message: e.message }, { cause: e }).toObject()
      return new APIError({ message: "Response decompression failed", isRetryable: true, metadata: { code: (e as FetchDecompressionError).code, message: e.message } }, { cause: e }).toObject()
    case APICallError.isInstance(e):
      const parsed = ProviderError.parseAPICallError({ providerID: ctx.providerID, error: e })
      if (parsed.type === "context_overflow") return new ContextOverflowError({ message: parsed.message, responseBody: parsed.responseBody }, { cause: e }).toObject()
      return new APIError({ message: parsed.message, statusCode: parsed.statusCode, isRetryable: parsed.isRetryable, responseHeaders: parsed.responseHeaders, responseBody: parsed.responseBody, metadata: parsed.metadata }, { cause: e }).toObject()
    case e instanceof Error: return new NamedError.Unknown({ message: errorMessage(e) }, { cause: e }).toObject()
    default:
      try {
        const parsed = ProviderError.parseStreamError(e)
        if (parsed) {
          if (parsed.type === "context_overflow") return new ContextOverflowError({ message: parsed.message, responseBody: parsed.responseBody }, { cause: e }).toObject()
          return new APIError({ message: parsed.message, isRetryable: parsed.isRetryable, responseBody: parsed.responseBody }, { cause: e }).toObject()
        }
      } catch {}
      return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject()
  }
}