import type {IdentityState} from './identity';

export const IDENTITY_CHANGED_EVENT='planck:identity-changed';
export const IDENTITY_POLL_INTERVAL=2000;

function canonical(value:unknown):unknown {
 if(Array.isArray(value))return value.map(canonical);
 if(value&&typeof value==='object')return Object.fromEntries(Object.entries(value).sort(([a],[b])=>a.localeCompare(b)).map(([key,item])=>[key,canonical(item)]));
 return value;
}

function snapshot(state:IdentityState){
 return JSON.stringify(canonical({...state,checks:[...state.checks].sort((a,b)=>a.id.localeCompare(b.id))}));
}

/** A late response must not roll back a saved request or resurrect an older one. */
export function identityStateChange(current:IdentityState|null,next:IdentityState,preserveDraft=false):'updated'|'unchanged'|'stale'|'editing' {
 if(!current)return 'updated';
 const before=current.request,after=next.request;
 if(before&&!after)return 'stale';
 if(before&&after){
  if(before.id===after.id&&after.version<before.version)return 'stale';
  if(before.id!==after.id&&Date.parse(after.created_at)<Date.parse(before.created_at))return 'stale';
  // Preserve both edited fields and their original version, so a save still
  // detects concurrent changes instead of silently overwriting them.
  if(preserveDraft&&before.status==='draft'&&after.status==='draft'&&before.id===after.id)return 'editing';
 }
 return snapshot(current)===snapshot(next)?'unchanged':'updated';
}

/** One read at a time; invalidate before writes so a pre-write read cannot win. */
export function createIdentityRefresh<T>(load:(signal:AbortSignal)=>Promise<T>,receive:(value:T)=>void,allowed:()=>boolean=()=>true){
 let controller:AbortController|null=null,revision=0,disposed=false;
 const invalidate=()=>{revision++;controller?.abort()};
 return {
  async refresh(){
   if(disposed||controller||!allowed())return;
   const ownController=new AbortController(),ownRevision=revision;controller=ownController;
   try{
    const value=await load(ownController.signal);
    if(!disposed&&!ownController.signal.aborted&&ownRevision===revision&&allowed())receive(value);
   }catch{/* Keep the last confirmed state on a transient background failure. */}
   finally{if(controller===ownController)controller=null}
  },
  invalidate,
  dispose(){disposed=true;invalidate()},
 };
}
