import { Hono } from 'hono'
import type { Env } from './env'
import { corsMiddleware } from './middleware/cors'
import { securityHeadersMiddleware } from './middleware/security-headers'
import { routes } from './routes'
import { contractorPortalRoutes } from './routes/contractor-portal-api'
import { handleCommsInboundBatch } from './queues/comms-inbound'
import { handleAiIndexUpdate } from './queues/ai-index-update'
import type { AiIndexUpdateJob } from '@zync/ai/rag'
import { handleAiTelegramMessage } from './queues/ai-telegram-message'
import type { AiTelegramMessageJob } from './queues/ai-telegram-message'
import { handleAuditLogBatch } from './queues/audit-log-consumer'
import type { AuditEvent } from '@zync/types'
import { taskModuleRouter } from './routes/tasks-module'
import { tasksSyncCronRoute } from './routes/cron/tasks-sync'
import { handleExpenseProcess } from './queues/expense-process'
import type { ExpenseProcessJob } from './queues/expense-process'
import { handleUniformExportJob, handleUniformExportError } from './queues/uniform-export'
import type { UniformExportJob } from './reports/uniform-format/types'
import { publicStatusRoutes } from './routes/public/status'
import { calendarRouter, googleOAuthRouter, outlookOAuthRouter, syncWebhooksRoute, schedulingWebhooksRoute, cronRouter as calendarCronRouter } from './routes/calendar/router'
import { recurringTaskGeneratorRoute } from './routes/cron/recurring-task-generator'
import { recurringExpenseGeneratorCron } from './routes/cron/recurring-expense-generator'
import { unifiedAttachmentsRouter } from './routes/unified-attachments'
import { publicFormRoutes } from './routes/marketing/public-form'
// wave-11 leaf-C: public (no-auth) routes
import { signRoute } from './routes/sign'
import { proposalsPublicRoute } from './routes/proposals-public'
import { catalogPublicRoute } from './routes/catalog-public'
import { tenantLogoPublicRoute } from './routes/public/tenant-logo'
// wave-12: multi-signatory-coordination cron
import { contractSigningRemindersCron } from './routes/cron/contract-signing-reminders'
// wave-12: oauth-authorization-code
import { oauthRouter } from './routes/oauth'
// wave-13: session-security crons — now dispatched via cron/runner.ts
// wave-14: lead-qualification-scoring
import { handleLeadScoreRecalcBatch } from './queues/lead-score-recalc'
import { leadReengagementCronRoute } from './routes/cron/lead-reengagement'
import type { LeadScoreRecalcJob } from './queues/lead-score-recalc'
// wave-15: scheduled-reports
import { runScheduledReportById } from './cron/scheduled-reports'
// cron dispatch: shared group runner
import { runCronGroup } from './cron/runner'
import { handleWebhookDeliverBatch } from './queues/webhook-deliver'
import { handleInvoiceGenerateMessages } from './queues/invoice-generate'
import { handleCalendarQueueJob } from './queues/calendar-sync'
import { handleInvoiceAdapterMessages } from './queues/invoice-adapter-sync'
import { handleAuthEmailMessages } from './queues/auth-email'
import { handleInboundEmail } from './intake/email-routing'
import type { ForwardableEmailMessage } from '@cloudflare/workers-types'
import { RECURRING_ALARM_DO_NAME } from './durable-objects/RecurringInvoiceAlarmDO'
import {
  setAdapterFactories,
  type AIAdapter,
  type AIProvider,
  type AIRequest,
} from '@platform-modules/ai'
import { createJobRegistry } from '@platform-modules/jobs'
import { createMail, type MailMessage } from '@platform-modules/mail'
import {
  helpdeskSchema,
  listAttachments as platformListHelpdeskAttachments,
  listMessages as platformListHelpdeskMessages,
  listTransitions as platformListHelpdeskTransitions,
  postMessage as platformPostHelpdeskMessage,
} from '@platform-modules/helpdesk'
import {
  type ComplianceStore,
} from '@platform-modules/marketing'
import {
  makeBrevoAdapter,
  type BrevoCreds,
} from '@platform-modules/marketing/brevo'
import {
  makeSelfManagedAdapter,
  type SelfManagedCreds,
} from '@platform-modules/marketing/self-managed'
import {
  makeAnthropicAdapter,
  type AnthropicCreds,
} from '@platform-modules/ai/anthropic'
import {
  makeOpenAiCompatAdapter,
  type OpenAiCompatCreds,
} from '@platform-modules/ai/openai-compat'
import {
  guardMagicBytes as platformGuardMagicBytes,
  imageDimensions as platformImageDimensions,
  presignGet as platformPresignGet,
  presignPut as platformPresignPut,
} from './integrations/platform/uploads'
import { recordAudit as platformRecordAudit } from './integrations/platform/audit'
import { createPlatformNotificationDeps } from './integrations/platform/notifications'
import {
  handlePlatformRealtimeBatch,
  publishPlatformRealtimeEvent,
  verifyPlatformRealtimeInternalSecret,
} from './integrations/platform/realtime'
import {
  createPlatformSearchRegistry,
  runPlatformSearch,
} from './integrations/platform/search'

/** Build a filtered batch view for handlers that process a single message type. */
function filterBatchByMessages(
  batch: MessageBatch<unknown>,
  messages: Message<unknown>[],
): MessageBatch<unknown> {
  return { queue: batch.queue, messages } as unknown as MessageBatch<unknown>
}

async function handleUniformFormatMessages(
  messages: Message<unknown>[],
  env: Env,
): Promise<void> {
  for (const msg of messages) {
    const job = msg.body as UniformExportJob
    try {
      await handleUniformExportJob(job, env)
      msg.ack()
    } catch (err) {
      await handleUniformExportError(job, env, err)
      msg.ack() // ack to avoid poison-loop (no DLQ on zync-jobs)
    }
  }
}

async function handleReportScheduleMessages(
  messages: Message<unknown>[],
  env: Env,
): Promise<void> {
  for (const msg of messages) {
    const schedJob = msg.body as {
      type: string
      scheduleId: string
      tenantId: string
      oneOff?: boolean
    }
    try {
      await runScheduledReportById(schedJob.scheduleId, schedJob.tenantId, env)
      msg.ack()
    } catch (oneOffErr) {
      console.error('report.schedule one-off failed', { scheduleId: schedJob.scheduleId, err: oneOffErr })
      msg.ack() // ack to avoid poison-loop (no DLQ on zync-jobs)
    }
  }
}

/**
 * zync-jobs is a shared queue — CF batches by queue, not by message type.
 * Group by body.type and dispatch each group so co-batched mixed types are never
 * implicitly acked by falling through.
 */
async function handleZyncJobsBatch(batch: MessageBatch<unknown>, env: Env): Promise<void> {
  const byType = new Map<string, Message<unknown>[]>()

  for (const msg of batch.messages) {
    const type = (msg.body as { type?: string } | null | undefined)?.type
    if (!type) {
      console.warn({ event: 'zync-jobs:missing-type', body: msg.body })
      msg.ack()
      continue
    }
    const group = byType.get(type)
    if (group) {
      group.push(msg)
    } else {
      byType.set(type, [msg])
    }
  }

  for (const [type, messages] of byType) {
    const view = filterBatchByMessages(batch, messages)
    switch (type) {
      case 'lead.score_recalc':
        await handleLeadScoreRecalcBatch(view as MessageBatch<LeadScoreRecalcJob>, env)
        continue
      case 'webhook.deliver':
        await handleWebhookDeliverBatch(view, env)
        continue
      case 'comms.inbound':
        await handleCommsInboundBatch(view, env)
        continue
      case 'uniform-format':
        await handleUniformFormatMessages(messages, env)
        continue
      case 'report.schedule':
        await handleReportScheduleMessages(messages, env)
        continue
      case 'retainer.invoice':
      case 'invoice.generate':
      case 'bulk_action':
        await handleInvoiceGenerateMessages(messages, env)
        continue
      case 'calendar.event.push':
      case 'calendar.event.update':
      case 'calendar.event.delete':
      case 'calendar.booking.create_task':
      case 'calendar.booking.create_ticket':
        for (const msg of messages) {
          try {
            await handleCalendarQueueJob(msg.body as never, env)
            msg.ack()
          } catch (err) {
            console.error('calendar queue job failed', { type, err })
            msg.ack()
          }
        }
        continue
      case 'invoice.push':
      case 'invoice.payment_sync':
        await handleInvoiceAdapterMessages(messages, env)
        continue
      case 'auth.email':
        await handleAuthEmailMessages(view, env)
        continue
      default:
        break
    }

    if (!registeredJobTypes.has(type)) {
      for (const msg of messages) {
        console.warn({ event: 'zync-jobs:unhandled-type', type, body: msg.body })
        msg.ack()
      }
      continue
    }
    await jobsRegistry.dispatch({ env, batch }, { type, payload: messages })
  }
}

function registerPilotAi(): void {
  setAdapterFactories({
    anthropic: (creds) => makeAnthropicAdapter(creds as AnthropicCreds),
    'openai-compat': (creds) => makeOpenAiCompatAdapter(creds as OpenAiCompatCreds),
    google: (creds) => ({
      provider: 'google',
      async call(request: AIRequest) {
        const { makeGoogleAdapter } = await import('@platform-modules/ai/google')
        return makeGoogleAdapter(creds as { apiKey: string }).call(request)
      },
    } satisfies AIAdapter),
  } satisfies Partial<Record<AIProvider, (creds: unknown) => AIAdapter>>)
}

function registerPilotUploads() {
  const uploads = {
    guardMagicBytes: platformGuardMagicBytes,
    imageDimensions: platformImageDimensions,
    presignGet: platformPresignGet,
    presignPut: platformPresignPut,
  }
  for (const [name, value] of Object.entries(uploads)) {
    if (typeof value !== 'function') {
      throw new Error(`Pilot uploads integration missing function export: ${name}`)
    }
  }
  return uploads
}

type PassthroughSchema<T> = {
  '~standard': {
    version: 1
    vendor: string
    validate(value: unknown): Promise<{ value: T }>
  }
}

function passthroughSchema<T>(): PassthroughSchema<T> {
  return {
    '~standard': {
      version: 1 as const,
      vendor: 'zync-api-bootstrap',
      validate: async (value: unknown) => ({ value: value as T }),
    },
  }
}

function registerMail() {
  return {
    sendMail(env: Env, msg: MailMessage) {
      return createMail({
        async send(message) {
          const response = await fetch('https://api.resend.com/emails', {
            method: 'POST',
            headers: {
              Authorization: `Bearer ${env.RESEND_API_KEY}`,
              'Content-Type': 'application/json',
              ...(message.idempotencyKey
                ? { 'Idempotency-Key': message.idempotencyKey }
                : {}),
            },
            body: JSON.stringify({
              from: message.from,
              to: Array.isArray(message.to) ? message.to : [message.to],
              cc: message.cc
                ? (Array.isArray(message.cc) ? message.cc : [message.cc])
                : undefined,
              bcc: message.bcc
                ? (Array.isArray(message.bcc) ? message.bcc : [message.bcc])
                : undefined,
              reply_to: message.replyTo,
              subject: message.subject,
              html: message.html,
              text: message.text,
              headers: message.headers,
              tags: message.tags
                ? Object.entries(message.tags).map(([name, value]) => ({ name, value }))
                : undefined,
            }),
          })

          const payload = await response.json() as { id?: string; message?: string }
          if (!response.ok || !payload.id) {
            throw new Error(payload.message ?? 'resend send failed')
          }

          return { id: payload.id, provider: 'resend' as const }
        },
      }).send(msg)
    },
  }
}

function registerJobs() {
  const registry = createJobRegistry<{
    env: Env
    batch: MessageBatch<unknown>
  }>()
  const messagesSchema = passthroughSchema<Message<unknown>[]>()

  registry.register<Message<unknown>[]>('lead.score_recalc', messagesSchema, async ({ env, batch }, messages) => {
    await handleLeadScoreRecalcBatch(filterBatchByMessages(batch, messages) as MessageBatch<LeadScoreRecalcJob>, env)
  })
  registry.register<Message<unknown>[]>('webhook.deliver', messagesSchema, async ({ env, batch }, messages) => {
    await handleWebhookDeliverBatch(filterBatchByMessages(batch, messages), env)
  })
  registry.register<Message<unknown>[]>('comms.inbound', messagesSchema, async ({ env, batch }, messages) => {
    await handleCommsInboundBatch(filterBatchByMessages(batch, messages), env)
  })
  registry.register<Message<unknown>[]>('uniform-format', messagesSchema, async ({ env }, messages) => {
    await handleUniformFormatMessages(messages, env)
  })
  registry.register<Message<unknown>[]>('report.schedule', messagesSchema, async ({ env }, messages) => {
    await handleReportScheduleMessages(messages, env)
  })
  registry.register<Message<unknown>[]>('retainer.invoice', messagesSchema, async ({ env }, messages) => {
    await handleInvoiceGenerateMessages(messages, env)
  })
  registry.register<Message<unknown>[]>('invoice.generate', messagesSchema, async ({ env }, messages) => {
    await handleInvoiceGenerateMessages(messages, env)
  })
  registry.register<Message<unknown>[]>('auth.email', messagesSchema, async ({ env, batch }, messages) => {
    await handleAuthEmailMessages(filterBatchByMessages(batch, messages), env)
  })

  return registry
}

function registerAudit() {
  const audit = {
    recordAudit: platformRecordAudit,
  }
  for (const [name, value] of Object.entries(audit)) {
    if (typeof value !== 'function') {
      throw new Error(`Platform audit integration missing function export: ${name}`)
    }
  }
  return audit
}

function registerNotifications() {
  const notifications = {
    createDeps: createPlatformNotificationDeps,
  }
  for (const [name, value] of Object.entries(notifications)) {
    if (typeof value !== 'function') {
      throw new Error(`Platform notifications integration missing function export: ${name}`)
    }
  }
  return notifications
}

function registerRealtime() {
  const realtime = {
    handleBatch: handlePlatformRealtimeBatch,
    publishEvent: publishPlatformRealtimeEvent,
    verifyInternalSecret: verifyPlatformRealtimeInternalSecret,
  }
  for (const [name, value] of Object.entries(realtime)) {
    if (typeof value !== 'function') {
      throw new Error(`Platform realtime integration missing function export: ${name}`)
    }
  }
  return realtime
}

function registerSearch() {
  const search = {
    createRegistry: createPlatformSearchRegistry,
    run: runPlatformSearch,
  }
  for (const [name, value] of Object.entries(search)) {
    if (typeof value !== 'function') {
      throw new Error(`Platform search integration missing function export: ${name}`)
    }
  }
  const registry = search.createRegistry()
  if (!registry || typeof registry !== 'object') {
    throw new Error('Platform search integration returned an invalid registry')
  }
  return search
}

function createBootstrapComplianceStore(): ComplianceStore {
  return {
    async isSuppressed() {
      return false
    },
    async getConsent(email) {
      return {
        email,
        doubleOptInConfirmed: true,
        trackingConsent: true,
      }
    },
    async setConsent() {},
    async addSuppression() {},
    async listSuppressed() {
      return []
    },
  }
}

function registerHelpdesk() {
  const helpdesk = {
    schema: helpdeskSchema,
    listAttachments: platformListHelpdeskAttachments,
    listMessages: platformListHelpdeskMessages,
    listTransitions: platformListHelpdeskTransitions,
    postMessage: platformPostHelpdeskMessage,
  }

  if (!helpdesk.schema || typeof helpdesk.schema !== 'object') {
    throw new Error('Platform helpdesk integration missing schema export')
  }

  for (const [name, value] of Object.entries(helpdesk).filter(([name]) => name !== 'schema')) {
    if (typeof value !== 'function') {
      throw new Error(`Platform helpdesk integration missing function export: ${name}`)
    }
  }

  return helpdesk
}

function registerMarketing() {
  const complianceStore = createBootstrapComplianceStore()

  const selfManaged = makeSelfManagedAdapter({
    from: 'noreply@example.com',
    mail: {
      async send() {
        return { id: 'bootstrap-mail', provider: 'bootstrap' }
      },
    },
    store: {
      async upsertContact() {},
      async removeContact() {},
      async tagContact() {},
      async createList(name) {
        return { id: 'bootstrap-list', name }
      },
      async lists() {
        return []
      },
      async saveCampaign() {},
      async getCampaign() {
        return null
      },
      async getListRecipients() {
        return []
      },
      async recordOpen() {},
      async recordClick() {},
      async getStats() {
        return { opens: 0, clicks: 0, bounces: 0, unsubs: 0 }
      },
      async createSegment(def) {
        return { id: 'bootstrap-segment', name: def.name, listId: def.listId, filter: def.filter }
      },
      async listSegments() {
        return []
      },
      async scheduleCampaign() {},
    },
    complianceStore,
  } satisfies SelfManagedCreds)

  const brevo = makeBrevoAdapter({
    apiKey: 'bootstrap',
    senderEmail: 'noreply@example.com',
    complianceStore,
  } satisfies BrevoCreds)

  const marketing = {
    selfManagedFactory: makeSelfManagedAdapter,
    brevoFactory: makeBrevoAdapter,
    selfManagedAdapter: makeSelfManagedAdapter({
      from: 'noreply@example.com',
      mail: {
        async send() {
          return { id: 'bootstrap-mail', provider: 'bootstrap' }
        },
      },
      store: {
        async upsertContact() {},
        async removeContact() {},
        async tagContact() {},
        async createList(name: string) {
          return { id: 'bootstrap-list', name }
        },
        async lists() {
          return []
        },
        async saveCampaign() {},
        async getCampaign() {
          return null
        },
        async getListRecipients() {
          return []
        },
        async recordOpen() {},
        async recordClick() {},
        async getStats() {
          return { opens: 0, clicks: 0, bounces: 0, unsubs: 0 }
        },
        async createSegment(def: { name: string; listId: string; filter: Record<string, string> }) {
          return { id: 'bootstrap-segment', name: def.name, listId: def.listId, filter: def.filter }
        },
        async listSegments() {
          return []
        },
        async scheduleCampaign() {},
      },
      complianceStore,
    } satisfies SelfManagedCreds),
    brevoAdapter: makeBrevoAdapter({
      apiKey: 'bootstrap',
      senderEmail: 'noreply@example.com',
      complianceStore,
    } satisfies BrevoCreds),
    complianceStore,
  }

  for (const [name, value] of Object.entries({
    selfManagedFactory: marketing.selfManagedFactory,
    brevoFactory: marketing.brevoFactory,
  })) {
    if (typeof value !== 'function') {
      throw new Error(`Platform marketing integration missing factory export: ${name}`)
    }
  }

  for (const [name, value] of Object.entries({
    selfManagedAdapter: marketing.selfManagedAdapter,
    brevoAdapter: marketing.brevoAdapter,
    selfManaged,
    brevo,
  })) {
    if (!value || typeof value !== 'object') {
      throw new Error(`Platform marketing integration returned an invalid adapter: ${name}`)
    }
  }

  return marketing
}

const registeredJobTypes = new Set([
  'lead.score_recalc',
  'webhook.deliver',
  'comms.inbound',
  'uniform-format',
  'report.schedule',
  'retainer.invoice',
  'invoice.generate',
  'auth.email',
])

const pilotUploads = registerPilotUploads()
registerPilotAi()
const platformMail = registerMail()
const jobsRegistry = registerJobs()
const platformAudit = registerAudit()
const platformNotifications = registerNotifications()
const platformRealtime = registerRealtime()
const platformSearch = registerSearch()
const platformHelpdesk = registerHelpdesk()
const platformMarketing = registerMarketing()
void pilotUploads
void platformMail
void platformAudit
void platformNotifications
void platformHelpdesk
void platformMarketing
void platformSearch

const app = new Hono<{ Bindings: Env }>()

function isProductionWorker(env: Env): boolean {
  return (env as Env & { ENVIRONMENT?: string }).ENVIRONMENT === 'production'
}

app.onError((err, c) => {
  if (isProductionWorker(c.env)) {
    console.error('UNHANDLED ERROR', err?.name, err?.message)
  } else {
    console.error('UNHANDLED ERROR', err?.name, err?.message, err?.stack?.slice(0, 500))
  }
  return c.json({ error: 'Internal server error' }, 500)
})

// Global middleware — security-headers + CORS run before route handlers
app.use('*', securityHeadersMiddleware)
app.use('*', corsMiddleware)

// wave-11 leaf-C: public (no-auth) routes — MUST be mounted BEFORE /api (routes) to avoid
// the auth middleware in proposalEditorRoute.use('*', authMiddleware) catching these requests.
app.route('/api/sign', signRoute)
app.route('/api/proposals', proposalsPublicRoute)
app.route('/api/catalog', catalogPublicRoute)
app.route('/api/public/tenant-logo', tenantLogoPublicRoute)
app.route('/p', proposalsPublicRoute)
app.route('/c', catalogPublicRoute)

// wave-12: OAuth 2.0 AS endpoints — top-level /oauth (NOT under /api)
// /oauth/authorize requires user session; /oauth/token + /oauth/revoke are server-to-server.
app.route('/oauth', oauthRouter)

// contractor-portal (wave 8): top-level redeem + API (email link hits /contractor-portal/redeem)
app.route('/contractor-portal', contractorPortalRoutes)

// All routes under /api/*
app.route('/api', routes)

// tasks-board-engine module routes
app.route('/api/tasks', taskModuleRouter)
app.route('/api/cron/tasks-sync', tasksSyncCronRoute)

// calendar-module routes
app.route('/api/calendar', calendarRouter)
app.route('/api/auth/google-calendar', googleOAuthRouter)
app.route('/api/auth/outlook', outlookOAuthRouter)
app.route('/api/auth/outlook-calendar', outlookOAuthRouter)
app.route('/api', syncWebhooksRoute)
app.route('/api', schedulingWebhooksRoute)
app.route('/api', calendarCronRouter)

// recurring-tasks: cron generator
app.route('/api/cron/recurring-task-generator', recurringTaskGeneratorRoute)

// wave-10 leaf3: recurring-expenses cron generator
app.route('/api/cron/generate-recurring-expenses', recurringExpenseGeneratorCron)

// wave-12: multi-signatory-coordination — daily signing reminders cron
app.route('/api/cron/contract-signing-reminders', contractSigningRemindersCron)

// wave-14: lead-lost-reengagement cron
app.route('/api/cron/lead-reengagement', leadReengagementCronRoute)

// unified-attachments (spec 41)
app.route('/api/attachments', unifiedAttachmentsRouter)
app.route('/api/attachments/unified', unifiedAttachmentsRouter)

// wave-7: marketing lead public APIs (NO auth)
app.route('/api', publicFormRoutes)

// system-status-page: top-level /status/* (RSS + unsubscribe)
app.route('/status', publicStatusRoutes)

// Lazy bootstrap for the recurring-invoice alarm chain. Per cold isolate, at most
// one non-blocking /ensure to the singleton DO, which self-heals the alarm if none
// is pending. Guarded on the secret existing so the flag is not burned before the
// secret lands (orchestrator sets secrets after deploy).
let alarmEnsured = false

// Worker export — fetch handler + queue consumer + cron dispatcher
export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    if (!alarmEnsured && env.RECURRING_ALARM_SECRET) {
      alarmEnsured = true
      ctx.waitUntil(
        env.RECURRING_ALARM_DO.get(env.RECURRING_ALARM_DO.idFromName(RECURRING_ALARM_DO_NAME))
          .fetch(
            new Request('https://do/ensure', {
              method: 'POST',
              headers: { 'x-zync-recurring-alarm': env.RECURRING_ALARM_SECRET },
            }),
          )
          .catch(() => {}),
      )
    }
    try {
      return await app.fetch(req, env, ctx)
    } catch (err: unknown) {
      const e = err as Error
      if ((env as Env & { ENVIRONMENT?: string }).ENVIRONMENT === 'production') {
        console.error('TOP_LEVEL_FETCH_ERROR', e?.name, e?.message)
      } else {
        console.error('TOP_LEVEL_FETCH_ERROR', e?.name, e?.message, e?.stack?.slice(0, 800))
      }
      return new Response(JSON.stringify({ error: 'Internal server error' }), {
        status: 500,
        headers: { 'Content-Type': 'application/json' },
      })
    }
  },
  async queue(batch: MessageBatch<unknown>, env: Env): Promise<void> {
    if (batch.queue === 'comms-inbound') {
      return handleCommsInboundBatch(batch, env)
    }
    if (batch.queue === 'ai-index-update') return handleAiIndexUpdate(batch as MessageBatch<AiIndexUpdateJob>, env)
    if (batch.queue === 'ai-telegram-message') return handleAiTelegramMessage(batch as MessageBatch<AiTelegramMessageJob>, env)
    if (batch.queue === 'audit-log-queue') {
      return handleAuditLogBatch(batch as MessageBatch<AuditEvent>, env)
    }
    if (batch.queue === 'expense-process') return handleExpenseProcess(batch as MessageBatch<ExpenseProcessJob>, env)
    if (batch.queue === 'zync-realtime') {
      return (platformRealtime.handleBatch as (queueBatch: MessageBatch<unknown>, queueEnv: Env) => Promise<void>)(batch, env)
    }
    if (batch.queue === 'zync-jobs') {
      return handleZyncJobsBatch(batch, env)
    }
  },
  async scheduled(event: ScheduledEvent, env: Env): Promise<void> {
    await runCronGroup(event.cron, env)
  },
  async email(message: ForwardableEmailMessage, env: Env): Promise<void> {
    await handleInboundEmail(message, env)
  },
}

// Durable Object exports — required by wrangler for DO binding validation
export { TenantRealtimeDO } from './durable-objects/TenantRealtimeDO'
export { PasswordHashDO } from './durable-objects/PasswordHashDO'
export { AuthWriteDO } from './durable-objects/AuthWriteDO'
export { RecurringInvoiceAlarmDO } from './durable-objects/RecurringInvoiceAlarmDO'
