import { useEffect, useState, useMemo, useCallback, useRef } from "react";
import { getAllReviews, getReviewStatus, createReview, addReviewers, removeReviewer, sendInvitations, setItemDecision, setFinalDecision, addPagesToReview, removePagesFromReview, } from "../services/reviewsService";
async function sendInvitationsOrThrow(assignmentIds) {
    const result = await sendInvitations(assignmentIds);
    if (result.type === "error") {
        const errorMessage = result.details || result.summary || "Failed to send invitations";
        throw new Error(typeof errorMessage === "string"
            ? errorMessage
            : JSON.stringify(errorMessage));
    }
    if (result.data &&
        typeof result.data === "object" &&
        "errors" in result.data &&
        Array.isArray(result.data.errors) &&
        result.data.errors.length > 0) {
        const responseData = result.data;
        const errorMap = new Map();
        responseData.errors.forEach((invitationError) => {
            const message = invitationError.message || JSON.stringify(invitationError);
            if (invitationError.assignmentId) {
                errorMap.set(invitationError.assignmentId, String(message));
            }
        });
        const allFailed = responseData.successfulCount === 0;
        const error = new Error(allFailed
            ? Array.from(errorMap.values()).join("; ")
            : "Some invitations failed to send");
        error.assignmentErrors = errorMap;
        throw error;
    }
}
export function useMultiReview({ addSocketMessageListener, socketConnectionVersion, }) {
    const [reviews, setReviews] = useState([]);
    const [selectedReviewId, setSelectedReviewId] = useState(null);
    const [reviewDetail, setReviewDetail] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);
    const loadReviews = useCallback(async (options) => {
        const silent = options?.silent === true;
        try {
            if (!silent) {
                setLoading(true);
                setError(null);
            }
            const result = await getAllReviews();
            // get() reports failures as error-typed results instead of throwing;
            // rethrow so failures reach the catch below instead of leaving the
            // list silently stuck on stale (or empty) data.
            if (result.type !== "success") {
                throw new Error(result.details || result.summary || "Failed to load reviews");
            }
            if (result.data) {
                setReviews(result.data);
            }
        }
        catch (err) {
            if (!silent) {
                setError(err instanceof Error ? err.message : "Failed to load reviews");
            }
        }
        finally {
            if (!silent) {
                setLoading(false);
            }
        }
    }, []);
    const loadReviewDetail = useCallback(async (reviewId, options) => {
        const silent = options?.silent === true;
        try {
            if (!silent) {
                setLoading(true);
                setError(null);
            }
            const result = await getReviewStatus(reviewId);
            if (result.type !== "success") {
                throw new Error(result.details || result.summary || "Failed to load review");
            }
            if (result.data) {
                setReviewDetail(result.data);
            }
        }
        catch (err) {
            if (!silent) {
                setError(err instanceof Error ? err.message : "Failed to load review");
            }
        }
        finally {
            if (!silent) {
                setLoading(false);
            }
        }
    }, []);
    const createNewReview = useCallback(async (request) => {
        try {
            setLoading(true);
            setError(null);
            const result = await createReview(request);
            if (result.data?.reviewId) {
                // Enhance items with names from the request before reloading
                if (selectedReviewId === result.data.reviewId || !selectedReviewId) {
                    const statusResult = await getReviewStatus(result.data.reviewId);
                    if (statusResult.data) {
                        // Create a map of itemId+language -> name/path from the request items
                        const nameMap = new Map();
                        request.items.forEach((item) => {
                            const key = `${item.descriptor.id}-${item.descriptor.language}`;
                            nameMap.set(key, {
                                name: item.descriptor.name,
                                path: item.descriptor.path,
                            });
                        });
                        // Enhance the items from backend with names from our map
                        const enhancedItems = statusResult.data.items.map((item) => {
                            const key = `${item.itemId}-${item.language}`;
                            const nameInfo = nameMap.get(key);
                            return {
                                ...item,
                                name: nameInfo?.name,
                                path: nameInfo?.path,
                            };
                        });
                        setReviewDetail({
                            ...statusResult.data,
                            items: enhancedItems,
                        });
                    }
                }
                await loadReviews();
                return result.data.reviewId;
            }
            return null;
        }
        catch (err) {
            setError(err instanceof Error ? err.message : "Failed to create review");
            return null;
        }
        finally {
            setLoading(false);
        }
    }, [selectedReviewId, loadReviews]);
    const addReviewersToReview = useCallback(async (reviewId, reviewers) => {
        let reviewersAdded = false;
        try {
            setLoading(true);
            setError(null);
            const addResult = await addReviewers(reviewId, reviewers);
            if (addResult.type !== "success") {
                throw new Error(addResult.details ||
                    addResult.summary ||
                    "Failed to add reviewers");
            }
            reviewersAdded = true;
            const statusResult = await getReviewStatus(reviewId);
            if (statusResult.type !== "success" || !statusResult.data) {
                throw new Error(statusResult.details ||
                    statusResult.summary ||
                    "Failed to load added reviewers");
            }
            const addedEmails = new Set(reviewers.map((reviewer) => reviewer.email.trim().toLowerCase()));
            const assignmentIds = statusResult.data.assignments
                .filter((assignment) => !assignment.invitationSentDate &&
                addedEmails.has(assignment.reviewerEmail.trim().toLowerCase()))
                .map((assignment) => assignment.assignmentId) ?? [];
            if (assignmentIds.length > 0) {
                await sendInvitationsOrThrow(assignmentIds);
            }
        }
        catch (err) {
            setError(err instanceof Error
                ? err.message
                : reviewersAdded
                    ? "Reviewer added, but the invitation could not be sent"
                    : "Failed to add reviewers");
        }
        finally {
            if (reviewersAdded) {
                if (selectedReviewId === reviewId) {
                    await loadReviewDetail(reviewId);
                }
                await loadReviews();
            }
            setLoading(false);
        }
    }, [selectedReviewId, loadReviewDetail, loadReviews]);
    const removeReviewerFromReview = useCallback(async (assignmentId) => {
        try {
            setLoading(true);
            setError(null);
            await removeReviewer(assignmentId);
            if (selectedReviewId && reviewDetail) {
                await loadReviewDetail(selectedReviewId);
            }
            await loadReviews();
        }
        catch (err) {
            setError(err instanceof Error ? err.message : "Failed to remove reviewer");
        }
        finally {
            setLoading(false);
        }
    }, [selectedReviewId, reviewDetail, loadReviewDetail, loadReviews]);
    const sendReviewInvitations = useCallback(async (assignmentIds) => {
        try {
            setLoading(true);
            setError(null);
            await sendInvitationsOrThrow(assignmentIds);
            if (selectedReviewId) {
                await loadReviewDetail(selectedReviewId);
            }
            await loadReviews();
        }
        catch (err) {
            const errorMessage = err instanceof Error ? err.message : "Failed to send invitations";
            setError(errorMessage);
            // Re-throw so UI components can catch and handle errors individually
            throw err;
        }
        finally {
            setLoading(false);
        }
    }, [selectedReviewId, loadReviewDetail, loadReviews]);
    const makeItemDecision = useCallback(async (assignmentId, itemId, itemLanguage, itemVersion, decision) => {
        try {
            setLoading(true);
            setError(null);
            await setItemDecision(assignmentId, itemId, itemLanguage, itemVersion, decision);
            if (selectedReviewId) {
                await loadReviewDetail(selectedReviewId);
            }
            await loadReviews();
            // Auto-rejection: If all reviewers reject an item, set final decision to rejected
            if (decision === "Rejected" && reviewDetail && selectedReviewId) {
                const itemDecisions = reviewDetail.assignments
                    .map((assignment) => {
                    const decision = assignment.decisions?.find((d) => d.itemId === itemId && d.itemLanguage === itemLanguage);
                    return decision?.decision;
                })
                    .filter(Boolean);
                // Check if all reviewers have rejected this item
                if (itemDecisions.length === reviewDetail.assignments.length &&
                    itemDecisions.every((d) => d === "Rejected")) {
                    await setFinalDecision(selectedReviewId, "Rejected");
                    await loadReviewDetail(selectedReviewId);
                    await loadReviews();
                }
            }
        }
        catch (err) {
            setError(err instanceof Error ? err.message : "Failed to set decision");
        }
        finally {
            setLoading(false);
        }
    }, [selectedReviewId, reviewDetail, loadReviewDetail, loadReviews]);
    const setReviewFinalDecision = useCallback(async (reviewId, decision) => {
        try {
            setLoading(true);
            setError(null);
            await setFinalDecision(reviewId, decision);
            await loadReviewDetail(reviewId);
            await loadReviews();
        }
        catch (err) {
            setError(err instanceof Error ? err.message : "Failed to set final decision");
        }
        finally {
            setLoading(false);
        }
    }, [loadReviewDetail, loadReviews]);
    const addPages = useCallback(async (reviewId, items) => {
        try {
            setLoading(true);
            setError(null);
            await addPagesToReview(reviewId, items);
            if (selectedReviewId === reviewId) {
                // Enhance items with names before reloading
                const result = await getReviewStatus(reviewId);
                if (result.data) {
                    // Create a map of itemId+language -> name/path from existing items
                    const existingNameMap = new Map();
                    if (reviewDetail?.items) {
                        reviewDetail.items.forEach((item) => {
                            const key = `${item.itemId}-${item.language}`;
                            existingNameMap.set(key, {
                                name: item.name,
                                path: item.path,
                            });
                        });
                    }
                    // Add names from the items we just added
                    items.forEach((item) => {
                        const key = `${item.descriptor.id}-${item.descriptor.language}`;
                        existingNameMap.set(key, {
                            name: item.descriptor.name,
                            path: item.descriptor.path,
                        });
                    });
                    // Enhance the items from backend with names from our map
                    const enhancedItems = result.data.items.map((item) => {
                        const key = `${item.itemId}-${item.language}`;
                        const nameInfo = existingNameMap.get(key);
                        return {
                            ...item,
                            name: nameInfo?.name,
                            path: nameInfo?.path,
                        };
                    });
                    setReviewDetail({
                        ...result.data,
                        items: enhancedItems,
                    });
                }
            }
            await loadReviews();
        }
        catch (err) {
            setError(err instanceof Error ? err.message : "Failed to add pages");
        }
        finally {
            setLoading(false);
        }
    }, [selectedReviewId, reviewDetail, loadReviewDetail, loadReviews]);
    const removePages = useCallback(async (reviewId, items) => {
        try {
            setLoading(true);
            setError(null);
            await removePagesFromReview(reviewId, items);
            if (selectedReviewId === reviewId) {
                await loadReviewDetail(reviewId);
            }
            await loadReviews();
        }
        catch (err) {
            setError(err instanceof Error ? err.message : "Failed to remove pages");
        }
        finally {
            setLoading(false);
        }
    }, [selectedReviewId, loadReviewDetail, loadReviews]);
    useEffect(() => {
        loadReviews();
    }, [loadReviews]);
    useEffect(() => {
        if (selectedReviewId) {
            loadReviewDetail(selectedReviewId);
        }
    }, [selectedReviewId, loadReviewDetail]);
    useEffect(() => {
        return addSocketMessageListener((message) => {
            if (message.type === "reviews-updated" ||
                message.type === "review-updated") {
                // Broadcasts are background synchronization, not user-initiated
                // mutations. Keeping them silent prevents unrelated review activity
                // from disabling this review's decision controls while they refresh.
                loadReviews({ silent: true });
                if (selectedReviewId) {
                    loadReviewDetail(selectedReviewId, { silent: true });
                }
            }
        });
    }, [
        selectedReviewId,
        loadReviews,
        loadReviewDetail,
        addSocketMessageListener,
    ]);
    // Refetch on every socket (re)connect: "reviews-updated" broadcasts sent
    // while this client was disconnected or still connecting are gone for good,
    // so the connect event is the only reliable point to catch up on them.
    const lastRefetchedConnectionVersion = useRef(0);
    useEffect(() => {
        if (!socketConnectionVersion)
            return;
        if (socketConnectionVersion === lastRefetchedConnectionVersion.current)
            return;
        lastRefetchedConnectionVersion.current = socketConnectionVersion;
        loadReviews({ silent: true });
        if (selectedReviewId) {
            loadReviewDetail(selectedReviewId, { silent: true });
        }
    }, [socketConnectionVersion, selectedReviewId, loadReviews, loadReviewDetail]);
    // Slow polling safety net: a broadcast can also be lost while the socket
    // stays connected (server-side broadcast pressure, tab throttling). Reviews
    // must not stay stale until the next reconnect, so refresh silently in the
    // background while the reviews UI is mounted.
    useEffect(() => {
        const interval = setInterval(() => {
            loadReviews({ silent: true });
            if (selectedReviewId) {
                loadReviewDetail(selectedReviewId, { silent: true });
            }
        }, 20000);
        return () => clearInterval(interval);
    }, [selectedReviewId, loadReviews, loadReviewDetail]);
    return useMemo(() => ({
        reviews,
        selectedReviewId,
        reviewDetail,
        loading,
        error,
        setSelectedReviewId,
        createNewReview,
        addReviewersToReview,
        removeReviewerFromReview,
        sendReviewInvitations,
        makeItemDecision,
        setReviewFinalDecision,
        addPages,
        removePages,
        refreshReviews: loadReviews,
        refreshReviewDetail: selectedReviewId
            ? () => loadReviewDetail(selectedReviewId)
            : undefined,
    }), [
        reviews,
        selectedReviewId,
        reviewDetail,
        loading,
        error,
        createNewReview,
        addReviewersToReview,
        removeReviewerFromReview,
        sendReviewInvitations,
        makeItemDecision,
        setReviewFinalDecision,
        addPages,
        removePages,
        loadReviews,
        loadReviewDetail,
    ]);
}
//# sourceMappingURL=useMultiReview.js.map