/**
 * Arbeitnow public job board API.
 *
 * https://www.arbeitnow.com/api/job-board-api?page=N
 *
 * No auth. Returns 100 results per page, sorted by creation date.
 * No server-side search — we paginate and filter client-side by
 * title + tags against each pipeline search term.
 *
 * Aggregates listings from Greenhouse, SmartRecruiters, Join,
 * TeamTailor, Recruitee, and Comeet.
 */

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

const API_URL = "https://www.arbeitnow.com/api/job-board-api";
const MAX_PAGES = 5;

interface ArbeitnowJob {
  slug?: string;
  company_name?: string;
  title?: string;
  description?: string;
  remote?: boolean;
  url?: string;
  tags?: string[];
  job_types?: string[];
  location?: string;
  created_at?: number;
}

interface ArbeitnowResponse {
  data?: ArbeitnowJob[];
  links?: { next?: string | null };
  meta?: { current_page?: number };
}

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

function matchesTerm(job: ArbeitnowJob, term: string): boolean {
  const lower = term.toLowerCase();
  if (job.title?.toLowerCase().includes(lower)) return true;
  if (
    Array.isArray(job.tags) &&
    job.tags.some(
      (t) => typeof t === "string" && t.toLowerCase().includes(lower),
    )
  )
    return true;
  return false;
}

function mapJob(raw: ArbeitnowJob): 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)
    : [];

  const jobTypes = Array.isArray(raw.job_types)
    ? raw.job_types
        .filter((t): t is string => typeof t === "string" && t.length > 0)
        .join(", ")
    : undefined;

  const datePosted =
    typeof raw.created_at === "number"
      ? new Date(raw.created_at * 1000).toISOString()
      : undefined;

  return {
    source: "arbeitnow",
    sourceJobId: asString(raw.slug),
    title: asString(raw.title) ?? "Unknown Title",
    employer: asString(raw.company_name) ?? "Unknown Employer",
    jobUrl,
    applicationLink: jobUrl,
    location: asString(raw.location) ?? "Unknown",
    isRemote: raw.remote === true,
    jobType: jobTypes || undefined,
    datePosted,
    jobDescription: asString(raw.description),
    disciplines: tags.length > 0 ? tags.join(", ") : undefined,
  };
}

async function fetchPage(page: number): Promise<ArbeitnowResponse> {
  const url = `${API_URL}?page=${page}`;
  const response = await fetch(url, {
    headers: { Accept: "application/json" },
  });
  if (!response.ok) {
    throw new Error(`Arbeitnow request failed with status ${response.status}`);
  }
  return (await response.json()) as ArbeitnowResponse;
}

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

    const maxJobs = context.settings.arbeitnowMaxJobsPerTerm
      ? Number.parseInt(context.settings.arbeitnowMaxJobsPerTerm, 10)
      : 100;

    const terms = context.searchTerms.length > 0 ? context.searchTerms : [];
    const seen = new Set<string>();
    const out: CreateJobInput[] = [];

    try {
      for (let page = 1; page <= MAX_PAGES; page += 1) {
        if (context.shouldCancel?.()) break;
        if (out.length >= maxJobs * Math.max(terms.length, 1)) break;

        context.onProgress?.({
          phase: "list",
          termsProcessed: 0,
          termsTotal: 1,
          currentUrl: `page ${page}`,
          detail: `Arbeitnow: fetching page ${page}`,
        });

        const body = await fetchPage(page);
        const jobs = Array.isArray(body.data) ? body.data : [];

        if (jobs.length === 0) break;

        for (const raw of jobs) {
          if (terms.length > 0 && !terms.some((t) => matchesTerm(raw, t))) {
            continue;
          }
          const mapped = mapJob(raw);
          if (!mapped) continue;
          const key = mapped.sourceJobId || mapped.jobUrl;
          if (seen.has(key)) continue;
          seen.add(key);
          out.push(mapped);
        }

        context.onProgress?.({
          phase: "list",
          termsProcessed: 0,
          termsTotal: 1,
          currentUrl: `page ${page}`,
          jobPagesProcessed: out.length,
          detail: `Arbeitnow: page ${page} done (${out.length} matched so far)`,
        });

        if (!body.links?.next) break;
      }
    } 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;
