/*
 * Conversation manager 
 * Created by: Michael Pastushkov <michael@pastushkov.com>
 */

const HistoryManager = require('./HistoryManager');
const TopicRouter = require('./TopicRouter');
const utils = require('../utils/utils');

class ConversationManager {

    constructor(model, embedModel, labelModel) {
        this.model = model;
        this.embedModel = embedModel;
        this.labelModel = labelModel;
        this.router = new TopicRouter({ embedModel: embedModel, labelModel: labelModel });
        this.history = new HistoryManager();
        this.lastTopic = null;
        this.lastTopicAt = 0;
        this.followUpWindowMs = 15 * 60 * 1000;
        this.followUpMinSim = 0.70;
        this.followUpHardStickBelow = 40;
    }

    async ask(prompt, callback) {

        // Determine the topic
        const now = Date.now();
        const meta = await this.router.resolve(prompt); // { key, bestSim, isNew }
        let topic = meta.key;
        let followUp = false;

        if (this.lastTopic && (now - this.lastTopicAt) < this.followUpWindowMs && this.looksLikeFollowUp(prompt)) {
            // Enrich with last assistant turn from lastTopic
            const lastThread = this.history.threads.get(this.lastTopic);
            const lastAssistant = lastThread?.turns.slice().reverse().find(t => t.role === 'assistant')?.content || '';
            const enriched = (lastAssistant ? (lastAssistant + '\n') : '') + prompt;

            // Compare enriched vector to last topic centroid
            const [vec, centroid] = await Promise.all([
                this.router.embed(enriched, this.embedModel),
                this.router.getCentroid(this.lastTopic) // add method below
            ]);
            const sim = centroid ? this.router.cos(vec, centroid) : 0;

            // Stick to last topic if enriched sim is decent,
            // or if plain router also thought it's close (meta.bestSim), whichever is stronger
            if (sim >= this.followUpHardStickBelow || meta.key === this.lastTopic || meta.bestSim >= this.followUpMinSim) {
                topic = this.lastTopic;
                followUp = true;
            }
        }

        // Remember current topic state
        this.lastTopic = topic;
        this.lastTopicAt = now;

        ////
        const messages = this.history.buildMessages(prompt, topic);

        utils.log(`model: ${this.model}`, 5);
        utils.log(messages, 5);

        const body = JSON.stringify({
            model: this.model,
            messages: messages,
            stream: true,
            keep_alive: "5m",
            // options: { // limited results with these, and no performance gains
            //     num_ctx: 1024,
            //     num_predict: 256,
            //     num_thread: 4,
            //     num_batch: 1024
            // }
        });

        // Making the main request to model
        const res = await fetch('http://127.0.0.1:11434/api/chat', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: body
        });

        if (!res.ok)
            throw new Error(await res.text());

        // --------------------------
        // Read and decode the response
        const reader = res.body.getReader();
        const decoder = new TextDecoder();

        let full = '';
        while (true) {
            const { value, done } = await reader.read();
            if (done)
                break;

            const chunk = decoder.decode(value, { stream: true });
            for (const line of chunk.split('\n').filter(Boolean)) {
                const j = utils.parse(line);
                if (j) {

                    // const thinkDelta = j.message?.reasoning ?? j.message?.thinking ?? '';
                    // if (thinkDelta)
                    //     callback(thinkDelta);

                    const delta = j.message?.content || '';
                    if (delta) {
                        full += delta;
                        callback(delta); // <-- send piece to client
                    }
                }
            }
        }

        callback('\n');

        this.history.addUser(topic, prompt);
        this.history.addAssistant(topic, full);
    }

    /** A language-agnostic “follow-up” heuristic based on shape only. */
    looksLikeFollowUp(text) {
        const w = text.trim().split(/\s+/);
        return w.length <= 8; // short turns tend to be follow-ups; no vocab lists
    }

}

module.exports = ConversationManager;

/* Testing * /

function log(data) {
    //console.log(data);
}

(async () => {

    const cm = new ConversationManager('llama3:8b');

    await cm.ask('сколько усов у кота?', log);
    await cm.ask('а хвостов?', log);

    // // Prompt 2 — different wording, same topic picked automatically
    // r = await cm.ask('Name the major ones (just names).');
    // console.log('\n\n[topic]', r.topic, '\n', r.text);

    // // Prompt 3 — likely a new topic
    // r = await cm.ask('Configure Nginx as a reverse proxy for an Express app.');
    // console.log('\n\n[topic]', r.topic, '\n', r.text);
})();

/**/