import { inArray } from 'drizzle-orm';
import type { Querier } from '@platform-modules/db';
import {
  contentEntries,
  createContentSearchProvider,
  type ContentSchema,
} from '@platform-modules/content';
import { searchEntities, type SearchResult } from '@platform-modules/search';

export type PublicSearchHit = {
  id: string;
  slug: string;
  title: string;
  snippetHtml: string;
  rank: number;
};

export type PublicContentSearchPage = {
  hasQuery: boolean;
  hits: PublicSearchHit[];
  nextCursor: string | null;
};

export async function loadPublicContentSearch(
  db: Querier<ContentSchema>,
  q: string,
  cursor?: string | null,
): Promise<PublicContentSearchPage> {
  // Bound the public query at the trust boundary (far past any real search) so an
  // anonymous request cannot amplify into thousands of `token:*` prefix terms +
  // ts_headline over every match (per-request FTS DoS). 256 chars is the cap.
  const MAX_QUERY_LEN = 256;
  const trimmed = q.trim().slice(0, MAX_QUERY_LEN);
  if (!trimmed) {
    return { hasQuery: false, hits: [], nextCursor: null };
  }

  let result: SearchResult;
  try {
    result = await searchEntities(
      // Scope public site search to the uniformly-addressable `post` type — `page`
      // entries (home/privacy) are bespoke-routed, not `/post/<slug>` targets.
      { content: createContentSearchProvider({ types: ['post'] }) },
      {
        query: trimmed,
        ctx: { db, viewer: null },
        entityTypes: ['content'],
        limit: 20,
        cursor: cursor ?? null,
      },
    );
  } catch {
    return { hasQuery: true, hits: [], nextCursor: null };
  }

  const group = result.groups.find((g) => g.entityType === 'content');
  if (!group || group.hits.length === 0) {
    return { hasQuery: true, hits: [], nextCursor: result.nextCursor };
  }

  const ids = group.hits.map((h) => h.id);
  let slugById = new Map<string, string>();
  try {
    const rows = await db
      .select({ id: contentEntries.id, slug: contentEntries.slug })
      .from(contentEntries)
      .where(inArray(contentEntries.id, ids));
    slugById = new Map(rows.map((r) => [r.id, r.slug]));
  } catch {
    return { hasQuery: true, hits: [], nextCursor: result.nextCursor };
  }

  const hits: PublicSearchHit[] = [];
  for (const hit of group.hits) {
    const slug = slugById.get(hit.id);
    if (!slug) continue;
    hits.push({
      id: hit.id,
      slug,
      title: hit.title ?? slug,
      snippetHtml: hit.snippetHtml ?? '',
      rank: hit.rank,
    });
  }

  return { hasQuery: true, hits, nextCursor: result.nextCursor };
}
