import {
    // 图片 id  语音 id  消息 id
    ImageId, VoiceId, MessageId,

    // 事件类型    群成员权限      性别
    EventType, EventEntityMap, GroupPermission, SEX,

    // 接口               原始消息类型  事件处理器类型
    MessageChainGetable, BotConfigGetable, MessageType,

    Awaitable,
    Processor,

} from './BaseType';

// 等待器
import { Waiter } from './Waiter';


export class Bot implements BotConfigGetable {
    // 成员属性
    public waiter: Waiter;

    // 类属性
    public static groupPermission: {
        [k in GroupPermission]: k
    };

    // 一些私有的实例属性
    private config: Bot.BotConfig;
    private eventProcessorMap: Bot.EventProcessorMap;
    private wsConnection: WebSocket;

    // 普通成员方法
    // implements BotConfigGetable
    getBaseUrl(): string;
    getQQ(): number;
    getVerifyKey(): string;
    getSessionKey(): string;

    /**
     * @description 连接到 mirai-api-http，并开启一个会话，重复调用意为重建会话
     * open 方法 1. 建立会话 2. 绑定 qq 3. 与服务端建立 WebSocket 连接
     * @param baseUrl 必选，mirai-api-http server 的地址
     * @param verifyKey 必选，mirai-api-http server 设置的 verifyKey
     * @param qq      必选，欲绑定的 qq 号，需要确保该 qq 号已在 mirai-console 登陆
     */
    open({ baseUrl, verifyKey, qq, singleMode }: Bot.OpenOptions): Promise<void>;

    /**
     * @description 监听 ws 消息
     */
    private __wsListen(): Promise<void>;

    /**
     * @description 关闭会话
     * @param keepProcessor 可选，是否保留事件处理器，默认值为 false，不保留
     * @param keepConfig    可选，是否保留 session baseUrl qq averifyKey，默认值为 false，不保留
     */
    close({ keepProcessor, keepConfig }?: Bot.CloseOptions): Promise<void>;

    /**
     * ! messageChain 将在未来被移除
     * @description 向 qq 好友 或 qq 群发送消息，若同时提供，则优先向好友发送消息
     * @param temp    可选，是否是临时会话，默认为 false
     * @param friend  二选一，好友 qq 号
     * @param group   二选一，群号
     * @param quote   可选，消息引用，使用发送时返回的 messageId
     * @param message 必选，Message 实例或 MessageType 数组
     * @returns messageId 可用于撤回该消息
     */
    sendMessage({ temp, friend, group, quote, message }: Bot.SendMessageOptions): Promise<MessageId>;

    /**
     * @description 向好友或群成员发送戳一戳
     * 如果提供了 group 参数则忽略 friend
     * mirai-api-http-v1.10.1 feature
     * @param friend 二选一，好友 qq 号
     * @param group  二选一，群成员所在群 
     * @param target 必选，目标 qq 号
     */
    sendNudge({ friend, group, target }: Bot.SendNudgeOptions): Promise<void>;

    /**
     * @description 添加一个事件处理器
     * 框架维护的 WebSocket 实例会在 ws 的事件 message 下分发 Mirai http server 的消息
     * 回调函数 (data) => any，data 的结构取决于消息类型，详见 mirai-api-http 的文档
     * 而对于 ws 的其他事件 error, close, unexpectedResponse，其回调函数分别为
     * - 'error':               (err: Error) => void
     * - 'close':               (code: number, message: string) => void
     * - 'unexpected-response': (request: http.ClientRequest, response: http.IncomingMessage) => void
     * @param eventType 必选，事件类型
     * @param callback  必选，回调函数
     * @returns handle 事件处理器的标识，用于移除该处理器
     */
    on<U extends EventType, E>(eventType: U, callback: Processor<[U], E>): number;
    on<U extends EventType[], E>(eventType: U, callback: Processor<U, E>): number[];

    /**
     * @description 添加一个一次性事件处理器，回调一次后自动移除
     * @param eventType 必选，事件类型
     * @param callback  必选，回调函数
     * @param strict    可选，是否严格检测调用，由于消息可能会被中间件拦截
     *                  当为 true 时，只有开发者的处理器结束后才会移除该处理器
     *                  当为 false 时，即使消息被拦截，也会移除该处理器
     */
    one<U extends EventType>(eventType: U, callback: Processor<[U]>, strict: boolean): void;
    one<U extends EventType[]>(eventType: U, callback: Processor<U>, strict: boolean): void;

    /**
     * @description 移除一个事件处理器
     * @param eventType 必选，事件类型
     * @param handle    可选，事件处理器标识(或数组)，由 on 方法
     *                  返回，未提供时将移除该事件下的所有处理器
     */
    off(eventType: EventType, handle?: number | number[]): void;

    /**
     * @description 移除所有事件处理器
     * @param eventType 可选，事件类型(或数组)
     */
    offAll(eventType?: EventType | EventType[]): void;

    /**
     * @description 获取 config
     */
    getSessionConfig(): Promise<Bot.SessionConfig>;

    /**
     * @description 设置 config
     * @param cacheSize       可选，插件缓存大小
     * @param enableWebsocket 可选，websocket 状态
     */
    setSessionConfig({ cacheSize, enableWebsocket }: Bot.SessionConfig): Promise<void>;

    /**
     * @description 撤回由 messageId 确定的消息
     * @param messageId 欲撤回消息的 messageId
     */
    recall({ messageId, target }: Bot.RecallOptions): Promise<void>;

    /**
     * @description 上传图片至服务器，返回指定 type 的 imageId，url，及 path
     * @param type     可选，"friend" 或 "group" 或 "temp"，默认为 "group"
     * @param img      二选一，图片二进制数据
     * @param filename 二选一，图片文件路径
     */
    uploadImage({ type, img, filename }: Bot.UploadImageOptions): Promise<Bot.ImageInfo>;


    /**
     * @description 上传语音至服务器，返回 voiceId, url 及 path
     * @param type     目前仅支持 "group"，请忽略该参数
     * @param voice    二选一，语音二进制数据
     * @param filename 二选一，语音文件路径
     */
    uploadVoice({ type, voice, filename }: Bot.UploadVoiceOptions): Promise<Bot.VoiceInfo>;

    /**
     * @description 获取好友列表
     */
    getFriendList(): Promise<Bot.FriendInfo[]>;

    /**
     * @description 获取群列表
     */
    getGroupList(): Promise<Bot.GroupInfo[]>;

    /**
     * @description 获取指定群的成员列表
     * @param group 必选，欲获取成员列表的群号
     */
    getMemberList({ group }: Bot.GetMemberListOptions): Promise<Bot.MemberInfo[]>;

    /**
     * @description 获取群成员信息
     * @param group 必选，群成员所在群号
     * @param qq    必选，群成员的 qq 号
     */
    getMemberInfo({ group, qq }: Bot.GetMemberInfoOptions): Promise<Bot.MemberDetails>;

    /**
     * @description 获取群成员信息
     * @param qq    必选，用户的 qq 号
     */
    getUserProfile({ qq }: Bot.GetUserProfileOptions): Promise<Bot.UserProfile>;

    /**
     * @description 设置群成员信息
     * @param group 必选，群成员所在群号
     * @param qq    必选，群成员的 qq 号
     * @param name  可选，要设置的群名片
     * @param title 可选，要设置的群头衔
     * @param permission 可选，要设置的群头衔
     */
    setMemberInfo({ group, qq, name, title, permission }: Bot.SetMemberInfoOptions): Promise<void>;

    /**
     * @description 获取群公告列表迭代器
     * @param group 必选，群号
     * @returns 迭代器
     */
    getAnnoIter({ group }: Bot.GetAnnoIterOptions): AsyncGenerator<Bot.AnnoInfo>;

    /**
     * @description 发布群公告
     * @param group 必选，群号
     * @param content 必选，公告内容
     */
    publishAnno({ group, content, pinned }: Bot.PublishAnnoOptions): Promise<void>;

    /**
     * @description 删除群公告
     * @param {number} group 必选，群号
     * @param {string} fid 必选，公告 id
     * @reaturns {void}
     */
    deleteAnno({ group, fid }: Bot.DeleteAnnoOptions): Promise<void>;

    /**
     * @description 禁言群成员
     * @param group 必选，欲禁言成员所在群号
     * @param qq    必选，欲禁言成员 qq 号
     * @param time  必选，禁言时长，单位: s (秒)
     */
    mute({ group, qq, time }: Bot.MuteOptions): Promise<void>;

    /**
     * @description 全员禁言
     * @param group 必选，欲全员禁言的群号
     */
    muteAll({ group }: Bot.MuteAllOptions): Promise<void>;

    /**
     * @description 解除禁言
     * @param group 必选，欲解除禁言的成员所在群号
     * @param qq    必选，欲解除禁言的成员 qq 号
     */
    unmute({ group, qq }: Bot.UnmuteOptions): Promise<void>;

    /**
     * @description 解除全员禁言
     * @param group 必选，欲解除全员禁言的群号
     */
    unmuteAll({ group }: Bot.UnmuteAllOptions): Promise<void>;

    /**
     * @description 移除群成员
     * @param group   必选，欲移除的成员所在群号
     * @param qq      必选，欲移除的成员 qq 号
     * @param message 可选，默认为空串 ""，信息
     */
    removeMember({ group, qq, message }: Bot.RemoveMemberOptions): Promise<void>;

    /**
     * @description 删除好友
     * @param qq 欲删除的好友 qq 号
     */
    removeFriend({ qq }: Bot.RemoveFriendOptions): Promise<void>;

    /**
     * @description 移除群成员
     * @param group   必选，欲移除的成员所在群号
     */
    quitGroup({ group }: Bot.QuitGroupOptions): Promise<void>;

    /**
     * @description 获取群配置
     * @param group 必选，群号
     */
    getGroupConfig({ group }: Bot.GetGroupOptions): Promise<Bot.GroupConfig>;

    /**
     * @description 设置群配置
     * @param target            必选，群号
     * @param name	            可选，群名
     * @param announcement	    可选，群公告
     * @param confessTalk	    可选，是否开启坦白说
     * @param allowMemberInvite 可选，是否允许群员邀请
     * @param autoApprove	    可选，是否开启自动审批入群
     * @param anonymousChat     可选，是否允许匿名聊天
     */
    setGroupConfig({
        group: number,
        name, announcement, confessTalk,
        allowMemberInvite, autoApprove, anonymousChat,
    }: Bot.SetGroupConfigOptions): Promise<void>;

    /**
     * @description 设置群精华消息
     * @param messageId 必选，消息 id
     */
    setEssence({ messageId }: Bot.SetEssenceOptions): Promise<void>;

    /**
     * @description 向 mirai-console 发送指令
     * @param command 必选，指令名
     */
    sendCommand({ command }: Bot.SendCommandOptions): Promise<Bot.MiraiConsoleMessage>;

    /**
     * @description 通过 messageId 获取消息
     * @param {number} target    可选, 目标 qq 号/群号, mah v2.6.0+ 新增该参数
     * @param {number} messageId 必选, 消息 id
     * @returns 
     */
    getMessageById({ messageId, target }: Bot.GetMessageByIdOptions): Promise<Bot.MessageFromMessageId>

    // 类方法
    /**
     * @description 检测该账号是否已经在 mirai-console 登录
     * @param baseUrl 必选，mirai-api-http server 的地址
     * @param verifyKey 必选，mirai-api-http server 设置的 verifyKey
     * @param qq      必选，qq 号
     */
    static isBotLoggedIn({ baseUrl, verifyKey, qq }: Bot.IsBotLoggedInOptions): Promise<boolean>;

}

// 类型
declare namespace Bot {

    interface BotConfig {
        baseUrl: string;
        qq: number;
        verifyKey: string;
        sessionKey: string;
    }

    // An index signature parameter type cannot be a union type. Consider using a mapped object type instead.
    type EventProcessorMap = {
        // 索引不能使用联合类型
        [eventType in EventType]: {
            [handler: number]: (data: EventEntityMap[eventType]) => Awaitable<void | any>;
        };
    }

    interface OpenOptions {
        baseUrl?: string;
        verifyKey?: string;
        qq?: number;
        singleMode?: boolean;
    }

    interface CloseOptions {
        keepProcessor?: boolean;
        keepConfig?: boolean;
    }

    interface SendMessageOptions {
        temp?: boolean;
        friend?: number;
        group?: number;
        quote?: MessageId;
        message?: MessageChainGetable | MessageType[];
    }

    interface SendNudgeOptions {
        friend?: number;
        group?: number;
        target: number;
    }

    interface SessionConfig {
        cacheSize?: number;
        enableWebsocket?: boolean;
    }

    interface RecallOptions {
        messageId: MessageId;
        target?: number;
    }

    interface UploadImageOptions {
        type?: 'friend' | 'group' | 'temp';
        img?: Buffer;
        filename?: string;
    }

    interface UploadVoiceOptions {
        type?: 'friend' | 'group' | 'temp';
        voice?: Buffer;
        filename?: string;
    }

    interface ImageInfo {
        imageId: ImageId;
        url: string;
        path: string;
    }

    interface VoiceInfo {
        voiceId: VoiceId;
        url: string;
        path: string;
    }

    interface FriendInfo {
        id: number;
        name: string;
        remark: string;
    }

    interface GroupInfo {
        id: number;
        name: string;
        permission: GroupPermission;
    }

    interface MemberInfo {
        id: number;
        name: string;
        permission: GroupPermission;
    }

    interface GetMemberInfoOptions {
        group: number;
        qq: number;
    }

    interface MemberDetails {
        id: number;
        joinTimestamp: number;
        lastSpeakTimestamp: number;
        memberName: string;
        nuteTimeRemaining: number;
        permission: GroupPermission;
        title: string;
    }

    interface GetMemberListOptions {
        group: number;
    }

    interface GetUserProfileOptions {
        qq: number;
    }

    interface UserProfile {
        nickname: string;
        email: string;
        age: number;
        level: number;
        sign: string;
        sex: SEX;
    }

    interface SetMemberInfoOptions {
        group: number;
        qq: number;
        name?: string;
        title?: string;
        permission?: GroupPermission
    }

    interface GetAnnoIterOptions {
        group: number;
    }

    interface PublishAnnoOptions {
        group: number;
        content: string;
        pinned: boolean;
    }

    interface DeleteAnnoOptions {
        group: number;
        fid: string;
    }
    interface AnnoInfo {
        group: { id: number; name: string; permission: GroupPermission; };
        content: string;
        senderId: number;
        fid: string;
        allConfirmed: boolean;
        confirmedMembersCount: number;
        publicationTime: number;
    }

    interface MuteOptions {
        group: number;
        qq: number;
        time: number;
    }

    interface MuteAllOptions {
        group: number;
    }

    interface UnmuteOptions {
        group: number;
        qq: number;
    }

    interface UnmuteAllOptions {
        group: number;
    }

    interface RemoveMemberOptions {
        group: number;
        qq: number;
        message?: string;
    }

    interface RemoveFriendOptions {
        qq: number;
    }

    interface QuitGroupOptions {
        group: number;
    }

    interface GetGroupOptions {
        group: number;
    }

    interface GroupConfig {
        name: string;
        announcement: string;
        confessTalk: boolean;
        allowMemberInvite: boolean;
        autoApprove: boolean;
        anonymousChat: boolean;
    }

    interface SetGroupConfigOptions {
        group: number;
        name?: string;
        announcement?: string;
        confessTalk?: boolean;
        allowMemberInvite?: boolean;
        autoApprove?: boolean;
        anonymousChat?: boolean;
    }

    interface SetEssenceOptions {
        messageId: number;
        target?: number;
    }

    interface SendCommandOptions {
        command: string[];
    }

    interface GetMessageByIdOptions {
        messageId: number;
        target?: number;
    }

    interface MessageFromMessageId {
        type: 'FriendMessage' | 'GroupMessage' | 'TempMessage';
        messageChain: MessageType[];
        sender: {
            id?: number;
            nickname?: string;
            remark?: string;
            memberName?: string;
            specialTitle?: string;
            permission?: GroupPermission;
            joinTimestamp?: number;
            lastSpeakTimestamp?: number;
            muteTimeRemaining?: number;
            group?: {
                id: number;
                name: string;
                permission: GroupPermission;
            }
        }
    }

    interface IsBotLoggedInOptions {
        baseUrl: string;
        verifyKey: string;
        qq: number;
    }

    interface MiraiConsoleMessage {
        message: string
    }
}
