import { Type } from "@sinclair/typebox";
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
import { toolResult, trackedFetch, isTrackedError, validParam, validEnum } from "./util.js";

const BASE = "https://api.openalex.org";

export function createOpenAlexTools(
  _ctx: OpenClawPluginToolContext,
  _api: OpenClawPluginApi,
) {
  // mailto in the User-Agent puts requests into OpenAlex's polite pool
  // (faster, more reliable rate limits than the anonymous common pool).
  const headers: Record<string, string> = {
    "User-Agent": "ResearchPlugins/1.0 (https://wentor.ai; mailto:research-plugins@wentor.ai)",
  };

  return [
    {
      name: "search_openalex",
      label: "Search Works (OpenAlex)",
      description:
        "Search academic works via OpenAlex (free, no key required). Covers 250M+ works across all disciplines.",
      parameters: Type.Object({
        query: Type.String({ description: "Search query for works" }),
        limit: Type.Optional(
          Type.Number({ description: "Max results (default 10, max 200)" }),
        ),
        from_year: Type.Optional(
          Type.Number({ description: "Filter works from this year onward" }),
        ),
        to_year: Type.Optional(
          Type.Number({ description: "Filter works up to this year" }),
        ),
        open_access: Type.Optional(
          Type.Boolean({ description: "Only open access works" }),
        ),
        sort_by: Type.Optional(
          Type.String({
            description: "Sort by: 'cited_by_count', 'publication_date', 'relevance_score'",
          }),
        ),
      }),
      execute: async (_toolCallId: string, input: {
        query: string;
        limit?: number;
        from_year?: number;
        to_year?: number;
        open_access?: boolean;
        sort_by?: string;
      }) => {
        const query = validParam(input?.query);
        if (!query) {
          return toolResult({ error: "query parameter is required and must not be empty" });
        }

        const SORT_BY = ["cited_by_count", "publication_date", "relevance_score"] as const;

        const filters: string[] = [];
        if (input.from_year) filters.push(`from_publication_date:${input.from_year}-01-01`);
        if (input.to_year) filters.push(`to_publication_date:${input.to_year}-12-31`);
        if (input.open_access) filters.push("is_oa:true");

        const params = new URLSearchParams({
          search: query,
          per_page: String(Math.min(input.limit ?? 10, 200)),
        });
        if (filters.length > 0) params.set("filter", filters.join(","));
        const sortBy = validParam(input.sort_by);
        if (sortBy && (SORT_BY as readonly string[]).includes(sortBy)) {
          params.set("sort", sortBy);
        }

        const tracked = await trackedFetch("openalex", `${BASE}/works?${params}`, { headers });
        if (isTrackedError(tracked)) return tracked;
        const data = await tracked.res.json();
        return toolResult({
          total_count: data.meta?.count,
          results: data.results?.map((w: Record<string, unknown>) => ({
            id: w.id,
            doi: w.doi,
            title: w.title,
            publication_date: w.publication_date,
            cited_by_count: w.cited_by_count,
            is_oa: w.open_access && (w.open_access as Record<string, unknown>).is_oa,
            oa_url:
              w.open_access && (w.open_access as Record<string, unknown>).oa_url,
            authors: Array.isArray(w.authorships)
              ? (w.authorships as Record<string, unknown>[]).slice(0, 5).map(
                  (a) => (a.author as Record<string, unknown>)?.display_name,
                )
              : [],
          })),
          _source_health: { source: "openalex", latency_ms: tracked.latency_ms },
        });
      },
    },
    {
      name: "get_work",
      label: "Get Work Details (OpenAlex)",
      description:
        "Get full details of a work by its OpenAlex ID, DOI, or other identifier.",
      parameters: Type.Object({
        work_id: Type.String({
          description:
            "Work identifier: OpenAlex ID (e.g. 'W2741809807'), DOI URL, or PMID",
        }),
      }),
      execute: async (_toolCallId: string, input: { work_id: string }) => {
        if (!input?.work_id) {
          return toolResult({ error: 'work_id parameter is required (e.g., "W2741809807" or a DOI like "10.1234/example")' });
        }
        const id = input.work_id.startsWith("10.")
          ? `https://doi.org/${input.work_id}`
          : input.work_id;
        const tracked = await trackedFetch("openalex", `${BASE}/works/${encodeURIComponent(id)}`, { headers });
        if (isTrackedError(tracked)) return tracked;
        const w = await tracked.res.json();
        return toolResult({
          id: w.id,
          doi: w.doi,
          title: w.title,
          abstract: w.abstract,
          publication_date: w.publication_date,
          type: w.type,
          cited_by_count: w.cited_by_count,
          is_oa: w.open_access?.is_oa,
          oa_url: w.open_access?.oa_url,
          venue: w.primary_location?.source?.display_name,
          authors: w.authorships?.map(
            (a: Record<string, unknown>) =>
              (a.author as Record<string, unknown>)?.display_name,
          ),
          concepts: w.concepts
            ?.slice(0, 10)
            .map((c: Record<string, unknown>) => c.display_name),
          referenced_works_count: w.referenced_works?.length,
          _source_health: { source: "openalex", latency_ms: tracked.latency_ms },
        });
      },
    },
    {
      name: "get_author_openalex",
      label: "Get Author (OpenAlex)",
      description:
        "Get author information from OpenAlex including publications, h-index, and affiliations.",
      parameters: Type.Object({
        author_id: Type.String({
          description:
            "Author identifier: OpenAlex ID (e.g. 'A5023888391'), ORCID, or name search",
        }),
      }),
      execute: async (_toolCallId: string, input: { author_id: string }) => {
        if (!input?.author_id) {
          return toolResult({ error: 'author_id parameter is required (OpenAlex ID e.g. "A5023888391", ORCID, or author name)' });
        }
        let url: string;
        if (
          input.author_id.startsWith("A") ||
          input.author_id.startsWith("https://")
        ) {
          url = `${BASE}/authors/${encodeURIComponent(input.author_id)}`;
        } else if (input.author_id.includes("-")) {
          url = `${BASE}/authors/orcid:${input.author_id}`;
        } else {
          const params = new URLSearchParams({
            search: input.author_id,
            per_page: "5",
          });
          const tracked = await trackedFetch("openalex", `${BASE}/authors?${params}`, { headers });
          if (isTrackedError(tracked)) return tracked;
          const data = await tracked.res.json();
          return toolResult({
            ...data,
            _source_health: { source: "openalex", latency_ms: tracked.latency_ms },
          });
        }

        const tracked = await trackedFetch("openalex", url, { headers });
        if (isTrackedError(tracked)) return tracked;
        const a = await tracked.res.json();
        return toolResult({
          id: a.id,
          display_name: a.display_name,
          orcid: a.orcid,
          works_count: a.works_count,
          cited_by_count: a.cited_by_count,
          h_index: a.summary_stats?.h_index,
          i10_index: a.summary_stats?.i10_index,
          affiliations: a.affiliations?.map(
            (af: Record<string, unknown>) =>
              (af.institution as Record<string, unknown>)?.display_name,
          ),
          top_concepts: a.x_concepts
            ?.slice(0, 5)
            .map((c: Record<string, unknown>) => c.display_name),
          _source_health: { source: "openalex", latency_ms: tracked.latency_ms },
        });
      },
    },
  ];
}
