import { getDb } from '@/shared/db';
import { searchMessages } from '@/entities/message';
import { searchEvents } from '@/entities/event';
import { searchContacts } from '@/entities/contact';
import { embedQuery } from '@/shared/model-boss';
import type { UnifiedSearchHit, UnifiedSearchResult } from './types';

const RRF_K = 60;

const WEIGHT_MESSAGE = Number(process.env.SEARCH_WEIGHT_MESSAGE ?? '1');
const WEIGHT_MAIL = Number(process.env.SEARCH_WEIGHT_MAIL ?? '1');
const WEIGHT_CALENDAR = Number(process.env.SEARCH_WEIGHT_CALENDAR ?? '1');
/**
 * Contact arm weight is intentionally large. At RRF k=60, the top message hit
 * scores 1/61 ≈ 0.0164; weight=5 makes even a rank-3 contact
 * (5 * 1/63 ≈ 0.0794) outrank the strongest body-mention. Encodes the rule:
 * "a contact named Shannon ranks above a stray message mentioning Shannon."
 */
const WEIGHT_CONTACT = Number(process.env.SEARCH_WEIGHT_CONTACT ?? '5');

const LIMIT_DEFAULT = 30;
const LIMIT_MAX = 100;

export async function unifiedSearch(
  query: string,
  opts: { deviceId?: string; limit?: number } = {},
): Promise<UnifiedSearchResult> {
  const limit = Math.min(opts.limit ?? LIMIT_DEFAULT, LIMIT_MAX);
  const armLimit = Math.min(limit * 2, 100);

  const db = getDb();

  // Fetch query embedding once — used for both message and mail hybrid search.
  // Contact search is purely lexical so it doesn't need the embedding.
  let queryEmbedding: number[] | undefined;
  try {
    queryEmbedding = await embedQuery(query);
  } catch {
    // Embedding service unavailable — fall back to lexical for all surfaces
  }

  const [contactHits, messageHits, mailHits, calendarHits] = await Promise.all([
    searchContacts(db, { query, deviceId: opts.deviceId, limit: armLimit }),
    searchMessages(db, {
      query,
      mode: 'hybrid',
      provider: 'imessage',
      deviceId: opts.deviceId,
      limit: armLimit,
      queryEmbedding,
    }),
    searchMessages(db, {
      query,
      mode: 'hybrid',
      provider: 'icloud',
      deviceId: opts.deviceId,
      limit: armLimit,
      queryEmbedding,
    }),
    searchEvents(db, query, { deviceId: opts.deviceId, limit: armLimit }),
  ]);

  // RRF: score = weight * 1/(k + rank_position)  (position is 1-based)
  const candidates: UnifiedSearchHit[] = [
    ...contactHits.map((hit, i) => ({
      surface: 'contact' as const,
      hit,
      score: WEIGHT_CONTACT * (1 / (RRF_K + i + 1)),
    })),
    ...messageHits.hits.map((hit, i) => ({
      surface: 'message' as const,
      hit,
      score: WEIGHT_MESSAGE * (1 / (RRF_K + i + 1)),
    })),
    ...mailHits.hits.map((hit, i) => ({
      surface: 'mail' as const,
      hit,
      score: WEIGHT_MAIL * (1 / (RRF_K + i + 1)),
    })),
    ...calendarHits.map((hit, i) => ({
      surface: 'calendar' as const,
      hit,
      score: WEIGHT_CALENDAR * (1 / (RRF_K + i + 1)),
    })),
  ];

  candidates.sort((a, b) => b.score - a.score);

  return {
    query,
    limit,
    results: candidates.slice(0, limit),
  };
}
