/**
 * GetChatTurnGroupsUseCase
 *
 * Server-side derivation of "user turn groups" for the chat thread.
 *
 * The raw interactive_messages table is a flat, append-only log of
 * user/assistant pairs. The chat UI wants to collapse every COMPLETED
 * user turn into a single named card ("Working on: …") so the thread
 * doesn't become an endless scroll of raw bubbles — but the MOST
 * RECENT turn must stay live so the user keeps seeing the reply that
 * is actively streaming in response to what they just typed.
 *
 * Grouping rules:
 *   1. Ignore messages that carry a `stepId` — those are setup-workflow
 *      messages and live inside the StepTracker, not the flat thread.
 *   2. Walk the remaining messages in chronological order. Every user
 *      message opens a new turn; every subsequent assistant message is
 *      attached to the open turn until the next user message.
 *   3. The LAST turn is never grouped (it is the live turn).
 *   4. `hiddenMessageIds` lists every message id that a client should
 *      FILTER OUT of the flat thread render — the client then draws
 *      a collapsible group card in its place.
 *
 * This use case is read-only. It speaks exclusively to the
 * IInteractiveMessageRepository port, so presentation layers can ask
 * for groups without any coupling to the underlying SQLite schema.
 *
 * Feature: application-chat turn grouping (see spec in CLAUDE memo).
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { inject, injectable } from 'tsyringe';
import { InteractiveMessageRole } from '../../../domain/generated/output.js';
const PREVIEW_MAX = 120;
const TITLE_MAX = 140;
const FALLBACK_TITLE = 'Working on your request';
function toEpochMs(raw) {
    if (raw instanceof Date)
        return raw.getTime();
    if (typeof raw === 'number' && Number.isFinite(raw))
        return raw;
    if (typeof raw === 'string') {
        const asNum = Number(raw);
        if (Number.isFinite(asNum))
            return asNum;
        const parsed = new Date(raw).getTime();
        return Number.isFinite(parsed) ? parsed : 0;
    }
    return 0;
}
function truncate(text, max) {
    if (text.length <= max)
        return text;
    return `${text.slice(0, Math.max(0, max - 1)).trimEnd()}…`;
}
function buildTitle(userText) {
    const cleaned = userText.trim().replace(/\s+/g, ' ');
    if (cleaned.length === 0)
        return FALLBACK_TITLE;
    const preview = truncate(cleaned, PREVIEW_MAX);
    return truncate(`Working on: ${preview}`, TITLE_MAX);
}
let GetChatTurnGroupsUseCase = class GetChatTurnGroupsUseCase {
    messages;
    constructor(messages) {
        this.messages = messages;
    }
    async execute(input) {
        const all = await this.messages.findByFeatureId(input.featureId);
        // Setup-workflow messages live inside the StepTracker — never
        // part of the flat-thread turn grouping.
        const flat = all.filter((m) => !m.stepId);
        if (flat.length === 0) {
            return { groups: [], currentTurn: null, hiddenMessageIds: [] };
        }
        const turns = [];
        let current = null;
        for (const m of flat) {
            if (m.role === InteractiveMessageRole.user) {
                current = { user: m, items: [m] };
                turns.push(current);
            }
            else if (current) {
                current.items.push(m);
            }
            // If we see an assistant message with NO open turn, it is an
            // orphan (very rare — only possible from out-of-order writes).
            // Drop it silently: it would not belong to any grouped card and
            // forcing it into one would be misleading.
        }
        if (turns.length === 0) {
            return { groups: [], currentTurn: null, hiddenMessageIds: [] };
        }
        // The final turn is the "current" one — the user's most recent
        // ask, which the client renders as an expanded in-progress card
        // with the live streaming indicator pinned inside it. Everything
        // before it is a completed group the client collapses by default.
        const completed = turns.slice(0, -1);
        const latest = turns[turns.length - 1];
        const completedGroups = completed.map((t) => buildGroup(t, 'completed'));
        const currentTurn = buildGroup(latest, 'in-progress');
        const hiddenMessageIds = [
            ...completedGroups.flatMap((g) => g.messageIds),
            ...currentTurn.messageIds,
        ];
        return { groups: completedGroups, currentTurn, hiddenMessageIds };
    }
};
GetChatTurnGroupsUseCase = __decorate([
    injectable(),
    __param(0, inject('IInteractiveMessageRepository')),
    __metadata("design:paramtypes", [Object])
], GetChatTurnGroupsUseCase);
export { GetChatTurnGroupsUseCase };
function buildGroup(t, status) {
    const first = t.items[0];
    const last = t.items[t.items.length - 1];
    const userText = t.user.content ?? '';
    const assistantMessageCount = t.items.filter((m) => m.role === InteractiveMessageRole.assistant).length;
    return {
        id: `turn-${t.user.id}`,
        title: buildTitle(userText),
        userMessagePreview: truncate(userText.trim().replace(/\s+/g, ' '), PREVIEW_MAX),
        messageIds: t.items.map((m) => m.id),
        assistantMessageCount,
        startedAt: toEpochMs(first.createdAt),
        endedAt: toEpochMs(last.createdAt),
        status,
    };
}
