/**
 * Jooble aggregator API.
 *
 * https://jooble.org/api/about — `POST https://jooble.org/api/{key}` with a
 * JSON body of `{ keywords, location, page, ResultOnPage }`.
 *
 * Requires JOOBLE_API_KEY (`joobleApiKey` setting).
 */

import type {
  ExtractorManifest,
  ExtractorRunResult,
} from "@shared/types/extractors";
import type { CreateJobInput } from "@shared/types/jobs";

const API_BASE = "https://jooble.org/api";

interface JoobleJob {
  id?: number | string;
  title?: string;
  location?: string;
  snippet?: string;
  salary?: string;
  source?: string;
  type?: string;
  link?: string;
  company?: string;
  updated?: string;
}
interface JoobleResponse {
  totalCount?: number;
  jobs?: JoobleJob[];
}

function asString(value: unknown): string | undefined {
  if (typeof value !== "string") return undefined;
  const trimmed = value.trim();
  return trimmed ? trimmed : undefined;
}

function mapJob(raw: JoobleJob): CreateJobInput | null {
  const jobUrl = asString(raw.link);
  if (!jobUrl) return null;
  return {
    source: "jooble",
    sourceJobId: raw.id != null ? String(raw.id) : undefined,
    title: asString(raw.title) ?? "Unknown Title",
    employer: asString(raw.company) ?? "Unknown Employer",
    jobUrl,
    applicationLink: jobUrl,
    location: asString(raw.location),
    jobType: asString(raw.type),
    salary: asString(raw.salary),
    datePosted: asString(raw.updated),
    jobDescription: asString(raw.snippet),
    companyDescription: asString(raw.source),
  };
}

async function fetchPage(args: {
  apiKey: string;
  keywords: string;
  location?: string;
  page: number;
  resultOnPage: number;
}): Promise<JoobleResponse> {
  const response = await fetch(
    `${API_BASE}/${encodeURIComponent(args.apiKey)}`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify({
        keywords: args.keywords,
        location: args.location ?? "",
        page: String(args.page),
        ResultOnPage: String(args.resultOnPage),
      }),
    },
  );
  if (!response.ok) {
    throw new Error(`Jooble request failed with status ${response.status}`);
  }
  return (await response.json()) as JoobleResponse;
}

export const manifest: ExtractorManifest = {
  id: "jooble",
  displayName: "Jooble",
  providesSources: ["jooble"],
  requiredEnvVars: ["JOOBLE_API_KEY"],
  async run(context): Promise<ExtractorRunResult> {
    if (context.shouldCancel?.()) return { success: true, jobs: [] };

    const apiKey =
      context.settings.joobleApiKey?.trim() ||
      process.env.JOOBLE_API_KEY?.trim();
    if (!apiKey) {
      return {
        success: false,
        jobs: [],
        error: "Jooble extractor requires JOOBLE_API_KEY",
      };
    }

    const maxJobsPerTerm = context.settings.joobleMaxJobsPerTerm
      ? Number.parseInt(context.settings.joobleMaxJobsPerTerm, 10)
      : 100;
    const resultOnPage = 50;

    const terms = context.searchTerms.length > 0 ? context.searchTerms : [""];
    const location =
      context.settings.searchCities?.split("|")[0]?.trim() || undefined;

    const seen = new Set<string>();
    const out: CreateJobInput[] = [];

    try {
      for (let i = 0; i < terms.length; i += 1) {
        if (context.shouldCancel?.()) break;
        const term = terms[i].trim();
        context.onProgress?.({
          phase: "list",
          termsProcessed: i,
          termsTotal: terms.length,
          currentUrl: term || "(all)",
          detail: `Jooble: term ${i + 1}/${terms.length}`,
        });

        let collected = 0;
        let page = 1;
        while (collected < maxJobsPerTerm && page < 50) {
          if (context.shouldCancel?.()) break;
          const body = await fetchPage({
            apiKey,
            keywords: term,
            location,
            page,
            resultOnPage,
          });
          const items = Array.isArray(body.jobs) ? body.jobs : [];
          if (items.length === 0) break;
          for (const raw of items) {
            const mapped = mapJob(raw);
            if (!mapped) continue;
            const key = mapped.sourceJobId || mapped.jobUrl;
            if (seen.has(key)) continue;
            seen.add(key);
            out.push(mapped);
            collected += 1;
            if (collected >= maxJobsPerTerm) break;
          }
          if (items.length < resultOnPage) break;
          page += 1;
        }

        context.onProgress?.({
          phase: "list",
          termsProcessed: i + 1,
          termsTotal: terms.length,
          currentUrl: term || "(all)",
          jobPagesProcessed: out.length,
          detail: `Jooble: completed term ${i + 1}/${terms.length} (${collected} found)`,
        });
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : "Unknown error";
      return { success: false, jobs: out, error: message };
    }

    return { success: true, jobs: out };
  },
};

export default manifest;
