/**
 * ChannelStore — CRUD for `chat_channels`.
 *
 * All authorization is handled at a higher layer (`ChatV2Service`);
 * this store only enforces DB-level invariants (FKs, unique indexes).
 *
 * @module services/chat-v2/sqlite/channel.store
 */
import { type ChatChannelRow, type ChatChannelType } from '../types.js';
import type { ChatDatabase } from './chat-db.js';
/** Payload for `ChannelStore.create`. */
export interface ChannelCreateInput {
    /**
     * Wire-level session binding. For `type='dm'` (default), this is the
     * agent's session ID and the partial unique index enforces 1:1 binding.
     * For `type='channel'`, callers should pass the empty string `''` —
     * channel rows are excluded from the dm-binding unique index by design.
     */
    agentSession: string;
    ownerUserId: string;
    name: string;
    purpose?: string | null;
    /**
     * Phase A (SEALED §3.1) — channel type. Defaults to `'dm'` to preserve
     * the Phase 1 / Week 2 contract for callers that omit this field.
     */
    type?: ChatChannelType;
    /** Phase A — required when `type='channel'`; null/empty for DMs. */
    teamId?: string | null;
    /** Phase A — optional project link for project-scoped channels. */
    projectId?: string | null;
    /** Phase A — for `type='dm'`, the resolved member-ID being DM'd. */
    targetMemberId?: string | null;
    /** Override the clock (tests). */
    nowMs?: number;
    /** Override the generated id (tests / deterministic callers). */
    id?: string;
}
/** SQLite-backed store for chat channels. */
export declare class ChannelStore {
    private readonly db;
    constructor(db: ChatDatabase);
    /**
     * Create a new channel. Enforces the 1:1 agent-binding by catching
     * the SQLite unique-constraint error and surfacing it as a
     * `ChatError(agent_already_bound, 409)`.
     *
     * @param input - The channel creation payload
     * @returns The inserted channel row
     * @throws {ChatError} `agent_already_bound` (409) if the agent is already bound
     *   to another active channel.
     */
    create(input: ChannelCreateInput): ChatChannelRow;
    /**
     * Look up a channel by its id (active or archived).
     *
     * @param id - The channel id
     * @returns The row, or null if not found
     */
    getById(id: string): ChatChannelRow | null;
    /**
     * Find the single active channel bound to an agent session, if any.
     * Uses the partial unique index, so at most one row is returned.
     *
     * @param agentSession - The agent session id
     * @returns The active channel row, or null
     */
    findActiveByAgentSession(agentSession: string): ChatChannelRow | null;
    /**
     * Find the most-recent active DM channel owned by a specific user and
     * bound to a specific agent session. Used by the `ensureDmChannel`
     * helper backing the /agents page — the owner-scoped variant is needed
     * because the `uq_channel_agent_dm_active` unique index was dropped
     * (see chat-db.ts:380-386), so multiple DM rows for the same agent
     * can coexist (e.g. created by legacy bridge code with the synthetic
     * `'system'` owner).
     *
     * Ordering matches `listByOwner`: most-recent activity first, so the
     * caller always lands on the DM the user was last using.
     *
     * @param ownerUserId - The user_id that must own the channel
     * @param agentSession - The agent session id
     * @returns The most-recent active DM channel row, or null
     */
    findActiveDmByOwnerAndAgent(ownerUserId: string, agentSession: string): ChatChannelRow | null;
    /**
     * Find the oldest active `type='channel'` row for a team, if any.
     *
     * Team channels are not owner-scoped — a team channel belongs to the
     * whole team — so this lookup is keyed on `team_id` alone. Ordering by
     * `created_at ASC` returns the team's original (e.g. `#general`) channel
     * stably, which is what `ensureTeamChannel` treats as the canonical
     * "the team already has a channel" marker.
     *
     * @param teamId - The team id whose channel to find
     * @returns The oldest active team channel row, or null when none exists
     */
    findActiveChannelByTeam(teamId: string): ChatChannelRow | null;
    /**
     * List channels owned by a user.
     *
     * Phase C — extended with `type` + `teamId` filter options so the
     * `GET /api/chat/channels` endpoint can serve the channel-rail's
     * grouped/workspace-scoped views without shipping every row to the
     * client. Filters compose with AND; passing `undefined` for either is
     * a no-op (the existing all-channels behavior).
     *
     * @param ownerUserId - The user_id whose channels to return
     * @param options - Listing options
     * @param options.includeArchived - When false (default), filter out archived rows
     * @param options.limit - Max rows to return (capped at 100)
     * @param options.type - Phase C: when set, filter rows to this channel type
     *   (`'dm'` or `'channel'`). Useful for the channel-rail's "DMs only" /
     *   "Channels only" views.
     * @param options.teamId - Phase C: when set, filter rows whose `team_id`
     *   matches. Used by the workspace-scoped Channels group; empty / null
     *   `team_id` rows are excluded by this filter on purpose (DMs and
     *   workspace-less rows belong to no team).
     * @returns Channel rows sorted by `last_message_at DESC, created_at DESC`
     */
    listByOwner(ownerUserId: string, options?: {
        includeArchived?: boolean;
        limit?: number;
        type?: ChatChannelType;
        teamId?: string;
    }): ChatChannelRow[];
    /**
     * List active "bridged" channels — inbound conversations from external
     * surfaces (currently Slack), persisted by the bridge under the synthetic
     * `'system'` owner and keyed by a `slack-…` id. These belong to no single
     * Crewly user, so they are surfaced in the consolidated chat list across
     * the (single-user OSS) account rather than via `listByOwner`.
     *
     * @param options.includeArchived - Include archived rows (default false).
     * @param options.limit - Max rows (capped at 200).
     * @returns Bridged channel rows, most-recent first.
     */
    listBridged(options?: {
        includeArchived?: boolean;
        limit?: number;
    }): ChatChannelRow[];
    /**
     * Mark a channel as archived (soft-delete). No-op if already archived.
     *
     * @param id - The channel id
     * @param nowMs - Optional override for the archive timestamp
     * @returns True if a row was updated (was active), false otherwise
     */
    archive(id: string, nowMs?: number): boolean;
    /**
     * Phase 6.0b — clear an archived flag. Inverse of {@link archive};
     * required by the legacy `unarchiveConversation` controller route.
     *
     * @param id - The channel id
     * @returns True if a row was updated (was archived), false otherwise
     */
    unarchive(id: string): boolean;
    /**
     * Phase 6.0b — rename a channel. Used by the legacy
     * `updateConversationTitle` controller route.
     *
     * @param id - The channel id
     * @param name - The new name
     * @returns True if a row was updated, false if the channel doesn't exist
     */
    rename(id: string, name: string): boolean;
    /**
     * Phase 6.0b — hard-delete a channel and all its messages. Note: this
     * is distinct from {@link archive} (soft delete via archived_at).
     * Used by the legacy `deleteConversation` controller route.
     *
     * FK ON DELETE CASCADE on `chat_messages.channel_id` cleans up
     * messages atomically with the channel row.
     *
     * @param id - The channel id to remove
     * @returns True if a row was removed, false if no such channel
     */
    hardDelete(id: string): boolean;
    /**
     * Bump `last_message_at` to a provided timestamp if it is greater than
     * the current value. Safe to call many times.
     *
     * @param id - The channel id
     * @param timestampMs - The timestamp to record
     */
    touchLastMessageAt(id: string, timestampMs: number): void;
}
//# sourceMappingURL=channel.store.d.ts.map