const HEX_COLOR_PATTERN = /^#?([0-9a-f]{6})$/i;

export function getRelativeLuminance(color: string) {
  const [red, green, blue] = parseHexColor(color).map((channel) => {
    const normalized = channel / 255;
    return normalized <= 0.04045
      ? normalized / 12.92
      : ((normalized + 0.055) / 1.055) ** 2.4;
  });

  return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
}

export function getContrastRatio(firstColor: string, secondColor: string) {
  const firstLuminance = getRelativeLuminance(firstColor);
  const secondLuminance = getRelativeLuminance(secondColor);
  const lighter = Math.max(firstLuminance, secondLuminance);
  const darker = Math.min(firstLuminance, secondLuminance);

  return (lighter + 0.05) / (darker + 0.05);
}

export function compositeHexColor(
  foreground: string,
  background: string,
  opacity: number
) {
  if (opacity < 0 || opacity > 1) {
    throw new RangeError("Opacity must be between 0 and 1.");
  }

  const foregroundChannels = parseHexColor(foreground);
  const backgroundChannels = parseHexColor(background);
  const compositeChannels = foregroundChannels.map((channel, index) =>
    Math.round(channel * opacity + backgroundChannels[index] * (1 - opacity))
  );

  return `#${compositeChannels
    .map((channel) => channel.toString(16).padStart(2, "0"))
    .join("")
    .toUpperCase()}`;
}

export function createCoolorsContrastUrl(foreground: string, background: string) {
  const foregroundHex = toPathHex(foreground);
  const backgroundHex = toPathHex(background);
  return `https://coolors.co/contrast-checker/${foregroundHex}-${backgroundHex}`;
}

function parseHexColor(color: string) {
  const match = color.match(HEX_COLOR_PATTERN);
  if (!match) {
    throw new TypeError(`Expected a six-digit HEX color, received "${color}".`);
  }

  return match[1].match(/.{2}/g)!.map((channel) => Number.parseInt(channel, 16));
}

function toPathHex(color: string) {
  parseHexColor(color);
  return color.replace("#", "").toLowerCase();
}
