(() => {
    'use strict';

    window.FotogenicAI = window.FotogenicAI || {};
    const Studio = window.FotogenicAI.Studio = window.FotogenicAI.Studio || {};
    const { __, sprintf } = Studio.i18n;

    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', 'store_queue']);
    const STATUS_LABELS = Object.freeze({
        idle: __( 'Not started', 'fotogenic-ai' ),
        loading: __( 'Loading', 'fotogenic-ai' ),
        queued: __( 'Queued', 'fotogenic-ai' ),
        starting: __( 'Starting', 'fotogenic-ai' ),
        processing: __( 'Processing', 'fotogenic-ai' ),
        finalizing: __( 'Saving results', 'fotogenic-ai' ),
        pausing: __( 'Pausing', 'fotogenic-ai' ),
        paused: __( 'Paused', 'fotogenic-ai' ),
        stopping: __( 'Stopping', 'fotogenic-ai' ),
        stopped: __( 'Stopped', 'fotogenic-ai' ),
        uncertain: __( 'Status needs confirmation', 'fotogenic-ai' ),
        partial: __( 'Completed with issues', 'fotogenic-ai' ),
        completed: __( 'Completed', 'fotogenic-ai' ),
        failed: __( 'Failed', 'fotogenic-ai' ),
        skipped: __( 'Skipped', 'fotogenic-ai' ),
        removed: __( 'Removed', 'fotogenic-ai' ),
    });

    const cleanText = (value, maximum = 240) => String(value ?? '')
        .replace(/[\u0000-\u001f\u007f]+/g, ' ')
        .replace(/\s+/g, ' ')
        .trim()
        .slice(0, Math.max(0, maximum));

    const positiveInteger = (value, fallback = 0, maximum = Number.MAX_SAFE_INTEGER) => {
        const number = Number(value);
        return Number.isSafeInteger(number) && number > 0 ? Math.min(number, maximum) : fallback;
    };

    const nonNegativeInteger = (value, fallback = 0, maximum = Number.MAX_SAFE_INTEGER) => {
        const number = Number(value);
        return Number.isSafeInteger(number) && number >= 0 ? Math.min(number, maximum) : fallback;
    };

    const safeUrl = (value) => {
        const raw = cleanText(value, 4096);
        if (!raw || /^(?:data|blob|file|javascript):/i.test(raw)) return '';
        if (raw.startsWith('/') && !raw.startsWith('//')) return raw;
        try {
            const parsed = new URL(raw);
            if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password) return '';
            return parsed.href;
        } catch (_) {
            return '';
        }
    };

    const safeCode = (value) => {
        const code = cleanText(value, 64).toLowerCase();
        return /^[a-z0-9][a-z0-9_-]*$/.test(code) ? code : '';
    };

    const knownValue = (value, allowed, fallback) => {
        const normalized = cleanText(value, 64).toLowerCase();
        return allowed.has(normalized) ? normalized : fallback;
    };

    const normalizeRoute = (value = {}) => ({
        workflow: knownValue(value.workflow, WORKFLOWS, 'product_images'),
        sourceType: knownValue(value.sourceType, SOURCE_TYPES, 'media'),
        scope: knownValue(value.scope, SCOPES, 'single'),
    });

	const normalizeOutputQuality = (value, workflow = '') => {
		if (workflow === 'increase_resolution') return '2K';
		const quality = cleanText(value, 16).toUpperCase();
		const supported = workflow === 'campaign_set'
			? ['2K', '4K']
			: ['1K', '2K', '4K'];
		return supported.includes(quality) ? quality : '';
	};

    const normalizeAttachment = (value = {}) => {
        if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
        const attachmentId = positiveInteger(value.attachmentId);
        const fullUrl = safeUrl(value.fullUrl);
        if (!attachmentId || !fullUrl) return null;
        const width = nonNegativeInteger(value.width, 0, 100000);
        const height = nonNegativeInteger(value.height, 0, 100000);
        return Object.freeze({
            attachmentId,
            fullUrl,
            thumbUrl: safeUrl(value.thumbUrl) || fullUrl,
            editUrl: safeUrl(value.editUrl),
            label: cleanText(value.label, 200) || sprintf( __( 'Image %d', 'fotogenic-ai' ), attachmentId ),
            alt: cleanText(value.alt, 300),
            width,
            height,
            bytes: nonNegativeInteger(value.bytes, 0, 10 ** 12),
            mime: cleanText(value.mime, 100).toLowerCase(),
            subjectId: positiveInteger(value.subjectId),
            groupId: cleanText(value.groupId, 100),
            canDelete: value.canDelete === true,
            createdAt: cleanText(value.createdAt, 100),
			modifiedAt: cleanText(value.modifiedAt, 100),
        });
    };

    const normalizeAttachments = (items, maximum = 100) => {
        const normalized = new Map();
        (Array.isArray(items) ? items : []).slice(0, Math.max(0, maximum)).forEach((item) => {
            const attachment = normalizeAttachment(item);
            if (attachment) normalized.set(attachment.attachmentId, attachment);
        });
        return [...normalized.values()];
    };

	const destinationLabel = (draft = {}) => {
		const workflow = cleanText(draft?.workflow, 64).toLowerCase();
		const sourceType = cleanText(draft?.sourceType, 64).toLowerCase();
		if (sourceType !== 'product' || !['product_images', 'campaign_set'].includes(workflow)) {
			return __( 'New Media Library attachments', 'fotogenic-ai' );
		}

		if (workflow === 'product_images') {
			const assignmentMode = cleanText(draft?.resultPolicy?.assignmentMode, 64).toLowerCase();
			if (assignmentMode === 'featured_replace') {
				return __( 'Save to Media Library, then replace the selected Product or Variation image after review', 'fotogenic-ai' );
			}
			if (assignmentMode === 'gallery_append') {
				return __( 'Save to Media Library, then append eligible Product results to galleries; Variations remain for review', 'fotogenic-ai' );
			}
			return __( 'Save to Media Library', 'fotogenic-ai' );
		}

		const groups = Array.isArray(draft?.sourcePolicy?.groups) ? draft.sourcePolicy.groups : [];
		if (!groups.length) {
			return cleanText(draft?.resultPolicy?.assignmentMode, 64).toLowerCase() === 'gallery_append'
				? __( 'Save to Media Library, then append to Product galleries', 'fotogenic-ai' )
				: __( 'Save to Media Library', 'fotogenic-ai' );
		}

		let parentAppend = false;
		let variationReview = false;
		let otherReview = false;
		groups.forEach((group) => {
			const targetType = cleanText(group?.target?.targetType, 64).toLowerCase();
			const assignmentMode = cleanText(group?.resultPolicy?.assignmentMode, 64).toLowerCase();
			if (targetType === 'product' && assignmentMode === 'gallery_append') {
				parentAppend = true;
				return;
			}
			if (targetType === 'variation') {
				variationReview = true;
				return;
			}
			otherReview = true;
		});

		if (parentAppend && variationReview) {
			return __( 'Save to Media Library, append parent Product results, and keep Variation results for review', 'fotogenic-ai' );
		}
		if (parentAppend && otherReview) {
			return __( 'Save to Media Library, append selected Product results, and keep other results for review', 'fotogenic-ai' );
		}
		if (parentAppend) {
			return __( 'Save to Media Library, then append to Product galleries', 'fotogenic-ai' );
		}
		if (variationReview && !otherReview) {
			return __( 'Save to Media Library', 'fotogenic-ai' );
		}
		return __( 'Save to Media Library', 'fotogenic-ai' );
	};

    const element = (tag, className = '', text) => {
        const node = document.createElement(tag);
        if (className) node.className = className;
        if (text !== undefined) node.textContent = String(text);
        return node;
    };

    const button = (label, className = '') => {
        const node = element('button', className, label);
        node.type = 'button';
        return node;
    };

    const requestState = ({ message, loading = false, onRetry = null }) => {
        const state = element('div', 'fotogenic-ai-picker-state');
        state.setAttribute('role', loading ? 'status' : 'alert');
        const icon = window.FotogenicAI.Shared.ui.icon.create(loading ? 'arrows-clockwise' : 'warning', { weight: 'regular', size: 20 });
        if (loading) icon.classList.add('fotogenic-ai-icon--spin');
        state.append(icon, element('strong', '', message));
        if (onRetry) {
            const retry = button(__( 'Retry', 'fotogenic-ai' ), 'secondary small');
            retry.addEventListener('click', onRetry);
            state.appendChild(retry);
        }
        return state;
    };

    const afterPaint = (callback) => {
        if (typeof window.requestAnimationFrame === 'function') {
            window.requestAnimationFrame(callback);
        } else {
            window.setTimeout(callback, 0);
        }
    };

    const focusHeading = (mount) => {
        const heading = mount?.querySelector?.('h1[tabindex="-1"], h2[tabindex="-1"], [data-fa-view-heading], h1, h2');
        if (heading?.focus) afterPaint(() => heading.focus({ preventScroll: true }));
    };

    const formatDimensions = (attachment) => (
        attachment?.width > 0 && attachment?.height > 0
            ? sprintf( __( '%1$d × %2$d px', 'fotogenic-ai' ), attachment.width, attachment.height )
            : __( 'Dimensions unavailable', 'fotogenic-ai' )
    );

    const formatBytes = (bytes) => {
        const size = nonNegativeInteger(bytes);
        if (!size) return __( 'File size unavailable', 'fotogenic-ai' );
        const units = [__( 'B', 'fotogenic-ai' ), __( 'KB', 'fotogenic-ai' ), __( 'MB', 'fotogenic-ai' ), __( 'GB', 'fotogenic-ai' )];
        const index = Math.min(units.length - 1, Math.floor(Math.log(size) / Math.log(1024)));
        const value = size / (1024 ** index);
        return sprintf( __( '%1$s %2$s', 'fotogenic-ai' ), new Intl.NumberFormat(undefined, { maximumFractionDigits: index ? 1 : 0 }).format(value), units[index] );
    };

    const formatFileType = (mime) => {
        const normalized = cleanText(mime, 100).toLowerCase();
        const subtype = normalized.startsWith('image/') ? normalized.slice(6).replace('+xml', '') : '';
        return subtype ? subtype.toUpperCase() : __( 'File type unavailable', 'fotogenic-ai' );
    };

    Studio.viewModel = Object.freeze({
        WORKFLOWS,
        SOURCE_TYPES,
        SCOPES,
        STATUS_LABELS,
        cleanText,
        positiveInteger,
        nonNegativeInteger,
        safeUrl,
        safeCode,
        knownValue,
        normalizeRoute,
		normalizeOutputQuality,
        normalizeAttachment,
        normalizeAttachments,
		destinationLabel,
        element,
        button,
        requestState,
        afterPaint,
        focusHeading,
        formatDimensions,
        formatBytes,
        formatFileType,
    });
})();
