declare global {
	interface Window {
		JetEngineDiviViews: any;
		JSFDiviViews: any;
	}
}

type GenericAttrsMap = Record<string, any>;

const normalizeToggleToOnOff = ( value: unknown ): 'on' | 'off' | null => {
	if ( value === true || value === 'true' || value === 'on' || value === 1 || value === '1' || value === 'yes' ) {
		return 'on';
	}

	if ( value === false || value === 'false' || value === 'off' || value === 0 || value === '0' || value === 'no' || value === '' ) {
		return 'off';
	}

	return null;
};

const isToggleEnabled = ( value: unknown ): boolean => {
	if ( Array.isArray( value ) ) {
		return value.length > 0;
	}

	return normalizeToggleToOnOff( value ) === 'on';
};

const formatDiviDefaultValue = ( key: string, value: any, toggleKeys: Set<string>, selectKeys: Set<string> ) => {
	if ( toggleKeys.has( key ) && 'boolean' === typeof value ) {
		return value ? 'on' : 'off';
	}

	if ( selectKeys.has( key ) && null !== value && undefined !== value ) {
		return String( value );
	}

	return value;
};

interface BuildDefaultAttrsPatchArgs {
	attrs?: GenericAttrsMap;
	defaults: Record<string, any>;
	toggleKeys?: Iterable<string>;
	selectKeys?: Iterable<string>;
}

interface BuildDefaultAttrValueArgs {
	key: string;
	value: any;
	toggleKeys?: Iterable<string>;
	selectKeys?: Iterable<string>;
}

interface GetEffectiveAttrValueArgs {
	attrs?: GenericAttrsMap;
	key: string;
	defaults?: Record<string, any>;
	toggleKeys?: Iterable<string>;
	selectKeys?: Iterable<string>;
}

const buildDefaultInnerContent = ( args: BuildDefaultAttrValueArgs ) => {
	const {
		key,
		value,
		toggleKeys = [],
		selectKeys = [],
	} = args;

	return {
		desktop: {
			value: formatDiviDefaultValue( key, value, new Set( toggleKeys ), new Set( selectKeys ) ),
		},
	};
};

const getEffectiveAttrValue = ( args: GetEffectiveAttrValueArgs ) => {
	const {
		attrs = {},
		key,
		defaults = {},
		toggleKeys = [],
		selectKeys = [],
	} = args;

	const currentValue = attrs?.[ key ]?.innerContent?.desktop?.value;

	if ( undefined !== currentValue && null !== currentValue ) {
		return currentValue;
	}

	return formatDiviDefaultValue( key, defaults[ key ], new Set( toggleKeys ), new Set( selectKeys ) );
};

const buildDefaultAttrsPatch = ( args: BuildDefaultAttrsPatchArgs ) => {
	const {
		attrs = {},
		defaults,
		toggleKeys = [],
		selectKeys = [],
	} = args;

	const toggleSet = new Set( toggleKeys );
	const selectSet = new Set( selectKeys );
	const patch: GenericAttrsMap = {};

	Object.keys( defaults ).forEach( ( key ) => {
		const currentValue = attrs?.[ key ]?.innerContent?.desktop?.value;
		const defaultValue = defaults[ key ];
		const defaultIsEmptyString = '' === defaultValue;
		const isToggleAttr = toggleSet.has( key );
		const normalizedToggle = isToggleAttr ? normalizeToggleToOnOff( currentValue ) : null;
		const shouldNormalizeToggle = isToggleAttr && null !== normalizedToggle && currentValue !== normalizedToggle;
		const shouldInitMissing = undefined === currentValue
			|| null === currentValue
			|| ( '' === currentValue && ! defaultIsEmptyString && ! isToggleAttr );

		if ( ! shouldInitMissing && ! shouldNormalizeToggle ) {
			return;
		}

		patch[ key ] = {
			innerContent: {
				desktop: {
					value: shouldNormalizeToggle
						? normalizedToggle
						: formatDiviDefaultValue( key, defaultValue, toggleSet, selectSet ),
				},
			},
		};
	} );

	return patch;
};

const getSchemaDefaultsSource = ( blockKey: string ) => {
	if ( window?.JetEngineDiviViews?.atts?.[ blockKey ] ) {
		return window.JetEngineDiviViews.atts[ blockKey ];
	}

	if ( window?.JSFDiviViews?.atts?.[ blockKey ] ) {
		return window.JSFDiviViews.atts[ blockKey ];
	}

	return {};
};

const getBlockDefaultsFromSchema = ( blockKey: string, fallback: Record<string, any> = {} ) => {
	const defaults = { ...fallback };
	const schema = getSchemaDefaultsSource( blockKey );

	Object.keys( schema ).forEach( ( key ) => {
		if ( Object.prototype.hasOwnProperty.call( schema[ key ], 'default' ) ) {
			defaults[ key ] = schema[ key ].default;
		}
	} );

	return defaults;
};

/**
 * Convert divi attributes into plain object
 */
const convertAttributes = (attributes: { [key: string]: any }) => {

	attributes = attributes || {};

	const converted: { [key: string]: any } = {};

	for ( const attr in attributes ) {
		converted[ attr ] = attributes[ attr ]?.innerContent?.desktop?.value;
	}

	Object.keys( converted ).forEach(key => {
		if ( converted[ key ] === undefined ) {
			delete converted[ key ];
		}
	});

	return converted;
}

interface Option {
	value: string | number;
	label: string | number;
}

interface OptionsGroup {
	label: string | number;
	values: Option[];
}

/**
 * Convert simple list of block-formatted options into divi format
 */
const convertSimpleOptions = ( options: Option[] ) => {

	const converted: { [key: string]: any } = {};

	for ( let i = 0; i < options.length; i++ ) {
		converted[ options[ i ].value ] = {
			label: options[ i ].label
		}
	}

	return converted;
}

/**
 * Convert object with options into divi format
 * @param options
 * @returns {any[]}
 */
const convertObjectOptions = ( options: { [key: string | number]: any } ) => {

	const converted: { [key: string]: any } = {};

	for ( const key in options ) {
		converted[ key ] = {
			label: options[ key ]
		}
	}

	return converted;
}

const convertGroupedOptions = ( options: OptionsGroup[] ) => {

	const converted: any[] = [];

	for ( let i = 0; i < options.length; i++ ) {
		converted.push( {
			label: options[ i ].label,
			options: convertSimpleOptions( options[ i ].values )
		} );
	}

	return converted;
}

const generatePlaceholderAttributes = ( blockName: string ) => {

	const attributes: { [key: string]: any } = {};

	const schema = getSchemaDefaultsSource( blockName );

	if ( schema ) {
		for ( const attr in schema ) {
			let value = schema[ attr ].default;

			// Divi toggles/conditions operate with `on`/`off` values.
			if ( typeof value === 'boolean' ) {
				value = value ? 'on' : 'off';
			}

			attributes[ attr ] = {
				innerContent: {
					desktop: {
						value,
					}
				}
			}
		}
	}

	return attributes;
}

const generateModuleAttributes = ( blockName: string ) => {

	const attributes: { [key: string]: any } = {};

	const schema = getSchemaDefaultsSource( blockName );

	for ( const attr in schema ) {
		attributes[ attr ] = {
			type: schema[ attr ].type,
		}
	}

	return attributes;
}

const generatePlaceholderFromMetaData = ( metadata: any ) => {

	const attributes: { [key: string]: any } = {};

	if ( metadata?.attributes ) {
		for ( const attr in metadata.attributes ) {
			if ( metadata.attributes[ attr ] && metadata.attributes[ attr ].placeholder ) {
				attributes[ attr ] = {
					innerContent: {
						desktop: {
							value: metadata.attributes[ attr ].placeholder,
						}
					}
				}
			}
		}
	}

	return attributes;
}

const extractBorderAttrs = ( v?: any ) => {

	const val =
		v?.desktop?.value ??
		v?.desktop?.default ??
		v?.value ??
		v;

	const allStyle = val?.styles?.all?.style ?? '';
	const allWidth = val?.styles?.all?.width ?? '';
	const allColor = val?.styles?.all?.color ?? '';
	const topStyle = val?.styles?.top?.style ?? allStyle;
	const rightStyle = val?.styles?.right?.style ?? allStyle;
	const bottomStyle = val?.styles?.bottom?.style ?? allStyle;
	const leftStyle = val?.styles?.left?.style ?? allStyle;
	const topWidth = val?.styles?.top?.width ?? allWidth;
	const rightWidth = val?.styles?.right?.width ?? allWidth;
	const bottomWidth = val?.styles?.bottom?.width ?? allWidth;
	const leftWidth = val?.styles?.left?.width ?? allWidth;
	const topColor = val?.styles?.top?.color ?? allColor;
	const rightColor = val?.styles?.right?.color ?? allColor;
	const bottomColor = val?.styles?.bottom?.color ?? allColor;
	const leftColor = val?.styles?.left?.color ?? allColor;
	const r     = val?.radius ?? {};

	const radius =
		r?.sync === 'on'
			? (r?.topLeft ?? '0')
			: `${r?.topLeft ?? '0'} ${r?.topRight ?? '0'} ${r?.bottomRight ?? '0'} ${r?.bottomLeft ?? '0'}`;

	const asResp = (x?: string) => (x != null ? { desktop: { default: x } } : undefined);

	return {
		style:  asResp(allStyle),
		width:  asResp(allWidth),
		color:  asResp(allColor),
		styleTop: asResp(topStyle),
		styleRight: asResp(rightStyle),
		styleBottom: asResp(bottomStyle),
		styleLeft: asResp(leftStyle),
		widthTop: asResp(topWidth),
		widthRight: asResp(rightWidth),
		widthBottom: asResp(bottomWidth),
		widthLeft: asResp(leftWidth),
		colorTop: asResp(topColor),
		colorRight: asResp(rightColor),
		colorBottom: asResp(bottomColor),
		colorLeft: asResp(leftColor),
		radius: asResp(radius),
	};
}

const extractTypographyAttrs = (v?: any) => {
	// unwrap font payload: `v` may be the whole object or under `v.font`
	const src = v?.font ?? v;

	const val =
		src?.desktop?.value ??
		src?.desktop?.default ??
		src?.value ??
		src;

	const asResp = (x?: string) =>
		x != null ? { desktop: { default: x } } : undefined;

	// Raw fields as returned by Divi
	const family        = val?.family ?? 'inherit';
	const size          = val?.size ?? undefined;         // e.g., "19px"
	const lineHeight    = val?.lineHeight ?? undefined;   // e.g., "1.9em"
	const letterSpacing = val?.letterSpacing ?? undefined; // e.g., "2px"
	const weight        = val?.weight ?? undefined;       // e.g., "300"
	const textAlign     = val?.textAlign ?? undefined;    // e.g., "center"
	const styleArr      = Array.isArray(val?.style) ? val.style : [];

	// Derive CSS properties from `style` flags
	// Possible flags: 'uppercase' | 'lowercase' | 'capitalize' | 'italic' | 'underline' | 'line-through'
	let textTransform: string | undefined;
	if (styleArr.includes('uppercase'))  textTransform = 'uppercase';
	else if (styleArr.includes('lowercase'))  textTransform = 'lowercase';
	else if (styleArr.includes('capitalize')) textTransform = 'capitalize';

	let fontStyle: string | undefined;
	if (styleArr.includes('italic')) fontStyle = 'italic';

	// text-decoration can be a combination (e.g., underline + line-through)
	const decorations: string[] = [];
	if (styleArr.includes('underline'))     decorations.push('underline');
	if (styleArr.includes('line-through'))  decorations.push('line-through');
	const textDecoration = decorations.length ? decorations.join(' ') : undefined;

	return {
		// Common CSS properties (each wrapped responsive-like)
		fontFamily:     asResp(family),
		fontSize:       asResp(size),
		lineHeight:     asResp(lineHeight),
		letterSpacing:  asResp(letterSpacing),
		fontWeight:     asResp(weight),
		fontStyle:      asResp(fontStyle),
		textTransform:  asResp(textTransform),
		textDecoration: asResp(textDecoration),
		textAlign:      asResp(textAlign),

		// If you also pass the whole object to `divi/text`, expose it here
		// so you can do: attr: extractTypographyAttrs(...).asTextAttr
		asTextAttr:     asResp(val),
	};
};

/**
 * Extract spacing (margin/padding) into responsive-ready object.
 * Margin and padding returned as single line strings: "top right bottom left".
 * Handles syncHorizontal / syncVertical flags automatically.
 */
const extractSpasingAttrs = (v?: any) => {
	// unwrap responsive holder
	const val =
		v?.desktop?.value ??
		v?.desktop?.default ??
		v?.value ??
		v;

	const asResp = (x?: any) => (x != null ? { desktop: { default: x } } : undefined);

	// normalize sides with sync flags
	const normalize = (s?: any) => {
		if (!s || typeof s !== 'object') return {};
		const out: any = { ...s };
		const { top, right, bottom, left, syncHorizontal, syncVertical } = s;

		if (syncVertical === 'on') {
			const v = top ?? bottom ?? '0';
			out.top = v;
			out.bottom = v;
		}

		if (syncHorizontal === 'on') {
			const h = left ?? right ?? '0';
			out.left = h;
			out.right = h;
		}

		return out;
	};

	const margin  = normalize(val?.margin);
	const padding = normalize(val?.padding);

	// stringify four sides into "top right bottom left"
	const stringify = (s: any) => {
		const top    = s?.top ?? '0';
		const right  = s?.right ?? '0';
		const bottom = s?.bottom ?? '0';
		const left   = s?.left ?? '0';
		return `${top} ${right} ${bottom} ${left}`;
	};

	const marginStr  = stringify(margin);
	const paddingStr = stringify(padding);

	// sides
	const mt = margin?.top;
	const mr = margin?.right;
	const mb = margin?.bottom;
	const ml = margin?.left;

	const pt = padding?.top;
	const pr = padding?.right;
	const pb = padding?.bottom;
	const pl = padding?.left;

	return {
		// whole blocks as single-line strings
		margin:  asResp(marginStr),
		padding: asResp(paddingStr),

		// individual sides
		marginTop:    asResp(mt),
		marginRight:  asResp(mr),
		marginBottom: asResp(mb),
		marginLeft:   asResp(ml),

		paddingTop:    asResp(pt),
		paddingRight:  asResp(pr),
		paddingBottom: asResp(pb),
		paddingLeft:   asResp(pl),
	};
};

const toCamelCase = (prop: string): string =>
    prop.replace(/-([a-z])/g, (_match: string, c: string) => c.toUpperCase());

const createTypographyStyles = (selector: string, typography: any) => {
    const properties = [
        'font-family',
        'font-size',
        'line-height',
        'letter-spacing',
        'font-weight',
        'font-style',
        'text-transform',
        'text-decoration',
        'text-align',
    ] as const;

    return properties.map((property) => ({
        componentName: 'divi/common' as const,
        props: {
            property,
            selector,
            attr: typography[toCamelCase(property)],
        },
    }));
};

const createBorderStyles = (selector: string, border: any) => {
    const properties = [
        'border-style',
        'border-width',
        'border-color',
        'border-top-style',
        'border-right-style',
        'border-bottom-style',
        'border-left-style',
        'border-top-width',
        'border-right-width',
        'border-bottom-width',
        'border-left-width',
        'border-top-color',
        'border-right-color',
        'border-bottom-color',
        'border-left-color',
        'border-radius',
    ] as const;

    const map: Record<(typeof properties)[number], keyof typeof border> = {
        'border-style': 'style',
        'border-width': 'width',
        'border-color': 'color',
        'border-top-style': 'styleTop',
        'border-right-style': 'styleRight',
        'border-bottom-style': 'styleBottom',
        'border-left-style': 'styleLeft',
        'border-top-width': 'widthTop',
        'border-right-width': 'widthRight',
        'border-bottom-width': 'widthBottom',
        'border-left-width': 'widthLeft',
        'border-top-color': 'colorTop',
        'border-right-color': 'colorRight',
        'border-bottom-color': 'colorBottom',
        'border-left-color': 'colorLeft',
        'border-radius': 'radius',
    };

    return properties.map((property) => ({
        componentName: 'divi/common' as const,
        props: {
            property,
            selector,
            attr: border[map[property]],
        },
    }));
};

const createSpacingStyles = (selector: string, spacing: any) => {
    const properties = [
        'padding',
        'margin',
    ] as const;

    const map: Record<(typeof properties)[number], keyof typeof spacing> = {
        padding: 'padding',
        margin: 'margin',
    };

    return properties.map((property) => ({
        componentName: 'divi/common' as const,
        props: {
            property,
            selector,
            attr: spacing[map[property]],
        },
    }));
};

export {
	convertAttributes,
	convertSimpleOptions,
	convertGroupedOptions,
	normalizeToggleToOnOff,
	isToggleEnabled,
	buildDefaultInnerContent,
	buildDefaultAttrsPatch,
	getBlockDefaultsFromSchema,
	getEffectiveAttrValue,
	generatePlaceholderAttributes,
	generateModuleAttributes,
	generatePlaceholderFromMetaData,
	convertObjectOptions,
	extractBorderAttrs,
	extractTypographyAttrs,
	extractSpasingAttrs,
    createTypographyStyles,
    createBorderStyles,
    createSpacingStyles
}
