/**
 * Comments REST client — thin, typed wrapper around the
 * `/documents/:id/comments` + `/comments/:id` endpoints shipped in slice 4
 * of Phase 3 (see `packages/cms-core/src/realtime/comments.ts`).
 *
 * The client lives in `cms-admin` so the side panel, the bubble-menu
 * "comment on selection" action, and any future MCP/AI hooks all share one
 * request path, error model, and DTO shape.
 *
 * Mirrors the server contract:
 *   POST   /documents/:documentId/comments     -> { body, anchor?, parentId? }
 *   GET    /documents/:documentId/comments?…   -> { data: CommentDTO[] }
 *   PATCH  /comments/:id                       -> { body }
 *   POST   /comments/:id/resolve               -> 200 with updated DTO
 *   POST   /comments/:id/reopen                -> 200 with updated DTO
 *   DELETE /comments/:id                       -> 200 with soft-deleted DTO
 */
import { cmsApi } from './api.js';
function encodeOptions(opts = {}) {
    const params = new URLSearchParams();
    if (opts.includeResolved)
        params.set('includeResolved', 'true');
    if (opts.includeDeleted)
        params.set('includeDeleted', 'true');
    if (opts.pageSize !== undefined)
        params.set('pageSize', String(opts.pageSize));
    const qs = params.toString();
    return qs ? `?${qs}` : '';
}
function failure(error, status) {
    return { ok: false, error, status };
}
function envelope(res) {
    if (res.error || res.data === undefined) {
        return failure(res.error ?? `Request failed (${res.status})`, res.status);
    }
    return { ok: true, result: res.data };
}
/**
 * Create a top-level comment or a reply. Pass `parentId` to thread the
 * comment under an existing one — the server enforces single-level
 * threading (replies cannot themselves be parents).
 */
export async function createComment(input) {
    if (typeof input.documentId !== 'string' || input.documentId.length === 0) {
        return failure('documentId is required', 0);
    }
    if (typeof input.body !== 'string' || input.body.trim().length === 0) {
        return failure('Comment cannot be empty', 0);
    }
    const body = { body: input.body.trim() };
    if (input.anchor)
        body.anchor = input.anchor;
    if (input.parentId)
        body.parentId = input.parentId;
    const res = await cmsApi(`/documents/${encodeURIComponent(input.documentId)}/comments`, {
        method: 'POST',
        body: JSON.stringify(body),
    });
    return envelope(res);
}
/**
 * List comments for a document. Active comments (not resolved, not deleted)
 * are returned by default; pass `includeResolved` to keep resolved threads
 * in the response. The server rejects `includeDeleted` for non-admins.
 */
export async function listComments(documentId, opts = {}) {
    if (typeof documentId !== 'string' || documentId.length === 0) {
        return failure('documentId is required', 0);
    }
    const path = `/documents/${encodeURIComponent(documentId)}/comments${encodeOptions(opts)}`;
    const res = await cmsApi(path, { method: 'GET' });
    return envelope(res);
}
/** Edit a comment body. Only the author or an admin is permitted. */
export async function updateComment(id, body) {
    if (!id)
        return failure('comment id is required', 0);
    if (typeof body !== 'string' || body.trim().length === 0) {
        return failure('Comment cannot be empty', 0);
    }
    const res = await cmsApi(`/comments/${encodeURIComponent(id)}`, {
        method: 'PATCH',
        body: JSON.stringify({ body: body.trim() }),
    });
    return envelope(res);
}
/** Resolve a thread. Write-role or the author may resolve. */
export async function resolveComment(id) {
    if (!id)
        return failure('comment id is required', 0);
    const res = await cmsApi(`/comments/${encodeURIComponent(id)}/resolve`, {
        method: 'POST',
        body: '{}',
    });
    return envelope(res);
}
/** Re-open a resolved thread. Same permission as resolve. */
export async function reopenComment(id) {
    if (!id)
        return failure('comment id is required', 0);
    const res = await cmsApi(`/comments/${encodeURIComponent(id)}/reopen`, {
        method: 'POST',
        body: '{}',
    });
    return envelope(res);
}
/**
 * Soft-delete a comment. The row is preserved so its replies don't lose
 * their thread root — the server simply stops returning it from the list
 * endpoint unless an admin asks for `includeDeleted`.
 */
export async function deleteComment(id) {
    if (!id)
        return failure('comment id is required', 0);
    const res = await cmsApi(`/comments/${encodeURIComponent(id)}`, {
        method: 'DELETE',
    });
    return envelope(res);
}
export function groupCommentsIntoThreads(comments) {
    const roots = new Map();
    const sorted = [...comments].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
    for (const c of sorted) {
        if (c.parentId === null) {
            roots.set(c.id, { root: c, replies: [] });
        }
    }
    for (const c of sorted) {
        if (c.parentId !== null) {
            const root = roots.get(c.parentId);
            if (root)
                root.replies.push(c);
        }
    }
    return Array.from(roots.values());
}
//# sourceMappingURL=comments-client.js.map