import { EXTRACTOR_SOURCE_IDS } from "@shared/extractors";
import {
  normalizeCountryKey,
  SUPPORTED_COUNTRY_KEYS,
} from "@shared/location-support";
import type { SponsorshipSignalId } from "@shared/sponsorship-signals";
import type { JobSource } from "@shared/types";
import { useCallback, useEffect, useMemo } from "react";
import { useSearchParams } from "react-router-dom";
import type {
  JobSort,
  SalaryFilter,
  SalaryFilterMode,
  SponsorFilter,
  WorkplaceFilter,
} from "./constants";
import { DEFAULT_SORT, sponsorshipSignalFilterOptions } from "./constants";

const allowedSponsorFilters: SponsorFilter[] = [
  "all",
  "confirmed",
  "potential",
  "not_found",
  "unknown",
];
const allowedSalaryModes: SalaryFilterMode[] = [
  "at_least",
  "at_most",
  "between",
];
const allowedSortKeys: JobSort["key"][] = [
  "discoveredAt",
  "score",
  "salary",
  "title",
  "employer",
];
const allowedSortDirections: JobSort["direction"][] = ["asc", "desc"];

const allowedWorkplaceFilters: WorkplaceFilter[] = [
  "all",
  "remote",
  "not_remote",
  "unknown",
];

const allowedSponsorshipSignals = new Set<string>(
  sponsorshipSignalFilterOptions,
);

const allowedJobSources = new Set<string>(EXTRACTOR_SOURCE_IDS);
const allowedCountryKeys = new Set(SUPPORTED_COUNTRY_KEYS);

const MAX_EMPLOYER_FILTER_TOKENS = 25;
const MAX_EMPLOYER_TOKEN_LENGTH = 120;

function normalizeEmployerFilterToken(value: string): string | null {
  const trimmed = value.trim();
  if (!trimmed) return null;
  if (trimmed.length > MAX_EMPLOYER_TOKEN_LENGTH) return null;
  return trimmed;
}

function parseEmployerFilterList(
  searchParams: URLSearchParams,
  key: string,
): string[] {
  const raw = searchParams.getAll(key);
  const out: string[] = [];
  const seen = new Set<string>();
  for (const item of raw) {
    const normalized = normalizeEmployerFilterToken(item);
    if (!normalized) continue;
    const dedupeKey = normalized.toLowerCase();
    if (seen.has(dedupeKey)) continue;
    if (out.length >= MAX_EMPLOYER_FILTER_TOKENS) break;
    seen.add(dedupeKey);
    out.push(normalized);
  }
  return out;
}

function parseYmdFilter(raw: string | null): string | null {
  if (!raw?.trim()) return null;
  const candidate = raw.trim();
  if (!/^\d{4}-\d{2}-\d{2}$/.test(candidate)) return null;
  if (!Number.isFinite(Date.parse(`${candidate}T12:00:00`))) return null;
  return candidate;
}

export const useOrchestratorFilters = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  useEffect(() => {
    if (!searchParams.has("q")) return;
    setSearchParams(
      (prev) => {
        prev.delete("q");
        return prev;
      },
      { replace: true },
    );
  }, [searchParams, setSearchParams]);

  const { sourcesFilter, sourcesExcludeFilter } = useMemo(() => {
    const rawInc = searchParams.getAll("source");
    const include = rawInc
      .map((value) => value.trim())
      .filter(
        (value): value is JobSource =>
          Boolean(value) && allowedJobSources.has(value),
      );
    const includeSet = new Set(include);
    const rawExc = searchParams.getAll("sourceExclude");
    const exclude = rawExc
      .map((value) => value.trim())
      .filter((value): value is JobSource => {
        if (!value || !allowedJobSources.has(value)) return false;
        const source = value as JobSource;
        return !includeSet.has(source);
      });
    return {
      sourcesFilter: [...new Set(include)],
      sourcesExcludeFilter: [...new Set(exclude)],
    };
  }, [searchParams]);

  const setSourceSelection = useCallback(
    (include: JobSource[], exclude: JobSource[]) => {
      setSearchParams(
        (prev) => {
          prev.delete("source");
          prev.delete("sourceExclude");
          const inc = [
            ...new Set(
              include.filter((source) => allowedJobSources.has(source)),
            ),
          ].sort((left, right) => left.localeCompare(right));
          const incSet = new Set(inc);
          const exc = [
            ...new Set(
              exclude.filter(
                (source) =>
                  allowedJobSources.has(source) && !incSet.has(source),
              ),
            ),
          ].sort((left, right) => left.localeCompare(right));
          for (const source of inc) prev.append("source", source);
          for (const source of exc) prev.append("sourceExclude", source);
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const { countriesFilter, countriesExcludeFilter } = useMemo(() => {
    const parseList = (raw: string | null) => {
      if (!raw?.trim()) return [];
      return raw
        .split(",")
        .map((segment) => normalizeCountryKey(segment.trim()))
        .filter((key) => key && allowedCountryKeys.has(key));
    };

    const include = [...new Set(parseList(searchParams.get("countries")))];
    const includeSet = new Set(include);
    const excludeRaw = parseList(searchParams.get("countriesExclude"));
    const exclude = [
      ...new Set(excludeRaw.filter((key) => !includeSet.has(key))),
    ];
    return {
      countriesFilter: include.sort((left, right) => left.localeCompare(right)),
      countriesExcludeFilter: exclude.sort((left, right) =>
        left.localeCompare(right),
      ),
    };
  }, [searchParams]);

  const setCountrySelection = useCallback(
    (includeKeys: string[], excludeKeys: string[]) => {
      setSearchParams(
        (prev) => {
          const inc = [
            ...new Set(
              includeKeys
                .map((key) => normalizeCountryKey(key))
                .filter((key) => key && allowedCountryKeys.has(key)),
            ),
          ].sort((left, right) => left.localeCompare(right));
          const incSet = new Set(inc);
          const exc = [
            ...new Set(
              excludeKeys
                .map((key) => normalizeCountryKey(key))
                .filter(
                  (key) =>
                    key && allowedCountryKeys.has(key) && !incSet.has(key),
                ),
            ),
          ].sort((left, right) => left.localeCompare(right));
          if (inc.length === 0) prev.delete("countries");
          else prev.set("countries", inc.join(","));
          if (exc.length === 0) prev.delete("countriesExclude");
          else prev.set("countriesExclude", exc.join(","));
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const sponsorFilter = useMemo((): SponsorFilter => {
    const raw = searchParams.get("sponsor") ?? "all";
    return allowedSponsorFilters.includes(raw as SponsorFilter)
      ? (raw as SponsorFilter)
      : "all";
  }, [searchParams]);

  const sponsorshipSignalsFilter = useMemo((): SponsorshipSignalId[] => {
    const raw = searchParams.getAll("sponsorSignal");
    const out: SponsorshipSignalId[] = [];
    const seen = new Set<string>();
    for (const value of raw) {
      const trimmed = value.trim();
      if (!trimmed || !allowedSponsorshipSignals.has(trimmed)) continue;
      if (seen.has(trimmed)) continue;
      seen.add(trimmed);
      out.push(trimmed as SponsorshipSignalId);
    }
    return out;
  }, [searchParams]);

  const hideSponsorBlockers = useMemo(
    () => searchParams.get("hideSponsorBlockers") === "1",
    [searchParams],
  );

  const setHideSponsorBlockers = useCallback(
    (value: boolean) => {
      setSearchParams(
        (prev) => {
          if (value) prev.set("hideSponsorBlockers", "1");
          else prev.delete("hideSponsorBlockers");
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const setSponsorshipSignalsFilter = useCallback(
    (values: SponsorshipSignalId[]) => {
      setSearchParams(
        (prev) => {
          prev.delete("sponsorSignal");
          const unique = [
            ...new Set(
              values.filter((value) => allowedSponsorshipSignals.has(value)),
            ),
          ];
          for (const value of unique) prev.append("sponsorSignal", value);
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const setSponsorFilter = useCallback(
    (value: SponsorFilter) => {
      setSearchParams(
        (prev) => {
          if (value === "all") prev.delete("sponsor");
          else prev.set("sponsor", value);
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const workplaceFilter = useMemo((): WorkplaceFilter => {
    const raw = searchParams.get("workplace") ?? "all";
    return allowedWorkplaceFilters.includes(raw as WorkplaceFilter)
      ? (raw as WorkplaceFilter)
      : "all";
  }, [searchParams]);

  const setWorkplaceFilter = useCallback(
    (value: WorkplaceFilter) => {
      setSearchParams(
        (prev) => {
          if (value === "all") prev.delete("workplace");
          else prev.set("workplace", value);
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const salaryFilter = useMemo((): SalaryFilter => {
    const modeRaw = searchParams.get("salaryMode") ?? "at_least";
    const mode = allowedSalaryModes.includes(modeRaw as SalaryFilterMode)
      ? (modeRaw as SalaryFilterMode)
      : "at_least";

    const minRaw =
      searchParams.get("salaryMin") ?? searchParams.get("minSalary");
    const minParsed = minRaw == null ? Number.NaN : Number.parseInt(minRaw, 10);
    const min = Number.isFinite(minParsed) && minParsed > 0 ? minParsed : null;

    const maxRaw = searchParams.get("salaryMax");
    const maxParsed = maxRaw == null ? Number.NaN : Number.parseInt(maxRaw, 10);
    const max = Number.isFinite(maxParsed) && maxParsed > 0 ? maxParsed : null;

    return { mode, min, max };
  }, [searchParams]);

  const setSalaryFilter = useCallback(
    (value: SalaryFilter) => {
      setSearchParams(
        (prev) => {
          if (value.mode === "at_least") prev.delete("salaryMode");
          else prev.set("salaryMode", value.mode);

          if (value.min == null || value.min <= 0) prev.delete("salaryMin");
          else prev.set("salaryMin", String(value.min));

          if (value.max == null || value.max <= 0) prev.delete("salaryMax");
          else prev.set("salaryMax", String(value.max));

          prev.delete("minSalary");
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const { foundAfterYmd, foundBeforeYmd } = useMemo(() => {
    return {
      foundAfterYmd: parseYmdFilter(searchParams.get("foundAfter")),
      foundBeforeYmd: parseYmdFilter(searchParams.get("foundBefore")),
    };
  }, [searchParams]);

  const setFoundDateRange = useCallback(
    (foundAfter: string | null, foundBefore: string | null) => {
      setSearchParams(
        (prev) => {
          const nextAfter = parseYmdFilter(foundAfter);
          const nextBefore = parseYmdFilter(foundBefore);
          if (nextAfter) prev.set("foundAfter", nextAfter);
          else prev.delete("foundAfter");
          if (nextBefore) prev.set("foundBefore", nextBefore);
          else prev.delete("foundBefore");
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const { employerIncludeFilter, employerExcludeFilter } = useMemo(() => {
    const include = parseEmployerFilterList(searchParams, "employer");
    const includeSet = new Set(include.map((value) => value.toLowerCase()));
    const exclude = parseEmployerFilterList(
      searchParams,
      "employerExclude",
    ).filter((value) => !includeSet.has(value.toLowerCase()));
    return {
      employerIncludeFilter: include,
      employerExcludeFilter: exclude,
    };
  }, [searchParams]);

  const setEmployerFilterTokens = useCallback(
    (include: string[], exclude: string[]) => {
      setSearchParams(
        (prev) => {
          prev.delete("employer");
          prev.delete("employerExclude");

          const normalizedInclude: string[] = [];
          const includeSeen = new Set<string>();
          for (const item of include) {
            const normalized = normalizeEmployerFilterToken(item);
            if (!normalized) continue;
            const key = normalized.toLowerCase();
            if (includeSeen.has(key)) continue;
            if (normalizedInclude.length >= MAX_EMPLOYER_FILTER_TOKENS) break;
            includeSeen.add(key);
            normalizedInclude.push(normalized);
          }
          const includeKeySet = new Set(
            normalizedInclude.map((value) => value.toLowerCase()),
          );

          const normalizedExclude: string[] = [];
          const excludeSeenLow = new Set<string>();
          for (const item of exclude) {
            const normalized = normalizeEmployerFilterToken(item);
            if (!normalized) continue;
            const key = normalized.toLowerCase();
            if (includeKeySet.has(key)) continue;
            if (excludeSeenLow.has(key)) continue;
            if (normalizedExclude.length >= MAX_EMPLOYER_FILTER_TOKENS) break;
            excludeSeenLow.add(key);
            normalizedExclude.push(normalized);
          }

          for (const value of normalizedInclude) prev.append("employer", value);
          for (const value of normalizedExclude)
            prev.append("employerExclude", value);
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const applySettingsCompanySkipList = useMemo(
    () => searchParams.get("skipList") !== "0",
    [searchParams],
  );

  const hidePriorSkips = useMemo(
    () => searchParams.get("hidePriorSkips") !== "0",
    [searchParams],
  );

  const setApplySettingsCompanySkipList = useCallback(
    (value: boolean) => {
      setSearchParams(
        (prev) => {
          if (value) prev.delete("skipList");
          else prev.set("skipList", "0");
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const setHidePriorSkips = useCallback(
    (value: boolean) => {
      setSearchParams(
        (prev) => {
          if (value) prev.delete("hidePriorSkips");
          else prev.set("hidePriorSkips", "0");
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const sort = useMemo((): JobSort => {
    const sortValue = searchParams.get("sort");
    if (!sortValue) return DEFAULT_SORT;

    const [key, direction] = sortValue.split("-");
    if (
      !allowedSortKeys.includes(key as JobSort["key"]) ||
      !allowedSortDirections.includes(direction as JobSort["direction"])
    ) {
      return DEFAULT_SORT;
    }

    return {
      key: key as JobSort["key"],
      direction: direction as JobSort["direction"],
    };
  }, [searchParams]);

  const setSort = useCallback(
    (newSort: JobSort) => {
      setSearchParams(
        (prev) => {
          if (
            newSort.key === DEFAULT_SORT.key &&
            newSort.direction === DEFAULT_SORT.direction
          ) {
            prev.delete("sort");
          } else {
            prev.set("sort", `${newSort.key}-${newSort.direction}`);
          }
          return prev;
        },
        { replace: true },
      );
    },
    [setSearchParams],
  );

  const resetFilters = useCallback(() => {
    setSearchParams(
      (prev) => {
        prev.delete("source");
        prev.delete("sourceExclude");
        prev.delete("countries");
        prev.delete("countriesExclude");
        prev.delete("sponsor");
        prev.delete("sponsorSignal");
        prev.delete("hideSponsorBlockers");
        prev.delete("workplace");
        prev.delete("salaryMode");
        prev.delete("salaryMin");
        prev.delete("salaryMax");
        prev.delete("minSalary");
        prev.delete("sort");
        prev.delete("foundAfter");
        prev.delete("foundBefore");
        prev.delete("employer");
        prev.delete("employerExclude");
        prev.delete("skipList");
        prev.delete("hidePriorSkips");
        return prev;
      },
      { replace: true },
    );
  }, [setSearchParams]);

  return {
    searchParams,
    sourcesFilter,
    sourcesExcludeFilter,
    setSourceSelection,
    countriesFilter,
    countriesExcludeFilter,
    setCountrySelection,
    sponsorFilter,
    setSponsorFilter,
    sponsorshipSignalsFilter,
    setSponsorshipSignalsFilter,
    hideSponsorBlockers,
    setHideSponsorBlockers,
    workplaceFilter,
    setWorkplaceFilter,
    salaryFilter,
    setSalaryFilter,
    foundAfterYmd,
    foundBeforeYmd,
    setFoundDateRange,
    employerIncludeFilter,
    employerExcludeFilter,
    setEmployerFilterTokens,
    applySettingsCompanySkipList,
    setApplySettingsCompanySkipList,
    hidePriorSkips,
    setHidePriorSkips,
    sort,
    setSort,
    resetFilters,
  };
};
