import {
  normalizeCountryKey,
  SUPPORTED_COUNTRY_KEYS,
} from "./location-support.js";
import { normalizeStringArray } from "./normalize-string-array.js";
import { inferCountryKeysFromJobLocation } from "./search-cities.js";

const supportedCountryKeySet = new Set(SUPPORTED_COUNTRY_KEYS);

/** Location strings that do not pin a hiring country (check description too). */
const VAGUE_LOCATION_VALUES = new Set([
  "worldwide",
  "global",
  "anywhere",
  "remote",
  "wfh",
  "work from home",
  "unknown",
  "multiple locations",
  "multiple countries",
]);

const VAGUE_COUNTRY_KEYS = new Set(["worldwide", "global"]);

export interface JobBlockedCountrySignals {
  location?: string | null;
  jobDescription?: string | null;
  title?: string | null;
}

/**
 * Parse stored settings value for blocked countries.
 * Accepts JSON string array (normal) or legacy plain comma/newline-separated text.
 * Tokens are normalized to canonical country keys (e.g. "India" → "india").
 */
export function resolveBlockedCountriesFromStoredString(
  raw: string | undefined,
): string[] {
  if (!raw?.trim()) return [];
  try {
    const parsed: unknown = JSON.parse(raw);
    if (Array.isArray(parsed)) {
      return normalizeBlockedCountryTokens(
        parsed.filter((item): item is string => typeof item === "string"),
      );
    }
  } catch {
    // Legacy or corrupted JSON: treat as token list
  }
  return normalizeBlockedCountryTokens(
    raw
      .split(/[\n,]/g)
      .map((segment) => segment.trim())
      .filter(Boolean),
  );
}

export function normalizeBlockedCountryTokens(tokens: string[]): string[] {
  const keys = new Set<string>();
  for (const token of normalizeStringArray(tokens)) {
    const key = normalizeCountryKey(token);
    if (supportedCountryKeySet.has(key)) keys.add(key);
  }
  return [...keys];
}

export function isVagueJobLocation(
  location: string | null | undefined,
): boolean {
  if (!location?.trim()) return true;
  const normalized = location.trim().toLowerCase();
  if (VAGUE_LOCATION_VALUES.has(normalized)) return true;
  const keys = inferCountryKeysFromJobLocation(location);
  if (keys.length === 0) return true;
  return keys.every((key) => VAGUE_COUNTRY_KEYS.has(key));
}

/**
 * Infer supported country keys mentioned anywhere in free text (title, description).
 */
export function inferCountryKeysFromJobText(
  text: string | null | undefined,
): string[] {
  if (!text?.trim()) return [];
  const keys = new Set(inferCountryKeysFromJobLocation(text));
  const lower = text.toLowerCase();
  for (const countryKey of SUPPORTED_COUNTRY_KEYS) {
    if (VAGUE_COUNTRY_KEYS.has(countryKey)) continue;
    const pattern = countryKey.replace(/\s+/g, "\\s+");
    if (new RegExp(`\\b${pattern}\\b`, "i").test(lower)) {
      keys.add(countryKey);
    }
  }
  return [...keys];
}

const CANADA_LOCATION_HINT_RE =
  /\b(ontario|quebec|british columbia|alberta|manitoba|saskatchewan|nova scotia|new brunswick|newfoundland|prince edward island|northwest territories|yukon|nunavut|toronto|vancouver|montreal|ottawa|calgary|edmonton|winnipeg|mississauga|halifax|victoria|saskatoon|regina|hamilton|kitchener)\b/i;

const CANADA_PROVINCE_ABBREV_RE =
  /(?:^|[,\s(])(ON|BC|AB|QC|MB|SK|NS|NB|NL|PE|YT|NT|NU)(?:[,\s).]|$)/;

function locationSuggestsCanada(location: string | null | undefined): boolean {
  if (!location?.trim()) return false;
  return (
    CANADA_LOCATION_HINT_RE.test(location) ||
    CANADA_PROVINCE_ABBREV_RE.test(location)
  );
}

function collectJobCountryKeys(
  signals: JobBlockedCountrySignals,
  options?: { alwaysScanText?: boolean },
): string[] {
  const keys = new Set<string>();
  for (const key of inferCountryKeysFromJobLocation(signals.location)) {
    keys.add(key);
  }
  if (locationSuggestsCanada(signals.location)) {
    keys.add("canada");
  }

  const scanText =
    options?.alwaysScanText === true || isVagueJobLocation(signals.location);
  if (scanText) {
    const blob = [signals.title, signals.jobDescription]
      .filter(Boolean)
      .join("\n");
    for (const key of inferCountryKeysFromJobText(blob)) {
      keys.add(key);
    }
  }
  return [...keys];
}

function substantiveCountryKeys(keys: readonly string[]): string[] {
  return keys.filter((key) => !VAGUE_COUNTRY_KEYS.has(key));
}

/**
 * When search geography is a single country (e.g. Canada), only keep jobs that
 * clearly hire in that country. Vague "Remote"/"Worldwide" with no allowed-country
 * signal is rejected; any other country mention is rejected.
 */
export function jobMatchesAllowedCountry(
  locationOrSignals: string | null | undefined | JobBlockedCountrySignals,
  allowedCountryKey: string,
): boolean {
  const allowed = normalizeCountryKey(allowedCountryKey);
  if (!supportedCountryKeySet.has(allowed)) return true;

  const signals: JobBlockedCountrySignals =
    typeof locationOrSignals === "object" && locationOrSignals !== null
      ? locationOrSignals
      : { location: locationOrSignals };

  const substantive = substantiveCountryKeys(
    collectJobCountryKeys(signals, { alwaysScanText: true }),
  );
  if (substantive.length === 0) return false;
  if (!substantive.includes(allowed)) return false;
  return substantive.every((key) => key === allowed);
}

/**
 * True when the job mentions a blocked country in location and/or (when location
 * is vague) title/description. Unknown location with no country in text is kept.
 */
export function jobMatchesBlockedCountries(
  locationOrSignals: string | null | undefined | JobBlockedCountrySignals,
  blockedCountryKeys: readonly string[],
): boolean {
  if (blockedCountryKeys.length === 0) return false;
  const blocked = new Set(blockedCountryKeys);
  const signals: JobBlockedCountrySignals =
    typeof locationOrSignals === "object" && locationOrSignals !== null
      ? locationOrSignals
      : { location: locationOrSignals };
  const jobCountries = collectJobCountryKeys(signals);
  if (jobCountries.length === 0) return false;
  return jobCountries.some((key) => blocked.has(key));
}
