/**
 * Typed contract for a brand kit extracted from a website (+ optional shared
 * source files). The extractor degrades gracefully: every field is best-effort,
 * absence is represented explicitly (empty arrays, undefined), never faked.
 *
 * A consuming product (gtm, creative) maps this onto its own durable brand
 * store — see the gtm `WorkspaceBrand`. This module owns only extraction; it
 * does NOT persist, render, or judge confidence.
 */
/** A logo / brand-mark candidate discovered on a page. */
interface BrandLogoCandidate {
    /** Absolute URL to the asset. */
    url: string;
    /** Where it came from — drives ranking and lets callers cite provenance. */
    source: 'favicon' | 'apple-touch-icon' | 'og:image' | 'img-logo' | 'svg-inline' | 'manifest';
    /** Higher = more likely the canonical logo. 0..1. */
    confidence: number;
    /** Declared width in px when known (from <link sizes> or <img width>). */
    width?: number;
    /** Declared height in px when known. */
    height?: number;
    /** MIME type when inferable from the extension. */
    mimeType?: string;
    /** alt / title text when present — a "logo" alt is a strong signal. */
    alt?: string;
}
/** A color extracted from the page, with where it was seen. */
interface BrandColor {
    /** Normalized 6- or 8-digit `#rrggbb` / `#rrggbbaa` hex (lowercase). */
    hex: string;
    /** How many distinct declarations referenced this color (popularity proxy). */
    occurrences: number;
    /** True when sourced from a `:root` CSS custom property (highest fidelity). */
    fromToken: boolean;
    /** The custom-property name when fromToken (e.g. `--accent`). */
    tokenName?: string;
}
/** A font-family discovered in CSS, with role inference. */
interface BrandFont {
    /** Primary family name, unquoted (e.g. `Inter`, `Playfair Display`). */
    family: string;
    /** The full declared stack as written, in order. */
    stack: string[];
    /** Inferred role from the selector the declaration was attached to. */
    role: 'display' | 'body' | 'unknown';
    /** Declaration count — popularity proxy for picking the dominant family. */
    occurrences: number;
}
/** A prominent product / hero image candidate (not a logo). */
interface BrandImage {
    url: string;
    source: 'og:image' | 'twitter:image' | 'img-hero' | 'img-content';
    /** alt text when present. */
    alt?: string;
    width?: number;
    height?: number;
}
/**
 * The full extracted kit. The shape every consumer reads. Each list is ranked
 * best-first; empty lists mean "found nothing", never a fabricated default.
 */
interface BrandKit {
    /** The site the kit was extracted from (normalized, absolute). */
    sourceUrl: string;
    /** Best-guess brand / product name from <title>, og:site_name, manifest. */
    name?: string;
    /** Tagline / description from meta description or og:description. */
    description?: string;
    /** Logo candidates, ranked best-first. */
    logos: BrandLogoCandidate[];
    /** Colors, ranked by token-first then popularity. */
    palette: BrandColor[];
    /** Fonts, ranked display-then-body then popularity. */
    fonts: BrandFont[];
    /** Prominent images (hero / og), ranked best-first. */
    images: BrandImage[];
    /** Absolute URLs / labels the kit was derived from — for provenance. */
    extractedFrom: string[];
}
/**
 * Typed outcome. Callers MUST inspect `succeeded` before reading `kit`.
 * No silent empties: a fetch failure is `succeeded:false` with a real error,
 * NOT an empty BrandKit that downstream code mistakes for "this brand has
 * nothing".
 */
type BrandExtractionResult = {
    succeeded: true;
    kit: BrandKit;
    warnings: string[];
} | {
    succeeded: false;
    error: string;
    stage: 'fetch' | 'parse' | 'input';
};
/** Fetch boundary, injectable so callers (Workers, Node, tests) supply their
 *  own fetch and so tests can run fully offline against fixture HTML. */
type FetchLike = (url: string, init?: {
    signal?: AbortSignal;
    headers?: Record<string, string>;
}) => Promise<{
    ok: boolean;
    status: number;
    text(): Promise<string>;
    headers?: {
        get(name: string): string | null;
    };
}>;
interface ExtractBrandKitOptions {
    /** Raw HTML for the page. When supplied, no network fetch happens — used by
     *  tests and by callers that already hold the HTML. */
    html?: string;
    /** Fetch implementation. Required when `html` is omitted. */
    fetchImpl?: FetchLike;
    /** Per-request timeout in ms (default 15000). Applied to the page fetch. */
    timeoutMs?: number;
    /** Cap on each ranked list (default 12). Keeps payloads bounded. */
    maxPerList?: number;
}

/**
 * Brand-kit extraction engine.
 *
 * Given a website URL (or raw HTML), parse the page and pull a typed BrandKit:
 * logo candidates, color palette, fonts, and prominent images. Parsing is
 * regex-based on purpose — this runs on Cloudflare Workers and other edge
 * runtimes where DOM libraries (jsdom/cheerio) are unavailable, and the page
 * structures we read (link tags, meta tags, CSS custom properties, font-family
 * declarations) are flat enough that a DOM buys nothing.
 *
 * Degrades gracefully: a missing favicon, no CSS tokens, or an image-only page
 * each narrow the kit rather than failing it. The ONLY hard failures are a bad
 * input (no html and no fetch) and a fetch error — both returned as a typed
 * `{ succeeded: false }` outcome so callers never mistake an empty kit for a
 * real one.
 */

/** Normalize a user-supplied site URL: add https:// when scheme-less, validate. */
declare function normalizeSiteUrl(raw: string): string | null;
/** Normalize any CSS color literal to #rrggbb / #rrggbbaa hex, or null. */
declare function normalizeColor(raw: string): string | null;
/** Parse already-fetched HTML into a BrandKit. Pure — no network. Exposed so
 *  callers that hold the HTML (or want to combine multiple pages) can reuse the
 *  parsing without re-fetching. */
declare function parseBrandKit(html: string, sourceUrl: string, maxPerList?: number): BrandKit;
/**
 * Fetch a website (or use supplied HTML) and extract its BrandKit.
 *
 * Returns a typed outcome — callers MUST check `succeeded`. A fetch failure or
 * empty input is a real, surfaced error, never an empty kit masquerading as a
 * result. Parsing itself never throws: a malformed page yields a sparse kit
 * with warnings, which is information, not failure.
 */
declare function extractBrandKit(url: string, options?: ExtractBrandKitOptions): Promise<BrandExtractionResult>;

/**
 * Pure mapping helpers from an extracted BrandKit onto consumable shapes.
 *
 * Extraction yields ranked candidate lists; products need decided roles
 * (background vs accent, display vs body). These helpers make the obvious,
 * defensible default choice and expose the reasoning so a confirmation UI can
 * show "we picked X — change it?". They NEVER invent a value: when a role can't
 * be filled from the kit, it is omitted, and the caller decides the fallback.
 */

/** Relative luminance (0..1) of an #rrggbb(aa) hex — for light/dark sorting. */
declare function luminance(hex: string): number;
interface DecidedPalette {
    /** Lightest neutral — page background. */
    background?: string;
    /** A raised neutral one step from background. */
    surface?: string;
    /** Darkest readable color — primary text. */
    textPrimary?: string;
    /** Mid-tone neutral — secondary text. */
    textSecondary?: string;
    /** Most saturated / popular brand color — accent. */
    accent?: string;
    /** Readable text color to sit on the accent (black or white by contrast). */
    accentText?: string;
}
/**
 * Assign palette roles from the ranked colors. Heuristic, not authoritative —
 * a confirmation step should let the user correct it. Roles only appear when
 * the kit actually contained a color that fits.
 */
declare function decidePalette(palette: BrandColor[]): DecidedPalette;
interface DecidedFonts {
    display?: BrandFont;
    body?: BrandFont;
}
/** Pick a display and body font from the ranked list. When only one usable
 *  font exists it fills both roles — a single-typeface brand is valid. */
declare function decideFonts(fonts: BrandFont[]): DecidedFonts;
/** Everything a confirmation step needs from a kit, with roles decided. */
interface DecidedBrandKit {
    name?: string;
    description?: string;
    sourceUrl: string;
    palette: DecidedPalette;
    fonts: DecidedFonts;
    /** Best logo URL, when any candidate was found. */
    primaryLogoUrl?: string;
    /** All logo URLs, ranked. */
    logoUrls: string[];
    /** Prominent image URLs, ranked. */
    imageUrls: string[];
    extractedFrom: string[];
}
/** Collapse a raw BrandKit into decided roles — the shape a product persists. */
declare function decideBrandKit(kit: BrandKit): DecidedBrandKit;

export { type BrandColor, type BrandExtractionResult, type BrandFont, type BrandImage, type BrandKit, type BrandLogoCandidate, type DecidedBrandKit, type DecidedFonts, type DecidedPalette, type ExtractBrandKitOptions, type FetchLike, decideBrandKit, decideFonts, decidePalette, extractBrandKit, luminance, normalizeColor, normalizeSiteUrl, parseBrandKit };
