/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.

package std.unittest.mock

import std.unittest.mock.internal.*
/*
 * Specifies additional default behaviour for mock objects.
 * Explicit stubs always override default behaviour.
 */
public enum StubMode {
    /**
     * Mock object will return default values for common 'simple' types.
     * Use to simplify mock object setup.
     * These values are 'negative' or 'empty'
     * Supported types are: Unit, number types, option types, Bool, String, Array, ArrayList, HashSet, HashMap.
     */
    | ReturnsDefaults
    /**
     * Mock object will treat its mutable properties and fields as if they are mutable fields.
     * Similar to using SyntheticField directly but less verbose.
     * Reading an unitialized field will result in an error.
     */
    | SyntheticFields
}

/**
 * Creates a mock object of type T. This object does not have any behaviour specified for its members by default.
 * Behaviour for mock object can be specified using @On macro.
 *
 * @returns mock object of type T.
 */
@Frozen
public func mock<T>(): T {
    return create<T>(Mock, []) { handler: CallHandlerImpl => unsafe { createMock<T>(handler) }}
}

/**
 * Creates a mock object of type T. This object does not have any behaviour specified for its members by default.
 * Behaviour for mock object can be specified using @On macro.
 *
 * @param modes - stub modes altering mock object default behaviour.
 * @returns mock object of type T.
 */
@Frozen
public func mock<T>(modes: Array<StubMode>): T {
    return create<T>(Mock, modes) { handler: CallHandlerImpl => unsafe { createMock<T>(handler) }}
}

/**
 * Creates a spy object of type T. Spy object wraps a passed in instance and delegates all calls to that instance by default.
 * Behaviour for members of the spy object can be overridden using @On macro.
 *
 * @param objectToSpyOn instance to spy on.
 * @returns spy object that spies on passed in instance of type T.
 */
@Frozen
public func spy<T>(objectToSpyOn: T): T {
    return create<T>(Spy, []) { handler: CallHandlerImpl => unsafe { createSpy<T>(handler, objectToSpyOn) }}
}

func create<T>(kind: MockKind, modes: Array<StubMode>, constructor: (CallHandlerImpl) -> T): T {
    MockFramework.session { session: MockSession =>
        let id = _FRAMEWORK.generateUniqueId()
        let createdAtSessionName = session.name
        let handler = CallHandlerImpl(id, createdAtSessionName)
        let objAsT = constructor(handler)
        let ref = (objAsT as Object) ?? internalError("Mock object does not inherit Object superclass")
        _FRAMEWORK.registerObject(MockObject(id, ref, kind, modes, createdAtSessionName))
        objAsT
    }
}