/**
 * Remotive public remote-jobs API.
 *
 * https://remotive.com/api/remote-jobs?limit=N&search=term
 *
 * No auth. Returns up to `limit` results per call with a `search` keyword
 * filter. We iterate pipeline search terms as the `search` parameter.
 */

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

const API_URL = "https://remotive.com/api/remote-jobs";

interface RemotiveJob {
  id?: number;
  url?: string;
  title?: string;
  company_name?: string;
  company_logo?: string;
  category?: string;
  tags?: string[];
  job_type?: string;
  publication_date?: string;
  candidate_required_location?: string;
  salary?: string;
  description?: string;
}

interface RemotiveResponse {
  jobs?: RemotiveJob[];
}

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

function normalizeJobType(raw: string | undefined): string | undefined {
  if (!raw) return undefined;
  return raw.replace(/_/g, " ").trim() || undefined;
}

function mapJob(raw: RemotiveJob): CreateJobInput | null {
  const jobUrl = asString(raw.url);
  if (!jobUrl) return null;

  const tags = Array.isArray(raw.tags)
    ? raw.tags.filter((t): t is string => typeof t === "string" && t.length > 0)
    : [];

  return {
    source: "remotive",
    sourceJobId: raw.id != null ? String(raw.id) : undefined,
    title: asString(raw.title) ?? "Unknown Title",
    employer: asString(raw.company_name) ?? "Unknown Employer",
    jobUrl,
    applicationLink: jobUrl,
    location: asString(raw.candidate_required_location) ?? "Remote",
    isRemote: true,
    jobType: normalizeJobType(raw.job_type),
    companyIndustry: asString(raw.category),
    companyLogo: asString(raw.company_logo),
    datePosted: asString(raw.publication_date),
    salary: asString(raw.salary),
    jobDescription: asString(raw.description),
    disciplines: tags.length > 0 ? tags.join(", ") : undefined,
  };
}

async function fetchJobs(
  search: string | null,
  limit: number,
): Promise<RemotiveJob[]> {
  const url = new URL(API_URL);
  url.searchParams.set("limit", String(Math.min(Math.max(limit, 1), 100)));
  if (search) url.searchParams.set("search", search);

  const response = await fetch(url.toString(), {
    headers: { Accept: "application/json" },
  });
  if (!response.ok) {
    throw new Error(`Remotive request failed with status ${response.status}`);
  }
  const body = (await response.json()) as RemotiveResponse;
  return Array.isArray(body.jobs) ? body.jobs : [];
}

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

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

    const terms = context.searchTerms.length > 0 ? context.searchTerms : [null];

    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];
        const search = term ? term.trim() : null;
        context.onProgress?.({
          phase: "list",
          termsProcessed: i,
          termsTotal: terms.length,
          currentUrl: search ?? "(all remote)",
          detail: `Remotive: term ${i + 1}/${terms.length}`,
        });

        const raw = await fetchJobs(search, maxJobsPerTerm);
        let collected = 0;
        for (const item of raw) {
          if (collected >= maxJobsPerTerm) break;
          const mapped = mapJob(item);
          if (!mapped) continue;
          const key = mapped.sourceJobId || mapped.jobUrl;
          if (seen.has(key)) continue;
          seen.add(key);
          out.push(mapped);
          collected += 1;
        }

        context.onProgress?.({
          phase: "list",
          termsProcessed: i + 1,
          termsTotal: terms.length,
          currentUrl: search ?? "(all remote)",
          jobPagesProcessed: out.length,
          detail: `Remotive: 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;
