import {groupBy} from '@crumbs/core/collections'
import type {
  CrumbReactionSummary,
  CrumbReactor,
} from '@crumbs/core/schemas'
import {and, asc, eq, inArray, sql} from 'drizzle-orm'

import {db, schema} from '../db/client'
import {countInt} from '../lib/sql'
import {notBlockedByViewer} from './blocks'
import {hydrateProfiles} from './friends'

// reaction tallies for a set of crumbs, keyed by crumb id. trailId scopes the
// tally to one board (the trail detail); without it reactions sum across every
// trail the crumb appears in (a crumb's popularity is not scoped to one
// board). a blocked user's reactions stay hidden either way. most-used emoji
// first, mirroring the trail chips
export const loadReactionSummaries = async (
  viewerId: string,
  crumbIds: string[],
  {trailId}: {trailId?: string} = {},
): Promise<Map<string, CrumbReactionSummary[]>> => {
  if (!crumbIds.length) return new Map()
  const rows = await db
    .select({
      crumbId: schema.crumbReactions.crumbId,
      emoji: schema.crumbReactions.emoji,
      count: countInt().as('count'),
      mine: sql<boolean>`bool_or(${schema.crumbReactions.userId} = ${viewerId})`.as(
        'mine',
      ),
    })
    .from(schema.crumbReactions)
    .where(
      and(
        trailId
          ? eq(schema.crumbReactions.trailId, trailId)
          : undefined,
        inArray(schema.crumbReactions.crumbId, crumbIds),
        notBlockedByViewer(schema.crumbReactions.userId, viewerId),
      ),
    )
    .groupBy(
      schema.crumbReactions.crumbId,
      schema.crumbReactions.emoji,
    )
    .orderBy(sql`count desc`)
  return groupBy(
    rows,
    (r) => r.crumbId,
    (r) => ({emoji: r.emoji, count: r.count, mine: r.mine}),
  )
}

// every person who reacted to a crumb in a trail, with the single emoji they
// picked and their viewer-relative profile. most-used emoji first (mirroring
// the summary chips), oldest reaction first within an emoji
export const listCrumbReactors = async (
  trailId: string,
  crumbId: string,
  viewerId: string,
): Promise<CrumbReactor[]> => {
  const rows = await db
    .select({
      userId: schema.crumbReactions.userId,
      emoji: schema.crumbReactions.emoji,
    })
    .from(schema.crumbReactions)
    .where(
      and(
        eq(schema.crumbReactions.trailId, trailId),
        eq(schema.crumbReactions.crumbId, crumbId),
        // a blocked member's reaction is hidden from the viewer
        notBlockedByViewer(schema.crumbReactions.userId, viewerId),
      ),
    )
    .orderBy(asc(schema.crumbReactions.createdAt))
  if (!rows.length) return []

  const profiles = await hydrateProfiles(
    viewerId,
    rows.map((r) => r.userId),
  )
  const emojiCount = new Map<string, number>()
  for (const r of rows)
    emojiCount.set(r.emoji, (emojiCount.get(r.emoji) ?? 0) + 1)

  return rows
    .flatMap((r) => {
      const user = profiles.get(r.userId)
      return user ? [{user, emoji: r.emoji}] : []
    })
    .sort(
      (a, b) =>
        (emojiCount.get(b.emoji) ?? 0) -
        (emojiCount.get(a.emoji) ?? 0),
    )
}
