/**
 * Maps every EditOperation type to its display bucket. Typed with `satisfies`
 * so adding a new `EditOperation["type"]` is a COMPILE error until it is mapped.
 */
const OPERATION_BUCKET = {
    "edit-field": "field",
    "add-component": "component-add",
    "link-component": "component-add",
    "remove-component": "component-remove",
    "move-component": "component-move",
    "duplicate-components": "component-move",
    "convert-to-shared": "component-shared",
    "synchronize-components": "component-shared",
    "update-rendering-parameters": "rendering",
    "update-rendering-rules": "personalization",
    "create-item": "created",
    "add-version": "created",
    "copy-items": "created",
    "clone-items": "created",
    "import-items": "created",
    "rename-item": "renamed",
    "change-template": "renamed",
    "move-items": "moved",
    "publish-item": "published",
    "delete-items": "deleted",
    "remove-version": "deleted",
};
/** Render order for buckets within an item row. */
export const ACTIVITY_BUCKET_ORDER = [
    "field",
    "component-add",
    "component-remove",
    "component-move",
    "component-shared",
    "rendering",
    "personalization",
    "created",
    "renamed",
    "moved",
    "deleted",
    "published",
    "suggested",
    "comment",
];
function normalizeToolName(name) {
    return (name ?? "").trim().toLowerCase().replace(/_/g, "-");
}
function parseJsonObject(text) {
    if (!text)
        return null;
    try {
        const parsed = JSON.parse(text);
        return parsed && typeof parsed === "object"
            ? parsed
            : null;
    }
    catch {
        return null;
    }
}
function asNonEmptyString(value) {
    return typeof value === "string" && value.length > 0 ? value : undefined;
}
function asNumber(value) {
    return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
/**
 * Extracts agent-authored suggested edits and comments from the agent transcript
 * tool calls (`suggest-field-edit` / `add-comment`). The transcript gives clean
 * per-agent attribution that the global feedback store lacks (it has no agentId).
 * Only successful, completed calls are returned.
 */
export function extractAgentFeedback(messages) {
    const out = [];
    for (const message of messages ?? []) {
        for (const call of message?.toolCalls ?? []) {
            const name = normalizeToolName(call.functionName);
            const isSuggestion = name === "suggest-field-edit";
            const isComment = name === "add-comment";
            if (!isSuggestion && !isComment)
                continue;
            if (call.functionError)
                continue;
            if (call.isCompleted === false)
                continue;
            if (!(call.functionResult ?? "").toLowerCase().includes("created")) {
                continue;
            }
            const args = parseJsonObject(call.functionArguments);
            if (!args)
                continue;
            const mainItemId = asNonEmptyString(args.mainItemId);
            const id = call.toolCallId || call.id;
            if (!mainItemId || !id)
                continue;
            out.push({
                id,
                kind: isSuggestion ? "suggested" : "comment",
                itemId: mainItemId,
                language: asNonEmptyString(args.mainItemLanguage) ??
                    asNonEmptyString(args.language) ??
                    asNonEmptyString(args.itemLanguage),
                version: asNumber(args.mainItemVersion),
                targetItemId: asNonEmptyString(args.itemId) ?? asNonEmptyString(args.componentId),
                fieldName: asNonEmptyString(args.field) ?? asNonEmptyString(args.fieldId),
                value: isSuggestion
                    ? asNonEmptyString(args.value)
                    : asNonEmptyString(args.comment),
                date: call.createdDate,
            });
        }
    }
    return out;
}
export function classifyOperation(op) {
    return OPERATION_BUCKET[op.type];
}
function descriptorFromId(id, ref) {
    return {
        id,
        language: ref.language,
        version: ref.version,
        database: ref.database,
    };
}
function targetFromItem(item, fallback, label) {
    if (item?.id) {
        return { item, path: item.path, label };
    }
    return { ...fallback, label };
}
function targetsFromItems(items, fallback) {
    const targets = items?.flatMap((item) => (item?.id ? [{ item, path: item.path }] : [])) ?? [];
    return targets.length > 0 ? targets : [fallback];
}
/**
 * Resolves which item row(s) an operation belongs to. Reads the type-specific
 * field rather than `mainItem`, which the client commonly sets to the current
 * page or parent (see editor/client/operations.tsx). Bulk ops fan out to one
 * target per affected item.
 */
export function affectedItems(op) {
    const main = op.mainItem;
    const mainTarget = { item: main, path: op.mainItemPath };
    switch (op.type) {
        case "rename-item": {
            const o = op;
            const item = o.item;
            return [targetFromItem(item, mainTarget, o.newName ?? item?.name)];
        }
        case "add-version": {
            const o = op;
            return [targetFromItem(o.item, mainTarget)];
        }
        case "remove-version": {
            const o = op;
            return [targetFromItem(o.item, mainTarget)];
        }
        case "publish-item": {
            const o = op;
            return [targetFromItem(o.item, mainTarget)];
        }
        case "create-item": {
            const o = op;
            if (o.createdItemId) {
                return [{ item: descriptorFromId(o.createdItemId, main), label: o.name }];
            }
            return [{ item: main, path: op.mainItemPath, label: o.name }];
        }
        case "import-items": {
            const o = op;
            if (o.createdItemIds?.length) {
                return o.createdItemIds.map((id) => ({ item: descriptorFromId(id, main) }));
            }
            return [targetFromItem(o.targetItem, mainTarget)];
        }
        case "delete-items": {
            const o = op;
            return targetsFromItems(o.items, mainTarget);
        }
        case "move-items": {
            const o = op;
            if (o.itemIds?.length) {
                return o.itemIds.map((id) => ({ item: descriptorFromId(id, main) }));
            }
            return [mainTarget];
        }
        case "copy-items":
        case "clone-items": {
            const o = op;
            if (o.createdItemIds?.length) {
                return o.createdItemIds.map((id) => ({ item: descriptorFromId(id, main) }));
            }
            // Pending copy/clone: only a destination parent is known yet.
            if (o.targetParentId) {
                return [{ item: descriptorFromId(o.targetParentId, main) }];
            }
            return [mainTarget];
        }
        default:
            // edit-field, component ops, rendering ops, change-template: act on the page.
            return [mainTarget];
    }
}
/**
 * Within-op multiplicity that is NOT expressed via fan-out. Component ops are
 * not fanned (they target the page), so their multiplicity is componentIds.length;
 * multi-item ops fan out and contribute 1 per row.
 */
function effectCount(op) {
    switch (op.type) {
        case "remove-component":
            return Math.max(op.componentIds?.length ?? 0, 1);
        case "move-component":
            return Math.max(op.componentIds?.length ?? 0, 1);
        case "duplicate-components":
            return Math.max(op.componentIds?.length ?? 0, 1);
        default:
            return 1;
    }
}
function parseDate(date) {
    if (!date)
        return 0;
    const time = new Date(date).getTime();
    return Number.isNaN(time) ? 0 : time;
}
function leafOfPath(path) {
    if (!path)
        return undefined;
    const parts = path.split(/[\\/]/).filter(Boolean);
    return parts.length ? parts[parts.length - 1] : undefined;
}
function stripBraces(id) {
    return id.replace(/[{}]/g, "");
}
export function buildActivityOverview(operations, feedback = []) {
    const groups = new Map();
    let order = 0;
    const ensureGroup = (key, target) => {
        let group = groups.get(key);
        if (!group) {
            group = {
                itemId: key,
                representative: null,
                firstTarget: target,
                buckets: new Map(),
                operations: [],
                opIds: new Set(),
                activeOpIds: new Set(),
                feedback: [],
                languages: new Set(),
                hasExecuting: false,
                hasFailed: false,
                latestDate: 0,
                order: order++,
            };
            groups.set(key, group);
        }
        return group;
    };
    for (const op of operations) {
        if (!op?.mainItem)
            continue;
        const kind = classifyOperation(op);
        const undone = op.undone === true;
        const targets = affectedItems(op);
        for (const target of targets) {
            if (!target.item?.id)
                continue;
            const key = target.item.id.toLowerCase();
            const group = ensureGroup(key, target);
            if (!undone && !group.representative) {
                group.representative = target;
            }
            if (!group.opIds.has(op.id)) {
                group.opIds.add(op.id);
                group.operations.push(op);
            }
            if (target.item.language)
                group.languages.add(target.item.language);
            if (!undone) {
                group.activeOpIds.add(op.id);
                if (op.executionStatus === "executing")
                    group.hasExecuting = true;
                if (op.executionStatus === "failed")
                    group.hasFailed = true;
            }
            const time = parseDate(op.date);
            if (time > group.latestDate)
                group.latestDate = time;
            let bucket = group.buckets.get(kind);
            if (!bucket) {
                bucket = {
                    operations: [],
                    opIds: new Set(),
                    count: 0,
                    fieldIds: kind === "field" ? new Set() : undefined,
                };
                group.buckets.set(kind, bucket);
            }
            if (!bucket.opIds.has(op.id)) {
                bucket.opIds.add(op.id);
                bucket.operations.push(op);
            }
            if (!undone) {
                if (kind === "field") {
                    const fieldId = op.fieldId || op.id;
                    bucket.fieldIds.add(fieldId);
                    bucket.count = bucket.fieldIds.size;
                }
                else {
                    bucket.count += effectCount(op);
                }
            }
        }
    }
    for (const activity of feedback) {
        if (!activity?.itemId || !activity.id)
            continue;
        const key = activity.itemId.toLowerCase();
        const target = {
            item: {
                id: activity.itemId,
                language: activity.language ?? "",
                version: activity.version ?? 0,
            },
            path: activity.itemId.includes("/") ? activity.itemId : undefined,
        };
        const group = ensureGroup(key, target);
        group.feedback.push(activity);
        if (activity.language)
            group.languages.add(activity.language);
        const time = parseDate(activity.date);
        if (time > group.latestDate)
            group.latestDate = time;
        let bucket = group.buckets.get(activity.kind);
        if (!bucket) {
            bucket = { operations: [], opIds: new Set(), count: 0 };
            group.buckets.set(activity.kind, bucket);
        }
        if (!bucket.opIds.has(activity.id)) {
            bucket.opIds.add(activity.id);
            bucket.count += 1;
        }
    }
    const items = [];
    for (const group of groups.values()) {
        const buckets = [];
        for (const kind of ACTIVITY_BUCKET_ORDER) {
            const bucket = group.buckets.get(kind);
            if (bucket && bucket.count > 0) {
                buckets.push({ kind, count: bucket.count, operations: bucket.operations });
            }
        }
        const target = group.representative ?? group.firstTarget;
        const name = target.label ?? target.item.name ?? target.item.displayName;
        const path = target.path ?? target.item.path;
        const leaf = leafOfPath(path);
        const idOnly = !name && !leaf;
        const label = name ?? leaf ?? stripBraces(target.item.id);
        const operationsSorted = [...group.operations].sort((a, b) => parseDate(a.date) - parseDate(b.date));
        items.push({
            itemId: group.itemId,
            item: target.item,
            path,
            label,
            idOnly,
            buckets,
            operations: operationsSorted,
            feedback: [...group.feedback].sort((a, b) => parseDate(a.date) - parseDate(b.date)),
            languages: [...group.languages],
            hasExecuting: group.hasExecuting,
            hasFailed: group.hasFailed,
            activeOperationCount: group.activeOpIds.size,
            totalOperationCount: group.opIds.size,
        });
    }
    // Newest activity first; stable on insertion order for ties.
    items.sort((a, b) => {
        const ga = groups.get(a.itemId);
        const gb = groups.get(b.itemId);
        if (gb.latestDate !== ga.latestDate)
            return gb.latestDate - ga.latestDate;
        return ga.order - gb.order;
    });
    const activeOpIds = new Set();
    for (const op of operations) {
        if (op?.id && op.undone !== true)
            activeOpIds.add(op.id);
    }
    return {
        items,
        totalItems: items.length,
        totalOperations: activeOpIds.size,
    };
}
//# sourceMappingURL=agentActivitySummary.js.map