/**
 * Copyright (c) Whales Corp. 
 * All Rights Reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { Address } from "../address/Address";
import { Cell } from "../boc/Cell";
import { Dictionary } from "../dict/Dictionary";
import { StateInit } from "../types/StateInit";
import { Contract } from "./Contract";
import { ContractProvider } from "./ContractProvider";
import { SimpleLibrary } from "../types/SimpleLibrary";

export type OpenedContract<F> = {
    [P in keyof F]: P extends `${'get' | 'send'}${string}`
    ? (F[P] extends (x: ContractProvider, ...args: infer P) => infer R ? (...args: P) => R : never)
    : F[P];
}

export function openContract<T extends Contract>(src: T, factory: (params: { address: Address, init: StateInit | null }) => ContractProvider): OpenedContract<T> {

    // Resolve parameters
    let address: Address;
    let init: StateInit | null = null;

    if (!Address.isAddress(src.address)) {
        throw Error('Invalid address');
    }
    address = src.address;
    if (src.init) {
        if (!(src.init.code instanceof Cell)) {
            throw Error('Invalid init.code');
        }
        if (!(src.init.data instanceof Cell)) {
            throw Error('Invalid init.data');
        }
        init = src.init;
    }

    // Create executor
    let executor = factory({ address, init });

    // Create proxy
    return new Proxy<any>(src as any, {
        get(target, prop) {
            const value = target[prop];
            if (typeof prop === 'string' && (prop.startsWith('get') || prop.startsWith('send'))) {
                if (typeof value === 'function') {
                    return (...args: any[]) => value.apply(target, [executor, ...args]);
                }
            }
            return value;
        }
    }) as OpenedContract<T>;
}
