import * as duckdb from '@duckdb/duckdb-wasm';
import {scenarios,defaultParams} from './scenarios.js';
import {setupSQL,selectQuery,arrowRows,bounded,csv,checkedFile,parameters,sqlLiteral} from './common.js';
const $=id=>document.getElementById(id), url=p=>new URL(p,location.href).href;
let worker,db,connection,ready=false,busy=false,abort,epoch=0,result=null,upload=null,history=[];
function announce(text,error=false){$('status').textContent=text;$('status').className=error?'alert alert-danger':'alert alert-info';}
function invalidate(){result=null;for(const id of ['json','csv'])$(id).disabled=true;$('result').replaceChildren();$('chart').replaceChildren();$('count').textContent='Запрос изменён. Выполните его, чтобы получить результат.';}
function controls(value){busy=value;for(const id of ['scenario','sql','run','reset','back','file','clear-file','from','to','minimum','with-params'])$(id).disabled=value;$('stop').disabled=!value;}
function clearEngine(){worker?.terminate();worker=null;connection=null;db=null;ready=false;}
function stop(reason='Выполнение остановлено. Worker завершён; следующий запуск создаст базу заново.'){
 epoch++;clearEngine();abort?.(new Error(reason));abort=null;controls(false);invalidate();announce(reason);
}
async function guarded(task,timeout){
 let timer;const current=epoch;
 try{return await Promise.race([task(),new Promise((_,reject)=>{abort=reject;timer=setTimeout(()=>{if(current===epoch)stop('Превышено время ожидания. Worker остановлен; сократите запрос и повторите запуск.');},timeout);})]);}
 finally{clearTimeout(timer);if(current===epoch)abort=null;}
}
async function initialize(){
 if(ready)return;
 const initialEpoch=epoch;
 announce('Загружаем локальное ядро DuckDB и расширения — около 44 МБ до HTTP-сжатия. Первый запуск может занять время.');
 const nextWorker=new Worker(url('vendor/duckdb-browser-mvp.worker.js'));
 const nextDb=new duckdb.AsyncDuckDB(new duckdb.VoidLogger(),nextWorker);
 worker=nextWorker;db=nextDb;
 const assertActive=()=>{if(initialEpoch!==epoch||worker!==nextWorker)throw new Error('Предыдущая загрузка отменена.');};
 await nextDb.instantiate(url('vendor/duckdb-mvp.wasm'));assertActive();
 await nextDb.open({query:{castBigIntToDouble:false,castDecimalToDouble:false,castTimestampToDate:false}});assertActive();
 const nextConnection=await nextDb.connect();assertActive();connection=nextConnection;
 await nextConnection.query(`SET memory_limit='256MB'; SET threads=1; SET autoinstall_known_extensions=false; SET autoload_known_extensions=false;`);assertActive();
 for(const ext of ['json','parquet']){await nextConnection.query(`LOAD ${sqlLiteral(url('extensions/'+ext+'.duckdb_extension.wasm'))}`);assertActive();}
 for(const file of ['orders.csv','clients.csv','events.json','orders.parquet']){
  const response=await fetch(url('fixtures/'+file));assertActive();if(!response.ok)throw new Error('Не удалось загрузить '+file+': '+response.status);
  const bytes=new Uint8Array(await response.arrayBuffer());assertActive();
  await nextDb.registerFileBuffer(file,bytes);assertActive();
 }
 for(const q of setupSQL){await nextConnection.query(q);assertActive();}
 if(upload){
  await nextDb.registerFileBuffer('uploaded.'+upload.kind,upload.bytes);assertActive();
  const source=upload.kind==='csv'?"read_csv('uploaded.csv',header=true,all_varchar=true)":"read_json('uploaded.json',format='auto')";
  await nextConnection.query(`CREATE TABLE uploaded AS SELECT * FROM ${source}`);assertActive();
 }
 // After controlled imports, arbitrary SQL cannot read files, install extensions or change safety settings.
 await nextConnection.query('SET enable_external_access=false; SET lock_configuration=true');assertActive();
 const version=await nextDb.getVersion();assertActive();$('version').textContent='DuckDB '+version+' · WASM 1.32.0 · локальный Worker';
 ready=true;
}
function render(rows,columns,ms){
 $('count').textContent=`${rows.length} строк · ${Math.round(ms)} мс выполнения SQL · BIGINT и DECIMAL сохранены строками без потери точности`;
 const table=document.createElement('table');table.className='table table-striped table-sm align-middle';const caption=document.createElement('caption');caption.textContent='Результат SQL-запроса';caption.className='visually-hidden';table.append(caption);
 const head=document.createElement('thead'),tr=document.createElement('tr');for(const c of columns){const th=document.createElement('th');th.scope='col';th.textContent=c;tr.append(th);}head.append(tr);table.append(head);
 const body=document.createElement('tbody');for(const row of rows){const line=document.createElement('tr');for(const c of columns){const td=document.createElement('td'),value=row[c];td.textContent=value===null?'NULL':typeof value==='object'?JSON.stringify(value):String(value);if(value===null)td.className='text-muted';line.append(td);}body.append(line);}table.append(body);$('result').replaceChildren(table);
 if(!rows.length){const note=document.createElement('p');note.textContent='Запрос выполнен успешно: подходящих строк нет. Измените фильтр или период.';$('result').append(note);}
 const active=scenarios.find(s=>s.id===$('scenario').value),measure=active?.chart;
 if(measure&&columns.includes(measure)&&rows.length){
  const values=rows.slice(0,12).map(r=>Number(r[measure]));
  if(values.every(n=>Number.isSafeInteger(n)&&n>=0)){
   const heading=document.createElement('h3');heading.textContent='Быстрое сравнение · '+measure;$('chart').append(heading);
   const max=Math.max(1,...values);rows.slice(0,12).forEach((r,i)=>{const item=document.createElement('div');item.className='bar-row';const label=document.createElement('span');label.textContent=String(r[columns[0]])+' · '+values[i].toLocaleString('ru-RU');const bar=document.createElement('span');bar.className='bar';bar.style.width=Math.max(1,values[i]/max*100)+'%';item.append(label,bar);$('chart').append(item);});
   const note=document.createElement('p');note.className='small text-muted';note.textContent='Первые 12 строк. Масштаб линейный, подписи сохраняют исходные единицы (minor = копейки).';$('chart').append(note);
  }
 }
}
async function execute(){
 if(busy)return;invalidate();controls(true);const current=epoch;
 try{
  const query=selectQuery($('sql').value),params=$('with-params').checked?parameters($('from').value,$('to').value,$('minimum').value):[];
  if(!ready)await guarded(initialize,120000);
  if(current!==epoch)return;
  announce('Выполняем SQL…');const started=performance.now();
  const table=await guarded(async()=>{if(!params.length)return connection.query(query);const statement=await connection.prepare(query);try{return await statement.query(...params);}finally{await statement.close();}},10000);
  if(current!==epoch)return;
  const rows=bounded(arrowRows(table)),columns=table.schema.fields.map(f=>f.name);
  result={rows,columns};render(rows,columns,performance.now()-started);$('json').disabled=false;$('csv').disabled=false;announce('Готово. Измените SQL, сравните результат или скачайте отчёт.');
 }catch(e){if(current===epoch){announce(String(e.message||e).slice(0,1400),true);if(!ready)clearEngine();}}
 finally{if(current===epoch)controls(false);}
}
function select(id,record=true){
 const s=scenarios.find(s=>s.id===id);if(!s)return;
 if(record&&$('sql').value)history.push({id:$('scenario').dataset.current,sql:$('sql').value,params:$('with-params').checked,values:[$('from').value,$('to').value,$('minimum').value]});
 $('scenario').value=id;$('scenario').dataset.current=id;$('why').textContent=s.why;$('task').textContent=s.task;$('sql').value=s.sql;$('with-params').checked=!!s.params;invalidate();announce('Выбрано: '+s.title+'. Нажмите «Выполнить SQL».');
}
function download(name,text,type){const blob=new Blob([text],{type}),a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000);}
for(const s of scenarios){const o=document.createElement('option');o.value=s.id;o.textContent=s.title;$('scenario').append(o);}
[$('from').value,$('to').value,$('minimum').value]=defaultParams;
$('run').addEventListener('click',execute);$('stop').addEventListener('click',()=>stop());
$('scenario').addEventListener('change',()=>select($('scenario').value));$('reset').addEventListener('click',()=>{select($('scenario').value);[$('from').value,$('to').value,$('minimum').value]=defaultParams;});
$('back').addEventListener('click',()=>{const old=history.pop();if(!old){announce('История пока пуста. Переключите сценарий или сбросьте изменённый запрос.');return;}select(old.id,false);$('sql').value=old.sql;$('with-params').checked=old.params;[$('from').value,$('to').value,$('minimum').value]=old.values;});
for(const id of ['sql','from','to','minimum','with-params'])$(id).addEventListener('input',invalidate);
$('sql-file').addEventListener('click',()=>download('report.sql',$('sql').value+'\n','text/plain;charset=utf-8'));
$('json').addEventListener('click',()=>{if(result)download('report.json',JSON.stringify(result.rows,null,2)+'\n','application/json;charset=utf-8');});
$('csv').addEventListener('click',()=>{if(result)download('report.csv',csv(result.rows,result.columns),'text/csv;charset=utf-8');});
$('file').addEventListener('change',async()=>{
 const file=$('file').files[0];if(!file)return;invalidate();controls(true);const current=epoch;
 try{const kind=checkedFile(file.name,file.size),bytes=new Uint8Array(await file.arrayBuffer());if(current!==epoch)return;new TextDecoder('utf-8',{fatal:true}).decode(bytes);upload={kind,bytes};clearEngine();$('upload-note').textContent=file.name+' · '+file.size.toLocaleString('ru-RU')+' байт · таблица uploaded после запуска';$('sql').value='SELECT * FROM uploaded LIMIT 50';$('with-params').checked=false;announce('Файл выбран. Нажмите «Выполнить SQL»: база будет создана заново, файл останется в памяти браузера.');}
 catch(e){if(current===epoch)announce(e.message,true);}finally{if(current===epoch)controls(false);}
});
$('clear-file').addEventListener('click',()=>{upload=null;$('file').value='';$('upload-note').textContent='Свой файл не выбран.';clearEngine();select('schema');});
window.addEventListener('pagehide',()=>clearEngine());select('summary',false);
