"use strict";

const console = require('gifer-console');

const RING_MAX    = 200;
const MAX_PARTIAL = 64 * 1024;   // стеля незавершеного рядка (вивід без \n)

function PluginProcess(opts){
    var child   = opts.processFactory(opts.bootstrap, opts.args);
    var stopped = false;
    var waiters = [];
    var id      = opts.id || '?';
    var ring    = [];   // хвіст bg stdout/stderr для агрегації діагностики (host collectDiagnostics)

    child.on('message', function(message){ opts.onMessage(message); });
    child.on('exit', function(code){
        stopped = true;
        waiters.splice(0).forEach(function(resolve){ resolve(code); });
        opts.onExit(code);
    });

    // Захоплення bg-виводу. Стріми доступні лише при stdio:'pipe' і стають null після exit —
    // тому підписуємось у конструкторі (до exit), ring наповнюємо по ходу (спека 3.2, de-risking).
    captureStream(child.stdout, '',     console.log,  console.logLevel.DEBUG);
    captureStream(child.stderr, '.err', console.warn, console.logLevel.WARN);

    function captureStream(stream, tag, logFn, level){
        if (!stream) return;

        var buffered = '';

        stream.on('data', function(chunk){
            buffered += chunk.toString();

            var lines = buffered.split(/\r?\n/);

            buffered = lines.pop();

            // Вивід без \n (прогрес через самі \r) не дає жодного рядка, тож RING_MAX
            // його не обмежує — ріжемо сам недобудований хвіст, інакше він росте,
            // поки процес не з'їсть пам'ять main'а.
            if (buffered.length > MAX_PARTIAL){
                lines.push(buffered.slice(0, MAX_PARTIAL));
                buffered = '';
            }

            lines.forEach(function(line){
                if (!line) return;

                ring.push(line);
                if (ring.length > RING_MAX) ring.shift();

                logFn('[plugin:' + id + '] bg' + tag + ': ' + line, level);
            });
        });
    }

    this.getOutput = function(){ return ring.slice(); };

    this.send = function(envelope){
        if (stopped) throw new Error('plugin process is stopped');
        child.postMessage(envelope);
    };
    this.stop = function(graceMs){
        if (stopped) return Promise.resolve();

        child.postMessage({ type: 'shutdown', payload: {} });
        return new Promise(function(resolve){
            var timer = setTimeout(function(){
                if (!stopped){
                    console.warn('[plugins] bg process forced kill after ' + graceMs + 'ms grace', console.logLevel.WARN);
                    child.kill();
                }
                resolve();
            }, graceMs);

            waiters.push(function(){ clearTimeout(timer); resolve(); });
        });
    };
}

module.exports = PluginProcess;
