"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useUnpinChatMessage = exports.usePinChatMessage = exports.reduceChat = exports.useReportChatMessage = exports.useSubmitReport = exports.reduceChatIncremental = exports.useAddSystemMessage = exports.useDeleteChatMessage = exports.useCreateChatMessage = exports.useAddPendingHide = exports.usePendingHides = exports.useSetBadgeSlots = exports.useBadgeSlots = exports.useSetChatDraft = exports.useChatDraft = exports.useSetReplyToMessage = exports.useReplyToMessage = void 0;
const api_1 = require("@atproto/api");
const react_1 = require("react");
const streamplace_store_1 = require("../streamplace-store");
const xrpc_1 = require("../streamplace-store/xrpc");
const livestream_store_1 = require("./livestream-store");
const useReplyToMessage = () => (0, livestream_store_1.useLivestreamStore)((state) => state.replyToMessage);
exports.useReplyToMessage = useReplyToMessage;
const useSetReplyToMessage = () => {
    const store = (0, livestream_store_1.getStoreFromContext)();
    return (0, react_1.useCallback)((message) => {
        store.setState({ replyToMessage: message });
    }, [store]);
};
exports.useSetReplyToMessage = useSetReplyToMessage;
const useChatDraft = () => (0, livestream_store_1.useLivestreamStore)((state) => state.chatDraft);
exports.useChatDraft = useChatDraft;
const useSetChatDraft = () => {
    const store = (0, livestream_store_1.getStoreFromContext)();
    return (0, react_1.useCallback)((draft) => {
        store.setState({ chatDraft: draft });
    }, [store]);
};
exports.useSetChatDraft = useSetChatDraft;
const useBadgeSlots = () => (0, livestream_store_1.useLivestreamStore)((state) => state.badgeSlots);
exports.useBadgeSlots = useBadgeSlots;
const useSetBadgeSlots = () => {
    const store = (0, livestream_store_1.getStoreFromContext)();
    return (0, react_1.useCallback)((slots) => {
        store.setState({ badgeSlots: slots });
    }, [store]);
};
exports.useSetBadgeSlots = useSetBadgeSlots;
const usePendingHides = () => (0, livestream_store_1.useLivestreamStore)((state) => state.pendingHides);
exports.usePendingHides = usePendingHides;
const useAddPendingHide = () => {
    const store = (0, livestream_store_1.getStoreFromContext)();
    return (0, react_1.useCallback)((messageUri) => {
        const state = store.getState();
        if (!state.pendingHides.includes(messageUri)) {
            const newPendingHides = [...state.pendingHides, messageUri];
            const newState = (0, exports.reduceChat)(state, [], [], [messageUri]);
            store.setState({
                ...newState,
                pendingHides: newPendingHides,
            });
        }
    }, [store]);
};
exports.useAddPendingHide = useAddPendingHide;
const useCreateChatMessage = () => {
    const pdsAgent = (0, xrpc_1.usePDSAgent)();
    const store = (0, livestream_store_1.getStoreFromContext)();
    const userDID = (0, streamplace_store_1.useDID)();
    const userHandle = (0, streamplace_store_1.useHandle)();
    const chatProfile = (0, streamplace_store_1.useChatProfile)();
    return async (msg) => {
        if (!pdsAgent || !userDID) {
            throw new Error("No PDS agent or user DID found");
        }
        let state = store.getState();
        const streamerProfile = state.profile;
        if (!streamerProfile) {
            throw new Error("Profile not found");
        }
        const rt = new api_1.RichText({ text: msg.text });
        await rt.detectFacets(pdsAgent);
        // filter out any facets that aren't in the allowed list
        rt.facets = rt.facets?.filter((facet) => {
            return (
            // if all features are in the allowed list
            facet.features.every((feature) => [
                "app.bsky.richtext.facet#link",
                "app.bsky.richtext.facet#mention",
            ].includes(feature.$type)));
        });
        const record = {
            $type: "place.stream.chat.message",
            text: msg.text,
            createdAt: new Date().toISOString(),
            streamer: streamerProfile.did,
            facets: rt.facets,
            ...(msg.reply
                ? {
                    reply: {
                        root: {
                            cid: msg.reply.cid,
                            uri: msg.reply.uri,
                        },
                        parent: {
                            cid: msg.reply.cid,
                            uri: msg.reply.uri,
                        },
                    },
                }
                : {}),
        };
        const localChat = {
            uri: `local-${Date.now()}`,
            cid: "",
            author: {
                did: userDID,
                handle: userHandle || userDID,
            },
            record: record,
            indexedAt: new Date().toISOString(),
            chatProfile: chatProfile || undefined,
        };
        state = (0, exports.reduceChat)(state, [localChat], [], []);
        store.setState(state);
        try {
            await pdsAgent.com.atproto.repo.createRecord({
                repo: userDID,
                collection: "place.stream.chat.message",
                record,
            });
        }
        catch (err) {
            // Remove the optimistic message if the server call fails
            const currentState = store.getState();
            const updatedIndex = { ...currentState.chatIndex };
            for (const [key, existingMsg] of Object.entries(updatedIndex)) {
                if (existingMsg.uri === localChat.uri) {
                    delete updatedIndex[key];
                    break;
                }
            }
            store.setState({
                ...currentState,
                chatIndex: updatedIndex,
                chat: Object.keys(updatedIndex)
                    .sort((a, b) => {
                    const aTime = parseInt(a.split("-")[0], 10);
                    const bTime = parseInt(b.split("-")[0], 10);
                    return bTime - aTime;
                })
                    .map((key) => updatedIndex[key]),
            });
            throw err;
        }
    };
};
exports.useCreateChatMessage = useCreateChatMessage;
const useDeleteChatMessage = () => {
    const pdsAgent = (0, xrpc_1.usePDSAgent)();
    const userDID = (0, streamplace_store_1.useDID)();
    return async (uri) => {
        if (!pdsAgent) {
            throw new Error("No PDS agent found");
        }
        if (!userDID) {
            throw new Error("No user DID found");
        }
        const rkey = uri.split("/").pop();
        if (!rkey) {
            throw new Error("No rkey found");
        }
        return await pdsAgent.com.atproto.repo.deleteRecord({
            repo: userDID,
            collection: "place.stream.chat.message",
            rkey: rkey,
        });
    };
};
exports.useDeleteChatMessage = useDeleteChatMessage;
const useAddSystemMessage = () => {
    const store = (0, livestream_store_1.getStoreFromContext)();
    return (0, react_1.useCallback)((message) => {
        const state = store.getState();
        const newState = (0, exports.reduceChat)(state, [message], []);
        store.setState(newState);
    }, [store]);
};
exports.useAddSystemMessage = useAddSystemMessage;
const buildSortedChatList = (chatIndex, existingChatList, newMessages, removedKeys) => {
    const sortedKeys = Object.keys(chatIndex).sort((a, b) => {
        const aTime = parseInt(a.split("-")[0], 10);
        const bTime = parseInt(b.split("-")[0], 10);
        return bTime - aTime;
    });
    return sortedKeys
        .map((key) => chatIndex[key])
        .filter((msg) => !removedKeys.has(msg.uri));
};
const profileIsDifferent = (newProfile, oldProfile) => {
    if (!oldProfile) {
        return true;
    }
    if (!newProfile) {
        return false;
    }
    if (!oldProfile.color) {
        return true;
    }
    if (!newProfile.color) {
        // idk. shouldn't happen.
        return false;
    }
    const { red: newRed, green: newGreen, blue: newBlue } = newProfile.color;
    const { red: oldRed, green: oldGreen, blue: oldBlue } = oldProfile.color;
    return newRed !== oldRed || newGreen !== oldGreen || newBlue !== oldBlue;
};
const reduceChatIncremental = (state, newMessages, blocks, hideUris = []) => {
    if (newMessages.length === 0 &&
        blocks.length === 0 &&
        hideUris.length === 0) {
        return state;
    }
    const newChatIndex = { ...state.chatIndex };
    const newAuthors = { ...state.authors };
    let hasChanges = false;
    const removedKeys = new Set();
    for (const msg of newMessages) {
        if (msg.deleted) {
            hasChanges = true;
            // find and remove the message from the index
            for (const [key, message] of Object.entries(newChatIndex)) {
                if (message.uri === msg.uri) {
                    delete newChatIndex[key];
                    removedKeys.add(key);
                }
            }
        }
    }
    newMessages = newMessages.filter((msg) => msg.deleted !== true);
    // handle blocks
    if (blocks.length > 0) {
        const blockedDIDs = new Set(blocks.map((block) => block.record.subject));
        for (const [key, message] of Object.entries(newChatIndex)) {
            if (blockedDIDs.has(message.author.did)) {
                delete newChatIndex[key];
                removedKeys.add(key);
                hasChanges = true;
            }
        }
    }
    if (hideUris.length > 0) {
        for (const [key, message] of Object.entries(newChatIndex)) {
            if (hideUris.includes(message.uri)) {
                delete newChatIndex[key];
                removedKeys.add(key);
                hasChanges = true;
            }
        }
    }
    const messagesToAdd = [];
    for (const message of newMessages) {
        // don't worry about messages that will be hidden
        if (state.pendingHides.includes(message.uri)) {
            continue;
        }
        const date = new Date(message.record.createdAt);
        const key = `${date.getTime()}-${message.uri}`;
        // only change the ref if the profile is different to avoid re-renders elsewhere
        if (profileIsDifferent(message.chatProfile, newAuthors[message.author.did])) {
            newAuthors[message.author.did] = message.chatProfile;
        }
        // skip messages we already have
        if (newChatIndex[key] && newChatIndex[key].uri === message.uri) {
            continue;
        }
        // if we have a local message, replace it with the new one
        if (!message.uri.startsWith("local-")) {
            const existingLocalKey = Object.keys(newChatIndex).find((k) => {
                const msg = newChatIndex[k];
                return (msg.uri.startsWith("local-") &&
                    msg.record.text === message.record.text &&
                    msg.author.did === message.author.did &&
                    Math.abs(new Date(msg.record.createdAt).getTime() - date.getTime()) <
                        10000 // Within 10 seconds
                );
            });
            if (existingLocalKey) {
                delete newChatIndex[existingLocalKey];
                removedKeys.add(existingLocalKey);
                hasChanges = true;
            }
        }
        // add reply info
        let processedMessage = message;
        if (message.record.reply) {
            const reply = message.record.reply;
            const parentUri = reply?.parent?.uri || reply?.root?.uri;
            if (parentUri) {
                const parentMsgKey = Object.keys(newChatIndex).find((k) => newChatIndex[k].uri === parentUri);
                if (parentMsgKey) {
                    const parentMsg = newChatIndex[parentMsgKey];
                    // Don't allow replies to system messages
                    if (parentMsg.author.did !== "did:sys:system") {
                        processedMessage = {
                            ...message,
                            replyTo: {
                                $type: "place.stream.chat.defs#messageView",
                                cid: parentMsg.cid,
                                uri: parentMsg.uri,
                                author: parentMsg.author,
                                record: parentMsg.record,
                                chatProfile: parentMsg.chatProfile,
                                indexedAt: parentMsg.indexedAt,
                            },
                        };
                    }
                }
            }
        }
        messagesToAdd.push({ key, message: processedMessage });
        hasChanges = true;
    }
    // Add new messages to index
    for (const { key, message } of messagesToAdd) {
        newChatIndex[key] = message;
    }
    // only rebuild if we have changes
    if (!hasChanges) {
        return state;
    }
    // Build the new sorted chat list efficiently
    const newChatList = buildSortedChatList(newChatIndex, state.chat, messagesToAdd, removedKeys);
    // Clean up pendingHides - remove URIs that we've now processed
    let newPendingHides = state.pendingHides;
    if (hideUris.length > 0) {
        newPendingHides = state.pendingHides.filter((uri) => !hideUris.includes(uri));
    }
    return {
        ...state,
        authors: newAuthors,
        chatIndex: newChatIndex,
        chat: newChatList,
        pendingHides: newPendingHides,
    };
};
exports.reduceChatIncremental = reduceChatIncremental;
const useSubmitReport = () => {
    const pdsAgent = (0, xrpc_1.usePDSAgent)();
    const userDID = (0, streamplace_store_1.useDID)();
    return (0, react_1.useCallback)(async (subject, reasonType, reason) => {
        if (!pdsAgent || !userDID) {
            throw new Error("No PDS agent or user DID found");
        }
        try {
            const response = await pdsAgent.com.atproto.moderation.createReport({
                reasonType,
                reason,
                subject: subject,
            }, {
                headers: {
                // "atproto-proxy": `${userDID}#atproto_labeler`,
                },
            });
            return response;
        }
        catch (error) {
            console.error("Failed to submit report:", error);
            throw error;
        }
    }, [pdsAgent, userDID]);
};
exports.useSubmitReport = useSubmitReport;
const useReportChatMessage = () => {
    const submitReport = (0, exports.useSubmitReport)();
    return (0, react_1.useCallback)(async (message, reasonType, reason) => {
        const reportSubject = {
            $type: "com.atproto.repo.strongRef",
            uri: message.uri,
            cid: message.cid,
        };
        return await submitReport(reportSubject, reasonType, reason);
    }, [submitReport]);
};
exports.useReportChatMessage = useReportChatMessage;
exports.reduceChat = exports.reduceChatIncremental;
const usePinChatMessage = () => {
    const agent = (0, xrpc_1.usePDSAgent)();
    const store = (0, livestream_store_1.getStoreFromContext)();
    return async (messageUri, streamerDID, expiresAt) => {
        if (!agent || !agent.did) {
            throw new Error("No PDS agent or user DID found");
        }
        // If streamer, create directly
        if (agent.did === streamerDID) {
            // First delete any existing pinned records
            const listResult = await agent.com.atproto.repo.listRecords({
                repo: streamerDID,
                collection: "place.stream.chat.pinnedRecord",
            });
            for (const rec of listResult.data.records) {
                const rkey = rec.uri.split("/").pop();
                if (rkey) {
                    await agent.com.atproto.repo.deleteRecord({
                        repo: streamerDID,
                        collection: "place.stream.chat.pinnedRecord",
                        rkey,
                    });
                }
            }
            const record = {
                $type: "place.stream.chat.pinnedRecord",
                pinnedMessage: messageUri,
                createdAt: new Date().toISOString(),
                ...(expiresAt ? { expiresAt } : {}),
            };
            const result = await agent.com.atproto.repo.createRecord({
                repo: streamerDID,
                collection: "place.stream.chat.pinnedRecord",
                record,
            });
            return result;
        }
        // Otherwise, use delegated moderation endpoint
        const result = await agent.place.stream.moderation.createPin({
            streamer: streamerDID,
            messageUri,
            ...(expiresAt ? { expiresAt } : {}),
        });
        return result;
    };
};
exports.usePinChatMessage = usePinChatMessage;
const useUnpinChatMessage = () => {
    const agent = (0, xrpc_1.usePDSAgent)();
    const store = (0, livestream_store_1.getStoreFromContext)();
    return async (pinUri, streamerDID) => {
        if (!agent || !agent.did) {
            throw new Error("No PDS agent or user DID found");
        }
        // If streamer, delete directly
        if (agent.did === streamerDID) {
            const rkey = pinUri.split("/").pop();
            if (!rkey) {
                throw new Error("Invalid pin URI");
            }
            await agent.com.atproto.repo.deleteRecord({
                repo: streamerDID,
                collection: "place.stream.chat.pinnedRecord",
                rkey,
            });
            // Optimistically clear the pinned comment
            store.setState({ pinnedComment: null });
            return;
        }
        // Otherwise, use delegated moderation endpoint
        await agent.place.stream.moderation.deletePin({
            streamer: streamerDID,
            pinUri,
        });
        // Optimistically clear the pinned comment
        store.setState({ pinnedComment: null });
    };
};
exports.useUnpinChatMessage = useUnpinChatMessage;
//# sourceMappingURL=chat.js.map