/**
 * Working Nomads — public JSON feed.
 *
 * https://www.workingnomads.com/api/exposed_jobs/
 */

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

const API_URL = "https://www.workingnomads.com/api/exposed_jobs/";

interface WorkingNomadsJob {
  url?: string;
  title?: string;
  description?: string;
  company_name?: string;
  category_name?: string;
  tags?: string;
  location?: string;
  pub_date?: string;
}

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

function stripHtml(html: string): string {
  return html
    .replace(/<[^>]+>/g, " ")
    .replace(/&nbsp;/g, " ")
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/\s+/g, " ")
    .trim();
}

function matchesTerm(job: WorkingNomadsJob, term: string): boolean {
  const lower = term.toLowerCase();
  if (job.title?.toLowerCase().includes(lower)) return true;
  if (job.company_name?.toLowerCase().includes(lower)) return true;
  if (job.category_name?.toLowerCase().includes(lower)) return true;
  if (job.tags?.toLowerCase().includes(lower)) return true;
  const description = job.description ? stripHtml(job.description) : "";
  if (description.toLowerCase().includes(lower)) return true;
  return false;
}

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

  const title = asString(raw.title) ?? "Unknown Title";
  const employer = asString(raw.company_name) ?? "Unknown Employer";
  const location = asString(raw.location) ?? "Remote";
  const description = raw.description ? stripHtml(raw.description) : undefined;

  return {
    source: "workingnomads",
    sourceJobId: jobUrl.split("/").filter(Boolean).pop(),
    title,
    employer,
    jobUrl,
    applicationLink: jobUrl,
    location,
    isRemote: true,
    datePosted: asString(raw.pub_date),
    jobDescription: description,
    companyIndustry: asString(raw.category_name),
    disciplines: asString(raw.tags),
  };
}

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

    const maxJobs = context.settings.workingnomadsMaxJobsPerTerm
      ? Number.parseInt(context.settings.workingnomadsMaxJobsPerTerm, 10)
      : 100;
    const cap = Number.isFinite(maxJobs)
      ? Math.min(Math.max(maxJobs, 1), 500)
      : 100;

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

    context.onProgress?.({
      phase: "list",
      termsProcessed: 0,
      termsTotal: 1,
      currentUrl: API_URL,
      detail: "Working Nomads: fetching exposed_jobs API",
    });

    try {
      const response = await fetch(API_URL, {
        headers: { Accept: "application/json", "User-Agent": "JobOps/1.0" },
      });
      if (!response.ok) {
        throw new Error(
          `Working Nomads request failed with status ${response.status}`,
        );
      }

      const body = (await response.json()) as unknown;
      const rows = Array.isArray(body) ? body : [];

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

      for (const row of rows as WorkingNomadsJob[]) {
        if (out.length >= cap) break;
        if (terms.length > 0 && !terms.some((term) => matchesTerm(row, term))) {
          continue;
        }
        const mapped = mapJob(row);
        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: 1,
        termsTotal: 1,
        currentUrl: API_URL,
        jobPagesProcessed: out.length,
        detail: `Working Nomads: ${out.length} matched (${rows.length} total)`,
      });

      return { success: true, jobs: out };
    } catch (error) {
      const message = error instanceof Error ? error.message : "Unknown error";
      return { success: false, jobs: [], error: message };
    }
  },
};

export default manifest;
