import { randomUUID } from 'node:crypto';
import { HOME_ASSISTANT_SURFACE } from '../../channels/builtin/homeassistant.js';
import { postHomeAssistantChatMessage, readHomeAssistantRemoteSessionTtlMs, } from '../homeassistant-chat.js';
const DEFAULT_WAIT_TIMEOUT_MS = 120_000;
const MAX_WAIT_TIMEOUT_MS = 10 * 60_000;
export class HomeAssistantConversationRoutes {
    context;
    messageIndex = new Map();
    constructor(context) {
        this.context = context;
    }
    async handle(req) {
        const url = new URL(req.url);
        if (!url.pathname.startsWith('/api/homeassistant'))
            return null;
        if ((url.pathname === '/api/homeassistant' || url.pathname === '/api/homeassistant/health') && req.method === 'GET') {
            return Response.json(this.describeHealth());
        }
        if (url.pathname === '/api/homeassistant/conversation' && req.method === 'POST') {
            const body = await this.context.parseJsonBody(req);
            if (body instanceof Response)
                return body;
            return this.respondToSubmit(await this.submitConversation(body));
        }
        if (url.pathname === '/api/homeassistant/conversation/stream' && req.method === 'POST') {
            const body = await this.context.parseJsonBody(req);
            if (body instanceof Response)
                return body;
            return this.streamConversation(body);
        }
        if (url.pathname === '/api/homeassistant/conversation/cancel' && req.method === 'POST') {
            const body = await this.context.parseJsonBody(req);
            if (body instanceof Response)
                return body;
            return this.cancelConversation(body);
        }
        return null;
    }
    describeHealth() {
        const enabled = Boolean(this.context.configManager.get('surfaces.homeassistant.enabled'));
        return {
            ok: enabled,
            surface: HOME_ASSISTANT_SURFACE,
            enabled,
            defaultConversationId: String(this.context.configManager.get('surfaces.homeassistant.defaultConversationId') || 'goodvibes'),
            eventType: String(this.context.configManager.get('surfaces.homeassistant.eventType') || 'goodvibes_message'),
            remoteSessionTtlMs: this.readRemoteSessionTtlMs({}),
            endpoints: {
                conversation: '/api/homeassistant/conversation',
                stream: '/api/homeassistant/conversation/stream',
                cancel: '/api/homeassistant/conversation/cancel',
                webhook: '/webhook/homeassistant',
            },
            capabilities: [
                'conversation-submit-wait',
                'conversation-stream',
                'conversation-cancel',
                'stable-correlation',
                'isolated-remote-chat-session',
                'remote-session-ttl',
                'homeassistant-event-delivery',
            ],
        };
    }
    async submitConversation(body) {
        if (!Boolean(this.context.configManager.get('surfaces.homeassistant.enabled'))) {
            return {
                ok: false,
                acknowledged: true,
                messageId: '',
                conversationId: '',
                sessionId: '',
                routeId: '',
                mode: 'rejected',
                newSession: false,
                sessionExpired: false,
                status: 'rejected',
                error: 'Home Assistant surface is disabled.',
            };
        }
        const input = this.parseInput(body);
        if (!input.text) {
            return {
                ok: false,
                acknowledged: true,
                messageId: input.messageId,
                conversationId: input.conversationId,
                sessionId: '',
                routeId: '',
                mode: 'rejected',
                newSession: false,
                sessionExpired: false,
                status: 'rejected',
                error: 'Missing Home Assistant conversation message.',
            };
        }
        let posted;
        try {
            posted = await postHomeAssistantChatMessage({
                configManager: this.context.configManager,
                routeBindings: this.context.routeBindings,
                chatManager: this.context.chatManager,
                resolveDefaultProviderModel: this.context.resolveDefaultProviderModel,
            }, input, {
                wait: input.wait,
                timeoutMs: input.waitTimeoutMs,
                clientId: `homeassistant:${input.surfaceId}:${input.conversationId}`,
            });
        }
        catch (error) {
            return {
                ok: false,
                acknowledged: true,
                messageId: input.messageId,
                conversationId: input.conversationId,
                sessionId: '',
                routeId: '',
                mode: 'rejected',
                newSession: false,
                sessionExpired: false,
                status: 'rejected',
                error: error instanceof Error ? error.message : String(error),
            };
        }
        this.indexMessage(input, posted);
        const base = {
            ok: true,
            acknowledged: true,
            messageId: input.messageId,
            conversationId: input.conversationId,
            sessionId: posted.session.id,
            routeId: posted.binding.id,
            mode: 'remote-chat',
            newSession: posted.newSession,
            sessionExpired: posted.sessionExpired,
        };
        if (!input.wait) {
            return { ...base, status: 'running' };
        }
        if (posted.error) {
            const timedOut = posted.error.toLowerCase().includes('timed out');
            return {
                ...base,
                ok: false,
                status: timedOut ? 'timeout' : 'failed',
                ...(timedOut ? { timeoutMs: input.waitTimeoutMs } : {}),
                error: posted.error,
            };
        }
        return {
            ...base,
            status: 'completed',
            assistant: buildAssistantResult(posted.response ?? ''),
        };
    }
    respondToSubmit(result) {
        if (result.error === 'Home Assistant surface is disabled.')
            return Response.json(result, { status: 503 });
        if (result.status === 'rejected')
            return Response.json(result, { status: result.ok ? 202 : 400 });
        if (result.status === 'timeout')
            return Response.json(result, { status: 202 });
        if (result.status === 'failed')
            return Response.json(result, { status: 500 });
        if (result.status === 'cancelled')
            return Response.json(result, { status: 409 });
        return Response.json(result, { status: result.status === 'completed' ? 200 : 202 });
    }
    streamConversation(body) {
        const encoder = new TextEncoder();
        const stream = new ReadableStream({
            start: async (controller) => {
                const send = (event, data) => {
                    controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
                };
                try {
                    const result = await this.submitConversation({ ...body, wait: true });
                    send(result.status === 'completed' ? 'final' : 'error', result);
                    controller.close();
                }
                catch (error) {
                    send('error', { ok: false, error: error instanceof Error ? error.message : String(error) });
                    controller.close();
                }
            },
            cancel: () => undefined,
        });
        return new Response(stream, {
            headers: {
                'Content-Type': 'text/event-stream; charset=utf-8',
                'Cache-Control': 'no-cache, no-transform',
                Connection: 'keep-alive',
            },
        });
    }
    cancelConversation(body) {
        const indexed = readString(body.messageId ?? body.message_id)
            ? this.messageIndex.get(readString(body.messageId ?? body.message_id))
            : undefined;
        const sessionId = readString(body.sessionId ?? body.session_id) ?? indexed?.sessionId;
        if (!sessionId) {
            return Response.json({ ok: false, error: 'sessionId or known messageId is required.' }, { status: 400 });
        }
        const session = this.context.chatManager.closeSession(sessionId);
        return session
            ? Response.json({ ok: true, sessionId, status: 'cancelled' })
            : Response.json({ ok: false, sessionId, error: 'Unknown Home Assistant chat session.' }, { status: 404 });
    }
    parseInput(body) {
        const threadId = readString(body.threadId ?? body.thread_id);
        const userId = readString(body.userId ?? body.user_id);
        const displayName = readString(body.displayName ?? body.userName ?? body.user_name);
        const providerId = readString(body.providerId ?? body.provider);
        const modelId = readString(body.modelId ?? body.model);
        const tools = readStringList(body.tools);
        const haContext = readRecord(body.context);
        const conversationId = readString(body.conversationId ?? body.conversation_id)
            ?? threadId
            ?? readString(body.deviceId ?? body.device_id)
            ?? String(this.context.configManager.get('surfaces.homeassistant.defaultConversationId') || 'goodvibes');
        const surfaceId = readString(body.surfaceId ?? body.instanceId ?? body.instance_id)
            ?? readString(body.hassInstanceId ?? body.hass_instance_id)
            ?? 'homeassistant';
        const channelId = readString(body.areaId ?? body.area_id)
            ?? readString(body.entityId ?? body.entity_id)
            ?? conversationId;
        return {
            text: readString(body.body ?? body.message ?? body.text ?? body.prompt ?? body.task) ?? '',
            messageId: readString(body.messageId ?? body.message_id) ?? `ha-${randomUUID()}`,
            conversationId,
            surfaceId,
            channelId,
            ...(threadId ? { threadId } : {}),
            ...(userId ? { userId } : {}),
            ...(displayName ? { displayName } : {}),
            title: readString(body.title) ?? 'Home Assistant',
            ...(providerId ? { providerId } : {}),
            ...(modelId ? { modelId } : {}),
            ...(tools.length ? { tools } : {}),
            ...(haContext ? { context: haContext } : {}),
            publishEvent: body.publishEvent === true,
            wait: body.wait !== false,
            waitTimeoutMs: clampNumber(body.timeoutMs ?? body.waitTimeoutMs, DEFAULT_WAIT_TIMEOUT_MS, 1_000, MAX_WAIT_TIMEOUT_MS),
            remoteSessionTtlMs: this.readRemoteSessionTtlMs(body),
        };
    }
    indexMessage(input, posted) {
        this.messageIndex.set(input.messageId, {
            messageId: input.messageId,
            ...(posted.assistantMessageId ? { assistantMessageId: posted.assistantMessageId } : {}),
            sessionId: posted.session.id,
            routeId: posted.binding.id,
            conversationId: input.conversationId,
            createdAt: Date.now(),
        });
        if (this.messageIndex.size > 500) {
            const oldest = [...this.messageIndex.values()].sort((a, b) => a.createdAt - b.createdAt).slice(0, 100);
            for (const entry of oldest)
                this.messageIndex.delete(entry.messageId);
        }
    }
    readRemoteSessionTtlMs(body) {
        return readHomeAssistantRemoteSessionTtlMs(this.context.configManager, body.remoteSessionTtlMs);
    }
}
function buildAssistantResult(text) {
    return {
        text,
        speechText: text,
        status: 'completed',
        completedAt: Date.now(),
    };
}
function readString(value) {
    return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
}
function readStringList(value) {
    return Array.isArray(value)
        ? value.filter((entry) => typeof entry === 'string' && entry.trim().length > 0).map((entry) => entry.trim())
        : [];
}
function readRecord(value) {
    return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
}
function clampNumber(value, fallback, min, max) {
    const parsed = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : fallback;
    if (!Number.isFinite(parsed))
        return fallback;
    return Math.max(min, Math.min(max, Math.trunc(parsed)));
}
