function isIconList(item) {
    return 'type' in item && item.type === 'iconList';
}
function getProcessedItemTimestamp(item) {
    if (item.type === 'chat' || item.type === 'iconList') {
        return item.item.timestamp;
    }
    return undefined;
}
/**
 * Processes a list of chat list inputs by adding date headers and a "new items" indicator.
 *
 * @param items - The list of items to process. Pre-sorted by `timestamp` in ascending order.
 * @param dateTimeFormatter - A service used to format dates for the date headers.
 * @param lastVisitedTimestamp - The timestamp of the user's last visit. Used to position the
 *   "new items" indicator.
 *
 * @returns An array of processed items, including chat items, icon lists, date headers,
 *   and a "new items" indicator if applicable.
 */
export function processChatItems(items, dateTimeFormatter, lastVisitedTimestamp) {
    const initialContext = {
        items: items,
        processedItems: [],
        lastVisitedTimestamp: lastVisitedTimestamp,
        dateTimeFormatter: dateTimeFormatter,
    };
    // Add processors here, in the order they should be executed.
    // `insertNewItemIndicator` runs after `insertDateGroups` so the
    // indicator can be placed at top level when the seen/unseen
    // boundary falls between two groups, and only inside a group when
    // the boundary falls within a single day. That placement makes
    // `reverseProcessedItems` a pure two-level reverse — no group-
    // boundary hoisting needed.
    const processors = [
        insertNotifications,
        insertDateGroups,
        insertNewItemIndicator,
    ];
    const finalContext = createPipeline(initialContext, processors);
    return finalContext.processedItems;
}
function createPipeline(initialContext, processors) {
    // eslint-disable-next-line unicorn/no-array-reduce
    return processors.reduce((context, processor) => processor(context), initialContext);
}
/**
 * Reverses the chronological order of a processed-items array.
 *
 * Pure two-level reverse: the top-level array is reversed, and each
 * group's items are reversed. `insertNewItemIndicator` places the
 * indicator at top level when the seen/unseen boundary falls
 * between groups, and only nests it inside a group when the
 * boundary falls within a single day — so reversal never needs to
 * move an indicator across levels to preserve its separator role.
 *
 * Used by `limebb-chat-list` when rendering in `newest-on-top`
 * layout, so that DOM order continues to match visual order
 * (newest-first in that layout), keeping copy/paste, screen-reader,
 * and tab order aligned with what the user sees.
 *
 * @param items - processed items in ascending-by-timestamp order
 * @returns processed items in descending-by-timestamp order
 */
export function reverseProcessedItems(items) {
    return items.toReversed().map((item) => {
        if (item.type !== 'group') {
            return item;
        }
        return Object.assign(Object.assign({}, item), { items: item.items.toReversed() });
    });
}
/**
 *
 * @param context
 */
export function insertNotifications(context) {
    for (const item of context.items) {
        if (isIconList(item)) {
            context.processedItems.push({ type: 'iconList', item: item });
        }
        else {
            context.processedItems.push({ type: 'chat', item: item });
        }
    }
    return context;
}
/**
 *
 * @param context
 */
export function insertNewItemIndicator(context) {
    if (!context.lastVisitedTimestamp) {
        return context;
    }
    const groups = context.processedItems;
    const lastVisited = context.lastVisitedTimestamp;
    // Walk groups newest → oldest. Items and groups are sorted
    // ascending, so at most one group straddles the seen/unseen
    // boundary; the rest are entirely seen or entirely unseen. In an
    // active chat where the newest item is already seen, the loop
    // exits in O(1).
    for (let g = groups.length - 1; g >= 0; g--) {
        const group = groups[g];
        if (group.type !== 'group') {
            continue;
        }
        const seenIndex = findNewestSeenIndex(group.items, lastVisited);
        if (seenIndex === -1) {
            continue;
        }
        return placeIndicatorAtBoundary(context, groups, g, group, seenIndex);
    }
    // No seen item found — everything is unseen, no boundary to mark.
    return context;
}
function findNewestSeenIndex(items, lastVisited) {
    for (let i = items.length - 1; i >= 0; i--) {
        const timestamp = getProcessedItemTimestamp(items[i]);
        if (timestamp === undefined) {
            continue;
        }
        if (new Date(timestamp) <= lastVisited) {
            return i;
        }
    }
    return -1;
}
function placeIndicatorAtBoundary(context, groups, groupIndex, group, seenIndex) {
    const isLastItemInGroup = seenIndex === group.items.length - 1;
    const isLastGroup = groupIndex === groups.length - 1;
    // Newest seen item is the very last item overall → everything is
    // seen, nothing to mark.
    if (isLastItemInGroup && isLastGroup) {
        return context;
    }
    // Seen item is last in its group → boundary is between groups.
    // Place the indicator at top level so reversal stays a pure
    // two-level reverse.
    if (isLastItemInGroup) {
        return Object.assign(Object.assign({}, context), { processedItems: [
                ...groups.slice(0, groupIndex + 1),
                { type: 'newItemIndicator' },
                ...groups.slice(groupIndex + 1),
            ] });
    }
    // Boundary falls inside a single day — indicator stays nested.
    const newGroupItems = [
        ...group.items.slice(0, seenIndex + 1),
        { type: 'newItemIndicator' },
        ...group.items.slice(seenIndex + 1),
    ];
    return Object.assign(Object.assign({}, context), { processedItems: [
            ...groups.slice(0, groupIndex),
            Object.assign(Object.assign({}, group), { items: newGroupItems }),
            ...groups.slice(groupIndex + 1),
        ] });
}
/**
 *
 * @param context
 */
export function insertDateGroups(context) {
    const newProcessedItems = [];
    let currentGroup = null;
    let lastProcessedDate = null;
    for (const item of context.processedItems) {
        if (item.type !== 'chat' && item.type !== 'iconList') {
            continue;
        }
        const currentDate = context.dateTimeFormatter.format(new Date(item.item.timestamp), 'date');
        if (currentDate !== lastProcessedDate) {
            if (currentGroup) {
                newProcessedItems.push(currentGroup);
            }
            currentGroup = {
                type: 'group',
                groupType: 'dateGroup',
                heading: currentDate,
                items: [item],
            };
            lastProcessedDate = currentDate;
            continue;
        }
        currentGroup === null || currentGroup === void 0 ? void 0 : currentGroup.items.push(item);
    }
    if (currentGroup) {
        newProcessedItems.push(currentGroup);
    }
    return Object.assign(Object.assign({}, context), { processedItems: newProcessedItems });
}
