/* AI Project Universe V6.5 — local, deterministic SentiScope demo API. */
(() => {
  'use strict';
  if (window.__APU_SENTIMENT_API_V65__) return;
  window.__APU_SENTIMENT_API_V65__ = true;
  const nativeFetch = window.fetch.bind(window);
  const STORAGE_KEY = 'apu-sentiment-feedback-v6-5';
  const categories = ['Teaching Quality','Course Content','Assessment & Grading','Campus Facilities','Academic Services','Learning Resources','Student Support'];
  const seed = [
    {id:1,text:'The lecturer explains difficult concepts clearly and provides helpful examples.',category:'Teaching Quality',source:'Course Survey',sentiment:'positive',trueSentiment:'positive',polarityScore:.82,confidence:.93,keywords:['lecturer','clear','helpful','examples'],topics:['teaching','clarity']},
    {id:2,text:'The course content is relevant, current, and very useful for practical projects.',category:'Course Content',source:'Online Review',sentiment:'positive',trueSentiment:'positive',polarityScore:.76,confidence:.9,keywords:['content','relevant','practical','projects'],topics:['curriculum','projects']},
    {id:3,text:'Assignment instructions were confusing and the grading feedback arrived too late.',category:'Assessment & Grading',source:'Anonymous Form',sentiment:'negative',trueSentiment:'negative',polarityScore:-.73,confidence:.91,keywords:['assignment','confusing','grading','late'],topics:['assessment','feedback']},
    {id:4,text:'The library is quiet and comfortable, although more power outlets would help.',category:'Learning Resources',source:'Focus Group',sentiment:'positive',trueSentiment:'neutral',polarityScore:.24,confidence:.61,keywords:['library','comfortable','outlets'],topics:['library','facilities'],errorType:'Mixed sentiment',possibleReason:'A positive clause and a request for improvement occur in the same sentence.'},
    {id:5,text:'Wi-Fi in the laboratory disconnects frequently and interrupts our work.',category:'Campus Facilities',source:'Direct Feedback',sentiment:'negative',trueSentiment:'negative',polarityScore:-.68,confidence:.88,keywords:['wifi','laboratory','disconnects','interrupts'],topics:['network','facilities']},
    {id:6,text:'Academic advising answered my questions quickly and gave a clear study plan.',category:'Academic Services',source:'Exit Poll',sentiment:'positive',trueSentiment:'positive',polarityScore:.71,confidence:.89,keywords:['advising','quickly','clear','plan'],topics:['advising','planning']},
    {id:7,text:'The tutorial was not bad, but it could include more advanced examples.',category:'Course Content',source:'Course Survey',sentiment:'negative',trueSentiment:'neutral',polarityScore:-.16,confidence:.56,keywords:['tutorial','advanced','examples'],topics:['curriculum','examples'],errorType:'Negation',possibleReason:'The phrase “not bad” is semantically positive but contains a negative lexicon token.'},
    {id:8,text:'Counselling staff were respectful and made the support process feel safe.',category:'Student Support',source:'Anonymous Form',sentiment:'positive',trueSentiment:'positive',polarityScore:.79,confidence:.92,keywords:['counselling','respectful','support','safe'],topics:['wellbeing','support']},
    {id:9,text:'The examination schedule is acceptable and the room arrangement is standard.',category:'Assessment & Grading',source:'Exit Poll',sentiment:'neutral',trueSentiment:'neutral',polarityScore:.03,confidence:.84,keywords:['examination','schedule','room'],topics:['assessment','scheduling']},
    {id:10,text:'Great, another portal outage exactly when the registration deadline begins.',category:'Academic Services',source:'Online Review',sentiment:'positive',trueSentiment:'negative',polarityScore:.31,confidence:.54,keywords:['portal','outage','registration','deadline'],topics:['registration','system'],errorType:'Sarcasm',possibleReason:'The positive word “Great” is used sarcastically and reverses the intended meaning.'},
    {id:11,text:'Recorded lectures and digital notes make revision much easier.',category:'Learning Resources',source:'Course Survey',sentiment:'positive',trueSentiment:'positive',polarityScore:.69,confidence:.9,keywords:['recorded','lectures','notes','revision'],topics:['resources','revision']},
    {id:12,text:'The helpdesk ticket remained open for three days without an update.',category:'Student Support',source:'Direct Feedback',sentiment:'negative',trueSentiment:'negative',polarityScore:-.62,confidence:.87,keywords:['helpdesk','ticket','update'],topics:['support','response']}
  ].map((item,index)=>({...item,originalText:item.text,tokens:item.text.toLowerCase().replace(/[^a-z0-9\s]/g,'').split(/\s+/),tfidfScores:(item.keywords||[]).map((term,i)=>({term,score:Number((.42-i*.05).toFixed(3))})),createdAt:new Date(Date.UTC(2026,5,15,8+index)).toISOString()}));

  const positiveWords = new Set(['good','great','excellent','helpful','clear','useful','relevant','comfortable','quickly','safe','easier','respectful','current','practical','quiet']);
  const negativeWords = new Set(['bad','confusing','late','disconnects','interrupts','outage','without','difficult','slow','poor','problem','broken','unsafe']);
  const clone = value => JSON.parse(JSON.stringify(value));
  function read(){
    try { const data = JSON.parse(localStorage.getItem(STORAGE_KEY)); if (Array.isArray(data) && data.length) return data; } catch (_) {}
    return clone(seed);
  }
  function write(data){ try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch (_) {} }
  function classify(text){
    const tokens = String(text||'').toLowerCase().replace(/[^a-z0-9\s]/g,'').split(/\s+/).filter(Boolean);
    let score = 0; tokens.forEach(t => { if (positiveWords.has(t)) score++; if (negativeWords.has(t)) score--; });
    const polarity = Math.max(-1,Math.min(1,score/Math.max(3,Math.sqrt(tokens.length||1))));
    return {tokens,polarityScore:polarity,sentiment:polarity>.12?'positive':polarity<-.12?'negative':'neutral',confidence:Math.min(.95,.58+Math.abs(polarity)*.4)};
  }
  function response(data,status=200){ return Promise.resolve(new Response(JSON.stringify(data),{status,headers:{'content-type':'application/json; charset=utf-8','cache-control':'no-store'}})); }
  const avg = values => values.length ? values.reduce((a,b)=>a+b,0)/values.length : 0;
  function filtered(url){
    let data=read(); const category=url.searchParams.get('category'), sentiment=url.searchParams.get('sentiment'), limit=Number(url.searchParams.get('limit')||0);
    if(category && category!=='All') data=data.filter(x=>x.category===category);
    if(sentiment && sentiment!=='All') data=data.filter(x=>x.sentiment===sentiment);
    return limit>0?data.slice(0,limit):data;
  }
  function summary(data){
    const count = sentiment => data.filter(x=>x.sentiment===sentiment).length;
    const sources={}; data.forEach(x=>sources[x.source]=(sources[x.source]||0)+1);
    return {total:data.length,positive:count('positive'),negative:count('negative'),neutral:count('neutral'),avgPolarity:avg(data.map(x=>Number(x.polarityScore||0))),avgConfidence:avg(data.map(x=>Number(x.confidence||0))),recentCount:data.length,topSource:Object.entries(sources).sort((a,b)=>b[1]-a[1])[0]?.[0]||'—'};
  }
  function topicRows(data){
    const map=new Map();
    data.forEach(item=>(item.topics||['general']).forEach(topic=>{const row=map.get(topic)||{topic,count:0,positive:0,negative:0,neutral:0,polarity:[],words:new Map()};row.count++;row[item.sentiment]++;row.polarity.push(item.polarityScore||0);(item.keywords||[]).forEach(w=>row.words.set(w,(row.words.get(w)||0)+1));map.set(topic,row)}));
    return [...map.values()].map(r=>({topic:r.topic,count:r.count,positive:r.positive,negative:r.negative,neutral:r.neutral,avgPolarity:avg(r.polarity),topKeywords:[...r.words].sort((a,b)=>b[1]-a[1]).slice(0,5).map(x=>x[0])})).sort((a,b)=>b.count-a.count);
  }
  function keywordRows(data){
    const map=new Map();
    data.forEach(item=>(item.keywords||[]).forEach((term,index)=>{const row=map.get(term)||{term,frequency:0,score:0,sentiments:[]};row.frequency++;row.score+=.46-index*.055;row.sentiments.push(item.sentiment);map.set(term,row)}));
    return [...map.values()].map(r=>{const counts={positive:0,negative:0,neutral:0};r.sentiments.forEach(s=>counts[s]++);return{term:r.term,frequency:r.frequency,tfidfScore:Number((r.score/r.frequency).toFixed(3)),sentiment:Object.entries(counts).sort((a,b)=>b[1]-a[1])[0][0]}}).sort((a,b)=>b.tfidfScore-a.tfidfScore||b.frequency-a.frequency);
  }
  function categoryRows(data){
    return categories.map(category=>{const rows=data.filter(x=>x.category===category);return{category,total:rows.length,positive:rows.filter(x=>x.sentiment==='positive').length,negative:rows.filter(x=>x.sentiment==='negative').length,neutral:rows.filter(x=>x.sentiment==='neutral').length,avgPolarity:avg(rows.map(x=>x.polarityScore||0))}}).filter(x=>x.total).sort((a,b)=>b.total-a.total);
  }
  function confusion(data){
    const labels=['positive','negative','neutral']; const matrix=labels.map(()=>labels.map(()=>0));
    const labeled=data.filter(x=>labels.includes(x.trueSentiment)); labeled.forEach(x=>matrix[labels.indexOf(x.trueSentiment)][labels.indexOf(x.sentiment)]++);
    const total=labeled.length, correct=matrix.reduce((n,row,i)=>n+row[i],0); const accuracy=total?correct/total:0;
    const per=labels.map((_,i)=>{const tp=matrix[i][i],pred=matrix.reduce((n,row)=>n+row[i],0),actual=matrix[i].reduce((a,b)=>a+b,0);const precision=pred?tp/pred:0,recall=actual?tp/actual:0;return{precision,recall,f1:precision+recall?2*precision*recall/(precision+recall):0}});
    return {labels,matrix,accuracy,precision:avg(per.map(x=>x.precision)),recall:avg(per.map(x=>x.recall)),f1Score:avg(per.map(x=>x.f1))};
  }
  function recommendations(data){
    const cats=categoryRows(data); return cats.map((row,index)=>{const negativeRate=row.total?row.negative/row.total:0;const priority=negativeRate>=.45?'high':negativeRate>=.2?'medium':'low';const descriptions={
      'Assessment & Grading':'Publish clearer rubrics, return feedback sooner, and review the examination schedule with students.',
      'Campus Facilities':'Prioritize network reliability and verify laboratory infrastructure during peak study periods.',
      'Academic Services':'Add service-status alerts and define response-time targets for registration and advising requests.',
      'Student Support':'Track open support cases with visible ownership, escalation, and regular progress updates.',
      'Course Content':'Add advanced worked examples while keeping practical project alignment current.',
      'Learning Resources':'Expand access to recordings, digital notes, charging points, and quiet study spaces.',
      'Teaching Quality':'Continue clear explanations and collect module-level feedback for targeted improvement.'};
      return{id:index+1,priority,category:row.category,affectedCount:row.negative,title:`Improve ${row.category}`,description:descriptions[row.category]||'Review recurring feedback themes and assign a measurable improvement owner.'};
    }).sort((a,b)=>({high:0,medium:1,low:2}[a.priority]-({high:0,medium:1,low:2}[b.priority])));
  }
  function trends(data){
    const buckets=[['W1',data.slice(0,3)],['W2',data.slice(3,6)],['W3',data.slice(6,9)],['W4',data.slice(9)]];
    return buckets.map(([period,rows])=>({period,positive:rows.filter(x=>x.sentiment==='positive').length,negative:rows.filter(x=>x.sentiment==='negative').length,neutral:rows.filter(x=>x.sentiment==='neutral').length}));
  }

  window.fetch = function(input,init={}){
    const url = new URL(typeof input==='string'?input:input.url, location.href);
    if(url.origin!==location.origin || !url.pathname.startsWith('/api/')) return nativeFetch(input,init);
    const method=(init.method || (typeof input!=='string'&&input.method) || 'GET').toUpperCase();
    const data=read();
    if(url.pathname==='/api/feedback' && method==='GET') return response(filtered(url));
    if(url.pathname==='/api/feedback' && method==='POST'){
      let body={}; try{body=JSON.parse(init.body||'{}')}catch(_){}
      const analysis=classify(body.text); const item={id:Math.max(0,...data.map(x=>Number(x.id)||0))+1,text:String(body.text||'').trim(),originalText:String(body.text||'').trim(),category:body.category||categories[0],source:body.source||'Direct Feedback',trueSentiment:body.trueSentiment||null,...analysis,keywords:analysis.tokens.filter(t=>t.length>4).slice(0,6),topics:[String(body.category||'general').toLowerCase().replace(/\s*&\s*/g,'-').replace(/\s+/g,'-')],tfidfScores:analysis.tokens.filter(t=>t.length>4).slice(0,6).map((term,i)=>({term,score:Number((.42-i*.05).toFixed(3))})),createdAt:new Date().toISOString()};
      data.unshift(item);write(data);return response(item,201);
    }
    const feedbackMatch=url.pathname.match(/^\/api\/feedback\/(\d+)$/);
    if(feedbackMatch){const id=Number(feedbackMatch[1]);const item=data.find(x=>Number(x.id)===id);if(method==='GET')return item?response(item):response({error:'not_found'},404);if(method==='DELETE'){write(data.filter(x=>Number(x.id)!==id));return response({success:true});}}
    if(method!=='GET') return response({error:'method_not_allowed'},405);
    if(url.pathname==='/api/dashboard/summary') return response(summary(data));
    if(url.pathname==='/api/dashboard/trends') return response(trends(data));
    if(url.pathname==='/api/dashboard/topics') return response(topicRows(data));
    if(url.pathname==='/api/dashboard/keywords') {const limit=Number(url.searchParams.get('limit')||50);return response(keywordRows(data).slice(0,limit));}
    if(url.pathname==='/api/dashboard/categories') return response(categoryRows(data));
    if(url.pathname==='/api/dashboard/recommendations') return response(recommendations(data));
    if(url.pathname==='/api/dashboard/confusion-matrix') return response(confusion(data));
    if(url.pathname==='/api/dashboard/error-analysis') return response(data.filter(x=>x.trueSentiment&&x.trueSentiment!==x.sentiment).map(x=>({id:x.id,text:x.text,trueSentiment:x.trueSentiment,predictedSentiment:x.sentiment,polarityScore:x.polarityScore,errorType:x.errorType||'Context ambiguity',possibleReason:x.possibleReason||'Lexicon scoring does not fully represent the sentence context.'})));
    return nativeFetch(input,init);
  };
})();
