export const example={baseline:[{id:'edit',dependencies:[]},{id:'grade',dependencies:['edit']}],proposed:[{id:'edit',dependencies:[]},{id:'grade',dependencies:['edit']},{id:'social',dependencies:['grade']}]}; function cycles(items){const m=new Map(items.map(x=>[x.id,x.dependencies??[]])),vis=new Set,stack=new Set,out=[];function go(id){if(stack.has(id)){out.push(id);return}if(vis.has(id))return;vis.add(id);stack.add(id);for(const d of m.get(id)??[])if(m.has(d))go(d);stack.delete(id)}for(const id of m.keys())go(id);return out} export function evaluate(x){if(!Array.isArray(x?.baseline)||!Array.isArray(x?.proposed))return{valid:false,errors:[{code:'INPUT'}],matrix:[]};const errors=[],a=new Map(x.baseline.map(i=>[i.id,i])),b=new Map(x.proposed.map(i=>[i.id,i]));const dup=list=>new Set(list.map(x=>x.id)).size!==list.length;if(dup(x.baseline)||dup(x.proposed))errors.push({code:'DUPLICATE_ID'});const cyc=cycles(x.proposed);if(cyc.length)errors.push({code:'CYCLE',ids:cyc});const ids=new Set([...a.keys(),...b.keys()]),changed=new Set([...ids].filter(id=>!a.has(id)||!b.has(id)||JSON.stringify(a.get(id).dependencies??[])!==JSON.stringify(b.get(id).dependencies??[])));const reachesChange=(id,seen=new Set)=>{if(seen.has(id))return false;seen.add(id);return(b.get(id)?.dependencies??[]).some(d=>changed.has(d)||reachesChange(d,seen))};const matrix=[...ids].sort().map(id=>{let impact=!a.has(id)?'direct':!b.has(id)?'direct':changed.has(id)?'direct':'none';if(impact==='none'&&reachesChange(id))impact='possible';const unknown=(b.get(id)?.dependencies??[]).filter(d=>!b.has(d));if(unknown.length)impact='unknown';return{id,change:!a.has(id)?'added':!b.has(id)?'removed':changed.has(id)?'modified':'unchanged',impact,unknownDependencies:unknown}});return{valid:errors.length===0,errors,matrix}}