import type { CaptchaConcreteImageStyle, CaptchaImageStyle } from "../types.ts";

export const CAPTCHA_CONCRETE_IMAGE_STYLES = [
  "classic",
  "collision",
  "snow",
  "corrosion",
  "spiderweb",
  "cross-shadow",
  "split",
  "split2",
  "cut",
  "darts",
  "distortion",
  "stitch",
  "striped",
  "wave",
  "grid-noise",
  "scribble",
  "pixel",
  "broken-lines",
] as const satisfies readonly CaptchaConcreteImageStyle[];

export const CAPTCHA_IMAGE_STYLES = [
  "random",
  ...CAPTCHA_CONCRETE_IMAGE_STYLES,
] as const satisfies readonly CaptchaImageStyle[];

const CONCRETE_STYLE_SET = new Set<string>(CAPTCHA_CONCRETE_IMAGE_STYLES);
const STYLE_SET = new Set<string>(CAPTCHA_IMAGE_STYLES);

function styleValues(value: unknown): string[] {
  if (Array.isArray(value)) return value.flatMap((item) => styleValues(item));
  if (typeof value !== "string") return [];
  return value
    .split(",")
    .map((item) => item.trim().toLowerCase())
    .filter(Boolean);
}

export function isCaptchaImageStyle(value: unknown): value is CaptchaImageStyle {
  return typeof value === "string" && STYLE_SET.has(value.trim().toLowerCase());
}

export function normalizeCaptchaImageStyle(
  value: unknown,
  fallback: CaptchaImageStyle = "random",
): CaptchaImageStyle {
  if (value === undefined || value === null || value === "") return fallback;
  const normalized = String(value).trim().toLowerCase();
  if (!STYLE_SET.has(normalized)) {
    throw new RangeError(`imageStyle must be one of: ${CAPTCHA_IMAGE_STYLES.join(", ")}`);
  }
  return normalized as CaptchaImageStyle;
}

export function normalizeCaptchaImageStyleList(
  value: unknown,
  name: "allowedStyles" | "excludedStyles",
): CaptchaConcreteImageStyle[] {
  const styles: CaptchaConcreteImageStyle[] = [];
  for (const entry of styleValues(value)) {
    if (entry === "random") continue;
    if (!CONCRETE_STYLE_SET.has(entry)) {
      throw new RangeError(`${name} contains an unknown image style: ${entry}`);
    }
    const style = entry as CaptchaConcreteImageStyle;
    if (!styles.includes(style)) styles.push(style);
  }
  return styles;
}

export interface ResolveCaptchaImageStyleOptions {
  imageStyle?: unknown;
  allowedStyles?: unknown;
  excludedStyles?: unknown;
  randomizeStyle?: boolean;
  randomInt(min: number, max: number): number;
}

export interface ResolvedCaptchaImageStyle {
  requested: CaptchaImageStyle;
  resolved: CaptchaConcreteImageStyle;
  pool: CaptchaConcreteImageStyle[];
}

export function resolveCaptchaImageStyle(
  options: ResolveCaptchaImageStyleOptions,
): ResolvedCaptchaImageStyle {
  const requested = normalizeCaptchaImageStyle(options.imageStyle, "random");
  const allowed = normalizeCaptchaImageStyleList(options.allowedStyles, "allowedStyles");
  const excluded = new Set(
    normalizeCaptchaImageStyleList(options.excludedStyles, "excludedStyles"),
  );

  const source = allowed.length ? allowed : [...CAPTCHA_CONCRETE_IMAGE_STYLES];
  const pool = source.filter((style) => !excluded.has(style));

  if (!pool.length) {
    throw new RangeError(
      "No CAPTCHA image styles remain after applying allowedStyles and excludedStyles",
    );
  }

  if (!options.randomizeStyle && requested !== "random") {
    if (!pool.includes(requested)) {
      throw new RangeError(`imageStyle ${requested} is not available in the configured style pool`);
    }
    return { requested, resolved: requested, pool };
  }

  return {
    requested,
    resolved: pool[options.randomInt(0, pool.length - 1)]!,
    pool,
  };
}
