/**
 * Feishu Streaming Card Support
 *
 * Implements typing indicator and streaming text output for Feishu using
 * the Card Kit streaming API.
 *
 * Flow:
 * 1. Create a card entity with streaming_mode: true
 * 2. Send the card as a message (shows "[生成中...]" in chat preview)
 * 3. Stream text updates to the card using the cardkit API
 * 4. Close streaming mode when done
 */
import { getChildLogger } from "../logging.js";
const logger = getChildLogger({ module: "feishu-streaming" });
const FEISHU_BASE_URL = "https://open.feishu.cn/open-apis";
// Token cache
let cachedToken = null;
/**
 * Get tenant access token (with caching)
 */
async function getTenantAccessToken(credentials) {
    // Check cache
    if (cachedToken && cachedToken.expiresAt > Date.now() + 60000) {
        return cachedToken.token;
    }
    const response = await fetch(`${FEISHU_BASE_URL}/auth/v3/tenant_access_token/internal`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
            app_id: credentials.appId,
            app_secret: credentials.appSecret,
        }),
    });
    const result = (await response.json());
    if (result.code !== 0 || !result.tenant_access_token) {
        throw new Error(`Failed to get tenant access token: ${result.msg}`);
    }
    // Cache token (expire 2 hours, we refresh 1 minute early)
    cachedToken = {
        token: result.tenant_access_token,
        expiresAt: Date.now() + (result.expire ?? 7200) * 1000,
    };
    return result.tenant_access_token;
}
/**
 * Create a streaming card entity
 */
export async function createStreamingCard(credentials, title) {
    const cardJson = {
        schema: "2.0",
        ...(title
            ? {
                header: {
                    title: {
                        content: title,
                        tag: "plain_text",
                    },
                },
            }
            : {}),
        config: {
            streaming_mode: true,
            summary: {
                content: "[生成中...]",
            },
            streaming_config: {
                print_frequency_ms: { default: 50 },
                print_step: { default: 2 },
                print_strategy: "fast",
            },
        },
        body: {
            elements: [
                {
                    tag: "markdown",
                    content: "⏳ Thinking...",
                    element_id: "streaming_content",
                },
            ],
        },
    };
    const response = await fetch(`${FEISHU_BASE_URL}/cardkit/v1/cards`, {
        method: "POST",
        headers: {
            Authorization: `Bearer ${await getTenantAccessToken(credentials)}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            type: "card_json",
            data: JSON.stringify(cardJson),
        }),
    });
    const result = (await response.json());
    if (result.code !== 0 || !result.data?.card_id) {
        throw new Error(`Failed to create streaming card: ${result.msg}`);
    }
    logger.debug(`Created streaming card: ${result.data.card_id}`);
    return { cardId: result.data.card_id };
}
/**
 * Send a streaming card as a message
 */
export async function sendStreamingCard(client, receiveId, cardId, receiveIdType = "chat_id") {
    const content = JSON.stringify({
        type: "card",
        data: { card_id: cardId },
    });
    const res = await client.im.message.create({
        params: { receive_id_type: receiveIdType },
        data: {
            receive_id: receiveId,
            msg_type: "interactive",
            content,
        },
    });
    if (res.code !== 0 || !res.data?.message_id) {
        throw new Error(`Failed to send streaming card: ${res.msg}`);
    }
    logger.debug(`Sent streaming card message: ${res.data.message_id}`);
    return { messageId: res.data.message_id };
}
/**
 * Update streaming card text content
 */
export async function updateStreamingCardText(credentials, cardId, elementId, text, sequence) {
    const response = await fetch(`${FEISHU_BASE_URL}/cardkit/v1/cards/${cardId}/elements/${elementId}/content`, {
        method: "PUT",
        headers: {
            Authorization: `Bearer ${await getTenantAccessToken(credentials)}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            content: text,
            sequence,
            uuid: `stream_${cardId}_${sequence}`,
        }),
    });
    const result = (await response.json());
    if (result.code !== 0) {
        logger.warn(`Failed to update streaming card text: ${result.msg}`);
        // Don't throw - streaming updates can fail occasionally
    }
}
/**
 * Close streaming mode on a card
 */
export async function closeStreamingMode(credentials, cardId, sequence, finalSummary) {
    // Build config object - summary must be set to clear "[生成中...]"
    const configObj = {
        streaming_mode: false,
        summary: { content: finalSummary || "" },
    };
    const settings = { config: configObj };
    const response = await fetch(`${FEISHU_BASE_URL}/cardkit/v1/cards/${cardId}/settings`, {
        method: "PATCH",
        headers: {
            Authorization: `Bearer ${await getTenantAccessToken(credentials)}`,
            "Content-Type": "application/json; charset=utf-8",
        },
        body: JSON.stringify({
            settings: JSON.stringify(settings),
            sequence,
            uuid: `close_${cardId}_${sequence}`,
        }),
    });
    // Check response
    const result = (await response.json());
    if (result.code !== 0) {
        logger.warn(`Failed to close streaming mode: ${result.msg}`);
        return false;
    }
    logger.debug(`Closed streaming mode for card: ${cardId}`);
    return true;
}
/**
 * High-level streaming card manager
 */
export class FeishuStreamingSession {
    client;
    credentials;
    state = null;
    updateQueue = Promise.resolve();
    closed = false;
    constructor(client, credentials) {
        this.client = client;
        this.credentials = credentials;
    }
    /**
     * Start a streaming session - creates and sends a streaming card
     */
    async start(receiveId, receiveIdType = "chat_id", title) {
        if (this.state) {
            logger.warn("Streaming session already started");
            return;
        }
        try {
            const { cardId } = await createStreamingCard(this.credentials, title);
            const { messageId } = await sendStreamingCard(this.client, receiveId, cardId, receiveIdType);
            this.state = {
                cardId,
                messageId,
                sequence: 1,
                elementId: "streaming_content",
                currentText: "",
            };
            logger.info(`Started streaming session: cardId=${cardId}, messageId=${messageId}`);
        }
        catch (err) {
            logger.error(`Failed to start streaming session: ${String(err)}`);
            throw err;
        }
    }
    /**
     * Update the streaming card with new text (appends to existing)
     */
    async update(text) {
        if (!this.state || this.closed)
            return;
        // Queue updates to ensure order
        this.updateQueue = this.updateQueue.then(async () => {
            if (!this.state || this.closed)
                return;
            this.state.currentText = text;
            this.state.sequence += 1;
            try {
                await updateStreamingCardText(this.credentials, this.state.cardId, this.state.elementId, text, this.state.sequence);
            }
            catch (err) {
                logger.debug(`Streaming update failed (will retry): ${String(err)}`);
            }
        });
        await this.updateQueue;
    }
    /**
     * Finalize and close the streaming session.
     * Returns true if the card was closed successfully, false on failure
     * (caller should fallback to a regular message).
     */
    async close(finalText, summary) {
        if (!this.state || this.closed)
            return false;
        this.closed = true;
        // Wait for pending updates
        await this.updateQueue;
        const text = finalText ?? this.state.currentText;
        this.state.sequence += 1;
        try {
            // Update final text
            if (text) {
                await updateStreamingCardText(this.credentials, this.state.cardId, this.state.elementId, text, this.state.sequence);
            }
            // Close streaming mode
            this.state.sequence += 1;
            const closed = await closeStreamingMode(this.credentials, this.state.cardId, this.state.sequence, summary ?? truncateForSummary(text));
            if (!closed) {
                logger.warn(`Streaming card close API failed, caller should fallback`);
                return false;
            }
            logger.info(`Closed streaming session: cardId=${this.state.cardId}`);
            return true;
        }
        catch (err) {
            logger.error(`Failed to close streaming session: ${String(err)}`);
            return false;
        }
    }
    /**
     * Check if session is active
     */
    isActive() {
        return this.state !== null && !this.closed;
    }
    /**
     * Get the message ID of the streaming card
     */
    getMessageId() {
        return this.state?.messageId ?? null;
    }
}
/**
 * Truncate text to create a summary for chat preview
 */
function truncateForSummary(text, maxLength = 50) {
    if (!text)
        return "";
    const cleaned = text.replace(/\n/g, " ").trim();
    if (cleaned.length <= maxLength)
        return cleaned;
    return cleaned.slice(0, maxLength - 3) + "...";
}
