const ASSET_COLOR_PREFIX = /^(?:data:|blob:|https?:|file:|url\(|\/|\.\.?(?:\/|\\))/i;
const IGNORED_COLORS = new Set(['', 'none', 'transparent', 'currentcolor', 'inherit', 'initial', 'unset']);

function expandShortHex(value: string): string {
  const hex = value.slice(1);
  if (hex.length !== 3 && hex.length !== 4) return value;
  return `#${[...hex].map((part) => `${part}${part}`).join('')}`;
}

/** Normalize declared print colors without treating image URLs as colors. */
export function normalizePricingColor(value: unknown): string | null {
  if (typeof value !== 'string') return null;
  let normalized = value.trim().toLowerCase();
  if (IGNORED_COLORS.has(normalized) || ASSET_COLOR_PREFIX.test(normalized)) return null;
  if (/^#[0-9a-f]{3,4}$/i.test(normalized)) normalized = expandShortHex(normalized);
  if (/^#[0-9a-f]{6,8}$/i.test(normalized)) return normalized;
  if (/^(?:rgb|rgba|hsl|hsla)\(/i.test(normalized)) return normalized.replace(/\s+/g, '');
  if (/^[a-z][a-z0-9-]*$/i.test(normalized)) return normalized;
  return null;
}

function collectStyleColors(style: string, out: Set<string>): void {
  for (const declaration of String(style ?? '').split(';')) {
    const separator = declaration.indexOf(':');
    if (separator < 0) continue;
    const property = declaration.slice(0, separator).trim().toLowerCase();
    if (property !== 'fill' && property !== 'stroke' && property !== 'color') continue;
    const color = normalizePricingColor(declaration.slice(separator + 1));
    if (color) out.add(color);
  }
}

/** Extract explicit fill/stroke colors from SVG markup. Raster pixels are intentionally not inspected. */
export function collectPricingColorsFromSvg(svg: unknown): string[] {
  if (typeof svg !== 'string' || !svg.trim()) return [];
  const out = new Set<string>();

  if (typeof DOMParser !== 'undefined') {
    try {
      const document = new DOMParser().parseFromString(svg, 'image/svg+xml');
      document.querySelectorAll('*').forEach((element) => {
        for (const attribute of ['fill', 'stroke', 'color']) {
          const color = normalizePricingColor(element.getAttribute(attribute));
          if (color) out.add(color);
        }
        collectStyleColors(element.getAttribute('style') ?? '', out);
      });
      if (out.size) return [...out];
    } catch {}
  }

  const attributePattern = /\b(?:fill|stroke|color)\s*=\s*(["'])(.*?)\1/gi;
  for (const match of svg.matchAll(attributePattern)) {
    const color = normalizePricingColor(match[2]);
    if (color) out.add(color);
  }
  const stylePattern = /\bstyle\s*=\s*(["'])(.*?)\1/gi;
  for (const match of svg.matchAll(stylePattern)) collectStyleColors(match[2], out);
  return [...out];
}

function collectColorInput(value: unknown, out: Set<string>, depth: number): void {
  if (depth > 4 || value == null) return;
  if (typeof value === 'string') {
    const color = normalizePricingColor(value);
    if (color) out.add(color);
    return;
  }
  if (Array.isArray(value)) {
    value.forEach((entry) => collectColorInput(entry, out, depth + 1));
    return;
  }
  if (typeof value !== 'object') return;

  const record = value as Record<string, unknown>;
  collectColorInput(record.color, out, depth + 1);
  collectColorInput(record.fill, out, depth + 1);
  collectColorInput(record.stroke, out, depth + 1);
  collectColorInput(record.colors, out, depth + 1);
  collectColorInput(record.fills, out, depth + 1);
  collectColorInput(record.strokes, out, depth + 1);
  if (Array.isArray(record.colorStops)) {
    record.colorStops.forEach((stop) => collectColorInput((stop as any)?.color, out, depth + 1));
  }
}

/** Collect colors explicitly declared by an object, metadata entry or adapter payload. */
export function collectDeclaredPricingColors(...values: unknown[]): string[] {
  const out = new Set<string>();
  values.forEach((value) => collectColorInput(value, out, 0));
  return [...out];
}
