/**
 * Document comments — anchored, threaded discussion attached to documents.
 *
 * This module owns the *pure* comment logic: validation, normalization, and
 * the storage-agnostic CRUD entry points. The HTTP layer in `api/handlers.ts`
 * is a thin wrapper that handles auth + JSON envelope; everything else lives
 * here so the unit tests don't need a Request loop.
 *
 * Storage shape — see `packages/cms-core/prisma/cms-schema.prisma`,
 * model `DocumentComment`. We require only a minimal Prisma surface so the
 * helpers can be tested against an in-memory fake.
 *
 * Anchors are Yjs *relative positions* serialised as Base64. Storing them in
 * a JSON column (rather than Bytes) keeps the REST contract trivial: the
 * client posts `{ from: "AQ...", to: "AQ..." }`, we round-trip the strings.
 * Slice 5 reconstitutes them with `Y.createAbsolutePositionFromRelativePosition`
 * to paint anchored highlights in the editor; nothing in the API layer needs
 * to interpret the bytes.
 */
/** Maximum length we accept for a comment body (markdown source). */
export declare const MAX_COMMENT_BODY_LENGTH = 10000;
/** Cap on returned comments per `listComments` call. */
export declare const MAX_COMMENT_LIST_PAGE_SIZE = 200;
/** Default page size when the caller does not request one. */
export declare const DEFAULT_COMMENT_LIST_PAGE_SIZE = 100;
/**
 * Yjs relative-position anchor. The two ends are Base64-encoded
 * `Y.encodeRelativePosition` bytes; slice 5 turns them back into absolute
 * positions for rendering.
 *
 * Slice 4 (this module) treats the anchor as opaque: we only validate that
 * each side is a reasonably short string so we don't store arbitrary blobs.
 */
export interface CommentAnchor {
    from: string;
    to: string;
}
/**
 * Public REST shape of a comment. Mirrors the Prisma row plus a derived
 * `isResolved` flag so the client doesn't have to repeat the null check.
 */
export interface CommentDTO {
    id: string;
    documentId: string;
    parentId: string | null;
    userId: string | null;
    body: string;
    anchor: CommentAnchor | null;
    resolvedAt: string | null;
    resolvedById: string | null;
    deletedAt: string | null;
    createdAt: string;
    updatedAt: string;
    isResolved: boolean;
}
/** Input for creating a new comment. */
export interface CreateCommentInput {
    documentId: string;
    userId: string;
    body: string;
    anchor?: CommentAnchor | null;
    parentId?: string | null;
}
/** Input for updating an existing comment. Only the body is editable. */
export interface UpdateCommentInput {
    body: string;
}
/** Filter passed to `listComments`. */
export interface ListCommentsFilter {
    documentId: string;
    /** Include resolved threads. Default: `false`. */
    includeResolved?: boolean;
    /** Include soft-deleted comments. Default: `false`. */
    includeDeleted?: boolean;
    /** Page size (1..`MAX_COMMENT_LIST_PAGE_SIZE`). */
    pageSize?: number;
}
/**
 * Minimal Prisma surface we depend on. The real `PrismaClient` satisfies it,
 * but tests inject a small in-memory fake.
 */
export interface CommentsDB {
    documentComment: {
        findUnique: (args: {
            where: {
                id: string;
            };
            [key: string]: unknown;
        }) => Promise<DocumentCommentRow | null>;
        findMany: (args: {
            where: Record<string, unknown>;
            orderBy?: Record<string, 'asc' | 'desc'>;
            take?: number;
        }) => Promise<DocumentCommentRow[]>;
        create: (args: {
            data: {
                documentId: string;
                userId: string | null;
                body: string;
                anchor: CommentAnchor | null;
                parentId: string | null;
            };
        }) => Promise<DocumentCommentRow>;
        update: (args: {
            where: {
                id: string;
            };
            data: Partial<{
                body: string;
                resolvedAt: Date | null;
                resolvedById: string | null;
                deletedAt: Date | null;
            }>;
        }) => Promise<DocumentCommentRow>;
    };
}
/** Row shape returned by Prisma. We accept Date or ISO strings so fakes are cheap. */
export interface DocumentCommentRow {
    id: string;
    documentId: string;
    parentId: string | null;
    userId: string | null;
    body: string;
    anchor: CommentAnchor | null;
    resolvedAt: Date | string | null;
    resolvedById: string | null;
    deletedAt: Date | string | null;
    createdAt: Date | string;
    updatedAt: Date | string;
}
/** Discriminated result type used by the service entry points. */
export type CommentsResult<T> = {
    ok: true;
    value: T;
} | {
    ok: false;
    error: CommentsError;
};
export interface CommentsError {
    /**
     * Stable machine-readable code. The HTTP layer maps each to a status:
     * - validation → 400
     * - not_found  → 404
     * - forbidden  → 403
     * - conflict   → 409 (e.g. resolving an already-resolved thread)
     */
    code: 'validation' | 'not_found' | 'forbidden' | 'conflict';
    message: string;
}
/**
 * Strip a comment row down to the public DTO. Centralised so the response
 * shape stays consistent between create / list / update / resolve.
 */
export declare function toCommentDTO(row: DocumentCommentRow): CommentDTO;
/**
 * Parse + validate an anchor blob coming from an untrusted client. Returns
 * `null` when the caller did not provide one (document-level comments).
 */
export declare function parseAnchor(input: unknown): CommentsResult<CommentAnchor | null>;
/** Normalize whitespace + enforce length limits on a posted body. */
export declare function normaliseBody(input: unknown): CommentsResult<string>;
/**
 * Create a comment. Enforces:
 *   - body validation (non-empty, length-capped)
 *   - anchor shape validation
 *   - single-level threading: a `parentId` must reference an existing,
 *     non-deleted, non-reply comment under the same document.
 *
 * The caller (HTTP handler) is responsible for auth — once we reach here we
 * trust the supplied `userId`.
 */
export declare function createComment(db: CommentsDB, input: CreateCommentInput): Promise<CommentsResult<CommentDTO>>;
/**
 * List comments for a document. Returns the rows ordered oldest-first so a
 * naive client can render threads top-down. Pagination is "show the first
 * N"; the higher-level UI in slice 5 will switch to cursor pagination once
 * volume justifies it.
 */
export declare function listComments(db: CommentsDB, filter: ListCommentsFilter): Promise<CommentsResult<CommentDTO[]>>;
/**
 * Edit a comment body. Only the author can edit, and only if the comment is
 * not soft-deleted. Resolved threads remain editable so a moderator can
 * append context.
 */
export declare function updateComment(db: CommentsDB, id: string, input: UpdateCommentInput, actor: {
    userId: string;
    isAdmin: boolean;
}): Promise<CommentsResult<CommentDTO>>;
/**
 * Mark a comment thread as resolved. Either the comment's author or any
 * admin / write-role caller can resolve. Resolving an already-resolved
 * comment is a no-op (returns the existing row) so a duplicate click in the
 * UI does not surface as an error.
 */
export declare function resolveComment(db: CommentsDB, id: string, actor: {
    userId: string;
    canResolve: boolean;
}): Promise<CommentsResult<CommentDTO>>;
/**
 * Re-open a resolved thread. Same permission as `resolveComment`.
 */
export declare function reopenComment(db: CommentsDB, id: string, actor: {
    userId: string;
    canResolve: boolean;
}): Promise<CommentsResult<CommentDTO>>;
/**
 * Soft-delete a comment. The row is preserved (so replies don't lose their
 * thread root) but stops being returned by `listComments` unless explicitly
 * requested with `includeDeleted: true`.
 *
 * Only the author or an admin may delete.
 */
export declare function deleteComment(db: CommentsDB, id: string, actor: {
    userId: string;
    isAdmin: boolean;
}): Promise<CommentsResult<CommentDTO>>;
//# sourceMappingURL=comments.d.ts.map