/**
 * WunderPaint - extension API type definitions (API 2.10.0).
 *
 * Drop this file next to your extension source and reference it from a
 * jsconfig.json/tsconfig.json to get IntelliSense for `window.WPIE.api`
 * and `window.WPIE.bridge`. The runtime surface is additive: members are
 * never renamed or removed, so these types only ever grow.
 *
 * Docs: https://developers.wp-image-editor.com
 */

/* ------------------------------- documents ------------------------------ */

/** A document layer. Only the common fields are typed; layers carry many
 *  type-specific properties (see docs/extending.md). */
export interface WpieLayer {
	id: string;
	type: 'shape' | 'text' | 'image' | 'group' | string;
	name?: string;
	x: number;
	y: number;
	w: number;
	h: number;
	rotation?: number;
	opacity?: number;
	visible?: boolean;
	locked?: boolean;
	parent?: string | null;
	children?: string[];
	/** Shape path in layer-local coordinates (0..w / 0..h). */
	pathD?: string;
	fill?: string;
	stroke?: string;
	text?: string;
	fontFamily?: string;
	fontSize?: number;
	binding?: string;
	/** Generator re-entry descriptor: { id: 'my-plugin/name', params }. */
	generator?: { id: string; params: Record<string, unknown> };
	[ key: string ]: unknown;
}

export interface WpieDoc {
	w: number;
	h: number;
	bg?: string;
	name?: string;
	brandKitId?: string | number;
	[ key: string ]: unknown;
}

/** The live editor handed to every callback. */
export interface WpieEditor {
	state: {
		doc: WpieDoc;
		layers: WpieLayer[];
		selection: string[];
		activeId: string | null;
		[ key: string ]: unknown;
	};
	dispatch( action: { type: string; [ key: string ]: unknown } ): void;
	/** Take a named history snapshot after your edits. */
	commit( label: string ): void;
	undo(): void;
	redo(): void;
}

/** Toasts plus ~40 dialog openers (openChart, openQr, openStock, ...). */
export interface WpieExtras {
	toasts?: {
		success( message: string ): void;
		error( message: string ): void;
		toast?( message: string ): void;
	};
	[ opener: string ]: unknown;
}

export interface WpieCallbackContext {
	editor: WpieEditor;
	extras: WpieExtras;
	/** The clicked/active layer where the hook provides one. */
	layer?: WpieLayer | null;
}

/* ------------------------------ post context ---------------------------- */

/** The per-post context a generator `resolve` / binding `resolve` gets. */
export interface WpiePostContext {
	title?: string;
	excerpt?: string;
	content?: string;
	author?: string;
	category?: string;
	date?: string;
	featuredUrl?: string;
	siteName?: string;
	siteUrl?: string;
	/** Server-resolved field map (post.*, wc.*, acf.*, meta.*, ...). */
	fields?: Record<string, string>;
	/** Active brand kit for the run. */
	brand?: {
		colors?: string[];
		fonts?: string[];
		logoUrl?: string;
		vars?: Array<{ key: string; value: string }>;
		[ key: string ]: unknown;
	};
	images?: { featured?: string; content?: string[]; gallery?: string[] };
	[ key: string ]: unknown;
}

/* ------------------------------- templates ------------------------------ */

/** A starter template (`wpie-template@1`), exactly what File → Export for
 *  Library → "Export as template" produces. */
export interface WpieTemplateDescriptor {
	format?: 'wpie-template@1';
	/** Stable id - strongly recommended (falls back to a name slug). */
	id?: string;
	name: string;
	/** Gallery category: a built-in id merges into that chip, own ids are
	 *  auto-prefixed with your vendor slug. */
	category?: string;
	doc: WpieDoc;
	layers: WpieLayer[];
}

/* ---------------------------------- api --------------------------------- */

export interface WpieApi {
	/** Extension API semver, e.g. '2.10.0'. */
	version: string;

	registerEffect( effect: {
		id: string;
		label: string;
		apply(
			imageData: { data: Uint8ClampedArray; width: number; height: number },
			params: Record<string, number>
		): { data: Uint8ClampedArray; width: number; height: number };
		params?: Record<string, unknown>;
	} ): void;

	registerMenuItem(
		menuId:
			| 'file' | 'edit' | 'image' | 'layer' | 'select' | 'filter'
			| 'view' | 'tools' | 'extensions' | 'help'
			| 'automation/content' | 'automation/images'
			| 'automation/design' | 'automation/actions',
		item: {
			label: string;
			run( ctx: WpieCallbackContext ): void | Promise<void>;
			when?( ctx: WpieCallbackContext ): boolean;
		}
	): void;

	registerPanel( panel: {
		id: string;
		title: string;
		render(
			el: HTMLElement,
			ctx: WpieCallbackContext
		): void | ( () => void );
	} ): void;

	registerPanelSection( section: {
		id: string;
		title: string;
		when?( layer: WpieLayer | null ): boolean;
		render(
			el: HTMLElement,
			ctx: WpieCallbackContext
		): void | ( () => void );
	} ): void;

	registerTool( tool: {
		id: string;
		label: string;
		icon?: string;
		handlers: {
			onDown?( pt: { x: number; y: number }, ctx: WpieCallbackContext ): void;
			onMove?( pt: { x: number; y: number }, ctx: WpieCallbackContext ): void;
			onUp?( pt: { x: number; y: number }, ctx: WpieCallbackContext ): void;
		};
	} ): void;

	registerExportFormat( format: {
		id: string;
		label: string;
		ext: string;
		encode( args: {
			doc: WpieDoc;
			layers: WpieLayer[];
			render( opts?: Record<string, unknown> ): Promise<HTMLCanvasElement>;
			scale?: number;
			quality?: number;
		} ): Promise<Blob>;
	} ): void;

	registerFilterPreset( preset: {
		id: string;
		label: string;
		/** CSS filter() value. */
		css: string;
	} ): void;

	registerGenerator( gen: {
		id: string;
		label: string;
		run( ctx: WpieCallbackContext ): void | Promise<void>;
		edit?( ctx: WpieCallbackContext ): void | Promise<void>;
		/** Per-post re-render for dynamic templates/batch runs. Return
		 *  replacement layers/patch, `{ hide: true }`, or nothing. */
		resolve?(
			layer: WpieLayer,
			ctx: WpiePostContext,
			helpers: {
				expandTokens( str: string ): string;
				hasTokens( str: string ): boolean;
				[ key: string ]: unknown;
			}
		): Promise<unknown> | unknown;
	} ): void;

	registerLibrarySection( section: {
		id: string;
		label: string;
		items:
			| WpieLibraryItem[]
			| ( ( query: string ) => WpieLibraryItem[] | Promise<WpieLibraryItem[]> );
	} ): void;

	registerTemplatePack( pack: {
		id: string;
		label: string;
		templates:
			| WpieTemplateDescriptor[]
			| ( () => WpieTemplateDescriptor[] | Promise<WpieTemplateDescriptor[]> );
		/** Own gallery categories; non-built-in ids are auto-prefixed with
		 *  your vendor slug, so packs can never collide. */
		categories?: Array<{ id: string; label: string }>;
	} ): void;

	registerAiTool( tool: {
		id: string;
		label: string;
		title?: string;
		run( ctx: WpieCallbackContext ): Promise<void>;
	} ): void;

	registerBinding( binding: {
		id: string;
		label: string;
		/** Picker group label (defaults to "Extensions"). */
		group?: string;
		/** Synchronous; throwing or returning nullish degrades to ''. */
		resolve( ctx: WpiePostContext ): string;
	} ): void;
}

export interface WpieLibraryItem {
	name: string;
	/** Preview image URL (data: URLs welcome). */
	preview?: string;
	asset?: {
		kind: 'user-element' | 'user-combo' | 'upload' | 'emoji' | string;
		descriptor?: Record<string, unknown>;
	};
	use?( ctx: WpieCallbackContext ): void;
}

/* --------------------------------- bridge ------------------------------- */

export interface WpieMountHandle {
	set?( value: unknown ): void;
	unmount(): void;
}

export interface WpieBridge {
	api: {
		ai: {
			/** Generic text or JSON-schema completion (API 2.10). */
			complete( opts: {
				prompt: string;
				system?: string;
				schema?: Record<string, unknown>;
				tier?: 'caption' | 'design';
				maxTokens?: number;
				provider?: string;
				model?: string;
			} ): Promise<{ text?: string; data?: unknown; cost?: number; provider?: string }>;
			generate( opts: Record<string, unknown> ): Promise<{ images: string[] }>;
			edit( opts: Record<string, unknown> ): Promise<{ images: string[] }>;
			caption( opts: Record<string, unknown> ): Promise<Record<string, string>>;
			template( opts: Record<string, unknown> ): Promise<unknown>;
			design( opts: Record<string, unknown> ): Promise<unknown>;
			layout( opts: Record<string, unknown> ): Promise<unknown>;
			seo( opts: Record<string, unknown> ): Promise<unknown>;
			svg( opts: Record<string, unknown> ): Promise<unknown>;
			inpaint( opts: Record<string, unknown> ): Promise<{ images: string[] }>;
			outpaint( opts: Record<string, unknown> ): Promise<{ images: string[] }>;
			variations( opts: Record<string, unknown> ): Promise<{ images: string[] }>;
		};
		geo: {
			search( q: string ): Promise<{ results: Array<Record<string, unknown>> }>;
			map( bbox: Record<string, unknown> ): Promise<Record<string, unknown>>;
		};
		saveToLibrary( fd: FormData ): Promise<unknown>;
		saveAsNew( fd: FormData ): Promise<unknown>;
		buildFormData(
			blobs: Record<string, Blob | null>,
			fields?: Record<string, string | number>
		): FormData;
		[ key: string ]: unknown;
	};

	dynamicContent: {
		resolveBindings( layers: WpieLayer[], ctx: WpiePostContext ): WpieLayer[];
		prepareGeneratorLayers( layers: WpieLayer[], ctx: WpiePostContext ): Promise<void>;
		expandTokens( str: string, ctx: WpiePostContext ): string;
		hasTokens( str: string ): boolean;
		bindingGroups( kit?: unknown ): Array<{
			label: string;
			items: Array<{ id: string; label: string; kind: string }>;
		}>;
		[ key: string ]: unknown;
	};

	documents: {
		makeShape( props: Partial<WpieLayer> ): WpieLayer;
		makeText( props: Partial<WpieLayer> ): WpieLayer;
		makeImage( props: Partial<WpieLayer> ): WpieLayer;
		makeGroup( props: Partial<WpieLayer> ): WpieLayer;
		createBlankDoc( w: number, h: number ): WpieDoc;
		loadImage( src: string ): Promise<HTMLImageElement>;
		hydrateLayers( layers: unknown[] ): Promise<WpieLayer[]>;
		serializeLayers( layers: WpieLayer[] ): unknown[];
	};

	raster: {
		renderToCanvas(
			doc: Partial<WpieDoc> & { id?: string },
			layers: WpieLayer[],
			opts?: { scale?: number; cache?: unknown }
		): Promise<HTMLCanvasElement>;
		renderToBlob(
			doc: Partial<WpieDoc>,
			layers: WpieLayer[],
			opts?: Record<string, unknown>
		): Promise<Blob>;
		/** The "Show document" backdrop: the document without one group. */
		docBackdrop(
			state: WpieEditor[ 'state' ],
			opts?: { exclude?: string | null }
		): Promise<HTMLCanvasElement>;
		sharedImageCache: unknown;
		[ key: string ]: unknown;
	};

	/** Canvas → WebM capture with the vp9 → vp8 → webm fallback chain. */
	video: {
		recordCanvas(
			canvas: HTMLCanvasElement,
			opts?: {
				fps?: number;
				durationMs?: number;
				bitsPerSecond?: number;
				onProgress?( elapsedMs: number ): void;
			}
		): { stop(): void; blob: Promise<Blob>; mimeType: string };
		pickRecorderMime(): string;
	};

	util: {
		/** Deterministic mulberry32 - the sequence is contract. */
		rng( seed: number ): () => number;
		EASINGS: Record<'linear' | 'easeIn' | 'easeOut' | 'easeInOut', ( t: number ) => number>;
		downloadBlob( blob: Blob, filename: string ): void;
	};

	colors: {
		buildScheme( opts?: Record<string, unknown> ): string[];
		randomSchemeInput( rand?: () => number ): Record<string, unknown>;
		schemeRoles( colors: string[] ): Record<string, string>;
		labelColorFor( color: string ): string;
		/** Dominant colors of an image/canvas, vibrancy-weighted. */
		extractPalette( source: CanvasImageSource, k?: number ): string[];
		/** WCAG contrast ratio between two hex colors. */
		contrastRatio( a: string, b: string ): number;
	};

	/** Framework-free DOM builders emitting the editor's dsm classes. */
	ui: {
		el( tag: string, cls: string | null, parent?: Element, text?: string ): HTMLElement;
		dialog( opts?: {
			title?: string;
			subtitle?: string;
			width?: number;
			onClose?(): void;
			closeOnBackdrop?: boolean;
		} ): {
			backdrop: HTMLElement;
			dialog: HTMLElement;
			head: HTMLElement;
			body: HTMLElement;
			foot: HTMLElement;
			close(): void;
		};
		section( parent: Element, opts?: { icon?: string; title?: string } ): HTMLElement;
		row( parent: Element, label: string ): HTMLElement;
		slider( parent: Element, opts: {
			label: string;
			min: number;
			max: number;
			step?: number;
			value: number;
			onInput?( value: number ): void;
			format?( value: number ): string;
		} ): { input: HTMLInputElement; set( value: number ): void };
		select( parent: Element, opts: {
			options: Array<string | { value: string; label: string }>;
			value?: string;
			onChange?( value: string ): void;
		} ): HTMLSelectElement;
		check( parent: Element, opts: {
			label: string;
			checked?: boolean;
			onChange?( checked: boolean ): void;
		} ): HTMLInputElement;
		btn( parent: Element, opts: {
			label: string;
			primary?: boolean;
			onClick?(): void;
		} ): HTMLButtonElement;
	};

	/** Per-user, per-extension JSON store (<= 32 KB per namespace). */
	storage: {
		get( ns: string ): Promise<Record<string, unknown>>;
		set( ns: string, value: Record<string, unknown> ): Promise<Record<string, unknown>>;
	};

	brand: {
		kits(): Array<{
			id: string;
			name: string;
			colors: string[];
			fonts: string[];
			[ key: string ]: unknown;
		}>;
		kitById( id: string | number | null | undefined ): Record<string, unknown> | null;
		/** Document's active kit → site kit → first usable kit → null. */
		activeKitColors( brandKitId?: string | number ): string[] | null;
	};

	qr: {
		payload( content: Record<string, unknown> ): string;
		ready( content: Record<string, unknown> ): boolean;
		/** Resolves to { canvas, modules, ecl } - draw result.canvas. */
		render(
			payload: string,
			style?: Record<string, unknown>
		): Promise<{ canvas: HTMLCanvasElement; modules: number; ecl: string }>;
		warnings( style?: Record<string, unknown> ): string[];
	};

	fonts: {
		ensureFont( family: string ): Promise<void>;
		ensureFontsForLayers( layers: WpieLayer[] ): Promise<void>;
		listFamilies(): string[];
	};

	iconsLib: {
		/** { name: pathD } Tabler map (24 grid, stroke). */
		loadTabler(): Promise<Record<string, string>>;
		/** [{ c: char, n: name, g: group }] emoji list. */
		loadEmoji(): Promise<Array<{ c: string; n: string; g: string }>>;
	};

	components: {
		mountColorButton(
			node: Element,
			opts: { color: string; onChange( color: string ): void; title?: string }
		): WpieMountHandle;
		mountFontPicker(
			node: Element,
			opts: {
				value: string;
				onChange( family: string ): void;
				width?: string | number;
				families?: string[];
			}
		): WpieMountHandle;
		mountVarButton(
			node: Element,
			opts: {
				getValue(): string;
				onChange( next: string ): void;
				inputEl?: HTMLElement | null;
				kit?: unknown;
				onKitChange?( id: string ): void;
			}
		): WpieMountHandle;
		mountStyleButton(
			node: Element,
			opts: { getValue(): string; onChange( next: string ): void; compact?: boolean }
		): WpieMountHandle;
		mountKitPicker(
			node: Element,
			opts: { value?: string; onChange( id: string ): void; allowEmpty?: boolean }
		): WpieMountHandle;
		mountMediaPicker(
			node: Element,
			opts: {
				onPick( item: { id: number; url: string; fullUrl?: string; title?: string } ): void;
				height?: number;
				selectedIds?: number[] | null;
			}
		): WpieMountHandle;
		mountIconPicker(
			node: Element,
			opts: {
				onPick(
					sel:
						| { type: 'icon'; name: string; path: string }
						| { type: 'emoji'; char: string; name: string }
				): void;
				icons?: boolean;
				emoji?: boolean;
				height?: number;
			}
		): WpieMountHandle;
		[ key: string ]: unknown;
	};

	providers: {
		PROVIDER_LABELS: Record<string, string>;
		hasTextProvider(): boolean;
	};

	[ group: string ]: unknown;
}

/* --------------------------------- window ------------------------------- */

export interface WpieGlobal {
	api: WpieApi;
	bridge: WpieBridge;
	locale?: string;
	restUrl?: string;
	nonce?: string;
	brand?: Record<string, unknown>;
	brandKits?: Array<Record<string, unknown>>;
	extensions?: Array<Record<string, unknown>>;
	[ key: string ]: unknown;
}

declare global {
	interface Window {
		WPIE?: WpieGlobal;
		wp?: {
			hooks: {
				addAction(
					hook: 'wpie.ready' | string,
					namespace: string,
					callback: ( api: WpieApi, bridge: WpieBridge ) => void
				): void;
			};
			[ key: string ]: unknown;
		};
	}
}

export {};
