import catalogJson from './data/google-fonts.json';

export type GoogleFontEntry = {
	family: string;
	category: string;
	variants: string[];
};

export type GoogleFontRequest = {
	family: string;
	weights: number[];
};

/** Empty string selects the theme/system font stack. */
export const DEFAULT_FONT = '';

export type BrowserFontEntry = {
	family: string;
	label: string;
	stack: string;
};

/** Web-safe / browser-installed fonts (never loaded via Google Fonts CSS). */
export const BROWSER_FONTS: BrowserFontEntry[] = [
	{ family: 'Arial', label: 'Arial', stack: 'Arial, Helvetica, sans-serif' },
	{
		family: 'Helvetica',
		label: 'Helvetica',
		stack: 'Helvetica, Arial, sans-serif',
	},
	{ family: 'Georgia', label: 'Georgia', stack: 'Georgia, serif' },
	{
		family: 'Times New Roman',
		label: 'Times New Roman',
		stack: '"Times New Roman", Times, serif',
	},
	{
		family: 'Courier New',
		label: 'Courier New',
		stack: '"Courier New", Courier, monospace',
	},
	{
		family: 'Verdana',
		label: 'Verdana',
		stack: 'Verdana, Geneva, sans-serif',
	},
	{
		family: 'Trebuchet MS',
		label: 'Trebuchet MS',
		stack: '"Trebuchet MS", sans-serif',
	},
	{
		family: 'Palatino',
		label: 'Palatino',
		stack: 'Palatino, "Palatino Linotype", serif',
	},
	{ family: 'sans-serif', label: 'Generic sans-serif', stack: 'sans-serif' },
	{ family: 'serif', label: 'Generic serif', stack: 'serif' },
	{ family: 'monospace', label: 'Generic monospace', stack: 'monospace' },
];

export const googleFontsCatalog: GoogleFontEntry[] =
	catalogJson as GoogleFontEntry[];

const FILTER_LIMIT = 50;
const MIN_WEIGHT = 100;
const MAX_WEIGHT = 900;

/**
 * Whether the family is a browser/web-safe font (not loaded from Google).
 * @param family
 */
export function isBrowserFontFamily( family: string ): boolean {
	return BROWSER_FONTS.some( ( entry ) => entry.family === family );
}

/**
 * Whether the family exists in the Google Fonts catalog.
 * @param catalog
 * @param family
 */
export function isGoogleFontFamily(
	catalog: GoogleFontEntry[],
	family: string
): boolean {
	return catalog.some( ( entry ) => entry.family === family );
}

export type CustomFontEntry = {
	id: string;
	family: string;
	url?: string;
	format?: string;
	attachment_id?: number;
};

/**
 * CSS `font-family` value for a stored family key.
 * Browser fonts use their stack; Google families are quoted; custom refs resolve via library.
 * @param family
 * @param customFonts Optional site-wide uploaded font library.
 */
export function cssFontFamilyValue(
	family: string,
	customFonts?: CustomFontEntry[] | null
): string {
	const trimmed = ( family || '' ).trim();
	if ( ! trimmed ) {
		return '';
	}
	const browser = BROWSER_FONTS.find( ( entry ) => entry.family === trimmed );
	if ( browser ) {
		return browser.stack;
	}
	if ( trimmed.startsWith( 'custom:' ) ) {
		const match = ( customFonts || [] ).find(
			( entry ) => entry.id === trimmed
		);
		if ( ! match?.family ) {
			return '';
		}
		return "'" + match.family.replace( /['"]/g, '' ) + "', sans-serif";
	}
	return '"' + trimmed.replace( /"/g, '' ) + '", sans-serif';
}

/**
 * Keep CSS font-weight integers in the 100–900 range (preserves 450/550/750).
 * @param value
 * @param fallback
 */
export function sanitizeWeight( value: unknown, fallback = 400 ): number {
	const safeFallback = clampWeight(
		Number.isFinite( Number( fallback ) ) ? Number( fallback ) : 400
	);
	if ( value === null || value === undefined || value === '' ) {
		return safeFallback;
	}
	const weight = Number( value );
	if ( ! Number.isFinite( weight ) ) {
		return safeFallback;
	}
	return clampWeight( Math.round( Math.abs( weight ) ) );
}

function clampWeight( weight: number ): number {
	if ( weight < MIN_WEIGHT ) {
		return MIN_WEIGHT;
	}
	if ( weight > MAX_WEIGHT ) {
		return MAX_WEIGHT;
	}
	return weight;
}

/**
 * Snap to hundred-step weights for Google Fonts CSS2 static faces.
 * Prefers the lighter neighbour when exactly halfway (e.g. 550 → 500).
 * @param weight
 */
export function snapGoogleFontWeight( weight: number ): number {
	const safe = clampWeight( weight );
	const lower = Math.floor( safe / 100 ) * 100;
	if ( lower < MIN_WEIGHT ) {
		return MIN_WEIGHT;
	}
	const upper = Math.min( MAX_WEIGHT, lower + 100 );
	if ( Math.abs( safe - lower ) <= Math.abs( safe - upper ) ) {
		return Math.max( MIN_WEIGHT, lower );
	}
	return upper;
}

/**
 * Case-insensitive family filter for the admin picker (capped for UI).
 * @param catalog
 * @param query
 */
export function filterFontFamilies(
	catalog: GoogleFontEntry[],
	query: string
): GoogleFontEntry[] {
	const needle = ( query || '' ).trim().toLowerCase();
	const source = ! needle
		? catalog
		: catalog.filter( ( entry ) =>
				entry.family.toLowerCase().includes( needle )
		  );
	return source.slice( 0, FILTER_LIMIT );
}

/**
 * Numeric weights available for a family (italics ignored).
 * @param catalog
 * @param family
 */
export function weightsForFamily(
	catalog: GoogleFontEntry[],
	family: string
): number[] {
	const entry = catalog.find( ( item ) => item.family === family );
	if ( ! entry ) {
		return [];
	}
	const weights = new Set< number >();
	for ( const variant of entry.variants ) {
		const match = /^(100|200|300|400|500|600|700|800|900)(italic)?$/.exec(
			variant
		);
		if ( match ) {
			weights.add( Number( match[ 1 ] ) );
		}
	}
	return Array.from( weights ).sort( ( a, b ) => a - b );
}

/**
 * Build a Google Fonts CSS2 stylesheet URL for the given family/weight requests.
 * Duplicate family entries are merged and weights de-duplicated.
 * Browser/web-safe and `custom:` families are never included.
 * When `catalog` is provided, only families present in that catalog are included.
 * @param requests
 * @param catalog  Optional Google Fonts catalog used to filter non-Google families.
 */
export function buildGoogleFontsCssUrl(
	requests: GoogleFontRequest[],
	catalog?: GoogleFontEntry[]
): string {
	const byFamily = new Map< string, Set< number > >();
	for ( const request of requests ) {
		const family = ( request.family || '' ).trim();
		if ( ! family ) {
			continue;
		}
		if ( isBrowserFontFamily( family ) || family.startsWith( 'custom:' ) ) {
			continue;
		}
		if ( catalog && ! isGoogleFontFamily( catalog, family ) ) {
			continue;
		}
		if ( ! byFamily.has( family ) ) {
			byFamily.set( family, new Set() );
		}
		const bucket = byFamily.get( family )!;
		for ( const raw of request.weights || [] ) {
			const numeric = Number( raw );
			if ( ! Number.isFinite( numeric ) ) {
				continue;
			}
			bucket.add(
				snapGoogleFontWeight(
					clampWeight( Math.round( Math.abs( numeric ) ) )
				)
			);
		}
	}

	const families: string[] = [];
	for ( const [ family, weightSet ] of byFamily ) {
		const weights = Array.from( weightSet ).sort( ( a, b ) => a - b );
		if ( ! weights.length ) {
			continue;
		}
		const encodedFamily = encodeURIComponent( family ).replace(
			/%20/g,
			'+'
		);
		families.push(
			`family=${ encodedFamily }:wght@${ weights.join( ';' ) }`
		);
	}
	if ( ! families.length ) {
		return '';
	}
	return `https://fonts.googleapis.com/css2?${ families.join(
		'&'
	) }&display=swap`;
}

type StyleFonts = {
	label_font?: string;
	label_weight?: number | string;
	headline_font?: string;
	headline_weight?: number | string;
	message_font?: string;
	message_weight?: number | string;
	[ key: string ]: unknown;
};

/**
 * Collect unique non-empty label/headline/message fonts and merged weights.
 * Includes browser/web-safe and custom refs (callers that enqueue Google CSS
 * should use collectGoogleFontsFromStyle instead).
 * @param style
 */
export function collectFontsFromStyle(
	style: StyleFonts | null | undefined
): GoogleFontRequest[] {
	if ( ! style || typeof style !== 'object' ) {
		return [];
	}
	const slots: Array< { familyKey: string; weightKey: string } > = [
		{ familyKey: 'label_font', weightKey: 'label_weight' },
		{ familyKey: 'headline_font', weightKey: 'headline_weight' },
		{ familyKey: 'message_font', weightKey: 'message_weight' },
	];
	const byFamily = new Map< string, Set< number > >();
	for ( const slot of slots ) {
		const family = String( style[ slot.familyKey ] ?? '' ).trim();
		if ( ! family ) {
			continue;
		}
		const weight = sanitizeWeight( style[ slot.weightKey ], 400 );
		if ( ! byFamily.has( family ) ) {
			byFamily.set( family, new Set() );
		}
		byFamily.get( family )!.add( weight );
	}
	return Array.from( byFamily.entries() ).map( ( [ family, weights ] ) => ( {
		family,
		weights: Array.from( weights ).sort( ( a, b ) => a - b ),
	} ) );
}

/**
 * Collect only Google Font families from a style map (excludes browser/web-safe and custom refs).
 * @param style
 * @param catalog
 */
export function collectGoogleFontsFromStyle(
	style: StyleFonts | null | undefined,
	catalog: GoogleFontEntry[] = googleFontsCatalog
): GoogleFontRequest[] {
	return collectFontsFromStyle( style ).filter( ( request ) =>
		isGoogleFontFamily( catalog, request.family )
	);
}
