"use strict";

/**
 * AtdtMsgEd — the local full-screen message editor (SPEC §7.5).
 * EditorCore is DOM-free (Node-tested); Editor renders into the terminal's
 * ScreenBuffer and is driven by term.js while mode === "editor".
 * Footer/keys follow §7.5: ^S save, ESC abort (confirm), ^Q quote picker.
 */
var AtdtMsgEd = (function () {

    var COLS = 79;          // §7.5: 79-column word-wrapped buffer
    var VIEW_ROWS = 23;     // rows 2..24; row 1 title, row 25 status
    var MAX_LINES = 1000;   // §7.5, raised in 1.6.0 with the board cap
    var MAX_BYTES = 61440;

    function bytesOf(s) {
        if (typeof TextEncoder !== "undefined") {
            return new TextEncoder().encode(s).length;
        }
        return s.length;
    }

    /**
     * Pure text-buffer logic: insert/overwrite, word wrap, joins, splits.
     * @constructor
     */
    function EditorCore(opts) {
        opts = opts || {};
        // A screen may cap the buffer (1.6.0: the auto-message wall holds
        // 8 lines). The cap is enforced HERE, while typing, so what the
        // caller sees is exactly what the server keeps - never a silent
        // server-side trim. No cap means the classic 200-line message.
        this.cap = (opts.cap && opts.cap > 0) ? Math.min(opts.cap, MAX_LINES) : MAX_LINES;
        this.lines = (opts.initial && opts.initial.length) ? opts.initial.slice() : [""];
        this.quote = (opts.quote || []).slice();
        this.row = 0;
        this.col = 0;
        this.insert = true;
        this.full = false; // sticky "hit a §7.5 limit" flag for the footer
    }

    EditorCore.prototype.line = function () {
        return this.lines[this.row];
    };

    EditorCore.prototype.clampCol = function () {
        this.col = Math.max(0, Math.min(this.col, this.line().length));
    };

    EditorCore.prototype.atLimits = function (extraLines) {
        if (this.lines.length + (extraLines || 0) > this.cap) { return true; }
        return bytesOf(this.lines.join("\n")) >= MAX_BYTES;
    };

    /** Word-wrap this.row down until it fits; cursor follows its character. */
    EditorCore.prototype.wrapRow = function () {
        while (this.lines[this.row].length > COLS) {
            var s = this.lines[this.row];
            var split = s.lastIndexOf(" ", COLS);
            if (split <= 0) { split = COLS; }
            var head = s.slice(0, split);
            var tail = s.slice(split).replace(/^ /, "");
            var eaten = s.length - head.length - tail.length; // the swallowed space (0 or 1)
            if (this.lines.length >= this.cap) {
                this.lines[this.row] = s.slice(0, COLS); // limit: hard-truncate
                this.full = true;
                this.clampCol();
                return;
            }
            this.lines[this.row] = head;
            this.lines.splice(this.row + 1, 0, tail);
            if (this.col > head.length) {
                this.row++;
                this.col = Math.max(0, this.col - head.length - eaten);
            }
        }
    };

    EditorCore.prototype.putChar = function (ch) {
        if (this.atLimits(0) && this.line().length >= COLS) {
            this.full = true;
            return;
        }
        var s = this.line();
        this.lines[this.row] = this.insert
            ? s.slice(0, this.col) + ch + s.slice(this.col)
            : s.slice(0, this.col) + ch + s.slice(this.col + 1);
        this.col++;
        this.wrapRow();
    };

    EditorCore.prototype.enter = function () {
        if (this.lines.length >= this.cap) {
            this.full = true;
            return;
        }
        var s = this.line();
        this.lines[this.row] = s.slice(0, this.col);
        this.lines.splice(this.row + 1, 0, s.slice(this.col));
        this.row++;
        this.col = 0;
    };

    EditorCore.prototype.backspace = function () {
        if (this.col > 0) {
            var s = this.line();
            this.lines[this.row] = s.slice(0, this.col - 1) + s.slice(this.col);
            this.col--;
        } else if (this.row > 0) {
            var prev = this.lines[this.row - 1];
            this.col = prev.length;
            this.lines[this.row - 1] = prev + this.line();
            this.lines.splice(this.row, 1);
            this.row--;
            this.wrapRow();
        }
    };

    EditorCore.prototype.del = function () {
        var s = this.line();
        if (this.col < s.length) {
            this.lines[this.row] = s.slice(0, this.col) + s.slice(this.col + 1);
        } else if (this.row < this.lines.length - 1) {
            this.lines[this.row] = s + this.lines[this.row + 1];
            this.lines.splice(this.row + 1, 1);
            this.wrapRow();
        }
    };

    EditorCore.prototype.move = function (name) {
        switch (name) {
            case "UP": this.row = Math.max(0, this.row - 1); this.clampCol(); break;
            case "DOWN": this.row = Math.min(this.lines.length - 1, this.row + 1); this.clampCol(); break;
            case "LEFT":
                if (this.col > 0) { this.col--; }
                else if (this.row > 0) { this.row--; this.col = this.line().length; }
                break;
            case "RIGHT":
                if (this.col < this.line().length) { this.col++; }
                else if (this.row < this.lines.length - 1) { this.row++; this.col = 0; }
                break;
            case "HOME": this.col = 0; break;
            case "END": this.col = this.line().length; break;
            case "PGUP": this.row = Math.max(0, this.row - (VIEW_ROWS - 1)); this.clampCol(); break;
            case "PGDN": this.row = Math.min(this.lines.length - 1, this.row + (VIEW_ROWS - 1)); this.clampCol(); break;
        }
    };

    /** Insert lines above the cursor row (quote picker ENTER). */
    EditorCore.prototype.insertLines = function (extra) {
        var room = Math.max(0, this.cap - this.lines.length);
        if (extra.length > room) {
            extra = extra.slice(0, room);
            this.full = true;
        }
        for (var i = 0; i < extra.length; i++) {
            this.lines.splice(this.row + i, 0, extra[i]);
        }
        this.row += extra.length;
        this.clampCol();
    };

    /**
     * The interactive editor over a ScreenBuffer. `frame` is the §7.4 mode
     * frame ({title, initial, quote, ...}); `submit(linesOrNull)` fires on
     * ^S / confirmed ESC.
     * @constructor
     */
    function Editor(frame, buf, submit) {
        this.core = new EditorCore({ initial: frame.initial, quote: frame.quote, cap: frame.cap });
        this.buf = buf;
        this.submit = submit;
        this.title = String(frame.title || "Editor");
        this.top = 0;
        this.state = "edit"; // edit | confirm | quote
        this.qIdx = 0;
        this.qSel = {};
        this.render();
    }

    Editor.prototype.key = function (name, printable) {
        var c = this.core;
        if (this.state === "confirm") {
            if (name === "Y" || name === "y") { this.submit(null); return; }
            this.state = "edit";
            this.render();
            return;
        }
        if (this.state === "quote") {
            this.quoteKey(name);
            return;
        }
        switch (name) {
            case "CTRL+S": this.submit(c.lines.slice()); return;
            case "ESC": this.state = "confirm"; this.render(); return;
            case "CTRL+Q":
                if (c.quote.length) { this.state = "quote"; this.render(); }
                return;
            case "ENTER": c.enter(); break;
            case "BACKSPACE": c.backspace(); break;
            case "DELETE": c.del(); break;
            case "INS": c.insert = !c.insert; break;
            case "TAB":
                for (var i = 0; i < 4; i++) { c.putChar(" "); }
                break;
            case "UP": case "DOWN": case "LEFT": case "RIGHT":
            case "HOME": case "END": case "PGUP": case "PGDN":
                c.move(name);
                break;
            default:
                if (printable && printable.length === 1 && printable >= " ") {
                    c.putChar(printable);
                }
        }
        this.render();
    };

    Editor.prototype.quoteKey = function (name) {
        var c = this.core;
        if (name === "ESC" || name === "CTRL+Q") { this.state = "edit"; }
        else if (name === "UP") { this.qIdx = Math.max(0, this.qIdx - 1); }
        else if (name === "DOWN") { this.qIdx = Math.min(c.quote.length - 1, this.qIdx + 1); }
        else if (name === " ") { this.qSel[this.qIdx] = !this.qSel[this.qIdx]; }
        else if (name === "ENTER") {
            var picked = [];
            for (var i = 0; i < c.quote.length; i++) {
                if (this.qSel[i]) { picked.push(c.quote[i]); }
            }
            if (picked.length) { c.insertLines(picked); }
            this.qSel = {};
            this.state = "edit";
        }
        this.render();
    };

    // ---- rendering -------------------------------------------------------

    Editor.prototype.text = function (x, y, s, fg, bg) {
        for (var i = 0; i < s.length && x + i < 80; i++) {
            this.buf.set(x + i, y, s.charCodeAt(i), fg, bg, false);
        }
    };

    Editor.prototype.bar = function (y, s, fg, bg) {
        this.buf.blankSpan(0, 79, y, fg, bg);
        this.text(1, y, s.slice(0, 78), fg, bg);
    };

    Editor.prototype.render = function () {
        var c = this.core;
        if (c.row < this.top) { this.top = c.row; }
        if (c.row >= this.top + VIEW_ROWS) { this.top = c.row - VIEW_ROWS + 1; }

        this.bar(0, this.title, 0, 3); // title bar: black on cyan (§7.5)
        for (var v = 0; v < VIEW_ROWS; v++) {
            var y = 1 + v;
            var idx = this.top + v;
            this.buf.blankSpan(0, 79, y, 7, 0);
            if (idx < c.lines.length) {
                this.text(0, y, c.lines[idx], 7, 0);
            }
        }
        if (this.state === "quote") {
            this.renderQuote();
        } else {
            // cursor: inverted cell
            var cy = 1 + (c.row - this.top);
            var cur = c.col < c.line().length ? c.line().charCodeAt(c.col) : 0x20;
            this.buf.set(c.col, cy, cur, 0, 7, false);
        }

        var status;
        if (this.state === "confirm") {
            status = "Abort message? [y/N]";
        } else if (this.state === "quote") {
            status = "QUOTE: arrows move - Space marks - Enter inserts - ESC cancels";
        } else {
            status = "line " + (c.row + 1) + "/" + c.lines.length
                + (c.cap < MAX_LINES ? " (max " + c.cap + ")" : "") + " col " + (c.col + 1)
                + "  " + (c.insert ? "INS" : "OVR")
                + "  ^S=Save  ESC=Abort" + (c.quote.length ? "  ^Q=Quote" : "")
                + (c.full ? "  [FULL]" : "");
        }
        this.bar(24, status, 0, 3);
    };

    Editor.prototype.renderQuote = function () {
        var c = this.core;
        var qTop = Math.max(0, Math.min(this.qIdx - 10, c.quote.length - (VIEW_ROWS - 2)));
        this.bar(1, "Original message - pick your quotes:", 15, 1);
        for (var v = 0; v < VIEW_ROWS - 1; v++) {
            var y = 2 + v;
            var idx = qTop + v;
            this.buf.blankSpan(0, 79, y, 7, 0);
            if (idx >= c.quote.length) { continue; }
            var mark = this.qSel[idx] ? "[x] " : "[ ] ";
            var fg = idx === this.qIdx ? 15 : (this.qSel[idx] ? 11 : 7);
            var bg = idx === this.qIdx ? 1 : 0;
            if (idx === this.qIdx) { this.buf.blankSpan(0, 79, y, fg, bg); }
            this.text(0, y, mark + c.quote[idx].slice(0, 74), fg, bg);
        }
    };

    return {
        COLS: COLS,
        MAX_LINES: MAX_LINES,
        MAX_BYTES: MAX_BYTES,
        EditorCore: EditorCore,
        Editor: Editor
    };
})();

if (typeof module !== "undefined" && module.exports) {
    module.exports = AtdtMsgEd;
}
