import { randomUUID } from "node:crypto";
import { DEFAULT_DIFF_TOOL_CODE_THEME, DIFF_TOOL_CODE_THEMES } from "./shared_types.js";
const emptyBrief = {
    content: "",
    loading: false,
};
const codeThemes = new Set(DIFF_TOOL_CODE_THEMES);
function createInitialState(options) {
    return {
        diffStyle: "split",
        overflowMode: "wrap",
        codeTheme: options.codeTheme ?? DEFAULT_DIFF_TOOL_CODE_THEME,
        sidebarOpen: false,
        collapsedFileIds: [],
        viewedFileIds: [],
        threads: [],
        brief: { ...emptyBrief },
    };
}
export class DiffToolReviewStateStore {
    state;
    constructor(options = {}) {
        this.state = createInitialState(options);
    }
    getState() {
        return {
            diffStyle: this.state.diffStyle,
            overflowMode: this.state.overflowMode,
            codeTheme: this.state.codeTheme,
            sidebarOpen: this.state.sidebarOpen,
            collapsedFileIds: [...this.state.collapsedFileIds],
            viewedFileIds: [...this.state.viewedFileIds],
            threads: this.state.threads.map(cloneThread),
            brief: cloneBrief(this.state.brief),
        };
    }
    updateState(patch) {
        if (patch.diffStyle === "split" || patch.diffStyle === "stacked") {
            this.state.diffStyle = patch.diffStyle;
        }
        if (patch.overflowMode === "wrap" || patch.overflowMode === "scroll") {
            this.state.overflowMode = patch.overflowMode;
        }
        if (patch.codeTheme && codeThemes.has(patch.codeTheme)) {
            this.state.codeTheme = patch.codeTheme;
        }
        if (typeof patch.sidebarOpen === "boolean") {
            this.state.sidebarOpen = patch.sidebarOpen;
        }
        if (Array.isArray(patch.collapsedFileIds)) {
            this.state.collapsedFileIds = patch.collapsedFileIds.filter((value) => typeof value === "string" && value.trim().length > 0);
        }
        if (Array.isArray(patch.viewedFileIds)) {
            this.state.viewedFileIds = patch.viewedFileIds.filter((value) => typeof value === "string" && value.trim().length > 0);
        }
    }
    findThread(id) {
        const thread = this.findThreadInternal(id);
        return thread ? cloneThread(thread) : undefined;
    }
    createThread(payload) {
        const id = randomUUID();
        this.state.threads.push({
            id,
            anchor: cloneAnchor(payload.anchor),
            messages: [{ role: "user", text: payload.body }],
            loading: false,
            resolved: false,
            collapsed: false,
        });
        return id;
    }
    addReply(id, text) {
        const thread = this.findThreadInternal(id);
        if (!thread) {
            return false;
        }
        thread.messages.push({ role: "user", text });
        thread.resolved = false;
        thread.collapsed = false;
        return true;
    }
    deleteThread(id) {
        const index = this.state.threads.findIndex((thread) => thread.id === id);
        if (index < 0) {
            return false;
        }
        this.state.threads.splice(index, 1);
        return true;
    }
    setThreadLoading(id, loading) {
        const thread = this.findThreadInternal(id);
        if (!thread) {
            return false;
        }
        thread.loading = loading;
        return true;
    }
    setThreadResolved(id, resolved) {
        const thread = this.findThreadInternal(id);
        if (!thread) {
            return false;
        }
        thread.resolved = resolved;
        thread.collapsed = resolved;
        return true;
    }
    setThreadCollapsed(id, collapsed) {
        const thread = this.findThreadInternal(id);
        if (!thread) {
            return false;
        }
        thread.collapsed = collapsed;
        return true;
    }
    startBriefGeneration() {
        this.state.brief = {
            content: this.state.brief.content,
            loading: true,
        };
    }
    applyBriefResult(result) {
        this.state.brief = {
            threadId: result.threadId,
            content: result.response,
            loading: false,
        };
    }
    setBriefLoading(loading) {
        this.state.brief = {
            ...(this.state.brief.threadId ? { threadId: this.state.brief.threadId } : {}),
            content: this.state.brief.content,
            loading,
        };
    }
    applyThreadResponse(id, result) {
        const thread = this.findThreadInternal(id);
        if (!thread) {
            return false;
        }
        thread.threadId = result.threadId;
        thread.messages.push({ role: "assistant", text: result.response });
        thread.loading = false;
        return true;
    }
    buildThreadAgentMessage(id) {
        const thread = this.findThreadInternal(id);
        if (!thread) {
            return undefined;
        }
        const userText = pendingUserText(thread);
        if (!userText) {
            return "";
        }
        const locationPrefix = thread.threadId || thread.anchor.kind !== "line"
            ? ""
            : `[${thread.anchor.filePath}:${thread.anchor.lineNumber} (${thread.anchor.side === "additions" ? "new" : "old"})]\n\n`;
        return `${locationPrefix}${userText}`;
    }
    buildReviewText(submissionMessage) {
        const trimmedSubmissionMessage = submissionMessage?.trim();
        const unresolvedThreads = this.state.threads.filter((thread) => !thread.resolved);
        if (unresolvedThreads.length === 0) {
            return trimmedSubmissionMessage || "(no comments)";
        }
        const guidance = [
            "The notes below include thread transcripts from the review. In those transcripts:",
            "",
            "- **user** is a comment written by the reviewer",
            "- **agent** is a generated reply within that review thread",
            "",
            "Treat thread dialogue as supporting review context, not automatically as a final conclusion.",
        ].join("\n");
        const threads = unresolvedThreads
            .map((thread, index) => {
            const location = thread.anchor.kind === "line"
                ? `${thread.anchor.filePath}:${thread.anchor.lineNumber} (${thread.anchor.side === "additions" ? "new" : "old"})`
                : "general discussion";
            const body = thread.messages
                .map((message) => `**${message.role === "assistant" ? "agent" : "user"}**\n\n${message.text}`)
                .join("\n\n");
            return `## thread ${index + 1}\n\n\`${location}\`\n\n${body}`;
        })
            .join("\n\n---\n\n");
        const message = trimmedSubmissionMessage
            ? `## submission message\n\n${trimmedSubmissionMessage}\n\n---\n\n`
            : "";
        return `${guidance}\n\n---\n\n${message}${threads}`;
    }
    findThreadInternal(id) {
        return this.state.threads.find((thread) => thread.id === id);
    }
}
function cloneBrief(brief) {
    return {
        ...(brief.threadId ? { threadId: brief.threadId } : {}),
        content: brief.content,
        loading: brief.loading,
    };
}
function cloneThread(thread) {
    return {
        id: thread.id,
        ...(thread.threadId ? { threadId: thread.threadId } : {}),
        anchor: cloneAnchor(thread.anchor),
        messages: thread.messages.map((message) => ({ ...message })),
        loading: thread.loading,
        resolved: thread.resolved,
        collapsed: thread.collapsed,
    };
}
function cloneAnchor(anchor) {
    return anchor.kind === "line" ? { ...anchor } : { kind: "detached" };
}
function pendingUserText(thread) {
    const pending = [];
    for (let index = thread.messages.length - 1; index >= 0; index -= 1) {
        const message = thread.messages[index];
        if (!message) {
            continue;
        }
        if (message.role === "assistant") {
            break;
        }
        pending.unshift(message.text);
    }
    return pending.join("\n\n");
}
//# sourceMappingURL=review_state.js.map