import Database from 'better-sqlite3';
import { logger } from '../logger.js';
import * as schemaRepo from './repositories/schema.js';
import * as jobsRepo from './repositories/jobs.js';
import { JobStatus, Job, CreateJobParams, SubtaskInfo } from './repositories/jobs.js';
import * as spacesRepo from './repositories/spaces.js';
import { Space, SpaceMemberRoleValue, SpaceMember, SpaceInviteRole, SpaceInvite, CreateSpaceParams } from './repositories/spaces.js';
import * as usersRepo from './repositories/users.js';
import { User, CreateUserParams, FindOrCreateByOAuthParams, CreateLocalUserParams, LocalOrg, LocalOrgMember, GiteaOrgInput, GiteaOrg } from './repositories/users.js';
import * as workerNodesRepo from './repositories/worker-nodes.js';
import { WorkerNode, UpsertWorkerNodeParams } from './repositories/worker-nodes.js';
import * as scheduledTasksRepo from './repositories/scheduled-tasks.js';
import { ScheduledTask, CreateScheduledTaskParams, UpdateScheduledTaskParams } from './repositories/scheduled-tasks.js';
import * as a2aRepo from './repositories/a2a.js';
import { A2aClientRow, A2aDelegationRow } from './repositories/a2a.js';
import * as reflectionRepo from './repositories/reflection.js';
import * as toolRequestsRepo from './repositories/tool-requests.js';
import { ToolRequestCategory, ToolRequestStatus, ToolRequest, ToolRequestAggregate, PackageRequest, PackageRequestStatus } from './repositories/tool-requests.js';
import * as gatewayRepo from './repositories/gateway.js';
import { GatewayVirtualKeySource, GatewayVirtualKey, GatewayKeyUsage } from './repositories/gateway.js';
import * as llmUsageRepo from './repositories/llm-usage.js';
import { LlmUsageIncrement, LlmUsageDailyAgg, LlmUsageHourlyIncrement, LlmUsageHourlyRow } from './repositories/llm-usage.js';
import * as notificationsRepo from './repositories/notifications.js';
import { PushSubscriptionRecord, UpsertPushSubscriptionInput, NotificationPrefs, NotificationPrefsUpdate } from './repositories/notifications.js';
import * as auditRepo from './repositories/audit.js';
import * as appSharesRepo from './repositories/app-shares.js';
import * as provenanceRepo from './repositories/provenance.js';
import * as localTasksRepo from './repositories/local-tasks.js';
import { LocalTask, MissionBrief, LocalTaskComment, CreateLocalTaskParams } from './repositories/local-tasks.js';
export { MISSION_BRIEF_FIELDS } from './repositories/local-tasks.js';
export type { TitleSource, LocalTask, MissionBriefField, MissionBrief, LocalTaskComment, CreateLocalTaskParams } from './repositories/local-tasks.js';
import * as taskSearchRepo from './repositories/task-search.js';
import * as calendarRepo from './repositories/calendar.js';
import { CalendarEvent, CalendarDayCounts, CalendarDayTask, CrossSpaceCalendarMonth } from './repositories/calendar.js';
export { enumerateLocalDays } from './repositories/calendar.js';
export type { CalendarEvent, CalendarDayCounts, CalendarDayTask, CrossCalendarSpace, CrossSpaceCalendarMonth } from './repositories/calendar.js';
export type { NotifyEventType, PushSubscriptionRecord, UpsertPushSubscriptionInput, NotificationPrefs, NotificationPrefsUpdate } from './repositories/notifications.js';
export type { LlmUsageIncrement, LlmUsageDailyAgg, LlmUsageHourlyIncrement, LlmUsageHourlyRow } from './repositories/llm-usage.js';
export type { GatewayVirtualKeySource, GatewayVirtualKey, GatewayKeyUsage } from './repositories/gateway.js';
export type { ToolRequestCategory, ToolRequestStatus, ToolRequest, ToolRequestAggregate, PackageRequest, PackageRequestStatus } from './repositories/tool-requests.js';
export type { A2aClientRow, A2aDelegationRow } from './repositories/a2a.js';
export type { ScheduledTaskKind, ScheduledTask, CreateScheduledTaskParams, UpdateScheduledTaskParams } from './repositories/scheduled-tasks.js';
export type { WorkerNode, UpsertWorkerNodeParams } from './repositories/worker-nodes.js';
export type { User, CreateUserParams, FindOrCreateByOAuthParams, CreateLocalUserParams, LocalOrg, LocalOrgMember, GiteaOrgInput, GiteaOrg } from './repositories/users.js';
export type { Space, SpaceMemberRoleValue, SpaceMember, SpaceInviteRole, SpaceInvite, CreateSpaceParams } from './repositories/spaces.js';
export type { JobStatus, JobRole, JobProfile, TaskClass, Job, CreateJobParams, SubtaskInfo } from './repositories/jobs.js';
export { localTaskRepoName } from './repositories/shared.js';


export class Repository {
  private readonly db: Database.Database;

  constructor(dbPath: string) {
    this.db = new Database(dbPath);
    this.db.pragma('journal_mode = WAL');
    this.db.pragma('foreign_keys = ON');
    this.db.pragma('busy_timeout = 5000');
    this.initSchema();
    logger.info(`Repository: initialized at ${dbPath}`);
  }

  private initSchema(): void {
    return schemaRepo.initSchema(this.db);
  }

  private runPreSchemaCompatibilityMigrations(): void {
    return schemaRepo.runPreSchemaCompatibilityMigrations(this.db);
  }

  private ensureColumn(tableName: string, columnName: string, definition: string): void {
    return schemaRepo.ensureColumn(this.db, tableName, columnName, definition);
  }

  private migrateWaitingSubtasksStatus(): void {
    return schemaRepo.migrateWaitingSubtasksStatus(this.db);
  }

  private insertJobSync(params: CreateJobParams): Job {
    return jobsRepo.insertJobSync(this.db, params);
  }

  private getJobSync(id: string): Job | null {
    return jobsRepo.getJobSync(this.db, id);
  }

  private async buildSubtaskInfos(subJobs: Job[], maxDepth: number = 3): Promise<SubtaskInfo[]> {
    return jobsRepo.buildSubtaskInfos(this.db, subJobs, maxDepth);
  }

  async createJob(params: CreateJobParams): Promise<Job> {
    return jobsRepo.createJob(this.db, params);
  }

  createJobIfNoPending(params: CreateJobParams, opts?: { blockOnToolRequestPause?: boolean }): { job: Job; created: boolean; blockedByToolRequest?: boolean } {
    return jobsRepo.createJobIfNoPending(this.db, params, opts);
  }

  async getJob(id: string, opts?: { viewer?: Express.User }): Promise<Job | null> {
    return jobsRepo.getJob(this.db, id, opts);
  }

  async createSpace(params: CreateSpaceParams): Promise<Space> {
    return spacesRepo.createSpace(this.db, params);
  }

  async getSpace(id: string, opts?: { viewer?: Express.User }): Promise<Space | null> {
    return spacesRepo.getSpace(this.db, id, opts);
  }

  async listSpaces(opts?: { viewer?: Express.User; includeArchived?: boolean }): Promise<Space[]> {
    return spacesRepo.listSpaces(this.db, opts);
  }

  async updateSpace(id: string, patch: { title?: string; description?: string; brandColor?: string | null; visibility?: 'private' | 'org' | 'public'; visibilityScopeOrgId?: string | null }): Promise<Space | null> {
    return spacesRepo.updateSpace(this.db, id, patch);
  }

  async archiveSpace(id: string): Promise<void> {
    return spacesRepo.archiveSpace(this.db, id);
  }

  async setSpaceWorkspaceDir(id: string, workspaceDir: string): Promise<void> {
    return spacesRepo.setSpaceWorkspaceDir(this.db, id, workspaceDir);
  }

  async hardDeleteSpace(id: string): Promise<void> {
    return spacesRepo.hardDeleteSpace(this.db, id);
  }

  async ensurePersonalSpace(ownerId: string, displayName?: string): Promise<Space> {
    return spacesRepo.ensurePersonalSpace(this.db, ownerId, displayName);
  }

  async addSpaceMember(params: {
    spaceId: string;
    userId: string;
    role: SpaceMemberRoleValue;
    invitedBy?: string | null;
  }): Promise<void> {
    return spacesRepo.addSpaceMember(this.db, params);
  }

  async listSpaceMembers(spaceId: string): Promise<SpaceMember[]> {
    return spacesRepo.listSpaceMembers(this.db, spaceId);
  }

  getSpaceMemberRole(spaceId: string, userId: string): SpaceMemberRoleValue | null {
    return spacesRepo.getSpaceMemberRole(this.db, spaceId, userId);
  }

  getSpaceToolPolicy(spaceId: string): string | null {
    return spacesRepo.getSpaceToolPolicy(this.db, spaceId);
  }

  setSpaceToolPolicy(spaceId: string, policyJson: string | null): void {
    return spacesRepo.setSpaceToolPolicy(this.db, spaceId, policyJson);
  }

  getSpacePythonPackages(spaceId: string): string | null {
    return spacesRepo.getSpacePythonPackages(this.db, spaceId);
  }

  setSpacePythonPackages(spaceId: string, packagesJson: string | null): void {
    return spacesRepo.setSpacePythonPackages(this.db, spaceId, packagesJson);
  }

  userCanViewSpace(spaceId: string, user: { id: string; role: string }): boolean {
    return spacesRepo.userCanViewSpace(this.db, spaceId, user);
  }

  async updateSpaceMemberRole(spaceId: string, userId: string, role: SpaceMemberRoleValue): Promise<void> {
    return spacesRepo.updateSpaceMemberRole(this.db, spaceId, userId, role);
  }

  async removeSpaceMember(spaceId: string, userId: string): Promise<void> {
    return spacesRepo.removeSpaceMember(this.db, spaceId, userId);
  }

  private rowToSpaceInvite(row: {
    token: string;
    space_id: string;
    role: string;
    created_by: string | null;
    created_at: string;
    expires_at: string | null;
    revoked_at: string | null;
  }): SpaceInvite {
    return spacesRepo.rowToSpaceInvite(row);
  }

  isSpaceInviteValid(invite: SpaceInvite | null | undefined): invite is SpaceInvite {
    return spacesRepo.isSpaceInviteValid(this.db, invite);
  }

  createSpaceInvite(params: {
    spaceId: string;
    role: SpaceInviteRole;
    createdBy?: string | null;
    expiresInDays?: number | null;
  }): SpaceInvite {
    return spacesRepo.createSpaceInvite(this.db, params);
  }

  getSpaceInvite(spaceId: string): SpaceInvite | null {
    return spacesRepo.getSpaceInvite(this.db, spaceId);
  }

  getSpaceInviteByToken(token: string): SpaceInvite | null {
    return spacesRepo.getSpaceInviteByToken(this.db, token);
  }

  revokeSpaceInvite(spaceId: string): void {
    return spacesRepo.revokeSpaceInvite(this.db, spaceId);
  }

  createAppShareLink(spaceId: string, appName: string, createdBy: string | null): { token: string } {
    return appSharesRepo.createAppShareLink(this.db, spaceId, appName, createdBy);
  }

  getAppShareLink(spaceId: string, appName: string): { token: string; revokedAt: string | null } | null {
    return appSharesRepo.getAppShareLink(this.db, spaceId, appName);
  }

  revokeAppShareLink(spaceId: string, appName: string): void {
    return appSharesRepo.revokeAppShareLink(this.db, spaceId, appName);
  }

  resolveAppShareToken(token: string): { spaceId: string; appName: string } | null {
    return appSharesRepo.resolveAppShareToken(this.db, token);
  }

  async resolveTaskFolderContext(taskId: number, userFolderRoot: string): Promise<import('../spaces/folder-resolver.js').FolderContext> {
    return localTasksRepo.resolveTaskFolderContext(this.db, taskId, userFolderRoot);
  }

  async createLocalTask(params: CreateLocalTaskParams): Promise<LocalTask> {
    return localTasksRepo.createLocalTask(this.db, params);
  }

  async getLocalTask(taskId: number, opts?: { viewer?: Express.User }): Promise<LocalTask | null> {
    return localTasksRepo.getLocalTask(this.db, taskId, opts);
  }

  async shareLocalTask(taskId: number): Promise<string> {
    return localTasksRepo.shareLocalTask(this.db, taskId);
  }

  async unshareLocalTask(taskId: number): Promise<void> {
    return localTasksRepo.unshareLocalTask(this.db, taskId);
  }

  async updateMissionBrief(taskId: number, patch: Partial<MissionBrief>): Promise<MissionBrief | null> {
    return localTasksRepo.updateMissionBrief(this.db, taskId, patch);
  }

  updateMissionBriefSync(taskId: number, patch: Partial<MissionBrief>): MissionBrief | null {
    return localTasksRepo.updateMissionBriefSync(this.db, taskId, patch);
  }

  getMissionBriefSync(taskId: number): MissionBrief | null {
    return localTasksRepo.getMissionBriefSync(this.db, taskId);
  }

  makeMissionBriefIO(taskId: number): import('../engine/tools/core.js').MissionBriefIO {
    return localTasksRepo.makeMissionBriefIO(this.db, taskId);
  }

  makeTaskConversationIO(taskId: number, transcriptPath?: string): import('../engine/tools/core.js').TaskConversationIO {
    return localTasksRepo.makeTaskConversationIO(this.db, taskId, transcriptPath);
  }

  makeTranscriptOnlyConversationIO(transcriptPath: string | undefined): import('../engine/tools/core.js').TaskConversationIO {
    return localTasksRepo.makeTranscriptOnlyConversationIO(transcriptPath);
  }

  makeWorkspaceTaskSearchIO(currentTaskId: number, ownerId: string): import('../engine/tools/core.js').WorkspaceTaskSearchIO {
    return taskSearchRepo.makeWorkspaceTaskSearchIO(this.db, currentTaskId, ownerId);
  }

  recordProvenance(params: {
    workspacePath: string;
    relPath: string;
    event: 'create' | 'modify';
    sourceKind?: import('../engine/tools/core.js').FileProvenanceSourceKind;
    spaceId?: string | null;
    taskId?: number | null;
    jobId?: string | null;
    piece?: string | null;
    movement?: string | null;
    checksum?: string | null;
    note?: string | null;
  }): void {
    return provenanceRepo.recordProvenance(this.db, params);
  }

  getProvenance(workspacePath: string, relPath: string): import('../engine/tools/core.js').FileProvenanceRecord | null {
    return provenanceRepo.getProvenance(this.db, workspacePath, relPath);
  }

  listProvenance(workspacePath: string, filters?: import('../engine/tools/core.js').FileProvenanceListFilters): import('../engine/tools/core.js').FileProvenanceRecord[] {
    return provenanceRepo.listProvenance(this.db, workspacePath, filters);
  }

  makeFileProvenanceIO(workspacePath: string): import('../engine/tools/core.js').FileProvenanceIO {
    return provenanceRepo.makeFileProvenanceIO(this.db, workspacePath);
  }

  async getLocalTaskByShareToken(token: string): Promise<LocalTask | null> {
    return localTasksRepo.getLocalTaskByShareToken(this.db, token);
  }

  async listLocalTasks(filter?: { ownerId?: string; viewer?: Express.User }): Promise<LocalTask[]> {
    return localTasksRepo.listLocalTasks(this.db, filter);
  }

  async updateLocalTask(taskId: number, updates: Partial<Omit<LocalTask, 'id' | 'createdAt' | 'latestJob'>>): Promise<void> {
    return localTasksRepo.updateLocalTask(this.db, taskId, updates);
  }

  async updateFeedback(taskId: number, feedback: {
    rating: 'good' | 'bad';
    tags: string[];
    comment: string | null;
  }): Promise<void> {
    return localTasksRepo.updateFeedback(this.db, taskId, feedback);
  }

  async addLocalTaskComment(taskId: number, author: string, body: string, kind: string = 'comment', attachments: string[] = []): Promise<LocalTaskComment> {
    return localTasksRepo.addLocalTaskComment(this.db, taskId, author, body, kind, attachments);
  }

  indexTaskComment(commentId: number, taskId: number, author: string, kind: string, createdAt: string, body: string, attachments: string | null): void {
    return taskSearchRepo.indexTaskComment(this.db, commentId, taskId, author, kind, createdAt, body, attachments);
  }

  private buildFtsMatch(terms: string[]): string {
    return taskSearchRepo.buildFtsMatch(terms);
  }

  async searchWorkspaceTasks(currentTaskId: number, ownerId: string, query: string, opts: { limit?: number; kind?: string; author?: string; taskId?: number } = {}): Promise<Array<{ taskId: number; taskTitle: string; commentId: number; author: string; kind: string; createdAt: string; text: string }>> {
    return taskSearchRepo.searchWorkspaceTasks(this.db, currentTaskId, ownerId, query, opts);
  }

  async readWorkspaceTaskAround(currentTaskId: number, ownerId: string, commentId: number, before: number, after: number) {
    return taskSearchRepo.readWorkspaceTaskAround(this.db, currentTaskId, ownerId, commentId, before, after);
  }

  async listLocalTaskComments(taskId: number): Promise<LocalTaskComment[]> {
    return localTasksRepo.listLocalTaskComments(this.db, taskId);
  }

  async getUninjectedComments(taskId: number, sinceId: number = 0): Promise<LocalTaskComment[]> {
    return localTasksRepo.getUninjectedComments(this.db, taskId, sinceId);
  }

  markCommentsInjected(commentIds: number[]): void {
    return localTasksRepo.markCommentsInjected(this.db, commentIds);
  }

  async getLatestResultComment(taskId: number): Promise<{ body: string; kind: string; createdAt: string } | null> {
    return localTasksRepo.getLatestResultComment(this.db, taskId);
  }

  getJobStatusSync(id: string): JobStatus | null {
    return jobsRepo.getJobStatusSync(this.db, id);
  }

  isWorkerBusy(workerId: string): boolean {
    return jobsRepo.isWorkerBusy(this.db, workerId);
  }

  listRunningJobOwnersByWorker(): Map<string, Array<{ user: string; kind: string }>> {
    return jobsRepo.listRunningJobOwnersByWorker(this.db);
  }

  async updateJob(id: string, updates: Partial<Omit<Job, 'id' | 'createdAt'>>): Promise<void> {
    return jobsRepo.updateJob(this.db, id, updates);
  }

  touchJobUpdatedAt(id: string): void {
    return jobsRepo.touchJobUpdatedAt(this.db, id);
  }

  resumeMcpWaitingJobs(ownerId: string, _serverId: string): number {
    return jobsRepo.resumeMcpWaitingJobs(this.db, ownerId, _serverId);
  }

  resumeToolRequestJob(jobId: string): number {
    return jobsRepo.resumeToolRequestJob(this.db, jobId);
  }

  resumePackageRequestJob(jobId: string): number {
    return jobsRepo.resumePackageRequestJob(this.db, jobId);
  }

  async lockIssue(repo: string, issueNumber: number, jobId: string): Promise<boolean> {
    return jobsRepo.lockIssue(this.db, repo, issueNumber, jobId);
  }

  async unlockIssue(repo: string, issueNumber: number): Promise<void> {
    return jobsRepo.unlockIssue(this.db, repo, issueNumber);
  }

  async deleteJobsForIssue(repo: string, issueNumber: number): Promise<number> {
    return jobsRepo.deleteJobsForIssue(this.db, repo, issueNumber);
  }

  async addAuditLog(jobId: string | null, action: string, actor: string, detail: object): Promise<void> {
    return auditRepo.addAuditLog(this.db, jobId, action, actor, detail);
  }

  oidcUpsert(model: string, id: string, payload: object, opts: { expiresAt?: number | null; grantId?: string | null; userCode?: string | null; uid?: string | null } = {}): void {
    return a2aRepo.oidcUpsert(this.db, model, id, payload, opts);
  }

  oidcFind(model: string, id: string): { payload: Record<string, unknown>; consumedAt: number | null } | undefined {
    return a2aRepo.oidcFind(this.db, model, id);
  }

  oidcFindByUid(uid: string): { payload: Record<string, unknown> } | undefined {
    return a2aRepo.oidcFindByUid(this.db, uid);
  }

  oidcFindByUserCode(userCode: string): { payload: Record<string, unknown> } | undefined {
    return a2aRepo.oidcFindByUserCode(this.db, userCode);
  }

  oidcConsume(model: string, id: string, consumedAt: number): void {
    return a2aRepo.oidcConsume(this.db, model, id, consumedAt);
  }

  oidcDestroy(model: string, id: string): void {
    return a2aRepo.oidcDestroy(this.db, model, id);
  }

  oidcRevokeByGrantId(grantId: string): void {
    return a2aRepo.oidcRevokeByGrantId(this.db, grantId);
  }

  createA2aClient(row: A2aClientRow): void {
    return a2aRepo.createA2aClient(this.db, row);
  }

  getA2aClient(clientId: string): A2aClientRow | undefined {
    return a2aRepo.getA2aClient(this.db, clientId);
  }

  listA2aClients(): A2aClientRow[] {
    return a2aRepo.listA2aClients(this.db);
  }

  setA2aClientStatus(clientId: string, status: 'active' | 'disabled'): void {
    return a2aRepo.setA2aClientStatus(this.db, clientId, status);
  }

  createA2aDelegation(row: A2aDelegationRow): void {
    return a2aRepo.createA2aDelegation(this.db, row);
  }

  private mapDelegation(r: any): A2aDelegationRow {
    return a2aRepo.mapDelegation(r);
  }

  getA2aDelegationByGrantId(grantId: string): A2aDelegationRow | undefined {
    return a2aRepo.getA2aDelegationByGrantId(this.db, grantId);
  }

  listA2aDelegationsForUser(userId: string): A2aDelegationRow[] {
    return a2aRepo.listA2aDelegationsForUser(this.db, userId);
  }

  revokeA2aDelegation(id: string, revokedAtIso: string): void {
    return a2aRepo.revokeA2aDelegation(this.db, id, revokedAtIso);
  }

  getA2aDelegationById(id: string): A2aDelegationRow | undefined {
    return a2aRepo.getA2aDelegationById(this.db, id);
  }

  listLiveA2aDelegationsForClient(clientId: string, nowIso: string): A2aDelegationRow[] {
    return a2aRepo.listLiveA2aDelegationsForClient(this.db, clientId, nowIso);
  }

  listNonTerminalA2aTasksByGrant(grantId: string, limit = 500): Array<{ id: string; jobId: string | null; payload: Record<string, unknown> }> {
    return a2aRepo.listNonTerminalA2aTasksByGrant(this.db, grantId, limit);
  }

  countNonTerminalA2aTasksByGrant(grantId: string): number {
    return a2aRepo.countNonTerminalA2aTasksByGrant(this.db, grantId);
  }

  cancelA2aLinkedJob(jobId: string): boolean {
    return a2aRepo.cancelA2aLinkedJob(this.db, jobId);
  }

  listA2aDelegationsForUserWithClient(userId: string): Array<A2aDelegationRow & { clientName: string | null; clientStatus: string | null }> {
    return a2aRepo.listA2aDelegationsForUserWithClient(this.db, userId);
  }

  listA2aDelegationsAllWithClient(): Array<
    A2aDelegationRow & { userId: string; clientName: string | null; clientStatus: string | null }
  > {
    return a2aRepo.listA2aDelegationsAllWithClient(this.db);
  }

  getSpaceA2aSkills(spaceId: string): string[] {
    return spacesRepo.getSpaceA2aSkills(this.db, spaceId);
  }

  setSpaceA2aSkills(spaceId: string, skills: string[]): void {
    return spacesRepo.setSpaceA2aSkills(this.db, spaceId, skills);
  }

  saveA2aTask(row: {
    id: string;
    contextId: string | null;
    jobId: string | null;
    localTaskId: number | null;
    payload: object;
    delegationId?: string;
    grantId?: string;
    actingUserId?: string;
  }): void {
    return a2aRepo.saveA2aTask(this.db, row);
  }

  loadA2aTask(id: string): { payload: Record<string, unknown> } | undefined {
    return a2aRepo.loadA2aTask(this.db, id);
  }

  getA2aTaskByJobId(jobId: string): { id: string; payload: Record<string, unknown> } | undefined {
    return a2aRepo.getA2aTaskByJobId(this.db, jobId);
  }

  listNonTerminalA2aTasks(limit = 500): Array<{ id: string; jobId: string | null; payload: Record<string, unknown> }> {
    return a2aRepo.listNonTerminalA2aTasks(this.db, limit);
  }

  async upsertWorkerNode(params: UpsertWorkerNodeParams): Promise<void> {
    return workerNodesRepo.upsertWorkerNode(this.db, params);
  }

  async updateWorkerNodeHealth(workerId: string, updates: { healthy: boolean; lastError?: string | null; inflightJobs?: number; availableModels?: string[]; enabled?: boolean }): Promise<void> {
    return workerNodesRepo.updateWorkerNodeHealth(this.db, workerId, updates);
  }

  async getWorkerNode(workerId: string): Promise<WorkerNode | null> {
    return workerNodesRepo.getWorkerNode(this.db, workerId);
  }

  async claimNextJob(workerId: string): Promise<Job | null> {
    return jobsRepo.claimNextJob(this.db, workerId);
  }

  async claimNextRetryJob(workerId: string): Promise<Job | null> {
    return jobsRepo.claimNextRetryJob(this.db, workerId);
  }

  async peekNextClaimable(workerId: string): Promise<Job | null> {
    return jobsRepo.peekNextClaimable(this.db, workerId);
  }

  async getJobsByStatus(status: JobStatus): Promise<Job[]> {
    return jobsRepo.getJobsByStatus(this.db, status);
  }

  async getLatestJobForIssue(repo: string, issueNumber: number): Promise<Job | null> {
    return jobsRepo.getLatestJobForIssue(this.db, repo, issueNumber);
  }

  async updateJobContext(jobId: string, payload: { promptTokens: number; limitTokens: number }): Promise<void> {
    return jobsRepo.updateJobContext(this.db, jobId, payload);
  }

  async recoverOrphanedJobs(): Promise<number> {
    return jobsRepo.recoverOrphanedJobs(this.db);
  }

  recoverStuckRunningJobs(staleMinutes: number): number {
    return jobsRepo.recoverStuckRunningJobs(this.db, staleMinutes);
  }

  requeueRunningJobs(): number {
    return jobsRepo.requeueRunningJobs(this.db);
  }

  getDistinctRepos(): string[] {
    return jobsRepo.getDistinctRepos(this.db);
  }

  getJobsByRepo(repoName: string): Job[] {
    return jobsRepo.getJobsByRepo(this.db, repoName);
  }

  getLatestJobsPerIssue(repoName: string): Job[] {
    return jobsRepo.getLatestJobsPerIssue(this.db, repoName);
  }

  async updateJobsVisibilityForTask(taskId: number, updates: { visibility: 'private' | 'org' | 'public'; visibilityScopeOrgId: string | null }): Promise<number> {
    return jobsRepo.updateJobsVisibilityForTask(this.db, taskId, updates);
  }

  async getSubJobs(parentJobId: string): Promise<Job[]> {
    return jobsRepo.getSubJobs(this.db, parentJobId);
  }

  async requeueParentJobIfAllSubtasksDone(parentJobId: string): Promise<boolean> {
    return jobsRepo.requeueParentJobIfAllSubtasksDone(this.db, parentJobId);
  }

  async deleteLocalTask(taskId: number, worktreeDir?: string): Promise<void> {
    return localTasksRepo.deleteLocalTask(this.db, taskId, worktreeDir);
  }

  requestJobCancel(jobId: string): boolean {
    return jobsRepo.requestJobCancel(this.db, jobId);
  }

  private mapScheduledTask(row: any): ScheduledTask {
    return scheduledTasksRepo.mapScheduledTask(row);
  }

  async createScheduledTask(params: CreateScheduledTaskParams): Promise<ScheduledTask> {
    return scheduledTasksRepo.createScheduledTask(this.db, params);
  }

  private getScheduledTaskSync(id: number): ScheduledTask | null {
    return scheduledTasksRepo.getScheduledTaskSync(this.db, id);
  }

  async getScheduledTask(id: number, opts?: { viewer?: Express.User }): Promise<ScheduledTask | null> {
    return scheduledTasksRepo.getScheduledTask(this.db, id, opts);
  }

  async listScheduledTasks(filter?: { viewer?: Express.User; spaceId?: string | null }): Promise<ScheduledTask[]> {
    return scheduledTasksRepo.listScheduledTasks(this.db, filter);
  }

  async getScheduledTasksDue(): Promise<ScheduledTask[]> {
    return scheduledTasksRepo.getScheduledTasksDue(this.db);
  }

  async updateScheduledTask(id: number, params: UpdateScheduledTaskParams): Promise<ScheduledTask | null> {
    return scheduledTasksRepo.updateScheduledTask(this.db, id, params);
  }

  async deleteScheduledTask(id: number): Promise<boolean> {
    return scheduledTasksRepo.deleteScheduledTask(this.db, id);
  }

  createUser(params: CreateUserParams): User {
    return usersRepo.createUser(this.db, params);
  }

  ensureLocalUser(): void {
    return usersRepo.ensureLocalUser(this.db);
  }

  setLocalPassword(userId: string, plainPassword: string): void {
    return usersRepo.setLocalPassword(this.db, userId, plainPassword);
  }

  verifyLocalPassword(userId: string, plainPassword: string): boolean {
    return usersRepo.verifyLocalPassword(this.db, userId, plainPassword);
  }

  hasLocalCredential(userId: string): boolean {
    return usersRepo.hasLocalCredential(this.db, userId);
  }

  createLocalUser(params: CreateLocalUserParams): User {
    return usersRepo.createLocalUser(this.db, params);
  }

  upsertLocalSystemAdmin(params: { email: string; password: string; name?: string }): User {
    return usersRepo.upsertLocalSystemAdmin(this.db, params);
  }

  private rowToLocalOrg(r: { id: string; name: string; created_by: string | null; created_at: string }): LocalOrg {
    return usersRepo.rowToLocalOrg(r);
  }

  createLocalOrg(name: string, createdBy: string | null): LocalOrg {
    return usersRepo.createLocalOrg(this.db, name, createdBy);
  }

  getLocalOrg(id: string): LocalOrg | null {
    return usersRepo.getLocalOrg(this.db, id);
  }

  listLocalOrgs(): LocalOrg[] {
    return usersRepo.listLocalOrgs(this.db);
  }

  renameLocalOrg(id: string, name: string): void {
    return usersRepo.renameLocalOrg(this.db, id, name);
  }

  deleteLocalOrg(id: string): void {
    return usersRepo.deleteLocalOrg(this.db, id);
  }

  addOrgMember(orgId: string, userId: string, role: string = 'member'): void {
    return usersRepo.addOrgMember(this.db, orgId, userId, role);
  }

  removeOrgMember(orgId: string, userId: string): void {
    return usersRepo.removeOrgMember(this.db, orgId, userId);
  }

  listOrgMembers(orgId: string): LocalOrgMember[] {
    return usersRepo.listOrgMembers(this.db, orgId);
  }

  listUserLocalOrgs(userId: string): Array<{ orgId: string; name: string }> {
    return usersRepo.listUserLocalOrgs(this.db, userId);
  }

  getUserById(id: string): User | null {
    return usersRepo.getUserById(this.db, id);
  }

  getUserByEmail(email: string): User | null {
    return usersRepo.getUserByEmail(this.db, email);
  }

  findOrCreateUserByOAuth(params: FindOrCreateByOAuthParams): User {
    return usersRepo.findOrCreateUserByOAuth(this.db, params);
  }

  listUsers(): User[] {
    return usersRepo.listUsers(this.db);
  }

  listActiveUsersInOrgs(orgIds: string[]): User[] {
    return usersRepo.listActiveUsersInOrgs(this.db, orgIds);
  }

  updateUser(id: string, updates: {
    status?: 'active' | 'pending' | 'disabled';
    role?: 'admin' | 'user';
    email?: string;
    name?: string;
    avatarUrl?: string | null;
    defaultVisibility?: 'private' | 'org' | 'public';
    defaultVisibilityOrgId?: string | null;
  }): void {
    return usersRepo.updateUser(this.db, id, updates);
  }

  deleteUser(id: string): void {
    return usersRepo.deleteUser(this.db, id);
  }

  deleteSessionsByUserId(userId: string): void {
    return usersRepo.deleteSessionsByUserId(this.db, userId);
  }

  replaceUserGiteaOrgs(userId: string, orgs: GiteaOrgInput[]): void {
    return usersRepo.replaceUserGiteaOrgs(this.db, userId, orgs);
  }

  listUserGiteaOrgs(userId: string): GiteaOrg[] {
    return usersRepo.listUserGiteaOrgs(this.db, userId);
  }

  recordPieceEdit(userId: string, pieceName: string, snapshotId: string): void {
    return reflectionRepo.recordPieceEdit(this.db, userId, pieceName, snapshotId);
  }

  countRecentPieceEdits(userId: string, pieceName: string, sinceMs: number): number {
    return reflectionRepo.countRecentPieceEdits(this.db, userId, pieceName, sinceMs);
  }

  recordReflectionRun(metric: {
      reflection_job_id: string;
      original_job_id: string | null;
      user_id: string;
      piece_name: string | null;
      outcome: 'applied' | 'partial' | 'abstained' | 'rejected' | 'failed';
      memory_changes: number;
      piece_edited: 0 | 1;
      tokens_in: number;
      tokens_out: number;
      duration_ms: number;
    }, pieceEdit?: { pieceName: string; snapshotId: string }): void {
    return reflectionRepo.recordReflectionRun(this.db, metric, pieceEdit);
  }

  recordReflectionMetric(row: {
      reflection_job_id: string;
      original_job_id: string | null;
      user_id: string;
      piece_name: string | null;
      outcome: 'applied' | 'partial' | 'abstained' | 'rejected' | 'failed';
      memory_changes: number;
      piece_edited: 0 | 1;
      tokens_in: number;
      tokens_out: number;
      duration_ms: number;
    }): void {
    return reflectionRepo.recordReflectionMetric(this.db, row);
  }

  aggregateReflectionMetrics(userId: string, sinceMs: number): {
    applied: number;
    partial: number;
    abstained: number;
    rejected: number;
    failed: number;
    tokensIn: number;
    tokensOut: number;
    pieceEdits: number;
    totalRuns: number;
  } {
    return reflectionRepo.aggregateReflectionMetrics(this.db, userId, sinceMs);
  }

  private tzModifier(tzOffsetMin: number): string {
    return calendarRepo.tzModifier(tzOffsetMin);
  }

  async createCalendarEvent(params: {
    spaceId: string;
    ownerId?: string | null;
    date: string;
    endDate?: string | null;
    time?: string | null;
    endTime?: string | null;
    title: string;
    description?: string | null;
    createdBy?: 'user' | 'agent';
    sourceTaskId?: number | null;
  }): Promise<CalendarEvent> {
    return calendarRepo.createCalendarEvent(this.db, params);
  }

  recordToolRequest(params: {
    id?: string;
    taskId?: string | null;
    jobId?: string | null;
    spaceId?: string | null;
    pieceName: string;
    movementName: string;
    toolName: string;
    reason?: string | null;
    category?: ToolRequestCategory;
    status?: ToolRequestStatus;
  }): string {
    return toolRequestsRepo.recordToolRequest(this.db, params);
  }

  listToolRequestsByTask(taskId: string): ToolRequest[] {
    return toolRequestsRepo.listToolRequestsByTask(this.db, taskId);
  }

  aggregateToolRequestsByPiece(pieceName: string): ToolRequestAggregate[] {
    return toolRequestsRepo.aggregateToolRequestsByPiece(this.db, pieceName);
  }

  getToolRequest(id: string): ToolRequest | null {
    return toolRequestsRepo.getToolRequest(this.db, id);
  }

  decideToolRequest(id: string, decision: { status: Exclude<ToolRequestStatus, 'pending'>; grantScope?: 'task' | 'piece' | null; decidedBy?: string | null }): boolean {
    return toolRequestsRepo.decideToolRequest(this.db, id, decision);
  }

  getGrantedTools(taskId: string): string[] {
    return toolRequestsRepo.getGrantedTools(this.db, taskId);
  }

  addGrantedTool(taskId: string, toolName: string): string[] {
    return toolRequestsRepo.addGrantedTool(this.db, taskId, toolName);
  }

  recordPackageRequest(params: {
    id?: string;
    taskId?: string | null;
    jobId?: string | null;
    spaceId?: string | null;
    pieceName?: string | null;
    movementName?: string | null;
    spec: string;
    normalizedName: string;
    reason?: string | null;
    status?: PackageRequestStatus;
  }): string {
    return toolRequestsRepo.recordPackageRequest(this.db, params);
  }

  listPackageRequestsByTask(taskId: string): PackageRequest[] {
    return toolRequestsRepo.listPackageRequestsByTask(this.db, taskId);
  }

  listDeclinedPackageNamesByJob(jobId: string): string[] {
    return toolRequestsRepo.listDeclinedPackageNamesByJob(this.db, jobId);
  }

  getPackageRequest(id: string): PackageRequest | null {
    return toolRequestsRepo.getPackageRequest(this.db, id);
  }

  decidePackageRequest(id: string, decision: { status: Exclude<PackageRequestStatus, 'pending'>; decidedBy?: string | null }): boolean {
    return toolRequestsRepo.decidePackageRequest(this.db, id, decision);
  }

  revertPackageRequestToPending(id: string): boolean {
    return toolRequestsRepo.revertPackageRequestToPending(this.db, id);
  }

  async listCalendarEvents(spaceId: string, range?: { from?: string; to?: string }): Promise<CalendarEvent[]> {
    return calendarRepo.listCalendarEvents(this.db, spaceId, range);
  }

  async getCalendarEvent(eventId: number): Promise<CalendarEvent | null> {
    return calendarRepo.getCalendarEvent(this.db, eventId);
  }

  async updateCalendarEvent(eventId: number, fields: { date?: string; endDate?: string | null; time?: string | null; endTime?: string | null; title?: string; description?: string | null }): Promise<CalendarEvent | null> {
    return calendarRepo.updateCalendarEvent(this.db, eventId, fields);
  }

  async deleteCalendarEvent(eventId: number): Promise<void> {
    return calendarRepo.deleteCalendarEvent(this.db, eventId);
  }

  private calendarTaskScope(spaceId: string, personalOwnerId?: string | null, alias = ''): { clause: string; params: unknown[] } {
    return calendarRepo.calendarTaskScope(spaceId, personalOwnerId, alias);
  }

  async getSpaceCalendarMonth(spaceId: string, opts: { monthStart: string; monthEnd: string; tzOffsetMin: number; personalOwnerId?: string | null }): Promise<{ days: Record<string, CalendarDayCounts>; events: CalendarEvent[] }> {
    return calendarRepo.getSpaceCalendarMonth(this.db, spaceId, opts);
  }

  async getSpaceCalendarDay(spaceId: string, date: string, tzOffsetMin: number, personalOwnerId?: string | null): Promise<{ tasks: CalendarDayTask[]; events: CalendarEvent[] }> {
    return calendarRepo.getSpaceCalendarDay(this.db, spaceId, date, tzOffsetMin, personalOwnerId);
  }

  async getCrossSpaceCalendarMonth(viewer: Express.User, opts: { monthStart: string; monthEnd: string; tzOffsetMin: number }): Promise<CrossSpaceCalendarMonth> {
    return calendarRepo.getCrossSpaceCalendarMonth(this.db, viewer, opts);
  }

  createGatewayVirtualKey(params: {
    id?: string;
    keyHash: string;
    keyPrefix: string;
    team: string;
    allowedModels?: string[] | null;
    source?: GatewayVirtualKeySource;
    createdBy?: string | null;
    createdAt?: string;
    /** Phase 2b: optional monthly tokens budget. null/undefined = unlimited. */
    tokensBudget?: number | null;
    /** Phase 2b: optional requests-per-minute cap. null/undefined = unlimited. */
    rateLimitRpm?: number | null;
  }): GatewayVirtualKey {
    return gatewayRepo.createGatewayVirtualKey(this.db, params);
  }

  updateGatewayVirtualKey(id: string, patch: {
      /**
       * Phase 3a follow-up: team is now patchable so the config-migration
       * importer can propagate a YAML-side team rename to the DB. Admin
       * PATCH never sends this field (the team is intentionally immutable
       * via the public API to avoid an admin accidentally rewriting the
       * owner of a key); the only caller is importConfigKeysToDb.
       */
      team?: string;
      tokensBudget?: number | null;
      rateLimitRpm?: number | null;
      allowedModels?: string[] | null;
    }): GatewayVirtualKey {
    return gatewayRepo.updateGatewayVirtualKey(this.db, id, patch);
  }

  findGatewayVirtualKeyByHash(keyHash: string): GatewayVirtualKey | null {
    return gatewayRepo.findGatewayVirtualKeyByHash(this.db, keyHash);
  }

  findGatewayVirtualKeyById(id: string): GatewayVirtualKey | null {
    return gatewayRepo.findGatewayVirtualKeyById(this.db, id);
  }

  listGatewayVirtualKeys(opts?: { team?: string; activeOnly?: boolean }): GatewayVirtualKey[] {
    return gatewayRepo.listGatewayVirtualKeys(this.db, opts);
  }

  revokeGatewayVirtualKey(id: string, revokedBy: string, at?: string): boolean {
    return gatewayRepo.revokeGatewayVirtualKey(this.db, id, revokedBy, at);
  }

  deleteGatewayVirtualKey(id: string): boolean {
    return gatewayRepo.deleteGatewayVirtualKey(this.db, id);
  }

  touchGatewayVirtualKeyLastUsed(id: string, at?: string): void {
    return gatewayRepo.touchGatewayVirtualKeyLastUsed(this.db, id, at);
  }

  getGatewayKeyUsage(keyId: string, periodStart: string): GatewayKeyUsage | null {
    return gatewayRepo.getGatewayKeyUsage(this.db, keyId, periodStart);
  }

  incrementGatewayKeyUsage(params: {
    keyId: string;
    period: string;
    tokensIn?: number;
    tokensOut?: number;
    requests?: number;
    at?: string;
  }): void {
    return gatewayRepo.incrementGatewayKeyUsage(this.db, params);
  }

  listGatewayKeyUsagesByKey(keyId: string, opts?: { limit?: number }): GatewayKeyUsage[] {
    return gatewayRepo.listGatewayKeyUsagesByKey(this.db, keyId, opts);
  }

  incrementLlmUsage(params: LlmUsageIncrement): void {
    return llmUsageRepo.incrementLlmUsage(this.db, params);
  }

  queryLlmUsageDaily(opts: { from: string; to: string; userId?: string }): LlmUsageDailyAgg[] {
    return llmUsageRepo.queryLlmUsageDaily(this.db, opts);
  }

  incrementLlmUsageHourly(params: LlmUsageHourlyIncrement): void {
    return llmUsageRepo.incrementLlmUsageHourly(this.db, params);
  }

  queryLlmUsageHourly(opts: {
    fromHour: string;
    toHour: string;
    userId?: string;
  }): LlmUsageHourlyRow[] {
    return llmUsageRepo.queryLlmUsageHourly(this.db, opts);
  }

  getUsageOrgMap(): Map<string, string> {
    return llmUsageRepo.getUsageOrgMap(this.db);
  }

  /** Return the underlying Database instance (needed by migrate.ts and session store) */
  getDb(): Database.Database {
    return this.db;
  }

  upsertPushSubscription(input: UpsertPushSubscriptionInput): { id: string } {
    return notificationsRepo.upsertPushSubscription(this.db, input);
  }

  listPushSubscriptionsForUser(userId: string): PushSubscriptionRecord[] {
    return notificationsRepo.listPushSubscriptionsForUser(this.db, userId);
  }

  getPushSubscriptionById(id: string): PushSubscriptionRecord | null {
    return notificationsRepo.getPushSubscriptionById(this.db, id);
  }

  deletePushSubscription(id: string): void {
    return notificationsRepo.deletePushSubscription(this.db, id);
  }

  markPushSubscriptionSuccess(id: string): void {
    return notificationsRepo.markPushSubscriptionSuccess(this.db, id);
  }

  markPushSubscriptionFailure(id: string): void {
    return notificationsRepo.markPushSubscriptionFailure(this.db, id);
  }

  getUserNotificationPrefs(userId: string): NotificationPrefs {
    return notificationsRepo.getUserNotificationPrefs(this.db, userId);
  }

  upsertUserNotificationPrefs(userId: string, update: NotificationPrefsUpdate): void {
    return notificationsRepo.upsertUserNotificationPrefs(this.db, userId, update);
  }

  markV1MigrationComplete(userId: string): boolean {
    return notificationsRepo.markV1MigrationComplete(this.db, userId);
  }

  close(): void {
    this.db.close();
  }
}

export { BrowserSessionRepo } from './browser-session-repo.js';
export type { BrowserSessionProfile, CreateProfileInput, AuditInput } from './browser-session-repo.js';
