/* война.com · 4.10.0-wip. Транспорт одного ядра; не удостоверение владельца. */
(function(root){
  'use strict';
  const K=root.ВойнаМетодика||(typeof require==='function'?require('./доказательный-контур.js'):null);
  let pageLease=null; // Допуск удерживается до окончания наблюдаемого завершения; только этот контекст.
  class Executor{
    constructor({factory=null,timeout=120000,cleanupTimeout=5000}={}){
      if(!Number.isFinite(timeout)||timeout<1||timeout>120000)throw Error('Предел времени должен быть от 1 до 120000 мс.');
      if(!Number.isFinite(cleanupTimeout)||cleanupTimeout<1||cleanupTimeout>30000)throw Error('Предел ожидания завершения должен быть от 1 до 30000 мс.');
      this.factory=factory;this.timeout=timeout;this.cleanupTimeout=cleanupTimeout;this.serial=0;this.active=null;
    }
    cancel(){this.active?.fail(new DOMException('Локальная обработка отменена. Внешние вызовы этим не прекращаются.','AbortError'));}
    run(command,data,{signal,isCurrent=()=>true,onStage=()=>{},envelope=false}={}){
      if(this.active||pageLease)return Promise.reject(Error(pageLease?.blocked?'Завершение прежнего потока не подтверждено. Новый запуск заблокирован; сохраните работу и перезагрузите страницу.':'Другая операция ещё выполняется или завершает очистку в этой странице.'));
      if(!Number.isSafeInteger(this.serial)||this.serial<0||this.serial>=Number.MAX_SAFE_INTEGER)return Promise.reject(Error('Исчерпан безопасный счётчик операций. Создайте новый исполнитель.'));
      if(!K.WORKER_COMMANDS.includes(command))return Promise.reject(Error('Неизвестная операция рабочего потока.'));
      if(signal?.aborted)return Promise.reject(new DOMException('Обработка отменена.','AbortError'));
      try{K.operationBudget(command,data);if(!isCurrent())throw Error('Снимок уже неактуален.');}catch(e){return Promise.reject(e);}
      const id=++this.serial,lease={};pageLease=lease;
      return new Promise((resolve,reject)=>{
        let worker,timer,done=false,next=1,exited=false,cleanupTimer,settled=false,observedComplete=null,lateStop=null;
        const release=()=>{if(lease.released)return;lease.released=true;clearTimeout(cleanupTimer);if(pageLease===lease)pageLease=null;if(this.active?.id===id)this.active=null;};
        const finish=(error,value)=>{
          if(done)return;done=true;clearTimeout(timer);signal?.removeEventListener('abort',abort);
          if(this.active)this.active.phase='очистка';
          const cleanupFailure=cause=>{
            if(settled)return;settled=true;lease.blocked=true;
            if(this.active)this.active.phase='завершение не подтверждено';
            const e=Error('Завершение прежнего потока не подтверждено; результат не принят, новый запуск заблокирован. '+String(cause?.message||cause||''));e.name='CleanupError';reject(e);
          };
          const complete=()=>{
            if(lease.released)return;release();if(settled)return;settled=true;
            // Данные могли измениться, пока terminate ожидал exit.
            try{if(!error&&lateStop)error=lateStop;if(!error&&signal?.aborted)error=new DOMException('Обработка отменена до принятия результата.','AbortError');
              if(!error&&!isCurrent())error=Error('Данные изменились во время завершения; результат не принят.');
            }catch(e){error=e;}
            if(error)reject(error);else resolve(value);
          };
          observedComplete=complete;
          try{
            if(!worker||exited){complete();return;}
            const end=worker.terminate();
            if(lease.released)return;
            if(end&&typeof end.then==='function'){
              cleanupTimer=setTimeout(()=>cleanupFailure('Истекло время ожидания остановки.'),this.cleanupTimeout);
              Promise.resolve(end).then(complete,cleanupFailure);
            }else{
              // Браузерный API не сообщает об освобождении физической памяти.
              // Возврат terminate — граница API, а не измерение числа потоков ОС.
              complete();
            }
          }catch(e){cleanupFailure(e);}
        };
        const abort=()=>finish(new DOMException('Локальная обработка отменена; прежняя работа сохранена.','AbortError'));
        this.active={id,phase:'работа',fail:e=>{if(done)lateStop=e;else finish(e);}};
        const message=m=>{
          if(done)return;
          try{
            if(m&&Number.isSafeInteger(m.id)&&m.id>0&&m.id<id)return; // старое сообщение не принимается
            if(!m||typeof m!=='object'||Array.isArray(m)||m.id!==id||m.protocol!==K.WORKER_PROTOCOL||m.release!==K.RELEASE||m.command!==command||m.seq!==next)throw Error('Нарушен протокол, редакция или порядок сообщений рабочего потока.');
            const fields=['id','protocol','release','command','seq','type',...(m.type==='stage'?['stage']:m.type==='error'?['name','message']:m.type==='result'?['value']:[])].sort();
            if(Object.keys(m).sort().join('\0')!==fields.join('\0'))throw Error('Недопустимые поля сообщения рабочего потока.');
            next++;
            if(m.type==='stage'){if(typeof m.stage!=='string'||!m.stage.trim()||m.stage.length>160)throw Error('Неверное имя этапа.');onStage(m.stage);return;}
            if(m.type==='error'){if(typeof m.message!=='string'||m.message.length>4000||typeof m.name!=='string'||m.name.length>64)throw Error('Неверная форма отказа рабочего потока.');const e=Error(m.message);e.name=m.name;finish(e);return;}
            if(m.type!=='result')throw Error('Неизвестный вид сообщения рабочего потока.');
            K.workerResultCheck(command,m.value);
            if(signal?.aborted)return abort();
            if(!isCurrent())throw Error('Данные изменились; устаревший результат не принят.');
            finish(null,m.value);
          }catch(e){finish(e);}
        };
        try{
          if(this.factory)worker=this.factory();
          else if(typeof Worker==='function')worker=new Worker(new URL('ресурсы/сценарии/поток-доказательного-контура.js?редакция='+encodeURIComponent(K.RELEASE),document.baseURI));
          else throw Error('Рабочий поток недоступен. Синхронная подмена не выполняется; прежняя работа сохранена.');
          if(typeof worker.on==='function'){
            worker.on('message',message);worker.on('messageerror',()=>finish(Error('Сообщение рабочего потока не удалось прочитать.')));worker.on('error',e=>finish(e));worker.on('exit',code=>{exited=true;if(done){observedComplete?.();return;}if(!done)finish(Error('Рабочий поток завершился до результата: '+code));});
          }else{worker.onmessage=e=>message(e.data);worker.onerror=e=>finish(Error(e.message||'Сбой рабочего потока.'));worker.onmessageerror=()=>finish(Error('Результат рабочего потока не удалось прочитать.'));}
          signal?.addEventListener('abort',abort,{once:true});if(signal?.aborted)return abort();
          timer=setTimeout(()=>finish(Error('Истекло время локальной обработки. Результат не принят.')),this.timeout);
          // Ни один исходный ArrayBuffer не передаётся с отсоединением.
          worker.postMessage({id,command,data,protocol:K.WORKER_PROTOCOL,release:K.RELEASE});
        }catch(e){finish(e);}
      });
    }
  }
  root.ВойнаИсполнитель=Executor;if(typeof module!=='undefined'&&module.exports)module.exports=Executor;
})(globalThis);
