import { getInspector } from "./inspect.mjs";
import { evalQ, EnsureCompletion, IsPromise, JSStringValue, ManagedRealm, NullValue, ObjectValue, SymbolValue, ThrowCompletion, UndefinedValue, Value, getHostDefinedErrorStack, skipDebugger, getCurrentStack, isECMAScriptFunctionObject, SymbolDescriptiveString, EnvironmentRecord, DeclarativeEnvironmentRecord, ObjectEnvironmentRecord, FunctionEnvironmentRecord, GlobalEnvironmentRecord, ModuleEnvironmentRecord, OrdinaryObjectCreate, Descriptor, isArgumentExoticObject, Agent, surroundingAgent, IsAccessorDescriptor, isIntegerIndex, isBuiltinFunctionObject, isArrayBufferObject, DataBlock, CallSite, } from '#self';
export class InspectorContext {
    #io;
    constructor(io) {
        this.#io = io;
    }
    realms = [];
    attachRealm(realm, agent) {
        const id = this.realms.length;
        const descriptor = {
            id,
            origin: 'vm://realm',
            name: 'engine262',
            uniqueId: id.toString(),
        };
        this.realms.push({
            realm,
            descriptor,
            agent,
            detach: () => {
                realm.HostDefined.attachingInspector = oldInspector;
            },
        });
        const oldInspector = realm.HostDefined.attachingInspector;
        realm.HostDefined.attachingInspector = this.#io;
        const oldPromiseRejectionTracker = realm.HostDefined.promiseRejectionTracker;
        realm.HostDefined.promiseRejectionTracker = (promise, operation) => {
            oldPromiseRejectionTracker?.(promise, operation);
            if (operation === 'reject') {
                this.#io.sendEvent['Runtime.exceptionThrown']({
                    timestamp: Date.now(),
                    exceptionDetails: this.createExceptionDetails(promise, true),
                });
            }
            else {
                const id = this.#exceptionMap.get(promise);
                if (id) {
                    this.#io.sendEvent['Runtime.exceptionRevoked']({
                        reason: 'Handler added to rejected promise',
                        exceptionId: id,
                    });
                }
            }
        };
        this.#io.sendEvent['Runtime.executionContextCreated']({ context: descriptor });
    }
    detachAgent(agent) {
        for (const realm of this.realms) {
            if (realm?.agent === agent) {
                this.detachRealm(realm.realm);
            }
        }
    }
    detachRealm(realm) {
        const index = this.realms.findIndex((c) => c?.realm === realm);
        if (index === -1) {
            return;
        }
        const { descriptor } = this.realms[index];
        realm.HostDefined.attachingInspector = undefined;
        this.realms[index] = undefined;
        this.#io.sendEvent['Runtime.executionContextDestroyed']({ executionContextId: descriptor.id, executionContextUniqueId: descriptor.uniqueId });
    }
    getRealm(realm) {
        if (realm === undefined) {
            if (surroundingAgent.runningExecutionContext && surroundingAgent.currentRealmRecord instanceof ManagedRealm) {
                realm = surroundingAgent.currentRealmRecord;
            }
            else {
                return undefined;
            }
        }
        if (typeof realm === 'string') {
            return this.realms.find((c) => c?.descriptor.uniqueId === realm);
        }
        else if (typeof realm === 'number') {
            return this.realms[realm];
        }
        return this.realms.find((c) => c?.realm === realm);
    }
    /** @deprecated in this case we are guessing the realm should be using, which may create bad result */
    getAnyRealm() {
        return this.realms.find(Boolean);
    }
    #idToObject = new Map();
    // id 0 is falsy, skip it
    #idToArrayBufferBlock = [undefined];
    #objectToId = new Map();
    #objectCounter = 1;
    #internObject(object, group = 'default') {
        if (this.#objectToId.has(object)) {
            return this.#objectToId.get(object);
        }
        const id = `${group}:${this.#objectCounter}`;
        this.#objectCounter += 1;
        this.#idToObject.set(id, object);
        this.#objectToId.set(object, id);
        return id;
    }
    releaseObject(id) {
        const object = this.#idToObject.get(id);
        if (object) {
            this.#idToObject.delete(id);
            this.#objectToId.delete(object);
        }
    }
    releaseObjectGroup(group) {
        for (const [id, object] of this.#idToObject.entries()) {
            if (id.startsWith(group)) {
                this.#idToObject.delete(id);
                this.#objectToId.delete(object);
            }
        }
    }
    getObject(objectId) {
        return this.#idToObject.get(objectId);
    }
    toRemoteObject(value, options) {
        return getInspector(value).toRemoteObject(value, (val) => this.#internObject(val, options.objectGroup), options.generatePreview);
    }
    getProperties({ objectId, accessorPropertiesOnly, generatePreview, nonIndexedPropertiesOnly, ownProperties, }) {
        const object = this.getObject(objectId);
        if (!(object instanceof ObjectValue)) {
            return { result: [] };
        }
        const wrap = (v) => this.toRemoteObject(v, { generatePreview });
        const properties = [];
        const internalProperties = [];
        const privateProperties = [];
        object.PrivateElements.forEach((value) => {
            privateProperties.push({
                name: value.Key.Description.stringValue(),
                value: value.Value ? wrap(value.Value) : undefined,
                get: value.Get ? wrap(value.Get) : undefined,
                set: value.Set ? wrap(value.Set) : undefined,
            });
        });
        const value = evalQ((Q) => {
            let p = object;
            while (p instanceof ObjectValue) {
                for (const key of Q(skipDebugger(p.OwnPropertyKeys()))) {
                    if (nonIndexedPropertiesOnly && isIntegerIndex(key)) {
                        continue;
                    }
                    const desc = Q(skipDebugger(p.GetOwnProperty(key)));
                    if (accessorPropertiesOnly && !IsAccessorDescriptor(desc)) {
                        continue;
                    }
                    if (desc instanceof UndefinedValue) {
                        return;
                    }
                    const descriptor = {
                        name: key instanceof JSStringValue
                            ? key.stringValue()
                            : SymbolDescriptiveString(key).stringValue(),
                        value: desc.Value && !('HostUninitializedBindingMarkerObject' in desc.Value) ? wrap(desc.Value) : undefined,
                        writable: desc.Writable === Value.true,
                        get: desc.Get ? wrap(desc.Get) : undefined,
                        set: desc.Set ? wrap(desc.Set) : undefined,
                        configurable: desc.Configurable === Value.true,
                        enumerable: desc.Enumerable === Value.true,
                        wasThrown: false,
                        isOwn: p === object,
                        symbol: key instanceof SymbolValue ? wrap(key) : undefined,
                    };
                    properties.push(descriptor);
                }
                if (ownProperties) {
                    break;
                }
                p = Q(skipDebugger(p.GetPrototypeOf()));
            }
        });
        if (value.Type === 'throw') {
            return {
                result: [],
                exceptionDetails: this.createExceptionDetails(value, false),
            };
        }
        if (IsPromise(object) === Value.true) {
            internalProperties.push({
                name: '[[PromiseState]]',
                value: {
                    type: 'string',
                    value: object.PromiseState,
                },
            });
            internalProperties.push({
                name: '[[PromiseResult]]',
                value: wrap(object.PromiseResult),
            });
        }
        if ('Prototype' in object) {
            internalProperties.push({
                name: '[[Prototype]]',
                value: wrap(object.Prototype),
            });
        }
        if (isBuiltinFunctionObject(object) && object.nativeFunction.section) {
            internalProperties.push({
                name: '[[Section]]',
                value: {
                    type: 'string',
                    value: object.nativeFunction.section,
                },
            });
        }
        if (isArrayBufferObject(object) && object.ArrayBufferData instanceof DataBlock) {
            internalProperties.push({
                name: '[[ArrayBufferByteLength]]',
                value: {
                    type: 'number',
                    value: object.ArrayBufferByteLength,
                },
            });
            this.#idToArrayBufferBlock.push(object.ArrayBufferData.buffer);
            internalProperties.push({
                name: '[[ArrayBufferData]]',
                value: {
                    type: 'number',
                    value: this.#idToArrayBufferBlock.length - 1,
                },
            });
        }
        return { result: properties, internalProperties, privateProperties };
    }
    #exceptionMap = new WeakMap();
    createExceptionDetails(completion, isPromise) {
        const value = completion instanceof ThrowCompletion ? completion.Value : completion;
        const stack = getHostDefinedErrorStack(value);
        const frames = InspectorContext.callSiteToCallFrame(stack);
        const exceptionId = this.#objectCounter;
        this.#objectCounter += 1;
        this.#exceptionMap.set(value, exceptionId);
        return {
            text: isPromise ? 'Uncaught (in promise)' : 'Uncaught',
            stackTrace: stack ? { callFrames: frames } : undefined,
            exception: getInspector(value).toRemoteObject(value, (val) => this.#internObject(val), false),
            lineNumber: frames[0]?.lineNumber || 0,
            columnNumber: frames[0]?.columnNumber || 0,
            exceptionId,
            scriptId: frames[0]?.scriptId,
            url: frames[0]?.url,
        };
    }
    static callSiteToCallFrame(callSite) {
        return callSite?.map((call) => call.toCallFrame()).filter(Boolean) || [];
    }
    createEvaluationResult(completion) {
        completion = EnsureCompletion(completion);
        if (!(completion.Value instanceof Value)) {
            throw new RangeError('Invalid completion value');
        }
        return {
            exceptionDetails: completion instanceof ThrowCompletion ? this.createExceptionDetails(completion, false) : undefined,
            result: this.toRemoteObject(completion.Value, {}),
        };
    }
    getDebuggerCallFrame() {
        const stacks = getCurrentStack(false);
        const length = surroundingAgent.executionContextStack.length;
        return stacks.map((stack, index) => {
            if (!stack.getScriptId()) {
                return undefined;
            }
            const scopeChain = [];
            let env = stack.context.LexicalEnvironment;
            while (env instanceof EnvironmentRecord) {
                const result = getDisplayObjectFromEnvironmentRecord(env);
                if (result) {
                    scopeChain.push({ type: result.type, object: this.toRemoteObject(result.object, {}) });
                }
                env = env.OuterEnv;
            }
            return {
                callFrameId: String(length - index - 1),
                functionName: stack.getFunctionName() || '<anonymous>',
                location: {
                    scriptId: stack.getScriptId(),
                    lineNumber: (stack.lineNumber || 1) - 1,
                    columnNumber: (stack.columnNumber || 1) - 1,
                },
                this: this.toRemoteObject(HostGetThisEnvironment(stack.context.LexicalEnvironment), {}),
                url: stack.getSpecifier() || '',
                canBeRestarted: false,
                functionLocation: isECMAScriptFunctionObject(stack.context.Function) ? {
                    lineNumber: (stack.context.Function.ECMAScriptCode?.location.start.line || 1) - 1,
                    columnNumber: (stack.context.Function.ECMAScriptCode?.location.start.column || 1) - 1,
                    scriptId: stack.getScriptId() || '',
                } : undefined,
                scopeChain,
            };
        }).filter(Boolean);
    }
    evaluateMode = 'script';
}
function HostGetThisEnvironment(env) {
    while (!(env instanceof NullValue)) {
        const exists = env.HasThisBinding();
        if (exists === Value.true) {
            const value = env.GetThisBinding();
            if (value instanceof ThrowCompletion) {
                return Value.undefined;
            }
            return value;
        }
        const outer = env.OuterEnv;
        env = outer;
    }
    throw new ReferenceError('No this environment found');
}
function getDisplayObjectFromEnvironmentRecord(record) {
    if (record instanceof DeclarativeEnvironmentRecord) {
        const object = OrdinaryObjectCreate(Value.null, ['HostInspectorScopePreview']);
        for (const [key, binding] of record.bindings) {
            const value = binding.initialized ? binding.value : OrdinaryObjectCreate(Value.null, ['HostUninitializedBindingMarkerObject']);
            if (isArgumentExoticObject(value)) {
                continue;
            }
            object.properties.set(key, Descriptor({
                Enumerable: isArgumentExoticObject(value) ? Value.false : Value.true,
                Value: value,
                Writable: binding.mutable ? Value.true : Value.false,
            }));
        }
        let type = 'block';
        if (record instanceof FunctionEnvironmentRecord) {
            type = 'local';
        }
        else if (record instanceof ModuleEnvironmentRecord) {
            type = 'module';
        }
        if (type !== 'local' && !object.properties.size) {
            return undefined;
        }
        return { type, object };
    }
    else if (record instanceof ObjectEnvironmentRecord) {
        return { type: record.IsWithEnvironment === Value.true ? 'with' : 'global', object: record.BindingObject };
    }
    else if (record instanceof GlobalEnvironmentRecord) {
        return { type: 'global', object: record.GlobalThisValue };
    }
    throw new TypeError('Unknown environment record');
}
//# sourceMappingURL=context.mjs.map