(() => {
    'use strict';

    window.FotogenicAI = window.FotogenicAI || {};
    const Studio = window.FotogenicAI.Studio = window.FotogenicAI.Studio || {};
    const wpI18n = window.wp.i18n;
    const __ = (text, domain) => wpI18n.__(text, domain);
    const _n = (single, plural, number, domain) => wpI18n._n(single, plural, number, domain);
    const i18n = Object.freeze({
        __,
        _n,
        sprintf: wpI18n.sprintf,
        labels: Object.freeze({
            product_images: __( 'Generate Images', 'fotogenic-ai' ),
            campaign_set: __( 'Campaign Set', 'fotogenic-ai' ),
            increase_resolution: __( 'Increase Resolution', 'fotogenic-ai' ),
            product: __( 'Product', 'fotogenic-ai' ),
            media: __( 'Media', 'fotogenic-ai' ),
            post: __( 'Post', 'fotogenic-ai' ),
            page: __( 'Page', 'fotogenic-ai' ),
            single: __( 'Single', 'fotogenic-ai' ),
            bulk: __( 'Bulk', 'fotogenic-ai' ),
        }),
    });
    Studio.i18n = i18n;
    const { sprintf } = i18n;

    const SCHEMA_VERSION = 1;
    const STORAGE_PREFIX = 'fotogenic-ai-studio-draft-v1:';
    const WORKFLOWS = new Set(['product_images', 'campaign_set', 'increase_resolution']);
    const SOURCE_TYPES = new Set(['product', 'media', 'post', 'page']);
    const SCOPES = new Set(['single', 'bulk']);
    const FIELD_TYPES = new Set(['string', 'integer', 'boolean', 'object']);
    const PRODUCT_SOURCE_MODES = new Set(['featured', 'featured_gallery', 'gallery_only']);
    const GENERATE_PRODUCT_ASSIGNMENT_MODES = new Set(['review', 'gallery_append', 'featured_replace']);
    const CAMPAIGN_PRODUCT_ASSIGNMENT_MODES = new Set(['review', 'gallery_append']);
    const MEDIA_QUERY_FILTERS = Object.freeze({
        type: new Set(['all', 'jpg', 'png', 'webp']),
        date: new Set(['all', 'today', 'week', 'month', 'older']),
        orientation: new Set(['all', 'square', 'landscape', 'portrait']),
        size: new Set(['all', 'under_300kb', '300kb_and_over']),
    });
    const FORBIDDEN_KEYS = new Set(['selectionoverrides', 'selectionoverridekeys']);
    const UUID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;

    const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);

    const clone = (value) => {
        if (Array.isArray(value)) {
            return value.map(clone);
        }
        if (isRecord(value)) {
            return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, clone(item)]));
        }
        return value;
    };

    const mergeRecipeDefaults = (defaults, stored) => {
        const base = isRecord(defaults) ? defaults : {};
        const source = isRecord(stored) ? stored : {};
        return Object.entries(base).reduce((result, [key, defaultValue]) => {
            if (!Object.hasOwn(source, key)) {
                result[key] = clone(defaultValue);
            } else if (isRecord(defaultValue) && isRecord(source[key])) {
                result[key] = mergeRecipeDefaults(defaultValue, source[key]);
            }
            return result;
        }, clone(source));
    };

    const hasForbiddenKey = (value) => {
        if (Array.isArray(value)) {
            return value.some(hasForbiddenKey);
        }
        if (!isRecord(value)) {
            return false;
        }
        return Object.entries(value).some(([key, item]) => (
            FORBIDDEN_KEYS.has(String(key).toLowerCase()) || hasForbiddenKey(item)
        ));
    };

    const stableValue = (value) => {
        if (Array.isArray(value)) {
            return value.map(stableValue);
        }
        if (isRecord(value)) {
            return Object.keys(value).sort().reduce((result, key) => {
                result[key] = stableValue(value[key]);
                return result;
            }, {});
        }
        return value;
    };

    const stableStringify = (value) => JSON.stringify(stableValue(value));

    const normalizePositiveIds = (values, maximum = 1) => {
        const ids = [];
        const seen = new Set();
        (Array.isArray(values) ? values : []).some((value) => {
            const id = Math.floor(Number(value));
            if (id > 0 && !seen.has(id)) {
                seen.add(id);
                ids.push(id);
            }
            return ids.length >= maximum;
        });
        return ids;
    };

    const validCardinality = (value) => (
        isRecord(value)
        && Number.isInteger(value.minimum)
        && Number.isInteger(value.maximum)
        && value.minimum >= 0
        && value.maximum >= value.minimum
    );

    const validateField = (name, field, path, errors) => {
        const fieldPath = path ? `${path}.${name}` : name;
        if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name) || FORBIDDEN_KEYS.has(name.toLowerCase())) {
            errors.push(sprintf( __( 'Invalid recipe field at %s.', 'fotogenic-ai' ), fieldPath ));
            return;
        }
        if (!isRecord(field) || !FIELD_TYPES.has(field.type)) {
            errors.push(sprintf( __( 'Invalid recipe definition at %s.', 'fotogenic-ai' ), fieldPath ));
            return;
        }
        if (field.required !== undefined && typeof field.required !== 'boolean') {
            errors.push(sprintf( __( 'Invalid required flag at %s.', 'fotogenic-ai' ), fieldPath ));
        }
        if (field.exposed !== undefined && typeof field.exposed !== 'boolean') {
            errors.push(sprintf( __( 'Invalid exposure flag at %s.', 'fotogenic-ai' ), fieldPath ));
        }
        if (field.minimum !== undefined && !Number.isFinite(Number(field.minimum))) {
            errors.push(sprintf( __( 'Invalid minimum at %s.', 'fotogenic-ai' ), fieldPath ));
        }
        if (field.maximum !== undefined && !Number.isFinite(Number(field.maximum))) {
            errors.push(sprintf( __( 'Invalid maximum at %s.', 'fotogenic-ai' ), fieldPath ));
        }
        if (field.maxLength !== undefined && (!Number.isInteger(field.maxLength) || field.maxLength < 1)) {
            errors.push(sprintf( __( 'Invalid maximum length at %s.', 'fotogenic-ai' ), fieldPath ));
        }
        if (field.enum !== undefined && (!Array.isArray(field.enum) || field.enum.length === 0)) {
            errors.push(sprintf( __( 'Invalid enum at %s.', 'fotogenic-ai' ), fieldPath ));
        }
        if (field.sources !== undefined && (
            !Array.isArray(field.sources) || field.sources.some((source) => !SOURCE_TYPES.has(source))
        )) {
            errors.push(sprintf( __( 'Invalid source restriction at %s.', 'fotogenic-ai' ), fieldPath ));
        }
        if (field.scopes !== undefined && (
            !Array.isArray(field.scopes) || field.scopes.some((scope) => !SCOPES.has(scope))
        )) {
            errors.push(sprintf( __( 'Invalid scope restriction at %s.', 'fotogenic-ai' ), fieldPath ));
        }
        if (field.type === 'object') {
            if (field.additionalProperties !== false || !isRecord(field.fields)) {
                errors.push(sprintf( __( 'Object field is not strict at %s.', 'fotogenic-ai' ), fieldPath ));
                return;
            }
            Object.entries(field.fields).forEach(([childName, child]) => {
                validateField(childName, child, fieldPath, errors);
            });
        }
    };

    const validateManifest = (rawManifest) => {
        const errors = [];
        if (!isRecord(rawManifest) || rawManifest.schemaVersion !== SCHEMA_VERSION || !Array.isArray(rawManifest.workflows)) {
            return { ok: false, error: __( 'The Studio workflow definition is unavailable.', 'fotogenic-ai' ) };
        }
		const operationalLimits = rawManifest.operationalLimits;
		if (
			!isRecord(operationalLimits)
			|| Object.keys(operationalLimits).sort().join(',') !== 'maxOutputsPerItem,maxRunItems,maxRunOutputs'
			|| !Number.isSafeInteger(operationalLimits.maxRunItems)
			|| operationalLimits.maxRunItems < 1
			|| !Number.isSafeInteger(operationalLimits.maxOutputsPerItem)
			|| operationalLimits.maxOutputsPerItem < 1
			|| !Number.isSafeInteger(operationalLimits.maxRunOutputs)
			|| operationalLimits.maxRunOutputs !== operationalLimits.maxRunItems * operationalLimits.maxOutputsPerItem
		) {
			return { ok: false, error: __( 'The Studio operational limits are unavailable.', 'fotogenic-ai' ) };
		}
        if (rawManifest.workflows.length === 0) {
            return { ok: false, error: __( 'The Studio workflow definition is empty.', 'fotogenic-ai' ) };
        }

        const seenWorkflows = new Set();
        rawManifest.workflows.forEach((workflow, workflowIndex) => {
            const label = sprintf( __( 'workflow %d', 'fotogenic-ai' ), workflowIndex + 1 );
            if (!isRecord(workflow) || !WORKFLOWS.has(workflow.id) || seenWorkflows.has(workflow.id)) {
                errors.push(sprintf( __( 'Invalid %s.', 'fotogenic-ai' ), label ));
                return;
            }
            seenWorkflows.add(workflow.id);
            if (!Array.isArray(workflow.sources) || workflow.sources.length === 0) {
                errors.push(sprintf( __( 'No sources are defined for %s.', 'fotogenic-ai' ), workflow.id ));
            } else {
                const seenSources = new Set();
                workflow.sources.forEach((source) => {
                    if (
                        !isRecord(source)
                        || !SOURCE_TYPES.has(source.id)
                        || seenSources.has(source.id)
                        || !Array.isArray(source.scopes)
                        || source.scopes.length === 0
                        || source.scopes.some((scope) => !SCOPES.has(scope))
                        || typeof source.requiresWooCommerce !== 'boolean'
                        || !isRecord(source.sourcePolicy)
                        || !Array.isArray(source.sourcePolicy.bulkModes)
                        || source.sourcePolicy.bulkModes.some((mode) => !PRODUCT_SOURCE_MODES.has(mode))
                        || new Set(source.sourcePolicy.bulkModes).size !== source.sourcePolicy.bulkModes.length
                        || !isRecord(source.selectionCardinality)
                        || !isRecord(source.inputCardinality)
                    ) {
                        errors.push(sprintf( __( 'Invalid source definition for %s.', 'fotogenic-ai' ), workflow.id ));
                        return;
                    }
                    seenSources.add(source.id);
                    if (source.id !== 'product' && source.sourcePolicy.bulkModes.length) {
                        errors.push(sprintf( __( 'Invalid source policy for %1$s/%2$s.', 'fotogenic-ai' ), workflow.id, source.id ));
                    }
                    const expectedInputScopes = source.scopes.map((scope) => (scope === 'bulk' ? 'bulkItem' : scope));
                    if (
                        source.scopes[0] !== 'single'
                        || new Set(source.scopes).size !== source.scopes.length
                        || Object.keys(source.selectionCardinality).join(',') !== source.scopes.join(',')
                        || source.scopes.some((scope) => (
                            !validCardinality(source.selectionCardinality[scope])
                            || source.selectionCardinality[scope].minimum < 1
                        ))
                    ) {
                        errors.push(sprintf( __( 'Invalid subject selection cardinality for %1$s/%2$s.', 'fotogenic-ai' ), workflow.id, source.id ));
                    }
                    const inputCardinalities = expectedInputScopes.map((scope) => source.inputCardinality[scope]);
                    if (
                        Object.keys(source.inputCardinality).join(',') !== expectedInputScopes.join(',')
                        || inputCardinalities.some((cardinality) => !isRecord(cardinality) || !validCardinality(cardinality.primary))
                    ) {
                        errors.push(sprintf( __( 'Invalid input cardinality for %1$s/%2$s.', 'fotogenic-ai' ), workflow.id, source.id ));
                    }
                    if (
                        inputCardinalities.some((cardinality) => (
                            isRecord(cardinality)
                            && cardinality.total !== undefined
                            && !validCardinality(cardinality.total)
                        ))
                    ) {
                        errors.push(sprintf( __( 'Invalid total input cardinality for %1$s/%2$s.', 'fotogenic-ai' ), workflow.id, source.id ));
                    }
                });
            }
            if (
                !isRecord(workflow.recipe)
                || workflow.recipe.type !== 'object'
                || workflow.recipe.additionalProperties !== false
                || !isRecord(workflow.recipe.fields)
            ) {
                errors.push(sprintf( __( 'Invalid recipe definition for %s.', 'fotogenic-ai' ), workflow.id ));
            } else {
                Object.entries(workflow.recipe.fields).forEach(([name, field]) => {
                    validateField(name, field, workflow.id, errors);
                });
            }
            if (!isRecord(workflow.outputCardinality) || !validCardinality(workflow.outputCardinality)) {
                errors.push(sprintf( __( 'Invalid output cardinality for %s.', 'fotogenic-ai' ), workflow.id ));
            }
        });

        if (hasForbiddenKey(rawManifest)) {
            errors.push(__( 'The workflow definition contains forbidden override metadata.', 'fotogenic-ai' ));
        }

        return errors.length
            ? { ok: false, error: errors[0], errors }
            : { ok: true, manifest: clone(rawManifest), errors: [] };
    };

    const getWorkflow = (manifest, workflowId) => (
        manifest.workflows.find((workflow) => workflow.id === workflowId) || null
    );

    const sourceIsAvailable = (source, wooAvailable) => (
        source && (!source.requiresWooCommerce || wooAvailable)
    );

    const routeSupported = (manifest, route, wooAvailable = true) => {
        if (!isRecord(route) || !WORKFLOWS.has(route.workflow) || !SOURCE_TYPES.has(route.sourceType) || !SCOPES.has(route.scope)) {
            return false;
        }
        const workflow = getWorkflow(manifest, route.workflow);
        const source = workflow?.sources?.find((entry) => entry.id === route.sourceType) || null;
        return sourceIsAvailable(source, wooAvailable) && source.scopes.includes(route.scope);
    };

	const composeScope = (route) => ['product_images', 'campaign_set'].includes(route?.workflow) ? 'bulk' : route?.scope;

	const executionRoute = (draft, route, sourceDefinition) => {
		if (!['product_images', 'campaign_set', 'increase_resolution'].includes(route.workflow)) {
			return route;
		}
		if (route.workflow === 'increase_resolution') {
			return route;
		}
        if (draft?.selection?.strategy === 'query') {
            return { ...route, scope: 'bulk' };
        }
        if (route.workflow === 'campaign_set') {
            const groupCount = Array.isArray(draft?.sourcePolicy?.groups)
                ? draft.sourcePolicy.groups.length
                : 0;
            return { ...route, scope: groupCount === 1 ? 'single' : 'bulk' };
        }
        const maximum = Math.max(
            1,
            Number(sourceDefinition?.selectionCardinality?.bulk?.maximum) || 1
        );
        const selectedIds = normalizePositiveIds(draft?.selection?.selectedIds, maximum);
        return { ...route, scope: selectedIds.length > 1 ? 'bulk' : 'single' };
    };

    const firstSupportedRoute = (manifest, preferred, wooAvailable = true) => {
        const workflowOrder = [preferred?.workflow, ...manifest.workflows.map((workflow) => workflow.id)]
            .filter((value, index, values) => value && values.indexOf(value) === index);
        for (const workflowId of workflowOrder) {
            const workflow = getWorkflow(manifest, workflowId);
            if (!workflow) {
                continue;
            }
            const sourceOrder = [preferred?.sourceType, ...workflow.sources.map((source) => source.id)]
                .filter((value, index, values) => value && values.indexOf(value) === index);
            for (const sourceType of sourceOrder) {
                const source = workflow.sources.find((entry) => entry.id === sourceType);
                if (!sourceIsAvailable(source, wooAvailable)) {
                    continue;
                }
                const preferredScope = ['product_images', 'campaign_set', 'increase_resolution'].includes(workflow.id) ? 'bulk' : preferred?.scope;
                const scope = source.scopes.includes(preferredScope) ? preferredScope : source.scopes[0];
                if (scope) {
                    return { workflow: workflow.id, sourceType: source.id, scope };
                }
            }
        }
        return null;
    };

    const normalizeInitialRoute = (context = {}, rootData = {}, manifest, wooAvailable = true) => {
        const requested = {
            workflow: String(context.workflow || rootData.workflow || '').toLowerCase(),
            sourceType: String(context.sourceType || rootData.sourceType || '').toLowerCase(),
            scope: String(context.scope || rootData.scope || '').toLowerCase(),
        };
        return routeSupported(manifest, requested, wooAvailable)
            ? requested
            : firstSupportedRoute(manifest, requested, wooAvailable);
    };

    const fieldApplies = (field, route) => (
        field.exposed !== false
        && (!Array.isArray(field.sources) || field.sources.includes(route.sourceType))
        && (!Array.isArray(field.scopes) || field.scopes.includes(route.scope))
    );

    const shouldOmit = (value, field) => {
        const omission = String(field.omission || '');
        if (value === undefined || value === null) {
            return !field.required;
        }
        if (typeof value === 'string' && value.trim() === '' && omission.includes('empty')) {
            return true;
        }
        if (typeof value === 'string' && value.trim().toLowerCase() === 'auto' && omission.includes('auto')) {
            return true;
        }
        if (
            isRecord(value)
            && typeof value.id === 'string'
            && value.id.trim().toLowerCase() === 'auto'
            && omission.includes('auto')
        ) {
            return true;
        }
        if (typeof value === 'number' && value === 0 && omission.includes('zero')) {
            return true;
        }
        if (isRecord(value) && Object.keys(value).length === 0 && omission.includes('empty')) {
            return true;
        }
        return false;
    };

    const normalizeFieldValue = (value, field, path, issues, route) => {
        if (!fieldApplies(field, route) || shouldOmit(value, field)) {
            return undefined;
        }
        if (field.type === 'string') {
            if (typeof value !== 'string') {
                issues.push(sprintf( __( '%s must be text.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            const normalized = value.trim();
            if (field.maxLength && normalized.length > field.maxLength) {
                issues.push(sprintf( __( '%s is too long.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            if (Array.isArray(field.enum) && !field.enum.includes(normalized)) {
                issues.push(sprintf( __( '%s has an unsupported value.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            return normalized;
        }
        if (field.type === 'integer') {
            const number = Number(value);
            if (!Number.isInteger(number)) {
                issues.push(sprintf( __( '%s must be a whole number.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            if (field.minimum !== undefined && number < Number(field.minimum)) {
                issues.push(sprintf( __( '%s is below its minimum.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            if (field.maximum !== undefined && number > Number(field.maximum)) {
                issues.push(sprintf( __( '%s is above its maximum.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            if (Array.isArray(field.enum) && !field.enum.includes(number)) {
                issues.push(sprintf( __( '%s has an unsupported value.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            return number;
        }
        if (field.type === 'boolean') {
            if (typeof value !== 'boolean') {
                issues.push(sprintf( __( '%s must be enabled or disabled.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            return value;
        }
        if (field.type === 'object') {
            const objectValue = typeof value === 'string' && field.optionSource ? { id: value } : value;
            if (!isRecord(objectValue)) {
                issues.push(sprintf( __( '%s must be an object.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            const unknown = Object.keys(objectValue).filter((key) => !Object.hasOwn(field.fields, key));
            if (unknown.length) {
                issues.push(sprintf( __( '%s contains an unsupported field.', 'fotogenic-ai' ), path ));
                return undefined;
            }
            const normalized = {};
            Object.entries(field.fields).forEach(([childName, childField]) => {
                const child = normalizeFieldValue(objectValue[childName], childField, `${path}.${childName}`, issues, route);
                if (child !== undefined) {
                    normalized[childName] = child;
                } else if (childField.required && !shouldOmit(objectValue[childName], childField)) {
                    issues.push(sprintf( __( '%1$s.%2$s is required.', 'fotogenic-ai' ), path, childName ));
                }
            });
            return shouldOmit(normalized, field) ? undefined : normalized;
        }
        return undefined;
    };

    const requirementsMet = (field, recipe) => {
        if (!isRecord(field.requires)) {
            return true;
        }
        return Object.entries(field.requires).every(([key, expected]) => {
            const actual = recipe[key];
            if (expected === 'positive-integer') {
                return Number.isInteger(actual) && actual > 0;
            }
            return actual === expected;
        });
    };

    const dynamicOptionValues = (field, path, optionSources) => {
        const sourceName = String(field.optionSource || '');
        const source = sourceName && isRecord(optionSources) ? optionSources[sourceName] : null;
        const entries = Array.isArray(source)
            ? source
            : (isRecord(source) ? (source[path] || source[path.split('.').at(-1)] || []) : []);
        return (Array.isArray(entries) ? entries : []).map((entry) => {
            if (isRecord(entry)) return String(entry.id ?? entry.value ?? '').trim();
            return String(entry || '').trim();
        }).filter(Boolean);
    };

    const validateDynamicOptions = (recipe, fields, optionSources, issues, parentPath = '') => {
        Object.entries(fields || {}).forEach(([name, field]) => {
            const path = parentPath ? `${parentPath}.${name}` : name;
            const value = recipe?.[name];
            if (field.type === 'object' && !field.optionSource) {
                if (isRecord(value)) validateDynamicOptions(value, field.fields, optionSources, issues, path);
                return;
            }
            if (!field.optionSource || value === undefined) return;
            const selected = field.type === 'object' ? String(value?.id || '').trim() : String(value || '').trim();
            const available = dynamicOptionValues(field, path, optionSources);
            if (!selected || !available.includes(selected)) {
                issues.push(sprintf( __( '%s is unavailable in the current server definition.', 'fotogenic-ai' ), path ));
            }
        });
    };

    const normalizeRecipe = (rawRecipe, recipeSchema, route, optionSources = {}) => {
        const issues = [];
        if (hasForbiddenKey(rawRecipe)) {
            return { value: {}, issues: [__( 'Recipe contains forbidden override metadata.', 'fotogenic-ai' )] };
        }
        const source = isRecord(rawRecipe) ? rawRecipe : {};
        const fields = recipeSchema.fields || {};
        const unknown = Object.keys(source).filter((key) => !Object.hasOwn(fields, key));
        if (unknown.length) {
            issues.push(sprintf( __( 'Unsupported recipe field: %s.', 'fotogenic-ai' ), unknown[0] ));
        }
        const normalized = {};
        Object.entries(fields).forEach(([name, field]) => {
            if (!fieldApplies(field, route)) {
                return;
            }
            const value = normalizeFieldValue(source[name], field, name, issues, route);
            if (value !== undefined) {
                normalized[name] = value;
            } else if (field.required && !shouldOmit(source[name], field)) {
                issues.push(sprintf( __( '%s is required.', 'fotogenic-ai' ), name ));
            } else if (field.required && source[name] === undefined) {
                issues.push(sprintf( __( '%s is required.', 'fotogenic-ai' ), name ));
            }
        });
        Object.entries(fields).forEach(([name, field]) => {
            if (Object.hasOwn(normalized, name) && !requirementsMet(field, normalized)) {
                delete normalized[name];
            }
        });
        if (
            route.workflow === 'product_images'
            && normalized.personPresence === true
            && !(Number.isInteger(normalized.subjectImageId) && normalized.subjectImageId > 0)
        ) {
            issues.push(__( 'Use a model requires a model reference.', 'fotogenic-ai' ));
        }
        validateDynamicOptions(normalized, fields, optionSources, issues);
        return { value: normalized, issues: [...new Set(issues)] };
    };

    const defaultFieldValue = (field, route, optionSources = {}, path = '') => {
        if (!fieldApplies(field, route)) {
            return undefined;
        }
		const available = field.optionSource
			? dynamicOptionValues(field, path, optionSources)
			: [];
        if (field.default !== undefined) {
			const defaultIdentity = field.type === 'object'
				? String(field.default?.id || '').trim()
				: String(field.default).trim();
			if (available.length && !available.includes(defaultIdentity)) {
				return field.type === 'object' ? { id: available[0] } : available[0];
			}
            return clone(field.default);
        }
        if (!field.required) {
            return undefined;
        }
		if (available.length) {
			return field.type === 'object' ? { id: available[0] } : available[0];
		}
        if (Array.isArray(field.enum) && field.enum.length) {
            return clone(field.enum[0]);
        }
        if (field.type === 'integer' && Number.isFinite(Number(field.minimum))) {
            return Number(field.minimum);
        }
        if (field.type === 'object') {
            const value = {};
            Object.entries(field.fields).forEach(([name, child]) => {
				const childPath = path ? `${path}.${name}` : name;
                const childValue = defaultFieldValue(child, route, optionSources, childPath);
                if (childValue !== undefined) {
                    value[name] = childValue;
                }
            });
            return Object.keys(value).length ? value : undefined;
        }
        return undefined;
    };

    const createDefaultRecipe = (recipeSchema, route, optionSources = {}) => {
        const recipe = {};
        Object.entries(recipeSchema.fields || {}).forEach(([name, field]) => {
			const value = defaultFieldValue(field, route, optionSources, name);
            if (value !== undefined) {
                recipe[name] = value;
            }
        });
        return recipe;
    };

    const storageKey = (route) => `${STORAGE_PREFIX}${route.workflow}:${route.sourceType}:${route.scope}`;

	const canonicalCampaignGroups = (rawGroups, route, sourceDefinition, selectedIds, recipeSchema, optionSources = {}) => {
        const issues = [];
        if (!Array.isArray(rawGroups)) {
            if (selectedIds.length) issues.push(__( 'Add a Campaign group for every selected exact target.', 'fotogenic-ai' ));
            return { groups: [], issues };
        }
        const maximumGroups = Math.max(1, Number(sourceDefinition?.selectionCardinality?.[route.scope]?.maximum) || 1);
        const inputContract = route.scope === 'single'
            ? sourceDefinition?.inputCardinality?.single
            : sourceDefinition?.inputCardinality?.bulkItem;
        const maximumInputs = Math.max(1, Number(inputContract?.primary?.maximum) || 1);
        const groupIds = new Set();
        if (rawGroups.length > maximumGroups) {
            issues.push(sprintf( __( 'Use no more than %d Campaign groups.', 'fotogenic-ai' ), maximumGroups ));
        }
        const groups = rawGroups.slice(0, maximumGroups).map((rawGroup, index) => {
            if (!isRecord(rawGroup) || !isRecord(rawGroup.target)) {
                issues.push(sprintf( __( 'Campaign group %d has an invalid identity.', 'fotogenic-ai' ), index + 1 ));
                return null;
            }
			const expectedGroupFields = ['id', 'ordinal', 'subjectId', 'target', 'primaryAttachmentId', 'supportingAttachmentIds', 'recipe', 'resultPolicy'];
            const expectedTargetFields = ['targetType', 'targetId', 'parentProductId', 'variationId'];
            if (
                Object.keys(rawGroup).length !== expectedGroupFields.length
                || !expectedGroupFields.every((field) => Object.hasOwn(rawGroup, field))
                || Object.keys(rawGroup.target).length !== expectedTargetFields.length
                || !expectedTargetFields.every((field) => Object.hasOwn(rawGroup.target, field))
            ) {
                issues.push(sprintf( __( 'Campaign group %d contains unsupported identity fields.', 'fotogenic-ai' ), index + 1 ));
                return null;
            }
            const id = String(rawGroup.id || '').toLowerCase();
            const subjectId = rawGroup.subjectId;
            const primaryAttachmentId = rawGroup.primaryAttachmentId;
            const target = {
                targetType: String(rawGroup.target.targetType || '').toLowerCase(),
                targetId: rawGroup.target.targetId,
                parentProductId: rawGroup.target.parentProductId,
                variationId: rawGroup.target.variationId,
            };
			const supportingAttachmentIds = Array.isArray(rawGroup.supportingAttachmentIds)
				? rawGroup.supportingAttachmentIds.slice()
				: [];
			const groupRecipe = normalizeRecipe(rawGroup.recipe, recipeSchema, route, optionSources);
			const requestedAssignment = String(rawGroup.resultPolicy?.assignmentMode || 'review');
			const resultPolicy = route.sourceType === 'product'
				? { assignmentMode: CAMPAIGN_PRODUCT_ASSIGNMENT_MODES.has(requestedAssignment) ? requestedAssignment : 'review' }
				: {};
			groupRecipe.issues.forEach((issue) => issues.push(sprintf(
				__( 'Campaign group %1$d: %2$s', 'fotogenic-ai' ),
				index + 1,
				issue
			)));
            const validProduct = route.sourceType === 'product' && (
                (target.targetType === 'product' && target.parentProductId === 0 && target.variationId === 0)
                || (target.targetType === 'variation' && target.parentProductId > 0 && target.variationId === target.targetId)
            );
            const validMedia = route.sourceType === 'media'
                && target.targetType === 'media'
                && target.parentProductId === 0
                && target.variationId === 0;
            if (
                !UUID_PATTERN.test(id)
                || groupIds.has(id)
                || rawGroup.ordinal !== index + 1
                || !Number.isSafeInteger(subjectId)
                || subjectId <= 0
                || !Number.isSafeInteger(primaryAttachmentId)
                || primaryAttachmentId <= 0
                || !Number.isSafeInteger(target.targetId)
                || !Number.isSafeInteger(target.parentProductId)
                || !Number.isSafeInteger(target.variationId)
                || target.targetId !== subjectId
                || !Array.isArray(rawGroup.supportingAttachmentIds)
                || supportingAttachmentIds.length > Math.max(0, maximumInputs - 1)
                || supportingAttachmentIds.some((attachmentId) => !Number.isSafeInteger(attachmentId) || attachmentId <= 0)
                || new Set(supportingAttachmentIds).size !== supportingAttachmentIds.length
                || supportingAttachmentIds.includes(primaryAttachmentId)
                || (route.sourceType === 'media' && supportingAttachmentIds.some((attachmentId) => selectedIds.includes(attachmentId)))
				|| !isRecord(rawGroup.recipe)
				|| !isRecord(rawGroup.resultPolicy)
				|| (!validProduct && !validMedia)
            ) {
                issues.push(sprintf( __( 'Campaign group %d has invalid or duplicate source roles.', 'fotogenic-ai' ), index + 1 ));
                return null;
            }
            groupIds.add(id);
            return {
                id,
                ordinal: index + 1,
                subjectId,
                target,
				primaryAttachmentId,
				supportingAttachmentIds,
				recipe: groupRecipe.value,
				resultPolicy,
			};
        }).filter(Boolean);
        const selected = [...selectedIds].sort((a, b) => a - b);
        const groupSubjects = [...new Set(groups.map((group) => group.subjectId))].sort((a, b) => a - b);
        if (selected.length && (selected.length !== groupSubjects.length || selected.some((id, index) => id !== groupSubjects[index]))) {
            issues.push(__( 'Campaign groups must match every selected exact target.', 'fotogenic-ai' ));
        }
        if (route.sourceType === 'media' && groups.length !== groupSubjects.length) {
            issues.push(__( 'Each selected Media source must own exactly one Campaign group.', 'fotogenic-ai' ));
        }
        return { groups, issues };
    };

	const canonicalSourcePolicy = (draft, route, sourceDefinition, selectedIds, recipeSchema, optionSources = {}) => {
		if (route.workflow === 'campaign_set') {
			const canonical = canonicalCampaignGroups(
				draft.sourcePolicy?.groups,
				route,
				sourceDefinition,
				selectedIds,
				recipeSchema,
				optionSources
			);
            return { value: { groups: canonical.groups }, issues: canonical.issues };
        }
        if (route.workflow === 'product_images' && route.sourceType === 'product') {
            const modes = Array.isArray(sourceDefinition?.sourcePolicy?.bulkModes)
                ? sourceDefinition.sourcePolicy.bulkModes
                : [];
            if (modes.length) {
                const requested = String(draft.sourcePolicy?.sourceMode || '');
                return { value: { sourceMode: modes.includes(requested) ? requested : modes[0] }, issues: [] };
            }
        }
        if (route.scope === 'bulk') {
            const modes = Array.isArray(sourceDefinition?.sourcePolicy?.bulkModes)
                ? sourceDefinition.sourcePolicy.bulkModes
                : [];
            if (route.sourceType !== 'product' || !modes.length) {
                return { value: {}, issues: [] };
            }
            const requested = String(draft.sourcePolicy?.sourceMode || '');
            return { value: { sourceMode: modes.includes(requested) ? requested : modes[0] }, issues: [] };
        }

        if (route.sourceType === 'media') {
            if (!selectedIds.length) return { value: {}, issues: [] };
            if (route.workflow !== 'campaign_set') {
                return { value: { attachmentIds: selectedIds.slice(0, 1) }, issues: [] };
            }
            const maximum = Math.max(
                1,
                Number(sourceDefinition?.inputCardinality?.single?.primary?.maximum) || 1
            );
            const attachmentIds = normalizePositiveIds(draft.sourcePolicy?.attachmentIds, maximum);
            return { value: { attachmentIds: attachmentIds.length ? attachmentIds : selectedIds.slice(0, 1) }, issues: [] };
        }
        const maximum = Math.max(
            1,
            Number(sourceDefinition?.inputCardinality?.single?.primary?.maximum) || 1
        );
        const attachmentIds = normalizePositiveIds(draft.sourcePolicy?.attachmentIds, maximum);
        return { value: attachmentIds.length ? { attachmentIds } : {}, issues: [] };
    };

	const canonicalDraft = (draft, recipeSchema, route, sourceDefinition, optionSources = {}) => {
		const canonicalRoute = executionRoute(draft, route, sourceDefinition);
		const recipeResult = normalizeRecipe(draft.recipe, recipeSchema, canonicalRoute, optionSources);
		const storeQueueQuery = canonicalRoute.workflow === 'product_images'
			&& canonicalRoute.sourceType === 'product'
			&& canonicalRoute.scope === 'bulk'
			&& draft.selection?.strategy === 'query';
		const maximum = storeQueueQuery
			? 10000
			: Math.max(1, Number(sourceDefinition?.selectionCardinality?.[canonicalRoute.scope]?.maximum) || 1);
        const selectedIds = normalizePositiveIds(draft.selection?.selectedIds, maximum);
        const excludedIds = normalizePositiveIds(draft.selection?.excludedIds, maximum)
            .filter((id) => !selectedIds.includes(id));
		const assignmentModes = canonicalRoute.workflow === 'campaign_set'
            ? CAMPAIGN_PRODUCT_ASSIGNMENT_MODES
            : GENERATE_PRODUCT_ASSIGNMENT_MODES;
        const productDelivery = canonicalRoute.sourceType === 'product'
            && ['product_images', 'campaign_set'].includes(canonicalRoute.workflow);
        const requestedAssignment = String(draft.resultPolicy?.assignmentMode || 'review');
		let resultPolicy = productDelivery
			? { assignmentMode: assignmentModes.has(requestedAssignment) ? requestedAssignment : 'review' }
			: {};
		const sourcePolicy = canonicalSourcePolicy(
			draft,
			canonicalRoute,
			sourceDefinition,
			selectedIds,
			recipeSchema,
			optionSources
		);
		let recipe = recipeResult.value;
		if (canonicalRoute.workflow === 'campaign_set' && sourcePolicy.value.groups?.length) {
			recipe = clone(sourcePolicy.value.groups[0].recipe);
			resultPolicy = clone(sourcePolicy.value.groups[0].resultPolicy);
		}
		if (canonicalRoute.workflow === 'campaign_set' && canonicalRoute.scope === 'bulk' && draft.selection?.strategy === 'query') {
			sourcePolicy.issues.push(__( 'Campaign groups require explicit exact-target selection.', 'fotogenic-ai' ));
		}
        return {
            draft: {
                schemaVersion: SCHEMA_VERSION,
                workflow: canonicalRoute.workflow,
                sourceType: canonicalRoute.sourceType,
                scope: canonicalRoute.scope,
                selection: {
                    strategy: draft.selection?.strategy === 'query' ? 'query' : 'explicit',
                    selectedIds,
                    excludedIds,
                    query: isRecord(draft.selection?.query) ? clone(draft.selection.query) : {},
                },
                sourcePolicy: sourcePolicy.value,
                recipe,
                resultPolicy,
            },
            issues: [...new Set([...recipeResult.issues, ...sourcePolicy.issues])],
        };
    };

    const createStore = ({ manifest, initialRoute, storage = window.sessionStorage } = {}) => {
        const subscribers = new Set();
        let route = clone(initialRoute);
        let storageAvailable = true;

        const definitionForRoute = () => {
            const workflow = getWorkflow(manifest, route.workflow);
            const source = workflow?.sources?.find((item) => item.id === route.sourceType) || null;
            return { workflow, source };
        };

        const blankDraft = () => {
            const { workflow, source } = definitionForRoute();
            const modes = Array.isArray(source?.sourcePolicy?.bulkModes) ? source.sourcePolicy.bulkModes : [];
            return {
                schemaVersion: SCHEMA_VERSION,
                workflow: route.workflow,
                sourceType: route.sourceType,
                scope: route.scope,
                selection: { strategy: 'explicit', selectedIds: [], excludedIds: [], query: {} },
                sourcePolicy: route.workflow === 'campaign_set' && route.scope === 'bulk'
                    ? { groups: [] }
                    : (route.sourceType === 'product' && route.scope === 'bulk' && modes.length
                        ? { sourceMode: modes[0] }
                        : {}),
                recipe: createDefaultRecipe(workflow.recipe, route, manifest.optionSources),
                resultPolicy: route.sourceType === 'product' && ['product_images', 'campaign_set'].includes(route.workflow)
                    ? { assignmentMode: 'review' }
                    : {},
            };
        };

        const readStoredDraft = () => {
            try {
                const raw = storage?.getItem?.(storageKey(route));
                if (!raw) {
                    return blankDraft();
                }
                const parsed = JSON.parse(raw);
                if (
                    !isRecord(parsed)
                    || parsed.schemaVersion !== SCHEMA_VERSION
                    || parsed.workflow !== route.workflow
                    || parsed.sourceType !== route.sourceType
                    || parsed.scope !== route.scope
                    || hasForbiddenKey(parsed)
                ) {
                    storage?.removeItem?.(storageKey(route));
                    return blankDraft();
                }
                const blank = blankDraft();
                return {
                    ...blank,
                    ...parsed,
                    recipe: mergeRecipeDefaults(blank.recipe, parsed.recipe),
                };
            } catch (_) {
                storageAvailable = false;
                return blankDraft();
            }
        };

        let state = {
            route,
            draft: readStoredDraft(),
            view: 'select',
            preflight: { status: 'idle', data: null, canonical: '', error: '' },
            bulkSnapshot: null,
        };

        const persist = () => {
            if (!storageAvailable) {
                return;
            }
            try {
                storage?.setItem?.(storageKey(route), JSON.stringify(state.draft));
            } catch (_) {
                storageAvailable = false;
            }
        };

        const notify = (reason) => {
            subscribers.forEach((subscriber) => subscriber(state, reason));
        };

        const clearPreflight = () => {
            state.preflight = { status: 'idle', data: null, canonical: '', error: '' };
            state.bulkSnapshot = null;
        };

        const updateDraft = (nextDraft, reason) => {
            state.draft = nextDraft;
            clearPreflight();
            persist();
            notify(reason);
        };

        const api = {
            subscribe(subscriber) {
                subscribers.add(subscriber);
                return () => subscribers.delete(subscriber);
            },
            getState() {
                return state;
            },
            getDefinitions() {
                return definitionForRoute();
            },
            getCanonical() {
                const { workflow, source } = definitionForRoute();
                return canonicalDraft(state.draft, workflow.recipe, route, source, manifest.optionSources);
            },
            getCanonicalString() {
                return stableStringify(api.getCanonical().draft);
            },
			isPreflightCurrent() {
				return state.preflight.status === 'ready'
					&& state.preflight.canonical === api.getCanonicalString()
					&& isRecord(state.preflight.data?.authority)
					&& state.preflight.data.authority.version === 1
					&& /^[a-f0-9]{64}$/.test(String(state.preflight.data.authority.fingerprint || ''))
					&& Number.isSafeInteger(Number(state.preflight.data.authority.expiresAt))
					&& Number(state.preflight.data.authority.expiresAt) > Math.floor(Date.now() / 1000);
            },
            isDirty() {
                const current = api.getCanonical().draft;
                const definitions = definitionForRoute();
                const blank = canonicalDraft(
                    blankDraft(),
                    definitions.workflow.recipe,
                    route,
                    definitions.source,
                    manifest.optionSources
                ).draft;
                return stableStringify(current) !== stableStringify(blank);
            },
            setRoute(nextRoute) {
                route = clone(nextRoute);
                state = {
                    route,
                    draft: readStoredDraft(),
                    view: 'select',
                    preflight: { status: 'idle', data: null, canonical: '', error: '' },
                    bulkSnapshot: null,
                };
                notify('route');
            },
			clearSourceSelection() {
				updateDraft({
					...state.draft,
					selection: {
						strategy: 'explicit',
						selectedIds: [],
						excludedIds: [],
						query: {},
					},
					sourcePolicy: {},
				}, 'source-selection-cleared');
			},
            setSelection(selectedIds, selectedItemsReason = 'selection') {
                const { source } = definitionForRoute();
                const scope = composeScope(route);
                const maximum = Math.max(1, Number(source.selectionCardinality?.[scope]?.maximum) || 1);
                const normalizedIds = normalizePositiveIds(selectedIds, maximum);
                const sourcePolicy = scope === 'single'
                    ? (route.sourceType === 'media' && normalizedIds.length ? { attachmentIds: normalizedIds.slice(0, 1) } : {})
                    : state.draft.sourcePolicy;
                updateDraft({
                    ...state.draft,
                    selection: {
                        ...state.draft.selection,
                        strategy: 'explicit',
                        selectedIds: normalizedIds,
                        excludedIds: [],
                        query: {},
                    },
                    sourcePolicy,
                }, selectedItemsReason);
            },
            toggleSelection(id) {
                const normalizedId = Math.floor(Number(id));
                if (normalizedId <= 0) {
                    return;
                }
                const { source } = definitionForRoute();
                const scope = composeScope(route);
                const maximum = Math.max(1, Number(source.selectionCardinality?.[scope]?.maximum) || 1);
                const selected = normalizePositiveIds(state.draft.selection?.selectedIds, maximum);
                const next = selected.includes(normalizedId)
                    ? selected.filter((itemId) => itemId !== normalizedId)
                    : (scope === 'single' ? [normalizedId] : [...selected, normalizedId].slice(0, maximum));
                api.setSelection(next);
            },
            setBulkQuery(query, { selectedIds = [], excludedIds = [], reason = 'bulk-query' } = {}) {
                if (composeScope(route) !== 'bulk' || route.workflow === 'campaign_set' || !isRecord(query) || hasForbiddenKey(query)) {
                    return false;
                }
                const { source } = definitionForRoute();
				const maximum = route.workflow === 'product_images' && route.sourceType === 'product'
					? 10000
					: Math.max(1, Number(source.selectionCardinality?.bulk?.maximum) || 1);
                const normalizedQuery = {
                    search: String(query.search || '').trim().replace(/\s+/g, ' ').slice(0, 100),
                };
                if (route.sourceType === 'product') {
                    normalizedQuery.categories = normalizePositiveIds(query.categories, 50);
                    normalizedQuery.stock = [...new Set((Array.isArray(query.stock) ? query.stock : [])
                        .map(String)
                        .filter((value) => ['instock', 'outofstock', 'onbackorder'].includes(value)))];
                } else if (route.sourceType === 'media') {
                    Object.entries(MEDIA_QUERY_FILTERS).forEach(([key, allowed]) => {
                        const value = String(query[key] || 'all');
                        normalizedQuery[key] = allowed.has(value) ? value : 'all';
                    });
                }
                const included = normalizePositiveIds(selectedIds, maximum);
                const excluded = normalizePositiveIds(excludedIds, maximum).filter((id) => !included.includes(id));
                updateDraft({
                    ...state.draft,
                    selection: {
                        strategy: 'query',
                        selectedIds: included,
                        excludedIds: excluded,
                        query: normalizedQuery,
                    },
                }, reason);
                return true;
            },
            applyBulkSnapshot(rawDraft, preflight, { fingerprint = '', snapshotFingerprint = '', originSelection = null } = {}) {
                if (
                    route.scope !== 'bulk'
                    || !isRecord(rawDraft)
                    || hasForbiddenKey(rawDraft)
                    || rawDraft.workflow !== route.workflow
                    || rawDraft.sourceType !== route.sourceType
                    || rawDraft.scope !== route.scope
                    || rawDraft.selection?.strategy !== 'explicit'
                    || !isRecord(preflight)
                    || !/^[a-f0-9]{64}$/.test(String(fingerprint || ''))
                    || !/^[a-f0-9]{64}$/.test(String(snapshotFingerprint || ''))
                    || String(preflight.draftFingerprint || '') !== String(fingerprint || '')
                ) {
                    return false;
                }
                const nextDraft = clone(rawDraft);
                const definitions = definitionForRoute();
                const normalized = canonicalDraft(nextDraft, definitions.workflow.recipe, route, definitions.source, manifest.optionSources);
                if (normalized.issues.length || stableStringify(normalized.draft) !== stableStringify(nextDraft)) {
                    return false;
                }
                state.draft = nextDraft;
                const canonical = api.getCanonicalString();
                state.preflight = { status: 'ready', data: clone(preflight), canonical, error: '' };
                state.bulkSnapshot = {
                    fingerprint: String(fingerprint || ''),
                    snapshotFingerprint: String(snapshotFingerprint || ''),
                    originSelection: isRecord(originSelection) ? clone(originSelection) : null,
                };
                persist();
                notify('bulk-snapshot');
                return true;
            },
			applySingleSnapshot(rawDraft, preflight) {
                if (
                    route.workflow !== 'product_images'
                    || !isRecord(rawDraft)
                    || hasForbiddenKey(rawDraft)
                    || rawDraft.workflow !== route.workflow
                    || rawDraft.sourceType !== route.sourceType
                    || rawDraft.scope !== 'single'
                    || rawDraft.selection?.strategy !== 'explicit'
                    || rawDraft.selection?.selectedIds?.length !== 1
                    || !isRecord(preflight)
                    || String(preflight.draftFingerprint || '') === ''
                ) {
                    return false;
                }
                const nextDraft = { ...clone(rawDraft), scope: route.scope };
                const definitions = definitionForRoute();
                const normalized = canonicalDraft(nextDraft, definitions.workflow.recipe, route, definitions.source, manifest.optionSources);
                if (
                    normalized.issues.length
                    || stableStringify(normalized.draft) !== stableStringify(rawDraft)
                    || String(preflight.draftFingerprint) === ''
                ) {
                    return false;
                }
                state.draft = nextDraft;
                const canonical = api.getCanonicalString();
                state.preflight = { status: 'ready', data: clone(preflight), canonical, error: '' };
                state.bulkSnapshot = null;
                persist();
                notify('single-snapshot');
				return true;
			},
			getBulkSnapshot() {
                return state.bulkSnapshot ? clone(state.bulkSnapshot) : null;
            },
            setRecipe(recipe) {
                if (!isRecord(recipe) || hasForbiddenKey(recipe)) {
                    updateDraft({ ...state.draft, recipe: {} }, 'forbidden-recipe');
                    return false;
                }
                updateDraft({ ...state.draft, recipe: clone(recipe) }, 'recipe');
                return true;
            },
            setSourcePolicy(sourcePolicy) {
                if (!isRecord(sourcePolicy) || hasForbiddenKey(sourcePolicy)) {
                    return false;
                }
                updateDraft({ ...state.draft, sourcePolicy: clone(sourcePolicy) }, 'source-policy');
                return true;
            },
            setResultPolicy(resultPolicy) {
                if (!isRecord(resultPolicy) || hasForbiddenKey(resultPolicy)) {
                    return false;
                }
                updateDraft({ ...state.draft, resultPolicy: clone(resultPolicy) }, 'result-policy');
                return true;
            },
            setView(view) {
                state.view = view;
                notify('view');
            },
            setPreflightPending(canonical) {
                state.preflight = { status: 'loading', data: null, canonical, error: '' };
                notify('preflight');
            },
            setPreflightReady(canonical, data) {
                if (canonical !== api.getCanonicalString()) {
                    return false;
                }
                state.preflight = { status: 'ready', data: clone(data), canonical, error: '' };
                notify('preflight');
                return true;
            },
			setPreflightError(canonical, message) {
                if (canonical !== api.getCanonicalString()) {
                    return false;
                }
                state.preflight = { status: 'error', data: null, canonical, error: String(message || '') };
                notify('preflight');
				return true;
			},
			invalidateAcceptedReview(canonical, message) {
				if (canonical !== api.getCanonicalString()) {
					return false;
				}
				state.preflight = { status: 'error', data: null, canonical, error: String(message || '') };
				state.bulkSnapshot = null;
				notify('review-expired');
				return true;
			},
            clearDrafts({ resetCurrent = true } = {}) {
                try {
                    const keys = [];
                    for (let index = 0; index < (storage?.length || 0); index += 1) {
                        const key = storage.key(index);
                        if (typeof key === 'string' && key.startsWith(STORAGE_PREFIX)) {
                            keys.push(key);
                        }
                    }
                    keys.forEach((key) => storage.removeItem(key));
                } catch (_) {
                    storageAvailable = false;
                }
                if (resetCurrent) {
                    state.draft = blankDraft();
                    clearPreflight();
                    notify('drafts-cleared');
                }
            },
            destroy() {
                subscribers.clear();
            },
        };

        return api;
    };

    Studio.store = Object.freeze({
        createStore,
        validateManifest,
        normalizeInitialRoute,
        isRecord,
        clone,
        routeSupported,
        firstSupportedRoute,
        normalizeRecipe,
        createDefaultRecipe,
        canonicalDraft,
        hasForbiddenKey,
        stableStringify,
    });
})();
