import { derived, get, writable, type Readable } from "svelte/store"
import { ConnectorLifecycleEvents, createVoiceConnector, WS_URL } from "./helper"
import type { WerkRealtimeConnector } from "./helper"

export type VoiceStatus = "idle" | "connecting" | "connected" | "error"

interface VoicebotPreviewController {
    status: Readable<VoiceStatus>
    errorMessage: Readable<string>
    statusHint: Readable<string>
    setup: () => void
    teardown: () => void
    start: () => Promise<void>
    stop: (silent?: boolean) => Promise<void>
    toggle: () => Promise<void>
}

const isBrowser = typeof window !== "undefined"

const extractErrorMessage = (err: unknown, fallback: string) => {
    if (err instanceof DOMException) {
        if (err.name === "NotAllowedError") return "Zugriff auf das Mikrofon wurde verweigert."
        if (err.name === "NotFoundError") return "Kein Mikrofon gefunden oder verfügbar."
        if (err.name === "NotReadableError") return "Auf das Mikrofon konnte nicht zugegriffen werden."
        if (err.name === "SecurityError") {
            return "Der Browser blockiert den Zugriff – bitte die Seite über HTTPS öffnen."
        }
    }
    if (err instanceof Error && err.message) return err.message
    return fallback
}

export const createVoicebotPreviewController = (): VoicebotPreviewController => {
    const statusStore = writable<VoiceStatus>("idle")
    const errorStore = writable("")

    const statusHint = derived([statusStore, errorStore], ([$status, $error]) => {
        switch ($status) {
            case "idle":
                return "Tippen, um die Demo zu starten"
            case "connecting":
                return "Verbindung wird aufgebaut …"
            case "connected":
                return "Live – sprechen Sie jetzt"
            case "error":
                return $error || "Verbindung fehlgeschlagen"
            default:
                return "Voice-Demo"
        }
    })

    let connector: WerkRealtimeConnector | null = null
    let detachHandlers: Array<() => void> = []
    let startPromise: Promise<void> | null = null
    let stopPromise: Promise<void> | null = null
    let closing = false

    const ensureConnector = () => {
        if (!isBrowser || connector) return

        const instance = createVoiceConnector()

        const detachConnecting = instance.onLifecycle(ConnectorLifecycleEvents.CONNECTING, () => {
            statusStore.set("connecting")
            errorStore.set("")
        })
        const detachConnected = instance.onLifecycle(ConnectorLifecycleEvents.CONNECTED, () => {
            statusStore.set("connected")
            closing = false
        })
        const detachDisconnected = instance.onLifecycle(ConnectorLifecycleEvents.DISCONNECTED, () => {
            if (closing) {
                closing = false
                return
            }
            if (get(statusStore) !== "error") {
                statusStore.set("idle")
                errorStore.set("")
            }
        })
        const detachError = instance.onLifecycle(ConnectorLifecycleEvents.ERROR, (evt) => {
            const message =
                typeof evt?.message === "string" && evt.message.trim().length
                    ? evt.message
                    : "Verbindung fehlgeschlagen"
            errorStore.set(`${message} (${WS_URL})`)
            statusStore.set("error")
            closing = false
        })

        detachHandlers = [detachConnecting, detachConnected, detachDisconnected, detachError]
        connector = instance
    }

    const cleanupConnector = () => {
        detachHandlers.forEach((fn) => fn())
        detachHandlers = []
        const instance = connector
        connector = null
        if (instance) {
            void instance.stop()
        }
        startPromise = null
        stopPromise = null
        closing = false
    }

    const stop = async (silent = false) => {
        if (!connector) return
        if (stopPromise) {
            await stopPromise
            return
        }

        closing = true
        if (!silent && get(statusStore) !== "error") {
            statusStore.set("idle")
            errorStore.set("")
        }

        stopPromise = connector.stop()

        try {
            await stopPromise
        } catch (err) {
            console.error("VoicebotPreview stop error", err)
            if (!silent) {
                errorStore.set(extractErrorMessage(err, "Verbindung konnte nicht beendet werden."))
                statusStore.set("error")
            }
        } finally {
            stopPromise = null
            closing = false
        }
    }

    const start = async () => {
        if (!isBrowser) {
            statusStore.set("error")
            errorStore.set("Die Sprach-Demo steht nur im Browser zur Verfügung.")
            return
        }

        ensureConnector()
        if (!connector || startPromise) return

        await stop(true)

        statusStore.set("connecting")
        errorStore.set("")

        startPromise = (async () => {
            await connector!.start()
            connector!.setInputMuted(false)
            connector!.setOutputMuted(false)
        })()

        try {
            await startPromise
        } catch (err) {
            console.error("VoicebotPreview start error", err)
            errorStore.set(extractErrorMessage(err, "Verbindung konnte nicht aufgebaut werden."))
            statusStore.set("error")
            closing = false
        } finally {
            startPromise = null
        }
    }

    const toggle = async () => {
        const current = get(statusStore)
        if (current === "connecting") return
        if (current === "connected") {
            await stop()
            return
        }
        await start()
    }

    const setup = () => {
        ensureConnector()
    }

    const teardown = () => {
        void stop(true)
        cleanupConnector()
    }

    return {
        status: { subscribe: statusStore.subscribe },
        errorMessage: { subscribe: errorStore.subscribe },
        statusHint,
        setup,
        teardown,
        start,
        stop,
        toggle,
    }
}
