/** User/Assistant schemas + Events + Cursor — extracted from message-v2.ts */

import { SessionID, MessageID, PartID } from "../core/schema"
import { Snapshot } from "@/snapshot"
import { SyncEvent } from "../../sync"
import { BusEvent } from "@/bus/bus-event"
import { Effect, Schema, Types } from "effect"
import { zod } from "@/util/effect-zod"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { ModelID, ProviderID } from "@/provider/schema"
import { _Part } from "./message-parts"
import { AssistantErrorSchema, AssistantErrorZod, _Format } from "./message-errors"
export { AssistantErrorSchema, AssistantErrorZod, _Format, _Part as Part }
export type { AssistantError } from "./message-errors"
import z from "zod"

const messageBase = { id: MessageID, sessionID: SessionID }

export const EditorContext = Schema.Struct({
  visibleFiles: Schema.optional(Schema.Array(Schema.String)), openTabs: Schema.optional(Schema.Array(Schema.String)),
  activeFile: Schema.optional(Schema.String), shell: Schema.optional(Schema.String),
})
export type EditorContext = Types.DeepMutable<Schema.Schema.Type<typeof EditorContext>>

export const User = Schema.Struct({
  ...messageBase, role: Schema.Literal("user"), time: Schema.Struct({ created: NonNegativeInt }), format: Schema.optional(_Format),
  summary: Schema.optional(Schema.Struct({ title: Schema.optional(Schema.String), body: Schema.optional(Schema.String), diffs: Schema.Array(Snapshot.FileDiff) })),
  agent: Schema.String, model: Schema.Struct({ providerID: ProviderID, modelID: ModelID, variant: Schema.optional(Schema.String) }),
  system: Schema.optional(Schema.String), tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), editorContext: Schema.optional(EditorContext),
}).annotate({ identifier: "UserMessage" }).pipe(withStatics((s) => ({ zod: zod(s) })))
export type User = Types.DeepMutable<Schema.Schema.Type<typeof User>>

export const Assistant = Schema.Struct({
  ...messageBase, role: Schema.Literal("assistant"), time: Schema.Struct({ created: NonNegativeInt, completed: Schema.optional(NonNegativeInt) }),
  error: Schema.optional(AssistantErrorSchema), parentID: MessageID, modelID: ModelID, providerID: ProviderID, mode: Schema.String, agent: Schema.String,
  path: Schema.Struct({ cwd: Schema.String, root: Schema.String }), summary: Schema.optional(Schema.Boolean), cost: Schema.Finite,
  tokens: Schema.Struct({ total: Schema.optional(NonNegativeInt), input: NonNegativeInt, output: NonNegativeInt, reasoning: NonNegativeInt, cache: Schema.Struct({ read: NonNegativeInt, write: NonNegativeInt }) }),
  structured: Schema.optional(Schema.Any), variant: Schema.optional(Schema.String), finish: Schema.optional(Schema.String),
}).annotate({ identifier: "AssistantMessage" }).pipe(withStatics((s) => ({ zod: zod(s) })))
export type Assistant = Omit<Types.DeepMutable<Schema.Schema.Type<typeof Assistant>>, "error"> & { error?: import("./message-errors").AssistantError }

const _Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "Message" })
export const Info = Object.assign(_Info, { zod: zod(_Info) as unknown as z.ZodType<User | Assistant> })
export type Info = User | Assistant

export const WithParts = Schema.Struct({ info: _Info, parts: Schema.Array(_Part) }).pipe(withStatics((s) => ({ zod: zod(s) })))
export type WithParts = { info: Info; parts: import("./message-parts").Part[] }

const UpdatedEventSchema = Schema.Struct({ sessionID: SessionID, info: _Info })
const RemovedEventSchema = Schema.Struct({ sessionID: SessionID, messageID: MessageID })
const PartUpdatedEventSchema = Schema.Struct({ sessionID: SessionID, part: _Part, time: NonNegativeInt })
const PartRemovedEventSchema = Schema.Struct({ sessionID: SessionID, messageID: MessageID, partID: PartID })

export const Event = {
  Updated: SyncEvent.define({ type: "message.updated", version: 1, aggregate: "sessionID", schema: UpdatedEventSchema }),
  Removed: SyncEvent.define({ type: "message.removed", version: 1, aggregate: "sessionID", schema: RemovedEventSchema }),
  PartUpdated: SyncEvent.define({ type: "message.part.updated", version: 1, aggregate: "sessionID", schema: PartUpdatedEventSchema }),
  PartDelta: BusEvent.define("message.part.delta", Schema.Struct({ sessionID: SessionID, messageID: MessageID, partID: PartID, field: Schema.String, delta: Schema.String })),
  PartRemoved: SyncEvent.define({ type: "message.part.removed", version: 1, aggregate: "sessionID", schema: PartRemovedEventSchema }),
}

const Cursor = Schema.Struct({ id: MessageID, time: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)) })
type Cursor = typeof Cursor.Type
const decodeCursor = Schema.decodeUnknownSync(Cursor)
export const cursor = {
  encode(input: Cursor) { return Buffer.from(JSON.stringify(input)).toString("base64url") },
  decode(input: string) { return decodeCursor(JSON.parse(Buffer.from(input, "base64url").toString("utf8"))) },
}
