
import { Emitter } from '../../../../base/common/event.js';
import Severity$1 from '../../../../base/common/severity.js';
import { isObject, isString } from '../../../../base/common/types.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { localize } from '../../../../nls.js';
import { ExpressionContainer } from './debugModel.js';

let topReplElementCounter = 0;
const getUniqueId = () => `topReplElement:${topReplElementCounter++}`;
class ReplOutputElement {
    constructor(session, id, value, severity, sourceData, expression) {
        this.session = session;
        this.id = id;
        this.value = value;
        this.severity = severity;
        this.sourceData = sourceData;
        this.expression = expression;
        this._count = 1;
        this._onDidChangeCount = ( new Emitter());
    }
    toString(includeSource = false) {
        let valueRespectCount = this.value;
        for (let i = 1; i < this.count; i++) {
            valueRespectCount += (valueRespectCount.endsWith("\n") ? "" : "\n") + this.value;
        }
        const sourceStr = (this.sourceData && includeSource) ? ` ${this.sourceData.source.name}` : "";
        return valueRespectCount + sourceStr;
    }
    getId() {
        return this.id;
    }
    getChildren() {
        return this.expression?.getChildren() || Promise.resolve([]);
    }
    set count(value) {
        this._count = value;
        this._onDidChangeCount.fire();
    }
    get count() {
        return this._count;
    }
    get onDidChangeCount() {
        return this._onDidChangeCount.event;
    }
    get hasChildren() {
        return !!this.expression?.hasChildren;
    }
}
class ReplVariableElement {
    constructor(session, expression, severity, sourceData) {
        this.session = session;
        this.expression = expression;
        this.severity = severity;
        this.sourceData = sourceData;
        this.id = generateUuid();
        this.hasChildren = expression.hasChildren;
    }
    getSession() {
        return this.session;
    }
    getChildren() {
        return this.expression.getChildren();
    }
    toString() {
        return ( this.expression.toString());
    }
    getId() {
        return this.id;
    }
}
class RawObjectReplElement {
    static {
        this.MAX_CHILDREN = 1000;
    }
    constructor(id, name, valueObj, sourceData, annotation) {
        this.id = id;
        this.name = name;
        this.valueObj = valueObj;
        this.sourceData = sourceData;
        this.annotation = annotation;
    }
    getId() {
        return this.id;
    }
    getSession() {
        return undefined;
    }
    get value() {
        if (this.valueObj === null) {
            return "null";
        } else if (Array.isArray(this.valueObj)) {
            return `Array[${this.valueObj.length}]`;
        } else if (isObject(this.valueObj)) {
            return "Object";
        } else if (isString(this.valueObj)) {
            return `"${this.valueObj}"`;
        }
        return String(this.valueObj) || "";
    }
    get hasChildren() {
        return (Array.isArray(this.valueObj) && this.valueObj.length > 0) || (isObject(this.valueObj) && Object.getOwnPropertyNames(this.valueObj).length > 0);
    }
    evaluateLazy() {
        throw ( new Error("Method not implemented."));
    }
    getChildren() {
        let result = [];
        if (Array.isArray(this.valueObj)) {
            result = ( this.valueObj.slice(0, RawObjectReplElement.MAX_CHILDREN).map((v, index) => ( new RawObjectReplElement(`${this.id}:${index}`, String(index), v))));
        } else if (isObject(this.valueObj)) {
            result = ( Object.getOwnPropertyNames(this.valueObj).slice(0, RawObjectReplElement.MAX_CHILDREN).map((key, index) => ( new RawObjectReplElement(`${this.id}:${index}`, key, this.valueObj[key]))));
        }
        return Promise.resolve(result);
    }
    toString() {
        return `${this.name}\n${this.value}`;
    }
}
class ReplEvaluationInput {
    constructor(value) {
        this.value = value;
        this.id = generateUuid();
    }
    toString() {
        return this.value;
    }
    getId() {
        return this.id;
    }
}
class ReplEvaluationResult extends ExpressionContainer {
    get available() {
        return this._available;
    }
    constructor(originalExpression) {
        super(undefined, undefined, 0, generateUuid());
        this.originalExpression = originalExpression;
        this._available = true;
    }
    async evaluateExpression(expression, session, stackFrame, context) {
        const result = await super.evaluateExpression(expression, session, stackFrame, context);
        this._available = result;
        return result;
    }
    toString() {
        return `${this.value}`;
    }
}
class ReplGroup {
    static {
        this.COUNTER = 0;
    }
    constructor(session, name, autoExpand, sourceData) {
        this.session = session;
        this.name = name;
        this.autoExpand = autoExpand;
        this.sourceData = sourceData;
        this.children = [];
        this.ended = false;
        this.id = `replGroup:${ReplGroup.COUNTER++}`;
    }
    get hasChildren() {
        return true;
    }
    getId() {
        return this.id;
    }
    toString(includeSource = false) {
        const sourceStr = (includeSource && this.sourceData) ? ` ${this.sourceData.source.name}` : "";
        return this.name + sourceStr;
    }
    addChild(child) {
        const lastElement = this.children.length ? this.children[this.children.length - 1] : undefined;
        if (lastElement instanceof ReplGroup && !lastElement.hasEnded) {
            lastElement.addChild(child);
        } else {
            this.children.push(child);
        }
    }
    getChildren() {
        return this.children;
    }
    end() {
        const lastElement = this.children.length ? this.children[this.children.length - 1] : undefined;
        if (lastElement instanceof ReplGroup && !lastElement.hasEnded) {
            lastElement.end();
        } else {
            this.ended = true;
        }
    }
    get hasEnded() {
        return this.ended;
    }
}
function areSourcesEqual(first, second) {
    if (!first && !second) {
        return true;
    }
    if (first && second) {
        return first.column === second.column && first.lineNumber === second.lineNumber && ( first.source.uri.toString()) === ( second.source.uri.toString());
    }
    return false;
}
class ReplModel {
    constructor(configurationService) {
        this.configurationService = configurationService;
        this.replElements = [];
        this._onDidChangeElements = ( new Emitter());
        this.onDidChangeElements = this._onDidChangeElements.event;
    }
    getReplElements() {
        return this.replElements;
    }
    async addReplExpression(session, stackFrame, expression) {
        this.addReplElement(( new ReplEvaluationInput(expression)));
        const result = ( new ReplEvaluationResult(expression));
        await result.evaluateExpression(expression, session, stackFrame, "repl");
        this.addReplElement(result);
    }
    appendToRepl(
        session,
        {
            output,
            expression,
            sev,
            source
        }
    ) {
        const clearAnsiSequence = "\u001b[2J";
        const clearAnsiIndex = output.lastIndexOf(clearAnsiSequence);
        if (clearAnsiIndex !== -1) {
            this.removeReplExpressions();
            this.appendToRepl(session, {
                output: ( localize(9510, "Console was cleared")),
                sev: Severity$1.Ignore
            });
            output = output.substring(clearAnsiIndex + clearAnsiSequence.length);
        }
        if (expression) {
            this.addReplElement(output ? ( new ReplOutputElement(session, getUniqueId(), output, sev, source, expression)) : ( new ReplVariableElement(session, expression, sev, source)));
            return;
        }
        this.appendOutputToRepl(session, output, sev, source);
    }
    appendOutputToRepl(session, output, sev, source) {
        const config = this.configurationService.getValue("debug");
        const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined;
        if (previousElement instanceof ReplOutputElement && previousElement.severity === sev && areSourcesEqual(previousElement.sourceData, source)) {
            if (!previousElement.value.endsWith("\n") && !previousElement.value.endsWith("\r\n") && previousElement.count === 1) {
                const combinedOutput = previousElement.value + output;
                this.replElements[this.replElements.length - 1] = ( new ReplOutputElement(session, getUniqueId(), combinedOutput, sev, source));
                this._onDidChangeElements.fire(undefined);
                if (config.console.collapseIdenticalLines && combinedOutput.endsWith("\n")) {
                    this.tryCollapseCompleteLine(sev, source);
                }
                if (config.console.collapseIdenticalLines && combinedOutput.includes("\n")) {
                    const lines = this.splitIntoLines(combinedOutput);
                    if (lines.length > 1) {
                        this.applyLineLevelCollapsing(session, sev, source);
                    }
                }
                return;
            }
        }
        if (config.console.collapseIdenticalLines && output.includes("\n")) {
            this.processMultiLineOutput(session, output, sev, source);
        } else {
            if (previousElement instanceof ReplOutputElement && previousElement.severity === sev && areSourcesEqual(previousElement.sourceData, source)) {
                if (previousElement.value === output && config.console.collapseIdenticalLines) {
                    previousElement.count++;
                    return;
                }
            }
            const element = ( new ReplOutputElement(session, getUniqueId(), output, sev, source));
            this.addReplElement(element);
        }
    }
    tryCollapseCompleteLine(sev, source) {
        if (this.replElements.length < 2) {
            return;
        }
        const lastElement = this.replElements[this.replElements.length - 1];
        const secondToLastElement = this.replElements[this.replElements.length - 2];
        if (lastElement instanceof ReplOutputElement && secondToLastElement instanceof ReplOutputElement && lastElement.severity === sev && secondToLastElement.severity === sev && areSourcesEqual(lastElement.sourceData, source) && areSourcesEqual(secondToLastElement.sourceData, source) && lastElement.value === secondToLastElement.value && lastElement.count === 1 && lastElement.value.endsWith("\n")) {
            secondToLastElement.count += lastElement.count;
            this.replElements.pop();
            this._onDidChangeElements.fire(undefined);
        }
    }
    processMultiLineOutput(session, output, sev, source) {
        const lines = this.splitIntoLines(output);
        for (const line of lines) {
            if (line.length === 0) {
                continue;
            }
            const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined;
            if (previousElement instanceof ReplOutputElement && previousElement.severity === sev && areSourcesEqual(previousElement.sourceData, source) && previousElement.value === line) {
                previousElement.count++;
            } else {
                const element = ( new ReplOutputElement(session, getUniqueId(), line, sev, source));
                this.addReplElement(element);
            }
        }
    }
    splitIntoLines(text) {
        const lines = [];
        let start = 0;
        while (start < text.length) {
            const nextLF = text.indexOf("\n", start);
            if (nextLF === -1) {
                lines.push(text.substring(start));
                break;
            }
            lines.push(text.substring(start, nextLF + 1));
            start = nextLF + 1;
        }
        return lines;
    }
    applyLineLevelCollapsing(session, sev, source) {
        const lastElement = this.replElements[this.replElements.length - 1];
        if (!(lastElement instanceof ReplOutputElement) || lastElement.severity !== sev || !areSourcesEqual(lastElement.sourceData, source)) {
            return;
        }
        const lines = this.splitIntoLines(lastElement.value);
        if (lines.length <= 1) {
            return;
        }
        this.replElements.pop();
        for (const line of lines) {
            if (line.length === 0) {
                continue;
            }
            const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined;
            if (previousElement instanceof ReplOutputElement && previousElement.severity === sev && areSourcesEqual(previousElement.sourceData, source) && previousElement.value === line) {
                previousElement.count++;
            } else {
                const element = ( new ReplOutputElement(session, getUniqueId(), line, sev, source));
                this.addReplElement(element);
            }
        }
        this._onDidChangeElements.fire(undefined);
    }
    startGroup(session, name, autoExpand, sourceData) {
        const group = ( new ReplGroup(session, name, autoExpand, sourceData));
        this.addReplElement(group);
    }
    endGroup() {
        const lastElement = this.replElements[this.replElements.length - 1];
        if (lastElement instanceof ReplGroup) {
            lastElement.end();
        }
    }
    addReplElement(newElement) {
        const lastElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined;
        if (lastElement instanceof ReplGroup && !lastElement.hasEnded) {
            lastElement.addChild(newElement);
        } else {
            this.replElements.push(newElement);
            const config = this.configurationService.getValue("debug");
            if (this.replElements.length > config.console.maximumLines) {
                this.replElements.splice(0, this.replElements.length - config.console.maximumLines);
            }
        }
        this._onDidChangeElements.fire(newElement);
    }
    removeReplExpressions() {
        if (this.replElements.length > 0) {
            this.replElements = [];
            this._onDidChangeElements.fire(undefined);
        }
    }
    clone() {
        const newRepl = ( new ReplModel(this.configurationService));
        newRepl.replElements = this.replElements.slice();
        return newRepl;
    }
}

export { RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup, ReplModel, ReplOutputElement, ReplVariableElement };
