import { Hex, Address, Transport, Chain, PublicClient, WalletClient, Account, Prettify, BlockTag } from 'viem';
import { ArweaveIrys } from '@irys/sdk/node/flavours/arweave';

/** Re-export of the `JWKInterface` type, which represents an Arweave wallet.
 *
 * @example
 * const path = "./wallet.json";
 * const key = JSON.parse(fs.readFileSync(path, "utf-8")) as ArweaveWallet;
 */
type ArweaveWallet = ConstructorParameters<typeof ArweaveIrys>[0]["key"];
/** An Arweave key is considered to be an object with `arweave` field. */
type ArweaveStorageKey = {
    arweave: string;
};
/**
 * An Arweave wrapper for decentralized storage interface.
 *
 * It can be used to read Arweave values from the coordinator, as well as put Arweave-uploaded value to the coordinator.
 *
 * Note that for `write` operations, it uses Irys SDK to interact with the Arweave network,
 * for `put` and `balance` commands ,so it requires `@irys/sdk` peer dependency for those.
 *
 * @example
 * // read only
 * const storage = new ArweaveStorage();
 *
 * @example
 * // read & write
 * const storage = new ArweaveStorage();
 * const key: JWInterface = JSON.parse(fs.readFileSync("./wallet.json", "utf-8"));
 * storage.init(key);
 */
declare class ArweaveStorage implements DecentralizedStorage<Buffer, ArweaveStorageKey> {
    /** Byte threshold, beyond which data is uploaded to Arweave. */
    bytesLimit: number;
    /** Irys SDK instance. */
    irys?: ArweaveIrys;
    /** Base URL of the gateway. */
    baseUrl: string;
    /**
     * Initializes the Arweave storage with the given key.
     * @param key Arweave wallet object.
     * @param bytesLimit Number of bytes such that smaller data are not uploaded, default is 1024 bytes.
     */
    init(key: ArweaveWallet, bytesLimit?: number): void;
    /** Return an `ArweaveKey` object if `key` is a stringified object of the form `{ arweave: string }`.
     *
     * @example // a valid key
     * { arweave: "Zg6CZYfxXCWYnCuKEpnZCYfy7ghit1_v4-BCe53iWuA" }
     */
    isKey(key: string): ArweaveStorageKey | null;
    get(key: ArweaveStorageKey): Promise<Buffer | null>;
    /**
     * Uploads the given value to Arweave and returns the key.
     *
     * _Requires `init` to have been called._
     *
     * @param value Value to upload.
     * @returns Key to access the value.
     */
    put(value: Buffer): Promise<ArweaveStorageKey>;
    /**
     * Returns the balance of the Arweave wallet.
     *
     * _Requires `init` to have been called._
     *
     * @returns Balance in `bigint`.
     */
    balance(): Promise<bigint>;
    describe(): string;
}

/**
 * A key-value storage that can be used to store arbitrary data in a decentralized-storage fashion.
 * Most often, the `T` is a `Buffer` and `K` is anything custom defined.
 *
 * @template T The type of the data to store.
 * @template K The type of the key to use.
 */
interface DecentralizedStorage<T = unknown, K = string> {
    /** The number of bytes, after which storage shall be considered. */
    bytesLimit: number;
    /**
     * @param key The key to get the value for.
     * @returns The value at the key, or `null` if it does not exist.
     */
    get(key: K): Promise<T | null>;
    /**
     * Puts a value to the storage, returns the key.
     * @param value The value to put in the storage.
     * @returns The key that can be used to retrieve the value.
     */
    put(value: T): Promise<K>;
    /**
     * @param key The key to check.
     * @returns Whether the key if its valid, or `null` if its invalid.
     */
    isKey(key: string): K | null;
    /** Returns the name of the storage. */
    describe(): string;
}

/**
 * Task status as it appears within the contract.
 *
 * - `None`: Task has not been created yet. (default)
 * - `PendingGeneration`: Task is waiting for Oracle generation responses.
 * - `PendingValidation`: Task is waiting for validation by validator Oracles.
 * - `Completed`: The task has been completed.
 *
 * There are two scenarios:
 * - With validation, the flow is `None -> PendingGeneration -> PendingValidation -> Completed`.
 * - Without validation, the flow is `None -> PendingGeneration -> Completed`.
 *
 * Note that this type is compatible with the contract type (number).
 */
declare enum TaskStatus {
    None = 0,
    PendingGeneration = 1,
    PendingValidation = 2,
    Completed = 3
}
/** Collection of oracle task-related parameters. */
interface TaskParameters {
    /** Difficulty of the task. */
    difficulty: number;
    /** Number of generations. */
    numGenerations: number;
    /**  Number of validations. */
    numValidations: number;
}
/**
 * A task request for LLM generation.
 * Fees are stored here as well in case fee changes occur within the duration of a task.
 *
 * Adapted from [`LLMOracleTask.sol`](https://github.com/firstbatchxyz/dria-oracle-contracts/blob/master/src/LLMOracleTask.sol#L34).
 */
interface TaskRequest {
    /** Requesting address, also responsible of the fee payment. */
    requester: Address;
    /** Protocol string, such as `dria/0.1.0`. */
    protocol: string;
    /** Task parameters, e.g. difficulty and number of generations & validations. */
    parameters: TaskParameters;
    /** Task status. */
    status: TaskStatus;
    /** Fee paid to each generator per generation. */
    generatorFee: bigint;
    /** Fee paid to each validator per validated generation. */
    validatorFee: bigint;
    /** Fee paid to the platform */
    platformFee: bigint;
    /** Input data for the task, usually a human-readable string. */
    input: string;
    /** Allowed model names for the task. */
    models: string;
}
/** A task generation response.
 * @template O Output data type, defaults `Hex`.
 * @template M Metadata type, defaults `Hex`.
 */
interface TaskResponse<O = Hex, M = Hex> {
    /** Responding Oracle address. */
    responder: Address;
    /** Proof-of-Work nonce for SHA3(taskId, input, requester, responder, nonce) < difficulty. */
    nonce: bigint;
    /** Final validation score assigned by validators, stays 0 if there is no validation. */
    score: bigint;
    /** Output data for the task, usually the direct output of LLM. */
    output: O;
    /** Optional metadata for this generation. */
    metadata: M;
}
/** A task validation for a response.
 * @template M Metadata type, defaults `Hex`.
 */
interface TaskValidation<M = Hex> {
    /** Responding validator address. */
    validator: Address;
    /** Proof-of-Work nonce for SHA3(taskId, input, requester, responder, nonce) < difficulty. */
    nonce: bigint;
    /** Validation scores */
    scores: readonly bigint[];
    /** Optional metadata for this validation. */
    metadata: M;
}
/** A task validaiton score object.
 *
 * Within a task validation, we usually expect an array of these objects,
 * one for each generation.
 *
 * The `final_score` here is the actual score considered by the contract,
 * and `rationale` describes how the LLM decided that score.
 *
 * A score is expected to be a number between 1 and 5, inclusive; where 1 is worst and 5 is best.
 */
type TaskValidationScores = {
    helpfulness: number;
    instruction_following: number;
    truthfulness: number;
    /** The final score given by the node. */
    final_score: number;
    rationale: string;
};

/** A chat history entry. */
type ChatHistoryResponse = {
    /** Role, usually `user`, `assistant` or `system`. */
    role: string;
    /** Message content. */
    content: string;
};
/** A request with chat history. */
type ChatHistoryRequest = {
    /** Task id of which the output will act like history. */
    history_id: number | bigint;
    /** Message content. */
    content: string;
};
/** Return type for `request` function. */
type NewRequestReturnType = {
    txHash: Hex;
    protocol: string;
    input: string;
    models: Models;
    taskParameters: TaskParameters;
};
/** Optional arguments for `request`. */
type TaskRequestOptions = {
    taskParameters?: Partial<TaskParameters>;
    protocol?: string;
};

/**
 * Allowed Oracle models.
 *
 * The requested model(s) can be any of the following:
 *
 * - An array of model names, such as `["gemini-1.5-pro", "gpt-4o-mini"]`.
 * - `*` for any model randomly (of the responder).
 * - `!` for the first model (of the responder).
 *
 * You can look at the available models from [this repository](https://github.com/andthattoo/ollama-workflows/blob/main/src/program/models.rs#L14).
 */
type Models = string[] | "*" | "!";

/**
 * The Oracle client is used to interact with the Dria Oracles. It allows you to make requests, read responses, and process them.
 *
 * It can be instantiated with a `storage` as well, which can be used to store large data in a decentralized manner.
 * If the data to be written to contract is large, we can instead store that data in the storage and pass the key to the contract.
 * This key can then be used to fetch the data from the storage.
 *
 * @template T transport type, e.g. HTTP or WebSocket (usually inferred)
 * @template C chain type, e.g. Ethereum or Binance Smart Chain (usually inferred)
 * @template K storage key type, e.g. `ArweaveKey` (usually inferred)
 * @example
 * // without storage
 * const oracle = new Oracle({ public, wallet });
 * await oracle.init(coordinatorAddress);
 *
 * @example
 * // with storage (Arweave)
 * const wallet = JSON.parse(readFileSync("./path/to/wallet.json", "utf-8"));
 * const arweave = new ArweaveStorage(wallet);
 * const oracle = new Oracle({ public, wallet }, arweave);
 * await oracle.init(coordinatorAddress);
 */
declare class Oracle<T extends Transport, C extends Chain, K = unknown> {
    readonly client: {
        public: PublicClient<T, C>;
        wallet: WalletClient<T, C, Account>;
    };
    readonly storage?: DecentralizedStorage<Buffer, K> | undefined;
    coordinator?: ReturnType<InstanceType<typeof Oracle<Transport, Chain, K>>["Coordinator"]>;
    token?: ReturnType<InstanceType<typeof Oracle<Transport, Chain, K>>["Token"]>;
    taskParameters: TaskParameters;
    protocol: string;
    constructor(client: {
        public: PublicClient<T, C>;
        wallet: WalletClient<T, C, Account>;
    }, storage?: DecentralizedStorage<Buffer, K> | undefined);
    /**
     * Initialize the oracle client by setting up contract instances.
     * @param coordinatorAddress coordinator contract address
     * @returns initialized oracle client
     */
    init(coordinatorAddress: Address): Promise<this>;
    /**
     * Change the underlying default task parameters.
     * @param opts new default task parameters
     */
    withParameters(opts: Partial<TaskParameters>): this;
    /**
     * Change the underlying default protocol.
     *
     * The protocol is a string that should fit a `bytes32` type in Solidity. It is used
     * to identify the source of the request, and can be used within event filters.
     *
     * It should have to format `name/version`, e.g. `dria-oracle-sdk/0.x.x`.
     * @param protocol protocol name
     */
    withProtocol(protocol: string): this;
    /** Returns a new instance of the LLM coordinator contract.
     * @warning This is an internal method, and making it `private` will break
     * the type export due to the size of ABI-inferred types.
     */
    private Coordinator;
    /** Returns a new instance of an ERC20 token contract.
     * @warning This is an internal method, and making it `private` will break
     * the type export due to the size of ABI-inferred types.
     */
    private Token;
    /**
     * Make an oracle request.
     * @param input input string, or a chat input
     * - a string input can be anything, like "What is 2+2?"
     * - a chat input is an object `{historyId: number, content: string}` where
     * the `historyId` is a task id the output of which is to be used as history
     * @param models requested models, can be any of the following:
     * - `*` for all models
     * - `!` for first model of the responder
     * - `["model1", "model2", ...]` for specific models
     *
     * (defaults to `*`)
     * @param opts optional request arguments, such as `protocol` and `taskParameters`
     * @returns task transaction hash
     */
    request(input: string, models: Models, opts?: TaskRequestOptions): Promise<NewRequestReturnType>;
    request(input: Prettify<ChatHistoryRequest>, models: Models, opts?: TaskRequestOptions): Promise<NewRequestReturnType>;
    /**
     * Waits until the request transaction is mined and returns the task id.
     * @param txHash transaction hash for the request (see `request`)
     * @returns taskId
     */
    waitRequest(txHash: Hex): Promise<bigint>;
    /**
     * Alias for `getBestResponse` followed by `processResponse`.
     * @param taskId task id
     * @param kind task kind, e.g. `chat` for conversational models
     * @returns processed task response
     */
    read(taskId: bigint): Promise<TaskResponse<string | null, string | null>>;
    /**
     * Returns a boolean indicating if the task is completed.
     * @param taskId task id
     * @returns true if the task is completed, or `taskId` is 0
     */
    isCompleted(taskId: bigint | number): Promise<boolean>;
    /**
     * Returns the task request for a given task id.
     * @param taskId task id
     * @returns task request
     */
    getRequest(taskId: bigint | number): Promise<TaskRequest>;
    /**
     * Returns the highest scored response of a task.
     * Will throw an error if the task is not completed yet!
     * @param taskId task id
     * @returns task response with the highest score
     */
    getBestResponse(taskId: bigint | number): Promise<Prettify<TaskResponse>>;
    /**
     * Returns the validations of all generation responses for a task.
     * @param taskId task id
     * @returns array of task validations
     */
    getValidations(taskId: bigint | number): Promise<readonly Prettify<TaskValidation>[]>;
    /**
     * Returns the generation responses for a task.
     * @param taskId task id
     * @returns array of task responses
     */
    getResponses(taskId: bigint | number): Promise<readonly Prettify<TaskResponse>[]>;
    /**
     * Process the `output` and `metadata` of a task, with respect to the given storage.
     * @param response existing response object
     * @returns response object with processed `output` and `metadata`
     */
    processResponse(response: TaskResponse): Promise<TaskResponse<string | null, string | null>>;
    /**
     * Process the `metadata` of a task, with respect to the given storage.
     * @param response existing validation object
     * @returns validation object with processed `metadata`
     */
    processValidation(validation: TaskValidation): Promise<TaskValidation<TaskValidationScores[]>>;
    /** Shorthand to parse a string input to a chat history response. */
    toChatHistory(input: string): Prettify<ChatHistoryResponse>[];
    /**
     * Fetches the task events from the coordinator contract and returns its args.
     * A returned task event has the taskId, protocol, statusBefore, and statusAfter.
     * For a completed task, we are looking for the statusAfter to be `Completed` which is `3`.
     *
     * @param opts options for fetching tasks
     * - `protocol`: protocol name
     * - `from`: block to start from
     * - `to`: block to end at
     * - `status`: task status to filter by
     * @returns array of task events
     */
    getTaskEvents(opts: {
        protocol?: string;
        from?: bigint | BlockTag;
        to?: bigint | BlockTag;
        status?: TaskStatus;
    }): Promise<{
        taskId?: bigint | undefined;
        protocol?: `0x${string}` | undefined;
        statusBefore?: number | undefined;
        statusAfter?: number | undefined;
    }[]>;
    /**
     * Waits for a task to be completed, i.e. it should have all the required
     * generations and validations be done.
     * @param taskId task id
     */
    wait(taskId: bigint | number): Promise<void>;
    /** Returns the allowance of the client for the coordinator.
     * @returns allowance amount
     */
    allowance(): Promise<bigint>;
    /**
     * Approves the coordinator to spend the client's tokens.
     * @param amount amount to approve, defaults to max uint256 (infinite)
     * @returns transaction hash
     */
    approve(amount?: bigint): Promise<Hex>;
}

/**
 * Given a string, converts it to a `Hex` string.
 *
 * First, the string is converted to a `Uint8Array`.
 * - If `storage` is given and the bytearray is large enough, it is uploaded to the storage and its key in `Hex` is returned.
 * - Otherwise, the bytearray is converted to a `Hex` string.
 *
 * If storage is being used, make sure it can upload the data, e.g. `ArweaveStorage` must be `init`ed.
 *
 * @param bytes input string
 * @param storage decentralized storage, optional
 * @template K storage key type, inferred as `unknown` if storage is `undefined`
 * @returns a `Hex` string, with 0x prefix
 */
declare function stringToContractBytesWithStorage<K>(input: string, storage?: DecentralizedStorage<Buffer, K>): Promise<Hex>;
/**
 * Given a `bytes` Solidity type, converts it to a string.
 *
 * If `storage` is given, the resulting string is try-parsed as a key, and if it is a key,
 * the actual value is fetched from the storage.
 *
 * If no value is found at the storage, it returns `null`.
 *
 * @param input bytes
 * @param storage decentralized storage, optional
 * @template K storage key type, inferred as `unknown` if storage is `undefined`
 * @returns parsed string
 */
declare function contractBytesToStringWithStorage<K>(input: Hex, storage?: DecentralizedStorage<Buffer, K>): Promise<string | null>;

export { ArweaveStorage, type ArweaveWallet, type DecentralizedStorage, Oracle, type Models as OracleModels, TaskStatus, contractBytesToStringWithStorage, stringToContractBytesWithStorage };
