import Exa from "exa-js";
import type { ExaProviderConfig } from "../schema.ts";
import type {
  NormalizedFetchRequest,
  NormalizedFetchResponse,
  NormalizedSearchRequest,
  NormalizedSearchResponse,
  WebProvider,
} from "./types.ts";

export interface ExaClientLike {
  search(query: string, options?: Record<string, unknown>): Promise<any>;
  getContents(urls: string[] | string, options?: Record<string, unknown>): Promise<any>;
}

export type ExaClientFactory = (apiKey: string) => ExaClientLike;

export function buildSearchOptions(config: ExaProviderConfig, request: NormalizedSearchRequest) {
  return {
    contents: false,
    numResults: request.limit ?? config.options?.defaultSearchLimit ?? 5,
    includeDomains: request.includeDomains,
    excludeDomains: request.excludeDomains,
    startPublishedDate: request.startPublishedDate,
    endPublishedDate: request.endPublishedDate,
    category: request.category,
  };
}

export function buildFetchOptions(config: ExaProviderConfig, request: NormalizedFetchRequest) {
  const text = request.text ?? (!request.highlights && !request.summary);

  return {
    ...(text
      ? {
          text: {
            maxCharacters: request.textMaxCharacters ?? config.options?.defaultFetchTextMaxCharacters ?? 12000,
          },
        }
      : {}),
    ...(request.highlights
      ? {
          highlights: {
            maxCharacters: config.options?.defaultFetchHighlightsMaxCharacters ?? 1000,
          },
        }
      : {}),
    ...(request.summary ? { summary: true } : {}),
  };
}

export function createExaProvider(
  config: ExaProviderConfig,
  createClient: ExaClientFactory = (apiKey) => new Exa(apiKey) as unknown as ExaClientLike,
): WebProvider {
  const client = createClient(config.apiKey);

  return {
    name: config.name,
    type: config.type,

    async search(request: NormalizedSearchRequest): Promise<NormalizedSearchResponse> {
      const response = await client.search(request.query, buildSearchOptions(config, request));
      return {
        providerName: config.name,
        requestId: response.requestId,
        searchTime: response.searchTime,
        results: (response.results ?? []).map((item: any) => ({
          id: item.id,
          title: item.title ?? null,
          url: item.url,
          publishedDate: item.publishedDate,
          author: item.author,
          score: item.score,
        })),
      };
    },

    async fetch(request: NormalizedFetchRequest): Promise<NormalizedFetchResponse> {
      const requestIds: string[] = [];
      const options = buildFetchOptions(config, request);

      const results = await Promise.all(
        request.urls.map(async (url) => {
          try {
            const response = await client.getContents([url], options);
            if (response.requestId) {
              requestIds.push(response.requestId);
            }

            const item = response.results?.[0];
            if (!item) {
              return {
                url,
                title: null,
                error: "No content returned",
              };
            }

            return {
              url: item.url ?? url,
              title: item.title ?? null,
              text: typeof item.text === "string" ? item.text : undefined,
              highlights: Array.isArray(item.highlights) ? item.highlights : undefined,
              summary: typeof item.summary === "string" ? item.summary : undefined,
            };
          } catch (error) {
            return {
              url,
              title: null,
              error: (error as Error).message,
            };
          }
        }),
      );

      return {
        providerName: config.name,
        requestIds,
        results,
      };
    },
  };
}
