import { fetchEventSource } from "@microsoft/fetch-event-source"

export type Role = "user" | "assistant" | "system"

export interface ChatMessage {
    role: Role
    content: string
}

export interface ChatRequestPayload {
    user_id: string
    message: string
    session_id: string
}

type ServerEvent =
    | { type: "token"; delta: string }
    | { type: "heartbeat" }
    | { type: "error"; message: string }
    | { type: "final"; state: unknown }

// Optionale Konfiguration für Headers, URL usw.
export interface ChatOptions {
    /** Vollständige URL, z. B. "https://api.example.com/stream" */
    url: string
    /** Zusätzliche Request-Header (z. B. Auth) */
    headers?: Record<string, string>
    /** Fallback-Assistant-Fehlermeldung für die UI */
    fallbackErrorText?: string
    /** Offen lassen, wenn Tab im Hintergrund – sinnvoll für Safari */
    openWhenHidden?: boolean
    /** User-ID, wenn du sie nicht pro Aufruf übergibst */
    userId?: string
}

export class Chat {
    public messages: ChatMessage[] = []
    public isStreaming = false
    public lastFinalState: unknown = null

    private sessionId: string
    private opts: ChatOptions
    private controller: AbortController | null = null

    constructor(sessionId: string, opts: ChatOptions) {
        if (!sessionId) throw new Error("session_id ist erforderlich")
        if (!opts?.url) throw new Error("ChatOptions.url ist erforderlich")
        this.sessionId = sessionId
        this.opts = {
            fallbackErrorText: "Es ist ein Fehler aufgetreten.",
            openWhenHidden: true,
            ...opts,
        }
    }

    public abortActiveStream() {
        this.controller?.abort()
        this.controller = null
        this.isStreaming = false
    }

    /**
     * Sendet eine Benutzer-Nachricht und streamt die Assistent-Antwort in `messages`.
     * @param message Text der Benutzer-Nachricht
     * @param userId  optionaler Override der User-ID (falls nicht in opts.userId)
     */
    public async generateResponse(message: string, userId?: string): Promise<void> {
        const text = (message ?? "").trim()
        if (!text) return
        if (this.isStreaming) this.abortActiveStream()
        this.messages = [...this.messages, { role: "user", content: text }]
        const assistantIndex = this.messages.length
        this.messages = [...this.messages, { role: "assistant", content: "" }]

        const payload: ChatRequestPayload = {
            user_id: userId ?? this.opts.userId ?? "anonymous",
            message: text,
            session_id: this.sessionId,
        }

        this.controller = new AbortController()
        this.isStreaming = true
        this.lastFinalState = null

        try {
            await fetchEventSource(this.opts.url, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    ...(this.opts.headers ?? {}),
                },
                body: JSON.stringify(payload),
                signal: this.controller.signal,
                openWhenHidden: this.opts.openWhenHidden,

                onopen: async (res) => {
                    if (!res.ok) throw new Error(`HTTP ${res.status}`)
                },

                onmessage: (ev) => {
                    if (!ev.data) return
                    let parsed: ServerEvent | null = null
                    try {
                        parsed = JSON.parse(ev.data) as ServerEvent
                    } catch {
                        return
                    }

                    switch (parsed.type) {
                        case "token": {
                            const delta = parsed.delta ?? ""
                            const updated = {
                                ...this.messages[assistantIndex],
                                content: (this.messages[assistantIndex]?.content ?? "") + delta,
                            }
                            this.messages = [
                                ...this.messages.slice(0, assistantIndex),
                                updated,
                                ...this.messages.slice(assistantIndex + 1),
                            ]
                            break
                        }

                        case "heartbeat": {
                            break
                        }

                        case "error": {
                            const msg = parsed.message || this.opts.fallbackErrorText!
                            const updated = {
                                ...this.messages[assistantIndex],
                                content: (this.messages[assistantIndex]?.content || "") + `\n\n⚠️ ${msg}`,
                            }
                            this.messages = [
                                ...this.messages.slice(0, assistantIndex),
                                updated,
                                ...this.messages.slice(assistantIndex + 1),
                            ]
                            this.abortActiveStream()
                            break
                        }

                        case "final": {
                            this.lastFinalState = parsed.state
                            this.abortActiveStream()
                            break
                        }

                        default: {
                            break
                        }
                    }
                },

                onerror: (err) => {
                    const updated = {
                        ...this.messages[assistantIndex],
                        content:
                            (this.messages[assistantIndex]?.content || "") +
                            `\n\n⚠️ ${this.opts.fallbackErrorText} (${String(err)})`,
                    }
                    this.messages = [
                        ...this.messages.slice(0, assistantIndex),
                        updated,
                        ...this.messages.slice(assistantIndex + 1),
                    ]
                    throw err
                },
            })
        } finally {
            this.isStreaming = false
            this.controller = null
        }
    }
}
