/**
 * Cast Conductor Proprietary License v5
 * SPDX-License-Identifier: LicenseRef-CastConductor-Proprietary-v5
 * (See other module headers for full notice – kept concise here; consider refactor to shared banner util.)
 *
 * Container Preview Module
 * Responsibility: All logic for rendering container content previews (zoned & single),
 * scheduling/rotation, CSS injection, fitting, background application, and admin seeding helpers.
 * Extracted from monolith canvas-editor.js to reduce surface area and enable future inspector &
 * scheduling panel modules.
 */
import { ccveBus } from './event-bus.js';

export function initContainerPreviewState(editor) {
	if (!editor.containerPreviewState) editor.containerPreviewState = {}; // { [containerId]: { timerId, currentIndex, schedule: [] } }
}

export async function renderContainerPreview(editor, containerId, blocks) {
	const host = document.getElementById(`container-content-${containerId}`);
	if (!host) { try { console.debug('[CC] renderContainerPreview:no-host', { containerId }); } catch(_){} return; }
	if (!editor.showContentPreview) {
		host.innerHTML = '<div class="canvas-preview-placeholder">Preview hidden</div>';
		removeContainerPreviewStyles(containerId);
		return;
	}
	if (!Array.isArray(blocks) || blocks.length === 0) {
		host.innerHTML = '<div class="canvas-preview-placeholder">No assignments</div>';
		removeContainerPreviewStyles(containerId);
		return;
	}
	const containerObj = (editor._containersById || {})[String(containerId)] || null;
	const hasZones = !!(containerObj && containerObj.layout && Array.isArray(containerObj.layout.zones) && containerObj.layout.zones.length);
	if (hasZones) {
		return renderZonedContainer(editor, host, containerId, containerObj);
	}
	return renderSingleContainer(editor, host, containerId, blocks);
}

async function renderZonedContainer(editor, host, containerId, containerObj) {
	try {
		host.innerHTML = '';
		const zones = (containerObj.layout && Array.isArray(containerObj.layout.zones)) ? containerObj.layout.zones : [];
		const active = new Set(
			Array.isArray(containerObj.layout?.activeZoneIds) && containerObj.layout.activeZoneIds.length
				? containerObj.layout.activeZoneIds
				: zones.map(z => z.id)
		);
		if (editor.allowAdminAutoSeedingZones) {
			await maybeSeedUpperThird(editor, containerObj);
		}
		let renderedAny = false;
		for (const z of zones) {
			if (!active.has(z.id)) continue;
			const zid = String(z.id);
			let list = [];
			try { list = await editor.getZoneAssignments(containerId, zid); } catch(e) { console.warn('Zone assignments fetch failed', e); list = []; }
			const enabled = (list || []).filter(x => String(x.enabled ?? 1) !== '0');
			if (!enabled.length) continue;
			const chosen = enabled[0];
			const blockId = chosen.content_block_id;
			const seed = editor.getDeterministicSeed();
			const pvResp = await fetch(`${castconductorCanvasAjax.rest_url}castconductor/v5/canvas-editor/preview`, {
				method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': castconductorCanvasAjax.nonce },
				body: JSON.stringify({ block_id: blockId, item_index: 0, shuffle: false, seed })
			});
			if (!pvResp.ok) { console.warn('Zone preview HTTP error', { zone: zid, status: pvResp.status }); continue; }
			let pvData = null; try { pvData = await pvResp.json(); } catch(e) { console.warn('Zone preview JSON parse failed', e); continue; }
			if (!pvData?.success || !pvData.preview?.html) { console.warn('Zone preview response missing html', { zone: zid, pvData }); continue; }
			const wrap = document.createElement('div');
			wrap.className = 'zone-preview-wrap cc-fit-outer';
			wrap.style.cssText = `left:${parseInt(z.rect?.x||0,10)}px;top:${parseInt(z.rect?.y||0,10)}px;width:${parseInt(z.rect?.w||0,10)}px;height:${parseInt(z.rect?.h||0,10)}px;`;
			const inner = document.createElement('div'); inner.className = 'cc-fit-inner'; inner.innerHTML = pvData.preview.html; wrap.appendChild(inner);
			const needsRoku = /weather|location_time/.test(String(chosen.block_type||''));
			if (needsRoku) { const badge = document.createElement('div'); badge.className = 'zone-preview-badge'; badge.textContent = 'Roku viewer data required'; wrap.appendChild(badge); }
			host.appendChild(wrap);
			applyContainerPreviewStyles(containerId, pvData.preview.css);
			requestAnimationFrame(() => fitInnerToOuter(editor, inner, wrap));
			renderedAny = true;
		}
		if (!renderedAny) {
			const zoneCount = zones.length; const activeCount = Array.from(active).length;
			const msg = zoneCount ? `No zone previews. Zones=${zoneCount}, Active=${activeCount}. Assign blocks to active zones.` : 'No zones defined on this container.';
			host.innerHTML = `<div class="canvas-preview-placeholder">${editor.escapeHtml(msg)}</div>`;
			removeContainerPreviewStyles(containerId);
		}
	} catch(e) {
		console.error('Render zoned container preview failed:', e);
		host.innerHTML = '<div class="canvas-preview-placeholder">Preview unavailable</div>';
		removeContainerPreviewStyles(containerId);
	}
}

async function renderSingleContainer(editor, host, containerId, blocks) {
	const enabledBlocks = blocks.filter(b => String(b.enabled ?? 1) !== '0');
	const st = editor.containerPreviewState[String(containerId)] || { currentIndex: 0, schedule: [] };
	const schedule = Array.isArray(st.schedule) && st.schedule.length ? st.schedule : null;
	const candidateIds = (() => {
		if (schedule && schedule.length) {
			const start = Math.max(0, st.currentIndex % schedule.length);
			const ordered = schedule.slice(start).concat(schedule.slice(0, start));
			const set = new Set(enabledBlocks.map(b => String(b.content_block_id)));
			return ordered.filter(id => set.has(String(id)));
		}
		return enabledBlocks.map(b => b.content_block_id);
	})();
	let rendered = false; let lastError=''; const debugInfos=[];
	for (let i=0;i<candidateIds.length;i++) {
		const blockId = candidateIds[i];
		try {
			const seed = editor.getDeterministicSeed();
			const s = editor.containerPreviewState[String(containerId)] || { currentIndex:0, schedule:[] };
			const itemIndex = Array.isArray(s.schedule) && s.schedule.length ? (s.currentIndex % s.schedule.length) : 0;
			const pvResp = await fetch(`${castconductorCanvasAjax.rest_url}castconductor/v5/canvas-editor/preview`, { method:'POST', headers:{ 'Content-Type':'application/json','X-WP-Nonce':castconductorCanvasAjax.nonce }, body: JSON.stringify({ block_id:blockId, item_index:itemIndex, shuffle:!!editor.previewShuffle, seed }) });
			if (!pvResp.ok) { const errMsg = (pvResp.status===401||pvResp.status===403) ? 'Session expired (401/403). Refresh the page.' : `HTTP ${pvResp.status}`; lastError=errMsg; debugInfos.push({ blockId, itemIndex, ok:false, status:pvResp.status, error:errMsg }); continue; }
			let pvData=null; try { pvData=await pvResp.json(); } catch(e){ lastError='Invalid preview JSON'; debugInfos.push({ blockId, itemIndex, ok:false, error:'invalid_json' }); continue; }
			if (!pvData?.success || !pvData.preview?.html) { if (pvData?.message) lastError=String(pvData.message); debugInfos.push({ blockId, itemIndex, ok:false, error: pvData?.message||'missing_html', data: pvData }); continue; }
			const outer=document.createElement('div'); outer.className='cc-fit-outer';
			const inner=document.createElement('div'); inner.className='cc-fit-inner'; inner.innerHTML=pvData.preview.html; outer.appendChild(inner);
			const bt=(enabledBlocks.find(b => String(b.content_block_id)===String(blockId))||{}).block_type || '';
			if (/weather|location_time/.test(String(bt))) { const badge=document.createElement('div'); badge.className='container-preview-badge'; badge.textContent='Roku viewer data required'; outer.appendChild(badge); }
			host.innerHTML=''; host.appendChild(outer); applyContainerPreviewStyles(containerId, pvData.preview.css);
			requestAnimationFrame(() => fitInnerToOuter(editor, inner, outer)); debugInfos.push({ blockId, itemIndex, ok:true }); rendered=true; break;
		} catch(_) { if (!lastError) lastError='Preview request failed'; debugInfos.push({ blockId, ok:false, error:'request_failed' }); }
	}
	if (!rendered) {
		const msg = lastError ? `Preview unavailable: ${editor.escapeHtml(String(lastError))}` : 'Preview unavailable';
		const details = debugInfos.length ? `<div style="margin-top:6px; text-align:left; max-height:120px; overflow:auto; font-family:ui-monospace,monospace; font-size:10px; color:#94a3b8;">` + debugInfos.map(d => { const id=editor.escapeHtml(String(d.blockId ?? '?')); const it=editor.escapeHtml(String(d.itemIndex ?? 0)); const ok=d.ok?'ok':'fail'; const st=(d.status!==undefined)?` status=${editor.escapeHtml(String(d.status))}`:''; const er=d.error?` error=${editor.escapeHtml(String(d.error))}`:''; return `<div>• block ${id} item ${it}: ${ok}${st}${er}</div>`; }).join('') + `</div>` : '';
		host.innerHTML = `<div class="canvas-preview-placeholder">${msg}${details}</div>`;
		removeContainerPreviewStyles(containerId);
	}
}

export function startContainerPreviewCycle(editor, containerId, blocks) {
	const key=String(containerId);
	if (!editor.containerPreviewState[key]) editor.containerPreviewState[key]={ timerId:null, currentIndex:0, schedule:[] };
	stopContainerPreviewCycle(editor, containerId);
	const containerEl = document.querySelector(`.canvas-container[data-container-id="${key}"]`);
	const hasZones = !!(editor._containersById && editor._containersById[key] && editor._containersById[key].layout && Array.isArray(editor._containersById[key].layout.zones) && editor._containersById[key].layout.zones.length);
	if (hasZones) return;
	let intervalSec = parseInt(containerEl?.dataset?.rotationInterval || '15', 10);
	if (intervalSec < 10) intervalSec = 12; if (intervalSec > 20 && intervalSec < 25) intervalSec = 15;
	const enabledBlocks = Array.isArray(blocks) ? blocks.filter(b => String(b.enabled ?? 1) !== '0') : [];
	if (enabledBlocks.length <= 1 || !editor.showContentPreview) return;
	if (!editor.containerPreviewState[key].schedule || editor.containerPreviewState[key].schedule.length === 0) {
		buildContainerSchedule(editor, containerId, blocks, editor.previewShuffle);
	}
	const timerId = setInterval(() => {
		const s = editor.containerPreviewState[key] || (editor.containerPreviewState[key]={ timerId:null, currentIndex:0, schedule:[] });
		const schedLen = Array.isArray(s.schedule) ? s.schedule.length : 0;
		if (schedLen <= 0) buildContainerSchedule(editor, containerId, blocks, editor.previewShuffle);
		const len = (editor.containerPreviewState[key].schedule || []).length || enabledBlocks.length;
		s.currentIndex = (s.currentIndex + 1) % Math.max(1, len);
		renderContainerPreview(editor, containerId, blocks);
	}, Math.max(5, intervalSec) * 1000);
	editor.containerPreviewState[key].timerId = timerId;
}

export function stopContainerPreviewCycle(editor, containerId) {
	const key=String(containerId);
	const st=editor.containerPreviewState[key];
	if (st?.timerId) { clearInterval(st.timerId); st.timerId=null; }
}

export function stopAllPreviewCycles(editor) {
	Object.keys(editor.containerPreviewState).forEach(cid => stopContainerPreviewCycle(editor, cid));
}

export function buildContainerSchedule(editor, containerId, blocks, shuffle=false) {
	const key=String(containerId);
	const enabled = (blocks || []).filter(b => String(b.enabled ?? 1) !== '0');
	if (!enabled.length) { editor.containerPreviewState[key] = { ...(editor.containerPreviewState[key]||{}), schedule:[], currentIndex:0 }; return []; }
	let percents = enabled.map(b => ({ id: b.content_block_id, p: Math.max(0, Math.round(parseFloat(b.rotation_percentage || 0))) }));
	const sum = percents.reduce((a,x)=>a+x.p,0);
	if (sum !== 100 && sum > 0) {
		percents = percents.map(x => ({ id:x.id, p: Math.round((x.p / sum) * 100) }));
		let drift = 100 - percents.reduce((a,x)=>a+x.p,0);
		if (drift !== 0) { let idx=0; let max=-1; for (let i=0;i<percents.length;i++){ if (percents[i].p>max){ max=percents[i].p; idx=i; } } percents[idx].p = Math.max(0, percents[idx].p + drift); }
	}
	let total = percents.reduce((a,x)=>a+x.p,0);
	if (total === 0) { const eq = Math.floor(100 / percents.length); percents = percents.map(x => ({ id:x.id, p:eq })); percents[0].p += 100 - (eq * percents.length); total=100; }
	const gcd = (a,b)=> b===0 ? a : gcd(b, a % b);
	let g = percents[0].p; for (let i=1;i<percents.length;i++) g = gcd(g, percents[i].p);
	const counts = percents.map(x => ({ id:x.id, c: Math.max(1, Math.floor(x.p / Math.max(1,g))) }));
	const cycleLen = counts.reduce((a,x)=>a+x.c,0);
	const remain = counts.map(x => ({ ...x }));
	const schedule=[]; let lastId=null;
	for (let i=0;i<cycleLen;i++) { let pickIdx=-1; let best=-1; for (let j=0;j<remain.length;j++){ const r=remain[j]; if (r.c<=0) continue; const score = r.c - (r.id===lastId ? 0.5 : 0); if (score>best){ best=score; pickIdx=j; } } if (pickIdx===-1) break; const chosen=remain[pickIdx]; schedule.push(chosen.id); chosen.c -= 1; lastId=chosen.id; }
	let doShuffle = !!shuffle; try { const sToggle=document.querySelector(`.container-item[data-container-id="${key}"] .container-shuffle-toggle input[type="checkbox"]`); if (sToggle) doShuffle=!!sToggle.checked; } catch(_){}
	const finalSchedule = doShuffle ? shuffleArray(schedule) : schedule;
	editor.containerPreviewState[key] = { ...(editor.containerPreviewState[key]||{}), schedule: finalSchedule, currentIndex:0 };
	return finalSchedule;
}

export function shuffleArray(arr) {
	const a=arr.slice(); for (let i=a.length-1;i>0;i--){ const j=Math.floor(Math.random()*(i+1)); [a[i],a[j]]=[a[j],a[i]]; } return a;
}

export function applyContainerPreviewStyles(containerId, css) {
	const styleId = `cc-container-preview-styles-${containerId}`;
	let tag=document.getElementById(styleId); if (!tag) { tag=document.createElement('style'); tag.id=styleId; document.head.appendChild(tag); }
	tag.textContent = css || '';
}

export function removeContainerPreviewStyles(containerId) {
	const styleId = `cc-container-preview-styles-${containerId}`; const tag=document.getElementById(styleId); if (tag?.parentNode) tag.parentNode.removeChild(tag);
}

export function fitInnerToOuter(editor, inner, outer) {
	if (!inner || !outer) return; inner.style.transform='none'; inner.style.transformOrigin='top left';
	const ow=outer.clientWidth; const oh=outer.clientHeight; const rect=inner.getBoundingClientRect(); const iw=Math.max(1,Math.round(rect.width)); const ih=Math.max(1,Math.round(rect.height)); const sx=ow/iw; const sy=oh/ih; let s=Math.min(sx,sy);

	// Block-authoritative invariant for canonical Lower/Upper Third:
	// If the container is a lower_third/upper_third and the rendered content
	// already matches the logical 1280×240 or similar aspect, we skip scaling
	// so the block renders 1:1 relative to its authored geometry. This keeps
	// Scenes/Containers in parity with the Canvas Editor for the Track Info
	// lower third design while still allowing responsive scaling when used in
	// other container shapes.
	try {
		const container = outer.closest('.canvas-container');
		const pos = String(container?.dataset?.position || '').toLowerCase();
		if (pos === 'lower_third' || pos === 'upper_third') {
			// Logical stage is 1920×1080; lower_third containers are authored at
			// 1280×240 in the Canvas Editor. If the outer box is already close to
			// that authored size, treat this as canonical and avoid re-scaling.
			const isCanonicalWidth = ow >= 1240 && ow <= 1320; // ~1280 ± 40
			const isCanonicalHeight = oh >= 220 && oh <= 260; // ~240 ± 20
			if (isCanonicalWidth && isCanonicalHeight) {
				// Use scale=1; rely on the block's own CSS (authored geometry).
				s = 1;
			} else {
				// Non-canonical height/width for thirds: keep uniform scaling.
				s = Math.min(sx, sy);
			}
		}
	} catch(_) { }
	inner.style.transform = `scale(${s})`;
	try { let rafId=null; const scheduleRefit=()=>{ if (rafId) cancelAnimationFrame(rafId); rafId=requestAnimationFrame(()=>fitInnerToOuter(editor, inner, outer)); }; inner.querySelectorAll('img').forEach(img=>{ if(!img.complete){ img.addEventListener('load', scheduleRefit, { once:true }); } }); if (!inner.__ccFitObserver){ const obs=new MutationObserver(()=>scheduleRefit()); obs.observe(inner,{ childList:true, subtree:true, attributes:true, attributeFilter:['style','class'] }); inner.__ccFitObserver=obs; } } catch(_){}
}

export async function maybeSeedUpperThird(editor, containerObj) {
	try { if (!containerObj) return; const pos=String(containerObj.position||'').toLowerCase(); if (pos!=='upper_third') return; if (editor._seededUpperThirdUiOnce) return; const zones=(containerObj.layout && Array.isArray(containerObj.layout.zones)) ? containerObj.layout.zones : []; const ids=new Set(zones.map(z=>String(z.id))); if (!ids.has('upper_left') || !ids.has('upper_right')) return; const cid=String(containerObj.id); const left=await editor.getZoneAssignments(cid,'upper_left').catch(()=>[]); const right=await editor.getZoneAssignments(cid,'upper_right').catch(()=>[]); const isEmpty=(!left||left.length===0)||(!right||right.length===0); if (!isEmpty) return; const resp=await fetch(`${castconductorCanvasAjax.rest_url}castconductor/v5/content-blocks`, { headers:{ 'X-WP-Nonce':castconductorCanvasAjax.nonce } }); if (!resp.ok) return; const blocks=await resp.json(); const findByType=(t)=>(blocks||[]).find(b=>String(b.type||'')===t); const loc=findByType('location_time'); const weath=findByType('weather'); let seeded=false; if ((!left||left.length===0) && loc?.id) { await editor.putZoneAssignments(cid,'upper_left',[{ content_block_id: parseInt(loc.id,10), rotation_percentage:100, rotation_order:1, enabled:1 }]); seeded=true; } if ((!right||right.length===0) && weath?.id) { await editor.putZoneAssignments(cid,'upper_right',[{ content_block_id: parseInt(weath.id,10), rotation_percentage:100, rotation_order:1, enabled:1 }]); seeded=true; } if (seeded) { editor._seededUpperThirdUiOnce=true; editor.showNotification('Upper Third defaults applied (admin assist)','success'); } } catch(_){}
}

// Convenience bundle for batch import if desired
export const containerPreviewApi = {
	initContainerPreviewState,
	renderContainerPreview,
	startContainerPreviewCycle,
	stopContainerPreviewCycle,
	stopAllPreviewCycles,
	buildContainerSchedule,
	shuffleArray,
	applyContainerPreviewStyles,
	removeContainerPreviewStyles,
	fitInnerToOuter,
	maybeSeedUpperThird
};
