export as namespace zChat;

/**
 * Options to initialize Zendesk Chat.
 */
export interface InitProps {
    /**
     * Your Zendesk Chat account key.
     * */
    account_key: string;
    /**
     * Authentication options. Currently only JWT is supported.
     * */
    authentication?: {
        /**
         * A function that accepts a callback. When this function is called,
         * it should retrieve a new JWT and pass the JWT string to the callback as its first argument
         */
        jwt_fn?: (callback: (jwt: string) => void) => void;
    };
    /**
     * `true` to suppress errors from being logged in browser's console,
     * and `false` otherwise. Defaults to false if not specified. */
    suppress_console_error?: boolean;
}

/**
 * Initializes visitor session and creates connection to Zendesk Chat servers.
 * @param initProps
 */
export function init(initProps: InitProps): void;

/**
 * Returns the current status of the account.
 */
export function getAccountStatus(): 'online' | 'offline' | 'away';

/**
 * Returns the current state of the connection.
 */
export function getConnectionStatus(): 'connected' | 'connecting' | 'closed';

/**
 * Returns the information of the current visitor.
 */
export function getVisitorInfo(): VisitorInfo;

/**
 * Updates current visitor's information.
 * @param options the visitor info
 * @param callback
 */
export function setVisitorInfo(options: Partial<VisitorInfo>, callback?: (err: Error) => void): void;

/**
 * Sends out the visitor's path to the Zendesk Chat server.
 *
 * This is used to track the visitor's navigation history on your website.
 * It will appear as if the visitor has open a new web page,
 * especially useful for single-page apps.
 *
 * @param options object containing the page title and URL
 * @param callback
 */
export function sendVisitorPath(options: { title: string; url: string }, callback?: (err: Error) => void): void;

/**
 * Returns the visitor's queue position.
 * If the visitor is not in a queue, this will return 0.
 * @since 1.2.0
 */
export function getQueuePosition(): number;

/**
 * Returns an array of the account's departments.
 * For accounts using Zendesk Agent Workspace, ID of each department in the response is Support group ID.
 */
export function getAllDepartments(): Department[];

/**
 * Returns the information of a particular department based on the given ID.
 * For accounts using Zendesk Agent Workspace, ID of Support group should be supplied.
 * @param id ID of the department
 */
export function getDepartment(id: number): Department;

/**
 * Returns the current visitor's default department.
 * If the visitor's default department is not set, this will return `undefined`.
 * @param id ID of the department
 */
export function getVisitorDefaultDepartment(id?: number): Department;

/**
 * Set the current visitor’s default department.
 * For accounts using Zendesk Agent Workspace, ID of Support group should be supplied.
 * @param id ID of the department
 * @param callback
 */
export function setVisitorDefaultDepartment(id: number, callback?: (err: Error) => void): void;

/**
 * Clears the current visitor’s default department.
 * This will only work before the chat starts.
 * @param callback
 */
export function clearVisitorDefaultDepartment(callback?: (err: Error) => void): void;

/**
 * Sends a chat message from the current visitor.
 * This should only be used when the account is online.
 *
 * If this method is used when none of the agents are available to pick up a chat,
 * messages sent by visitors may result in missed chats.
 * @param msg Message to be sent
 * @param callback
 */
export function sendChatMsg(msg: string, callback?: (err: Error) => void): void;

/**
 * The size of each file attachment sent cannot exceed 20MB as of v1.1.4.
 * The previous limit was 5MB.
 * @param file
 * @param callback
 */
export function sendFile(file: File, callback?: (err: SendFileErrorMessage, data: {
    mime_type: string,
    name: string,
    size: number,
    url: string,
}) => void): void;

/**
 * Sends a single offline message from the current visitor.
 * This should only be used to send offline messages to an offline account or department.
 * @param options
 * @param options.name Name of visitor
 * @param options.email Email of visitor
 * @param options.phone Phone number of visitor
 * @param options.message Offline message to be sent
 * @param options.department ID of department for the message to be routed to
 * @param callback
 */
export function sendOfflineMsg(
    options: {
        name: string;
        email: string;
        phone?: string;
        message: string;
        department?: number;
    },
    callback: (err: Error) => void
): void;

/**
 * Adds new visitor tag(s) to the current visitor's session.
 * @param tags List of tags to be added
 * @param callback
 */
export function addTags(tags: string[], callback?: (err: Error) => void): void;

/**
 * Removes existing visitor tag(s) from current visitor's session.
 * @param tags List of tags to be removed
 * @param callback
 */
export function removeTags(tags: ReadonlyArray<string>, callback?: (err: Error) => void): void;

/**
 * Sends the visitor's current typing status to the agents.
 * Set `is_typing` as `true` to indicate that the visitor is currently typing and `false` otherwise.
 * @param is_typing Typing status of visitor
 */
export function sendTyping(is_typing: boolean): void;

/**
 * Returns the visitor's rating and/or comment for the current chat.
 * If the rating or comment is not available for the current chat, the value of the respective property will be `null`.
 */
export function getChatInfo(): { rating?: string; comment?: string };

/**
 * Sends the visitor's rating on the chat experience.
 * The rating argument can be one of the following values:
 *  - `'good'`:	Visitor is satisfied
 *  - `'bad'`: Visitor is unsatisfied
 *  - `null`: Visitor is removing the rating
 * @param rating Either `'good'`, `'bad'` or `null`
 * @param callback
 */
export function sendChatRating(rating: 'good' | 'bad' | undefined, callback?: (err: Error) => void): void;

/**
 * Sends the visitor's comment on the chat experience.
 * @param comment Visitor's comment
 * @param callback
 */
export function sendChatComment(comment: string, callback?: (err: Error) => void): void;

/**
 * Returns a boolean value indicating if there is an ongoing chat with the visitor.
 */
export function isChatting(): boolean;

/**
 * Returns an array of chat log messages in the current chat channel.
 */
export function getChatLog(): ChatEvent.ChatEventData[];

/**
 * Returns an array of information about the agents who are currently serving in the chat.
 */
export function getServingAgentsInfo(): AgentInfo[];

/**
 * Returns an account's operating hours.
 * For accounts using Zendesk Agent Workspace, ID of each department in the response is Support group ID.
 */
export function getOperatingHours(): OperatingHours;

/**
 * Schedules an email containing the chat transcript to be sent to `email` when the chat has ended.
 * For authenticated visitors with past chats, this method will schedule an email containing the transcript for the most recent conversation.
 * @param email A valid email for the chat transcript to be sent to
 * @param callback
 */
export function sendEmailTranscript(email: string, callback: (err: Error) => void): void;

/**
 * Fetches past chats for an authenticated visitor.
 * @param callback Callback function which is invoked when the request is completed.
 *   This function should accept two arguments, error and data.
 */
export function fetchChatHistory(callback?: (err: Error, data: {has_more: boolean, count: number}) => void): void;

/**
 * Updates the last read timestamp with the current server timestamp.
 */
export function markAsRead(): void;

/**
 * Reconnects the visitor's connection when it is disconnected due to idle timeout or intermittent connection.
 * This method will only work when connection status is `closed`.
 */
export function reconnect(): void;

/**
 * Ends the current chat session.
 * @param callback
 */
export function endChat(callback?: (err: Error) => void): void;

/**
 * Ends the current chat session.
 * @param options
 * @param options.clear_dept_id_on_chat_ended `true` to use visitor's default department on new chats,
 *   and `false` to continue using the same department as the previous chat. Defaults to `false` if not specified.
 * @param callback
 */
export function endChat(options?: {clear_dept_id_on_chat_ended: boolean}, callback?: (err: Error) => void): void;

/**
 * Logs out an authenticated visitor.
 * Upon calling this method, the connection will be closed.
 * You will receive a `connection_update` event with the `closed` state.
 *
 * If you want to re-initialize the Web SDK, ensure that you call zChat.init() again.
 */
export function logout(): void;

/**
 * Register an event handler to an event type.
 * @param event_name Event name to bind to
 * @param handler Event handler function
 */
export function on(event_name: EventName, handler: (event_data?: EventData) => void): void;

/**
 * De-register an event handler from an event type.
 * @param event_name Event name to unbind from
 * @param handler Event handler which was previously registered
 */
export function un(event_name: EventName, handler: (event_data?: EventData) => void): void;

export namespace ChatEvent {
    interface BaseChatEventData {
        nick: string;
        display_name: string;
        timestamp: number;
    }

    type ChatEventData =
        | BaseChatEventData & {
        type: 'chat.msg';
        msg: string;
        options: string[];
        structured_msg: StructuredMessage;
    }
        | BaseChatEventData & {
        type: 'chat.file';
        attachment: Attachment;
        deleted: boolean;
    }
        | BaseChatEventData & {
        type: 'chat.memberjoin';
    }
        | BaseChatEventData & {
        type: 'chat.memberleave';
    }
        | BaseChatEventData & {
        type: 'chat.request.rating';
    }
        | BaseChatEventData & {
        type: 'chat.rating';
        rating?: string;
        new_rating?: string;
    }
        | BaseChatEventData & {
        type: 'typing';
        typing: boolean;
    }
        | BaseChatEventData & {
        type: 'chat.comment';
        comment?: string;
        new_comment?: string;
    }
        | {
        type: 'chat.queue_position';
        queue_position: number;
        nick: 'system:queue'
    };

    interface Action {
        type: 'QUICK_REPLY_ACTION' | 'LINK_ACTION';
        value: string;
    }

    interface Button {
        text: string;
        action: Action;
    }

    interface Panel {
        heading: string;
        paragraph?: string;
        image_url: string;
        action: Action;
    }

    interface PanelTemplate {
        type: 'PANEL_TEMPLATE';
        panel: Panel;
        buttons: Button[];
    }

    interface ListItem {
        heading: string;
        paragraph: string;
        image_url?: string;
        action: Action;
    }

    type StructuredMessage =
        | {
        type: 'QUICK_REPLIES';
        msg: string;
        quick_replies: Button[];
    }
        | {
        type: 'PANEL_TEMPLATE';
        panel: Panel;
        buttons: Button[];
    }
        | {
        type: 'PANEL_TEMPLATE_CAROUSEL';
        items: PanelTemplate[];
    }
        | {
        type: 'LIST_TEMPLATE';
        items: ListItem[];
        buttons: Button[];
    };
}

export type SendFileErrorMessage =
    | 'NOT_SUPPORTED'
    | 'NOT_ALLOWED'
    | 'CONN_ERROR'
    | 'INVALID_EXTENSION'
    | 'EXCEED_SIZE_LIMIT'
    | 'INTERNAL_ERROR'
    | 'UNKNOWN_ERROR';

export interface VisitorInfo {
    /**
     * Display name of visitor. Maximum length 255 UTF-16 code units.
     */
    display_name: string;
    /**
     * Email of visitor, which must be matchable by `zChat.EMAIL_REGEX`.
     */
    email: string;
    /**
     * Phone number of visitor. Should be numeric with maximum length of 25 UTF-16 code units.
     */
    phone: string;
}

export interface Department {
    id: number;
    name: string;
    status: 'online' | 'offline';
}

export interface Attachment {
    metadata?: AttachmentMetadata;
    mime_type: string;
    name: string;
    size: number;
    url: string;
}

export interface AttachmentMetadata {
    width: number;
    height: number;
}

export type EventName =
    | 'account_status'
    | 'connection_update'
    | 'department_update'
    | 'visitor_update'
    | 'agent_update'
    | 'chat'
    | 'history'
    | 'typing'
    | 'error';

export type EventData =
    | ChatEvent.ChatEventData
    | {
    type: 'typing';
    nick: string;
    typing: boolean;
}
    | {
    type: 'last_read';
    nick: string;
    timestamp: number;
}
    | 'online'
    | 'away'
    | 'offline'
    | 'connecting'
    | 'connected'
    | 'closed';

export interface AgentInfo {
    /**
     * `nick` property of the agent
     */
    nick: string;
    /**
     * Name of the agent
     */
    display_name: string;
    /**
     * URL path to the avatar image of the agent
     */
    avatar_path: string;
    /**
     * Byline or title of the agent
     */
    title: string;
}

export interface Period {
    /**
     * Start of operating period, value refers to number of minutes since midnight
     * (0 means midnight, 1439 means 23:59)
     */
    start: number;
    /**
     * End of operating period, value refers to number of minutes since midnight
     * (0 means midnight, 1439 means 23:59)
     */
    end: number;
}

export interface Schedule {
    /**
     * Operating schedule for Sunday
     */
    0: Period[];
    /**
     * Operating schedule for Monday
     */
    1: Period[];
    /**
     * Operating schedule for Tuesday
     */
    2: Period[];
    /**
     * Operating schedule for Wednesday
     */
    3: Period[];
    /**
     * Operating schedule for Thursday
     */
    4: Period[];
    /**
     * Operating schedule for Friday
     */
    5: Period[];
    /**
     * Operating schedule for Saturday
     */
    6: Period[];
}

export interface OperatingHours {
    /**
     * `true` if operating hours is enabled
     */
    enabled: boolean;
    /**
     * `'account'` or `'department'`, depending on the type of operating hours set in dashboard
     */
    type: 'account' | 'department';
    /**
     * The account's operating hours schedule, is only set if type is `'account'`
     */
    account_schedule: Schedule;
    /**
     * Key-value pairs where keys are department IDs and values are `Schedule` objects, is only set if type is `'department'`
     */
    department_schedule: {[department_id: string]: Schedule};
    /**
     * Timezone of account
     */
    timezone: string;
}
