import {
  feedItemId,
  type FeedItem,
  type FeedItemType,
} from '@crumbs/core/schemas'
import {and, inArray} from 'drizzle-orm'

import {db, schema} from '../db/client'
import {
  keysetCompareDesc,
  keysetCursorWhere,
  keysetOrder,
  toPage,
} from '../lib/cursor'
import {loadCrumbResponsesByIds} from './crumbs'
import {loadTrailSummariesByIds} from './trails'
import {loadUserSummaries} from './users'

// a feed key: the sort identity of one activity item before hydration
type FeedKey = {
  type: FeedItemType
  entityId: string
  authorId: string
  createdAt: Date
}

const toKeyset = (k: FeedKey) => ({
  createdAt: k.createdAt,
  id: k.entityId,
})

// the activity feed: crumbs saved and trails created by the given authors,
// newest first on one shared keyset. each source is keyset-limited to
// limit + 1, so their union contains the global top limit + 1
export const listActivityFeed = async ({
  viewerId,
  authorIds,
  limit,
  cursor,
}: {
  viewerId: string
  authorIds: string[]
  limit: number
  cursor?: string
}): Promise<{items: FeedItem[]; nextCursor: string | null}> => {
  if (!authorIds.length) return {items: [], nextCursor: null}

  const keysetSelect = (
    table: typeof schema.crumbs | typeof schema.trails,
    type: FeedKey['type'],
  ) =>
    db
      .select({
        entityId: table.id,
        authorId: table.authorId,
        createdAt: table.createdAt,
      })
      .from(table)
      .where(
        and(
          inArray(table.authorId, authorIds),
          keysetCursorWhere(table.createdAt, table.id, cursor),
        ),
      )
      .orderBy(...keysetOrder(table.createdAt, table.id))
      .limit(limit + 1)
      .then((rows) => rows.map((r) => ({...r, type})))

  const [crumbKeys, trailKeys] = await Promise.all([
    keysetSelect(schema.crumbs, 'crumb'),
    keysetSelect(schema.trails, 'trail'),
  ])

  const merged = [...crumbKeys, ...trailKeys]
    .sort((a, b) => keysetCompareDesc(toKeyset(a), toKeyset(b)))
    .slice(0, limit + 1)
  const {page, nextCursor} = toPage(merged, limit, toKeyset)

  const idsOf = (type: FeedKey['type']) =>
    page.filter((k) => k.type === type).map((k) => k.entityId)
  const [crumbs, trails, authors] = await Promise.all([
    loadCrumbResponsesByIds(idsOf('crumb')),
    loadTrailSummariesByIds(viewerId, idsOf('trail')),
    loadUserSummaries([...new Set(page.map((k) => k.authorId))]),
  ])

  // a mid-request delete can orphan a key; drop it rather than 500
  const items: FeedItem[] = page.flatMap((k): FeedItem[] => {
    const author = authors.get(k.authorId)
    if (!author) return []
    const base = {
      id: feedItemId(k.type, k.entityId),
      author,
      createdAt: k.createdAt.toISOString(),
    }
    if (k.type === 'crumb') {
      const crumb = crumbs.get(k.entityId)
      return crumb ? [{...base, type: 'crumb' as const, crumb}] : []
    }
    const trail = trails.get(k.entityId)
    return trail ? [{...base, type: 'trail' as const, trail}] : []
  })

  return {items, nextCursor}
}
