(() => {
    'use strict';

    window.FotogenicAI = window.FotogenicAI || {};
    const Studio = window.FotogenicAI.Studio = window.FotogenicAI.Studio || {};
    const { __, _n, sprintf, labels } = Studio.i18n;
    const { element, destinationLabel } = Studio.viewModel;
    const icons = window.FotogenicAI.Shared?.ui?.icon;
    if (!icons) throw new Error('Fotogenic AI shared icon helper is required.');

    const numberOrNull = (value) => {
        const number = Number(value);
        return Number.isFinite(number) && number >= 0 ? number : null;
    };

    const finiteOrNull = (value) => {
        const number = Number(value);
        return Number.isFinite(number) ? number : null;
    };

	const normalizeAuthoritativePreflight = (payload) => {
		const authority = payload?.authority;
		const pricing = payload?.pricing;
		const evidence = payload?.authorityEvidence;
		if (
            !payload
			|| typeof payload !== 'object'
			|| Array.isArray(payload)
			|| Object.keys(payload).sort().join(',') !== 'authority,authorityEvidence,balance,balanceAfter,draftFingerprint,pricing,sourceFingerprint,totalCredits,totalItems,totalOutputs'
			|| !/^[a-f0-9]{64}$/.test(String(payload.draftFingerprint || '').trim())
			|| !/^[a-f0-9]{64}$/.test(String(payload.sourceFingerprint || '').trim())
			|| !pricing
			|| typeof pricing !== 'object'
			|| Array.isArray(pricing)
			|| Object.keys(pricing).sort().join(',') !== 'creditsPerOutput,quality,workflow'
			|| !Studio.viewModel.WORKFLOWS.has(String(pricing.workflow || ''))
			|| numberOrNull(pricing.creditsPerOutput) === null
			|| typeof pricing.quality !== 'string'
			|| !evidence
			|| typeof evidence !== 'object'
			|| Array.isArray(evidence)
			|| Object.keys(evidence).sort().join(',') !== 'configurationFingerprint,contextFingerprint,creditsFingerprint,pricingFingerprint'
			|| Object.values(evidence).some((fingerprint) => !/^[a-f0-9]{64}$/.test(String(fingerprint || '').trim()))
			|| !authority
			|| typeof authority !== 'object'
			|| Array.isArray(authority)
			|| Object.keys(authority).length !== 3
			|| authority.version !== 1
			|| !/^[a-f0-9]{64}$/.test(String(authority.fingerprint || '').trim())
			|| !Number.isSafeInteger(Number(authority.expiresAt))
			|| Number(authority.expiresAt) <= Math.floor(Date.now() / 1000)
        ) {
            return null;
        }
		const totalItems = numberOrNull(payload.totalItems);
        const totalOutputs = numberOrNull(payload.totalOutputs);
        const totalCredits = numberOrNull(payload.totalCredits);
        const balance = numberOrNull(payload.balance);
        const balanceAfter = finiteOrNull(payload.balanceAfter);
        if (
            totalItems === null
			|| !Number.isInteger(totalItems)
			|| totalItems < 1
			|| totalOutputs === null
            || !Number.isInteger(totalOutputs)
            || totalOutputs < 1
            || totalCredits === null
            || balance === null
            || balanceAfter === null
        ) {
            return null;
        }
        return {
            draftFingerprint: payload.draftFingerprint.trim(),
			sourceFingerprint: payload.sourceFingerprint.trim(),
			totalItems,
			pricing: {
				workflow: pricing.workflow,
				creditsPerOutput: Number(pricing.creditsPerOutput),
				quality: pricing.quality,
			},
			authorityEvidence: Object.fromEntries(
				Object.entries(evidence).map(([key, fingerprint]) => [key, String(fingerprint).trim()])
			),
			authority: {
				version: 1,
				fingerprint: authority.fingerprint.trim(),
				expiresAt: Number(authority.expiresAt),
			},
            totalOutputs,
            totalCredits,
            balance,
            balanceAfter,
		};
	};

	const normalizeStoreQueueReview = (payload) => {
		if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return null;
		const authority = payload.authority;
		const pricing = payload.pricing;
		const evidence = payload.authorityEvidence;
		const queueUuid = String(payload.queueUuid || '').toLowerCase();
		const snapshotFingerprint = String(payload.snapshotFingerprint || '').toLowerCase();
		const totalItems = numberOrNull(payload.selectedCount);
		const totalOutputs = numberOrNull(payload.totalOutputs);
		const totalCredits = numberOrNull(payload.totalCredits);
		const balance = numberOrNull(payload.balance);
		const balanceAfter = finiteOrNull(payload.balanceAfter);
		if (
			!/^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(queueUuid)
			|| !/^[a-f0-9]{64}$/.test(snapshotFingerprint)
			|| !Number.isSafeInteger(Number(payload.revision))
			|| Number(payload.revision) < 1
			|| !Number.isInteger(totalItems) || totalItems < 1
			|| !Number.isInteger(totalOutputs) || totalOutputs < 1
			|| totalCredits === null || balance === null || balanceAfter === null
			|| !pricing || typeof pricing !== 'object' || Array.isArray(pricing)
			|| !evidence || typeof evidence !== 'object' || Array.isArray(evidence)
			|| !authority || typeof authority !== 'object' || Array.isArray(authority)
			|| authority.version !== 1
			|| !/^[a-f0-9]{64}$/.test(String(authority.fingerprint || ''))
			|| !Number.isSafeInteger(Number(authority.expiresAt))
			|| Number(authority.expiresAt) <= Math.floor(Date.now() / 1000)
		) return null;
		return {
			storeQueue: true,
			queueUuid,
			revision: Number(payload.revision),
			snapshotFingerprint,
			totalItems,
			totalOutputs,
			totalCredits,
			balance,
			balanceAfter,
			pricing: JSON.parse(JSON.stringify(pricing)),
			authorityEvidence: JSON.parse(JSON.stringify(evidence)),
			authority: {
				version: 1,
				fingerprint: String(authority.fingerprint),
				expiresAt: Number(authority.expiresAt),
			},
		};
	};

    const runItemCount = (draft) => draft.workflow === 'campaign_set' && Array.isArray(draft.sourcePolicy?.groups)
        ? draft.sourcePolicy.groups.length
        : draft.selection.selectedIds.length;

    const estimateTier = (draft) => draft.workflow === 'increase_resolution'
        ? 'highQualityEnhanceGeneration'
        : (draft.workflow === 'campaign_set' ? 'campaignSetGenerationCreditsPerImage'
            : (draft.recipe.quality === '4K' ? 'imageGenerationUltraHighQuality' : 'imageGenerationHighQuality'));

    // Display-only estimates use the account's cached catalog, never start authority.
    const estimateReview = ({ draft, pricing, balance, selectedCount }) => {
        const totalItems = draft.selection.strategy === 'query' ? selectedCount : runItemCount(draft);
        const outputsPerItem = draft.workflow === 'increase_resolution' ? 1 : Number(draft.recipe.count);
        const totalOutputs = draft.workflow === 'campaign_set'
            ? draft.sourcePolicy.groups.reduce((total, group) => total + Number(group.recipe.count), 0)
            : totalItems * outputsPerItem;
        const tierKey = estimateTier(draft);
        const cachedCost = pricing?.available === true ? pricing.tiers?.[tierKey] : null;
        const unitCost = typeof cachedCost === 'number' ? numberOrNull(cachedCost) : null;
        const totalCredits = unitCost !== null && unitCost >= 0
            ? Math.round(unitCost * totalOutputs * 1000000) / 1000000
            : null;
        const currentBalance = typeof balance === 'number' ? numberOrNull(balance) : null;
        return {
            totalItems, totalOutputs, totalCredits, balance: currentBalance,
            balanceAfter: currentBalance !== null && totalCredits !== null ? currentBalance - totalCredits : null,
        };
    };

    const createEstimateCache = ({ pricing, balance, creditsCache, fetchContext }) => {
        let cachedPricing = pricing;
        let cachedBalance = typeof balance === 'number' && Number.isFinite(balance) ? balance : null;
        let inFlight = null;
        let controller = null;
        let version = 0;
        const attempted = new Set();
        const estimate = (input) => estimateReview({ ...input, pricing: cachedPricing,
            balance: creditsCache?.getBalance({ allowStale: true }) ?? cachedBalance });
        const load = async (input, { retry = false } = {}) => {
            if (inFlight) {
                const waitingVersion = version;
                await inFlight;
                return waitingVersion === version ? load(input) : estimate(input);
            }
            const current = estimate(input);
            const missingTier = current.totalCredits === null ? estimateTier(input.draft) : '';
            const tier = missingTier && (retry || !attempted.has(missingTier)) ? missingTier : '';
            const needsBalance = current.balance === null && (retry || !attempted.has('balance'));
            if (!tier && !needsBalance) return current;
            if (tier) attempted.add(tier);
            if (needsBalance) attempted.add('balance');
            const requestVersion = version;
            controller = new AbortController();
            inFlight = (async () => {
                const result = await fetchContext({ tier, balance: needsBalance, signal: controller.signal });
                if (requestVersion !== version || !result?.ok) return;
                if (result.data?.pricing?.available === true) cachedPricing = result.data.pricing;
                const credits = result.data?.credits;
                if (typeof credits?.balance === 'number' && Number.isFinite(credits.balance)) {
                    cachedBalance = credits.balance;
                    if (creditsCache?.getBalance({ allowStale: true }) == null) creditsCache?.setBalance(credits.balance, credits);
                }
            })().catch((error) => {
                if (error?.name !== 'AbortError') creditsCache?.reportFailure('estimate', 'network_error');
            }).finally(() => {
                if (requestVersion === version) inFlight = null;
            });
            await inFlight;
            return estimate(input);
        };
        return Object.freeze({ estimate, load, clear() {
            version += 1;
            controller?.abort();
            inFlight = null;
            cachedPricing = null;
            cachedBalance = null;
            attempted.clear();
        } });
    };

    const deriveReadiness = ({
        state,
        canonical,
        authConnected,
        preflightEndpointAvailable,
        bulkSnapshotEndpointAvailable,
        bulkMeta = {},
    }) => {
        const selectedCount = runItemCount(canonical.draft);
        const executionScope = canonical.draft.scope;
        const queryBacked = executionScope === 'bulk' && canonical.draft.selection.strategy === 'query';
        const snapshotRequired = executionScope === 'bulk' && !state.bulkSnapshot;
        const generateScale = canonical.draft.workflow === 'product_images';
		const storeQueueQuery = generateScale
			&& canonical.draft.sourceType === 'product'
			&& queryBacked;
        const matchingCount = Math.max(0, Math.floor(Number(bulkMeta.matchingCount)) || 0);
        const maximum = Math.max(1, Math.floor(Number(bulkMeta.maximum)) || 1);
        const estimatedCount = Math.max(
            0,
            matchingCount - canonical.draft.selection.excludedIds.length + selectedCount
        );
        const candidateCount = queryBacked ? estimatedCount : selectedCount;
        if (snapshotRequired) {
            if (canonical.issues.length) {
                return {
                    ready: false,
                    reason: Studio.recipeForm.humanizeIssue(canonical.issues[0]),
                    issue: canonical.issues[0],
                    state: 'error',
                    requiresSnapshot: true,
                    estimatedCount: candidateCount,
                };
            }
            if (!authConnected) {
                return {
                    ready: false,
                    reason: generateScale
                        ? __( 'Connect your Fotogenic account to prepare this multi-item review.', 'fotogenic-ai' )
                        : __( 'Connect your Fotogenic account to prepare this Bulk review.', 'fotogenic-ai' ),
                    state: 'error',
                    requiresSnapshot: true,
                    estimatedCount: candidateCount,
                };
            }
            if (!bulkSnapshotEndpointAvailable) {
                return {
                    ready: false,
                    reason: __( 'This page cannot prepare your review. Reload Studio and try again.', 'fotogenic-ai' ),
                    state: 'error',
                    requiresSnapshot: true,
                    estimatedCount: candidateCount,
                };
            }
            if ((queryBacked && matchingCount < 1) || candidateCount < 1) {
                return { ready: false, reason: __( 'No matching source is selected.', 'fotogenic-ai' ), state: 'error', requiresSnapshot: true, estimatedCount: candidateCount };
            }
            if (!storeQueueQuery && ((queryBacked && matchingCount > maximum) || (!queryBacked && candidateCount > maximum))) {
                return {
                    ready: false,
                    reason: generateScale
                        ? sprintf( __( 'This query exceeds the %d-item selection maximum. Narrow the filters or select items explicitly.', 'fotogenic-ai' ), maximum )
                        : sprintf( __( 'This query exceeds the %d-item Bulk maximum. Narrow the filters or select items explicitly.', 'fotogenic-ai' ), maximum ),
                    state: 'error',
                    requiresSnapshot: true,
                    estimatedCount: candidateCount,
                };
            }
            return {
                ready: true,
                reason: __( 'Ready to continue. Pricing and balance are checked when you generate.', 'fotogenic-ai' ),
                state: 'success',
                requiresSnapshot: true,
                estimatedCount: candidateCount,
            };
        }
        if (selectedCount === 0) {
            return { ready: false, reason: __( 'Select an eligible source to continue.', 'fotogenic-ai' ), state: 'error' };
        }
        if (executionScope === 'single' && selectedCount !== 1) {
            return { ready: false, reason: __( 'Single mode requires exactly one source.', 'fotogenic-ai' ), state: 'error' };
        }
        if (canonical.issues.length) {
            return {
                ready: false,
                reason: Studio.recipeForm.humanizeIssue(canonical.issues[0]),
                issue: canonical.issues[0],
                state: 'error',
            };
        }
        if (!authConnected) {
            return { ready: false, reason: __( 'Connect your Fotogenic account to confirm pricing.', 'fotogenic-ai' ), state: 'error' };
        }
        if (!preflightEndpointAvailable) {
            return { ready: false, reason: __( 'Authoritative pricing is unavailable.', 'fotogenic-ai' ), state: 'error' };
        }
        const exact = state.preflight.canonical === Studio.store.stableStringify(canonical.draft)
            ? normalizeAuthoritativePreflight(state.preflight.data)
            : null;
        return {
            ready: true,
            reason: __( 'Ready to continue. Pricing and balance are checked when you generate.', 'fotogenic-ai' ),
            state: 'success',
            requiresPreflight: true,
            exact,
        };
    };

    const formatCredits = (value) => {
        const number = numberOrNull(value);
		return number === null ? __( 'Not available', 'fotogenic-ai' ) : new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }).format(number);
    };

    const createSummary = ({ mount, validation, reviewButton, onFixIssue } = {}) => {
        const renderValidation = ({ state, message, actionLabel = '', onAction = null } = {}) => {
            const validationState = state === 'error' ? 'error' : (state === 'loading' ? 'loading' : 'success');
            const validationIcon = icons.create(validationState === 'error' ? 'warning' : 'info', {
                weight: validationState === 'error' ? 'fill' : 'regular',
                size: 16,
            });
            validationIcon.classList.add('fotogenic-ai-studio-validation__icon');
            validation.replaceChildren(
                validationIcon,
                element('span', 'fotogenic-ai-studio-validation__message', message)
            );
            validation.dataset.state = validationState;
            validation.classList.toggle('fotogenic-ai-studio-sr-only', validationState === 'loading');
            validation.setAttribute('aria-live', validationState === 'loading' ? 'off' : 'polite');
            validation.tabIndex = -1;
            if (actionLabel && typeof onAction === 'function') {
                const action = element('button', 'fotogenic-ai-studio-validation__action', actionLabel);
                action.type = 'button';
                action.addEventListener('click', onAction);
                validation.appendChild(action);
            }
        };

        const showError = (message, { actionLabel = '', onAction = null } = {}) => {
            renderValidation({ state: 'error', message, actionLabel, onAction });
            reviewButton.disabled = true;
            reviewButton.setAttribute('aria-disabled', 'true');
            reviewButton.setAttribute('aria-describedby', validation.id);
        };

        const render = ({
            state,
            canonical,
            workflow,
            authConnected,
            preflightEndpointAvailable,
            bulkSnapshotEndpointAvailable,
            bulkMeta = {},
        }) => {
            const readiness = deriveReadiness({
                state,
                canonical,
                authConnected,
                preflightEndpointAvailable,
                bulkSnapshotEndpointAvailable,
                bulkMeta,
            });
            const exact = readiness.exact || normalizeAuthoritativePreflight(
                state.preflight.canonical === Studio.store.stableStringify(canonical.draft)
                    ? state.preflight.data
                    : null
            );
            const matchingCount = Math.max(0, Math.floor(Number(bulkMeta.matchingCount)) || 0);
            const maximum = Math.max(1, Math.floor(Number(bulkMeta.maximum)) || 1);
            const itemCount = readiness.requiresSnapshot
                ? Math.max(0, Number(readiness.estimatedCount) || 0)
                : runItemCount(canonical.draft);
            const runParts = [
                labels[state.route.workflow] || state.route.workflow,
                labels[state.route.sourceType] || state.route.sourceType,
                ...(canonical.draft.workflow === 'product_images' ? [] : [labels[canonical.draft.scope] || canonical.draft.scope]),
            ];
            const rows = [
                [__( 'Run', 'fotogenic-ai' ), [
                    ...runParts,
                ].join(' · ')],
                [readiness.requiresSnapshot ? __( 'Selected estimate', 'fotogenic-ai' ) : __( 'Selected items', 'fotogenic-ai' ), itemCount],
                ...(canonical.draft.scope === 'bulk'
					? (canonical.draft.workflow === 'product_images' && canonical.draft.sourceType === 'product'
						? (canonical.draft.selection.strategy === 'query'
							? [
								[__( 'Matching Products', 'fotogenic-ai' ), matchingCount],
								[__( 'Processing', 'fotogenic-ai' ), __( 'Automatic Product batches', 'fotogenic-ai' )],
							]
							: [])
						: [
							[canonical.draft.selection.strategy === 'query' ? __( 'Matching sources', 'fotogenic-ai' ) : __( 'Sources on current page', 'fotogenic-ai' ), matchingCount || canonical.draft.selection.selectedIds.length],
							[__( 'Bulk maximum', 'fotogenic-ai' ), maximum],
						])
					: []),
                [exact ? __( 'Total outputs', 'fotogenic-ai' ) : __( 'Planned outputs', 'fotogenic-ai' ), exact ? exact.totalOutputs : __( 'Pending', 'fotogenic-ai' )],
				[__( 'Credits required', 'fotogenic-ai' ), exact ? formatCredits(exact.totalCredits) : __( 'Not available', 'fotogenic-ai' ), 'is-cost'],
				[__( 'Current balance', 'fotogenic-ai' ), exact ? formatCredits(exact.balance) : __( 'Not available', 'fotogenic-ai' )],
                ...(exact ? [[__( 'Balance after', 'fotogenic-ai' ), formatCredits(exact.balanceAfter)]] : []),
                [__( 'Destination', 'fotogenic-ai' ), destinationLabel(canonical.draft)],
            ];
            const metrics = element('dl', 'fotogenic-ai-studio-metrics');
            rows.forEach(([label, value, className]) => {
                const row = element('div');
                if (className) row.classList.add(className);
                row.append(element('dt', '', label), element('dd', '', value));
                metrics.appendChild(row);
            });

            const checklist = element('ul', 'fotogenic-ai-summary-readiness');
            const checks = [
                [itemCount > 0, __( 'Source', 'fotogenic-ai' ), __( 'needed', 'fotogenic-ai' )],
                [canonical.issues.length === 0, __( 'Recipe', 'fotogenic-ai' ), __( 'incomplete', 'fotogenic-ai' )],
                [Boolean(exact), readiness.requiresSnapshot ? __( 'Review check', 'fotogenic-ai' ) : __( 'Pricing', 'fotogenic-ai' ), __( 'pending', 'fotogenic-ai' )],
                [Boolean(exact) && exact.balance + 0.000001 >= exact.totalCredits, __( 'Balance', 'fotogenic-ai' ), __( 'pending', 'fotogenic-ai' )],
            ];
            checks.forEach(([complete, label, pending]) => {
                const item = element('li', complete ? 'is-complete' : 'is-pending', sprintf( __( '%1$s %2$s', 'fotogenic-ai' ), label, complete ? __( 'ready', 'fotogenic-ai' ) : pending ));
                checklist.appendChild(item);
            });
            mount.replaceChildren(metrics, checklist);

            renderValidation({ state: readiness.state, message: readiness.reason });
            if (readiness.issue && typeof onFixIssue === 'function') {
                const fix = element('button', 'fotogenic-ai-studio-validation__action', __( 'Fix this recipe option', 'fotogenic-ai' ));
                fix.type = 'button';
                fix.addEventListener('click', () => onFixIssue(readiness.issue));
                validation.appendChild(fix);
            }
            reviewButton.disabled = !readiness.ready;
            reviewButton.setAttribute('aria-disabled', readiness.ready ? 'false' : 'true');
			const reviewLabel = __( 'Continue to generate', 'fotogenic-ai' );
			const reviewArrow = icons.create('arrow-right', { weight: 'bold', size: 17 });
			reviewButton.replaceChildren(document.createTextNode(reviewLabel), reviewArrow);
			if (readiness.ready) reviewButton.removeAttribute('aria-describedby');
			else reviewButton.setAttribute('aria-describedby', validation.id);
			reviewButton.setAttribute('aria-label', reviewLabel);
            return readiness;
        };

        return Object.freeze({ render, showError });
    };

    Studio.summary = Object.freeze({
        createSummary,
        deriveReadiness,
        normalizeAuthoritativePreflight,
		normalizeStoreQueueReview,
        estimateReview,
        createEstimateCache,
    });
})();
