/**
 * Ashby public job board API.
 *
 * https://developers.ashbyhq.com/reference/posting-api-job-board
 *   GET https://api.ashbyhq.com/posting-api/job-board/{company}
 *
 * No auth. Each entry in `ashbyCompanies` is fetched independently.
 */

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

interface AshbyAddress {
  postalAddress?: {
    addressLocality?: string;
    addressRegion?: string;
    addressCountry?: string;
  };
}
interface AshbyJob {
  id?: string;
  title?: string;
  jobUrl?: string;
  applyUrl?: string;
  publishedAt?: string;
  employmentType?: string;
  isRemote?: boolean;
  team?: string;
  department?: string;
  location?: string;
  locationName?: string;
  secondaryLocations?: Array<{ location?: string; locationName?: string }>;
  address?: AshbyAddress;
  descriptionPlain?: string;
  descriptionHtml?: string;
}
interface AshbyResponse {
  jobs?: AshbyJob[];
  apiVersion?: string;
}

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

function readCompanies(raw: string | undefined): string[] {
  if (!raw) return [];
  try {
    const parsed = JSON.parse(raw);
    if (Array.isArray(parsed)) {
      return parsed
        .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
        .filter(Boolean);
    }
  } catch {
    // fall through
  }
  return raw
    .split(/[\n,;|]+/)
    .map((entry) => entry.trim())
    .filter(Boolean);
}

function locationFor(job: AshbyJob): string | undefined {
  const primary =
    asString(job.locationName) ?? asString(job.location) ?? undefined;
  const secondary =
    job.secondaryLocations
      ?.map((entry) => asString(entry.locationName) ?? asString(entry.location))
      .filter((value): value is string => Boolean(value)) ?? [];
  const all = [primary, ...secondary].filter((value): value is string =>
    Boolean(value),
  );
  return all.length > 0 ? all.join("; ") : undefined;
}

function mapJob(job: AshbyJob, company: string): CreateJobInput | null {
  const jobUrl = asString(job.jobUrl) ?? asString(job.applyUrl);
  if (!jobUrl) return null;
  const employer = company
    .split(/[-_]/)
    .filter(Boolean)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join(" ");
  return {
    source: "ashby",
    sourceJobId: asString(job.id),
    title: asString(job.title) ?? "Unknown Title",
    employer: employer || company,
    jobUrl,
    applicationLink: asString(job.applyUrl) ?? jobUrl,
    location: locationFor(job),
    isRemote: typeof job.isRemote === "boolean" ? job.isRemote : undefined,
    jobType: asString(job.employmentType),
    jobFunction: asString(job.team),
    companyIndustry: asString(job.department),
    datePosted: asString(job.publishedAt),
    jobDescription:
      asString(job.descriptionPlain) ?? asString(job.descriptionHtml),
  };
}

async function fetchCompany(company: string): Promise<AshbyJob[]> {
  const url = `https://api.ashbyhq.com/posting-api/job-board/${encodeURIComponent(company)}`;
  const response = await fetch(url, {
    headers: { Accept: "application/json" },
  });
  if (response.status === 404) return [];
  if (!response.ok) {
    throw new Error(
      `Ashby request for "${company}" failed with status ${response.status}`,
    );
  }
  const body = (await response.json()) as AshbyResponse;
  return Array.isArray(body.jobs) ? body.jobs : [];
}

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

    const companies = readCompanies(context.settings.ashbyCompanies);
    if (companies.length === 0) {
      return {
        success: true,
        jobs: [],
        error:
          "No Ashby companies configured. Set ASHBY_COMPANIES or the ashbyCompanies setting (comma- or newline-separated slugs).",
      };
    }

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

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

        let added = 0;
        const jobs = await fetchCompany(company);
        for (const job of jobs) {
          const mapped = mapJob(job, company);
          if (!mapped) continue;
          const key = mapped.sourceJobId || mapped.jobUrl;
          if (seen.has(key)) continue;
          seen.add(key);
          out.push(mapped);
          added += 1;
        }

        context.onProgress?.({
          phase: "list",
          termsProcessed: i + 1,
          termsTotal: companies.length,
          currentUrl: company,
          jobPagesProcessed: out.length,
          detail: `Ashby: ${company} → ${added} jobs (${out.length} total)`,
        });
      }
    } 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;
