/**
 * Theme tokens (colors / font sizes / font families) for style fields.
 */
import apiFetch from '@wordpress/api-fetch';
import { useEffect, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { DEFAULT_COLORS, DEFAULT_FONT_SIZES, DEFAULT_BLOCK_FONT_FAMILY } from '../styleFieldDefaults';
import { syncConfiguredFontStylesheets } from '../shared/themeFonts';

const THEME_UPDATED = 'joc-blockora-morph-theme-updated';
const THEME_CHANNEL = 'joc-blockora-morph-theme';

/** Bumps on each successful apply so in-flight GETs cannot clobber a newer save. */
let tokensEpoch = 0;

function readLocalized() {
	const fromAdmin =
		typeof jocbmAdmin !== 'undefined' ? jocbmAdmin : null;
	const fromEditor =
		typeof jocbmEditor !== 'undefined' ? jocbmEditor : null;
	const fromFront =
		typeof jocbmFront !== 'undefined' ? jocbmFront : null;
	const raw =
		fromAdmin?.themeConfig ||
		fromEditor?.themeConfig ||
		fromFront?.themeConfig ||
		{};
	const families =
		raw.fontFamilies ||
		fromAdmin?.fontFamilies ||
		fromEditor?.fontFamilies ||
		fromFront?.fontFamilies ||
		[];
	const themeColors =
		raw.themeColors ||
		fromAdmin?.themeColors ||
		fromEditor?.themeColors ||
		fromFront?.themeColors ||
		[];
	return {
		colors: Array.isArray( raw.colors ) ? raw.colors : [],
		themeColors: Array.isArray( themeColors ) ? themeColors : [],
		fontSizes: Array.isArray( raw.fontSizes ) ? raw.fontSizes : [],
		fontFamilies: Array.isArray( families ) ? families : [],
		fonts: Array.isArray( raw.fonts ) ? raw.fonts : [],
	};
}

function normalizeColors( list ) {
	return ( list || [] )
		.map( ( c ) => ( {
			name: c.name || c.slug || c.color,
			slug: c.slug || undefined,
			color: c.color,
		} ) )
		.filter( ( c ) => !! c.color );
}

function normalizeFontSizes( list ) {
	return ( list || [] )
		.map( ( s ) => ( {
			name: s.name || s.slug || s.size,
			slug: s.slug || undefined,
			size: s.size,
		} ) )
		.filter( ( s ) => !! s.size );
}

function normalizeFontFamilies( list ) {
	return ( list || [] )
		.map( ( f ) => ( {
			name: f.name || f.slug || f.fontFamily,
			slug: f.slug || undefined,
			fontFamily: f.fontFamily,
		} ) )
		.filter( ( f ) => !! f.fontFamily );
}

/**
 * Build picker entries from Global Settings `fonts` rows.
 *
 * @param {Array} fonts Config fonts.
 * @return {Array<{ name: string, slug?: string, fontFamily: string }>}
 */
function familiesFromConfigFonts( fonts ) {
	return ( fonts || [] )
		.map( ( f ) => {
			const family = String( f?.family || '' ).trim();
			if ( ! family ) {
				return null;
			}
			const fontFamily =
				family.includes( ',' ) || family === 'system-ui'
					? family
					: `"${ family }", sans-serif`;
			return {
				name: family,
				slug: f.slug || undefined,
				fontFamily,
			};
		} )
		.filter( Boolean );
}

/**
 * Prefer Blockora Morph-configured fonts, then theme/API list (dedupe by CSS).
 *
 * @param {Object} next Theme config payload.
 * @return {Array<{ name: string, slug?: string, fontFamily: string }>}
 */
function resolveFontFamilies( next ) {
	const fromConfig = normalizeFontFamilies(
		familiesFromConfigFonts( next?.fonts )
	);
	const fromList = normalizeFontFamilies(
		next?.fontFamilies ?? readLocalized().fontFamilies
	);
	const inter = {
		name: 'Inter',
		slug: 'inter',
		fontFamily: '"Inter", sans-serif',
	};
	const seen = new Set();
	const out = [];
	for ( const font of [ inter, ...fromConfig, ...fromList ] ) {
		const key = String( font.fontFamily || '' )
			.toLowerCase()
			.replace( /\s+/g, '' );
		if ( ! key || seen.has( key ) ) {
			continue;
		}
		seen.add( key );
		out.push( font );
	}
	return out;
}

function writeLocalizedGlobals( next, families ) {
	const targets = [];
	if ( typeof jocbmAdmin !== 'undefined' ) {
		targets.push( jocbmAdmin );
	}
	if ( typeof jocbmEditor !== 'undefined' ) {
		targets.push( jocbmEditor );
	}
	if ( typeof jocbmFront !== 'undefined' ) {
		targets.push( jocbmFront );
	}
	targets.forEach( ( target ) => {
		target.themeConfig = next;
		target.fontFamilies = families;
	} );
}

/**
 * Live theme tokens for ColorPalette / FontSizePicker / FontFamily.
 */
export function useThemeTokens() {
	const [ tokens, setTokens ] = useState( () => {
		const initial = readLocalized();
		return {
			colors: initial.colors,
			themeColors: initial.themeColors,
			fontSizes: initial.fontSizes,
			fontFamilies: resolveFontFamilies( initial ),
		};
	} );

	useEffect( () => {
		let cancelled = false;

		// Ensure fonts already in localized config are available for dropdowns.
		syncConfiguredFontStylesheets( readLocalized().fonts );

		const apply = ( next, { bump = false } = {} ) => {
			if ( cancelled || ! next ) {
				return;
			}
			if ( bump ) {
				tokensEpoch += 1;
			}
			const families = resolveFontFamilies( next );
			const localized = readLocalized();
			setTokens( {
				colors: normalizeColors( next.colors ),
				themeColors: normalizeColors(
					next.themeColors ?? localized.themeColors
				),
				fontSizes: normalizeFontSizes( next.fontSizes ),
				fontFamilies: families,
			} );
			writeLocalizedGlobals( next, families );
			syncConfiguredFontStylesheets( next.fonts );
		};

		const onUpdated = ( event ) => apply( event.detail, { bump: true } );

		window.addEventListener( THEME_UPDATED, onUpdated );

		let channel;
		if ( typeof BroadcastChannel !== 'undefined' ) {
			try {
				channel = new BroadcastChannel( THEME_CHANNEL );
				channel.onmessage = ( event ) => {
					if ( event?.data ) {
						apply( event.data, { bump: true } );
					}
				};
			} catch ( e ) {
				channel = null;
			}
		}

		const fetchEpoch = tokensEpoch;
		apiFetch( { path: '/joc-blockora-morph/v1/theme-config' } )
			.then( ( data ) => {
				// Ignore if a save/broadcast landed while this GET was in flight.
				if ( cancelled || fetchEpoch !== tokensEpoch ) {
					return;
				}
				apply( data );
			} )
			.catch( () => {} );

		return () => {
			cancelled = true;
			window.removeEventListener( THEME_UPDATED, onUpdated );
			if ( channel ) {
				channel.close();
			}
		};
		// eslint-disable-next-line react-hooks/exhaustive-deps -- init once
	}, [] );

	const jbmColors = normalizeColors( tokens.colors );
	const wpThemeColors = normalizeColors( tokens.themeColors );
	const themeFontSizes = normalizeFontSizes( tokens.fontSizes );
	const fontFamilies = normalizeFontFamilies( tokens.fontFamilies );

	return {
		themeColors: jbmColors,
		themeFontSizes,
		fontFamilies,
		colorPalettes: [
			{
				name: 'theme',
				title: __( 'Theme', 'joc-blockora-morph' ),
				colors: wpThemeColors,
			},
			{
				name: 'default',
				title: __( 'Default', 'joc-blockora-morph' ),
				colors: jbmColors.length ? jbmColors : DEFAULT_COLORS,
			},
		],
		fontSizes: themeFontSizes.length ? themeFontSizes : DEFAULT_FONT_SIZES,
		fontFamilyOptions: [
			{
				label: __( 'Inter', 'joc-blockora-morph' ),
				value: DEFAULT_BLOCK_FONT_FAMILY,
			},
			...fontFamilies
				.filter( ( f ) => {
					const css = String( f.fontFamily || '' )
						.replace( /\s+/g, '' )
						.toLowerCase();
					const inter = DEFAULT_BLOCK_FONT_FAMILY.replace(
						/\s+/g,
						''
					).toLowerCase();
					return css !== inter && ! css.startsWith( '"inter"' );
				} )
				.map( ( f ) => ( {
					label: f.name,
					value: f.fontFamily,
				} ) ),
		],
	};
}

/**
 * Notify style fields that Brand tokens changed (call after Theme save).
 *
 * @param {Object} config Theme config payload.
 */
export function broadcastThemeUpdate( config ) {
	tokensEpoch += 1;
	const families = resolveFontFamilies( config || {} );
	writeLocalizedGlobals( config, families );
	syncConfiguredFontStylesheets( config?.fonts );

	window.dispatchEvent(
		new CustomEvent( THEME_UPDATED, { detail: config } )
	);

	if ( typeof BroadcastChannel !== 'undefined' ) {
		try {
			const channel = new BroadcastChannel( THEME_CHANNEL );
			channel.postMessage( config );
			channel.close();
		} catch ( e ) {
			// Ignore unsupported / restricted environments.
		}
	}
}
