/**
 * Huntflow public career sites — SSR HTML listing + optional vacancy detail pages.
 *
 * https://{tenant}.huntflow.io/
 */

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

interface HuntflowListing {
  slug: string;
  title: string;
  location?: string;
}

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

function readTenants(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 decodeHtmlEntities(value: string): string {
  return value
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'");
}

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

function parseListings(html: string): HuntflowListing[] {
  const listings: HuntflowListing[] = [];
  const pattern =
    /<article class="_item_[^"]*">[\s\S]*?<a href="(\/vacancy\/[^"]+)"[^>]*>([^<]+)<\/a>[\s\S]*?<div class="_info_[^"]*">([^<]*)<\/div>/g;

  for (const match of html.matchAll(pattern)) {
    const slug = match[1]?.replace(/^\/vacancy\//, "").replace(/\/$/, "");
    const title = match[2]?.trim();
    if (!slug || !title) continue;
    listings.push({
      slug,
      title: decodeHtmlEntities(title),
      location: match[3]?.trim() || undefined,
    });
  }

  return listings;
}

function parseVacancyDetail(html: string): {
  description?: string;
  location?: string;
} {
  const positionMatch = html.match(/<h1 class="_position_[^"]*">([^<]+)<\/h1>/);
  const infoMatch = html.match(
    /<div class="_infoWrapper_[^"]*">[\s\S]*?<div>([^<]+)<\/div>/,
  );

  const sections: string[] = [];
  for (const match of html.matchAll(
    /<div class="_content_1phzm_2">[\s\S]*?<!--\[-->([\s\S]*?)<!--\]-->/g,
  )) {
    const text = stripHtml(match[1] ?? "");
    if (text) sections.push(text);
  }

  return {
    location: infoMatch?.[1]?.trim() || undefined,
    description:
      sections.length > 0
        ? sections.join("\n\n")
        : positionMatch?.[1]?.trim() || undefined,
  };
}

function matchesTerm(values: string[], term: string): boolean {
  const lower = term.toLowerCase();
  return values.some((value) => value.toLowerCase().includes(lower));
}

async function fetchText(url: string): Promise<string> {
  const response = await fetch(url, {
    headers: { Accept: "text/html", "User-Agent": "JobOps/1.0" },
  });
  if (!response.ok) {
    throw new Error(`Huntflow request failed (${response.status}) for ${url}`);
  }
  return response.text();
}

function tenantOrigin(tenant: string): string {
  const host = tenant.includes(".") ? tenant : `${tenant}.huntflow.io`;
  return host.startsWith("http") ? host : `https://${host}`;
}

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

    const tenants = readTenants(context.settings.huntflowTenants);
    if (tenants.length === 0) {
      return {
        success: true,
        jobs: [],
        error:
          "No Huntflow tenants configured. Set HUNTFLOW_TENANTS or huntflowTenants (comma- or newline-separated subdomains, e.g. apicworld).",
      };
    }

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

    try {
      for (let i = 0; i < tenants.length; i += 1) {
        if (context.shouldCancel?.()) break;
        const tenant = tenants[i];
        const origin = tenantOrigin(tenant);

        context.onProgress?.({
          phase: "list",
          termsProcessed: i,
          termsTotal: tenants.length,
          currentUrl: origin,
          detail: `Huntflow: ${tenant} (${i + 1}/${tenants.length})`,
        });

        const indexHtml = await fetchText(`${origin}/`);
        const employer = tenant
          .split(".")[0]
          .split(/[-_]/)
          .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
          .join(" ");

        for (const listing of parseListings(indexHtml)) {
          if (context.shouldCancel?.()) break;

          const haystack = [listing.title, listing.location ?? ""];
          if (
            terms.length > 0 &&
            !terms.some((term) => matchesTerm(haystack, term))
          ) {
            continue;
          }

          const jobUrl = `${origin}/vacancy/${listing.slug}`;
          let location = listing.location ?? "Unknown";
          let jobDescription: string | undefined;
          let isRemote = location.toLowerCase().includes("remote");

          if (enrichDetails) {
            try {
              const detailHtml = await fetchText(jobUrl);
              const detail = parseVacancyDetail(detailHtml);
              if (detail.location) location = detail.location;
              if (detail.description) jobDescription = detail.description;
              isRemote = location.toLowerCase().includes("remote");
            } catch {
              // keep listing row when detail fetch fails
            }
          }

          const key = jobUrl;
          if (seen.has(key)) continue;
          seen.add(key);

          out.push({
            source: "huntflow",
            sourceJobId: listing.slug,
            title: listing.title,
            employer,
            jobUrl,
            applicationLink: jobUrl,
            location,
            isRemote,
            jobDescription,
          });
        }
      }

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

export default manifest;
