/**
 * 播放模式
 */
type PLAY_MODE =
  | 'order' // 顺序播放（同列表循环，到最后一个会切到第一个继续播）
  | 'loop' // 单曲循环播放
  | 'random' // 随机播放（到最后一个会切到第一个继续播）
  | 'single' // 单个播放（播完即止）

/**
 * 播放器状态
 */
type PLAY_STATE =
  | 'ready' // 初始状态
  | 'advertising' // 广告中
  | 'loading' // 加载中（1.播放，2.恢复播放，3.seek）引起的声音加载
  | 'playing' // 播放中
  | 'paused' // 暂停
  | 'stoped' // 停止
  | 'finished' // 结束，完整播放结束时触发
  | 'loaderror' // 下载资源发生错误（不包含接口请求喜马拉雅声音出错的情形）

/**
 * 播放相关事件
 */
type PLAY_EVENT =
  | 'ready' // 播放器准备就绪时触发
  | 'unlock' // 当用户在网页任意位置第一次触摸或点击时，解锁浏览器播放限制时触发
  | 'play' // 播放
  | 'pause' // 暂停
  | 'resume' // 续播
  | 'stop' // 停止
  | 'end' // 播放结束
  | 'next' // 点击下一首| 或自动切换下一首
  | 'prev' // 点击上一首
  | 'mute' // 静音
  | 'unmute' // 恢复音量
  | 'loading' // 音频加载时触发
  | 'timeupdate' // 播放进度更新
  | 'initlast' // 从缓存恢复最近一次播放信息
  | 'change.volume' // 音量变化时触发
  | 'change.sound' // 声音切换
  | 'change.playlist' // 播放列表切换
  | 'change.playState' // 播放状态切换
  | 'change.playMode' // 切换播放模式
  | 'change.playbackRate' // 播放速率切换
  | 'loaderror' // 音频加载发生错误
  | 'notAllowedError' // 浏览器阻止音频播放
  | 'accessTokenError' // access_token 过期、失效、不存在事件
  | 'sound.needPay' // 需要购买
  | 'sound.notExist' // 喜马拉雅的声音不存在
  | 'sound.usable' // 喜马拉雅的声音可用
  | 'sound.unusable' // 喜马拉雅的声音不可用

/**
 * 播放速率
 */
type PLAY_BACKRATE = 0.5 | 1 | 2 | 3 | 4

/**
 * 播放器通用声音, 可直接播放(用户传入的第三方声音、喜马声音解析后生成)
 */
interface Sound {
  /** 音频 id */
  id: number
  /**  播放地址（目前支持 m4a, aac, mp3, wav 格式），喜马拉雅的音频地址会被加密 */
  src: string
  /** 音频标题 */
  title?: string
  /** 音频时长(单位: 秒) */
  duration?: number
}

/**
 * 声音集合
 */
interface Sounds<T> {
  [k: number]: T
}

/**
 * 播放列表
 */
type PlayList = number[]

/**
 * 初始化参数
 */
interface ConfigOptions {
  /** 喜马拉雅开放平台应用公钥 */
  app_key: string
  /** js 函数（标准登录授权、第三方账号登录授权模式下，必传），JSSDK 内部每一次请求开放平台接口时，都会执行传入的函数，获取访问令牌 access_token */
  get_access_token?: () => Promise<string> | string
  /** 返回 signature 的接口 URL 地址， 此接口由开发者服务端开发（免登录授权模式下，必传）*/
  sig_url?: string
  /** 用户设备 ID，必须保证唯一性；可以不传，JSSDK 内部默认会自动生成 */
  device_id?: string
  /** 请求超时时间（单位：毫秒，默认 10S） */
  timeout?: number
  /** 是否开启 debug 模式，开启后可以在 console 控制台查看 */
  debug?: boolean
}

/**
 *  播放器配置参数
 */
interface PlayerOptions extends Partial<ConfigOptions> {
  /** 播放列表，数组的元素为 Number 类型的声音 ID ，通常 ID 需要是喜马拉雅声音 ID；若非喜马拉雅声音，则必须是 sounds 属性中该声音已存在，[播放非喜马拉雅声音示例] (https://openact.ximalaya.com/web-jssdk/doc/#/content/player/no-xmly) */
  playlist: number[]
  /**  播放模式 */
  playMode?: PLAY_MODE
  /** 是否从localStorage中读取最近播放信息，加载时（如：刷新页面、关闭页面重新打开）恢复，包括：声音id、playlist、volume、position、sound、playmode、mute等 */
  initLast?: boolean
  /** 断点续播，即从上一次播放的位置续播，低版本ios不支持，备注：页面关闭时，会触发stop，播放位置回到了0，所以关闭页面后恢复播放是从头播，而刷新页面可以从之前的位置续播 */
  breakpoint?: boolean
  /** 声音不可播时，自动跳过播放下一个[自动跳过说明] (https://openact.ximalaya.com/web-jssdk/doc/#/content/player/params?id=%e8%87%aa%e5%8a%a8%e8%b7%b3%e8%bf%87) */
  autoSkip?: boolean
  /** 开启时，会在加载后尝试解锁浏览器对音频自动播放的限制（大多数浏览器都限制了音频自动播放）；并在用户首次触摸、点击页面时，触发 unlock 事件，关闭后将不会触发 unlock 事件。默认开启，如果此功能在使用时造成了影响，可以配置为 false 关闭。 */
  autoUnlock?: boolean
  /** 播放器的声音集合，通常不需要设置此项，[查看示例](https://openact.ximalaya.com/web-jssdk/doc/#/content/player/params?id=%e8%ae%be%e7%bd%aesounds) */
  sounds?: Record<number, Sound>
}

/**
 * 初始化方法，一次性配置 JSSDK 内部所有模块的参数
 * @param options 初始化参数
 */
export function config(options: ConfigOptions): void

export class XMplayer {
  /**
   * 版本号
   */
  static version: string
  /**
   * 用于配置 XMplayer 参数，入参由播放器参数、应用参数两部分组成。因为在 config 初始化方法被调用后，应用相关的参数已经全局配置好，通常你不需要注入应用参数
   */
  static setup(options: PlayerOptions): void
  /**
   * 初始化播放器
   * @param options
   */
  constructor(options: PlayerOptions)
  /**
   * 播放音频
   * @param id 声音 ID | 空 | 完整的声音信息
   */
  play(option?: number | Sound): void
  /**
   * 按播放列表索引播放
   * @param index playlist 数组列表中的索引（从0开始，必须是0或正整数，列表范围内的有效值）
   */
  playByIndex(index: number): void
  /**
   * 判断是否正在播放
   */
  isPlaying(): boolean
  /**
   * 暂停
   */
  pause(): void
  /**
   * 停止，重新播放会从头播。
   */
  stop(): void
  /**
   * 上一个/上一曲
   * （自动切到最后一个）
   */
  prev(): void
  /**
   * 下一个/下一曲
   * （自动切到第一个）
   */
  next(): void
  /**
   *获取播放列表，返回原始的 playlist，与播放模式无关。
   */
  getPlaylist(): PlayList
  /**
   * 获取当前的播放列表，与播放模式有关，例如：当前是”随机播放“，此时想获取当前播放列表，必须是 getCurrentPlaylist。大部分时候，getPlaylist 和 getCurrentPlaylist 返回的结果相同。
   */
  getCurrentPlaylist(): PlayList
  /**
   * 设置播放列表，如果当前处于随机播放模式，会被重置为 order 模式
   * @param playlist 播放列表，数组的元素值为声音的 ID，Number 类型
   */
  setPlaylist(playlist?: PlayList): void
  /**
   * 获取当前播放声音在播放列表中的索引
   */
  getCurrentIndex(): number
  /**
   * 获取当前 sound
   *  - 当前播放器加载的，未必是正在播放
   */
  getSound(): Sound | undefined
  /**
   * 获取 sounds
   */
  getSounds(): Sounds<Sound>
  /**
   * 设置 sounds
   * @param sounds 声音集合
   */
  setSounds(sounds: Sounds<Sound>)
  /**
   * 跳往（拖动）到某播放位置，或返回当前播放位置。
   * 如果当前播放声音是部分试听的付费声音，拖动到超过试听长度时，只能拖到试听长度结束前5秒的位置，例如：当前播放的是付费声音，且部分试听，* 仅允许试听180秒，player.seek(300) 操作拖动到300秒位置，因超出了180秒，所以仅会被拖动到175秒位置，并继续播放。
   *
   * @param position 指定的播放位置，单位：秒。若参数缺省，返回当前播放位置（单位：秒）
   */
  seek(position?: number): void
  /**
   * 前进，向后（向结束位置）拖动
   * @param  dif 间距，正数（单位：秒）, 若参数缺省，默认值为1，即前进1秒
   */
  seekForward(dif?: number): void
  /**
   * 后退，向前（向开始位置）拖动
   * @param  dif 间距，正数（单位：秒），若参数缺省，默认值为1，即后退1秒
   */
  seekBack(dif?: number): void
  /**
   * 获取正在播放音频的总播放时长（单位：秒）
   *  - 只有在有合法 src 时返回
   */
  getDuration(): number
  /**
   * 获取当前音量（即使静音，也会返回当前音量）
   */
  getVolume(): number
  /**
   * 设置音量。IOS 移动设备无法从 js 操控音量，所以 setVolume、upVolume、downVolume、mute、unmute 在 IOS 移动设备上都是不能正常工作的
   * @param volume 音量，0~1之间的数值
   */
  setVolume(volume: number): void
  /**
   * 音量增大，增大到1即不再继续增大
   * @param dif 增大的音量步长，0~1 之间的数值，若参数缺省，默认为0.1
   */
  upVolume(dif?: number): void
  /**
   * 音量减小，减小到1即不再继续减小
   * @param dif 减小的音量步长，0~1 之间的数值，若参数缺省，默认为0.1
   */
  downVolume(dif?: number): void
  /**
   * 静音
   */
  mute(): void
  /**
   * 恢复音量
   */
  unmute(): void
  /**
   * 获取当前播放速率
   */
  getPlaybackRate(): PLAY_BACKRATE
  /**
   * 设置播放速率
   *
   * @param playbackRate 播放速度，可选值：0.5、1、2、3、4
   */
  setPlaybackRate(playbackRate: PLAY_BACKRATE): void
  /**
   * 获取播放模式，[播放模式](https://openact.ximalaya.com/web-jssdk/doc/#/content/player/params?id=%e6%92%ad%e6%94%be%e6%a8%a1%e5%bc%8f)
   */
  getPlayMode(): PLAY_MODE
  /**
   * 设置播放模式
   * @param playMode 播放模式, [查看](https://openact.ximalaya.com/web-jssdk/doc/#/content/player/params?id=%e6%92%ad%e6%94%be%e6%a8%a1%e5%bc%8f)
   */
  setPlayMode(playMode: PLAY_MODE): void
  /**
   * 获取当前播放器状态，如果想要判断当前是否正在播放，请使用 isPlaying 方法
   * [播放器状态查看](https://openact.ximalaya.com/web-jssdk/doc/#/content/player/params?id=%e6%92%ad%e6%94%be%e5%99%a8%e7%8a%b6%e6%80%81)
   */
  getPlayState(): PLAY_STATE
  /**
   * 预加载声音
   * @param  id 声音id
   * @param success 成功的回调函数
   * @param error 失败的回调函数
   */
  preloadSound(id: number, success?: Function, error?: Function): void
  /**
   * 绑定自定义事件
   * @param name 自定义事件名称，[事件列表](https://openact.ximalaya.com/web-jssdk/doc/#/content/player/event?id=%e4%ba%8b%e4%bb%b6%e5%88%97%e8%a1%a8)
   * @param callback 回调函数，接收emit触发所有参数
   * @param ctx 回调函数的执行上下文
   * @returns 返回this，支持链式调用
   */
  on(name: PLAY_EVENT, callback: (...args: any) => void): this
  /**
   * 绑定自定义事件（仅一次）
   * @param name 自定义事件名称，[事件列表](https://openact.ximalaya.com/web-jssdk/doc/#/content/player/event?id=%e4%ba%8b%e4%bb%b6%e5%88%97%e8%a1%a8)
   * @param callback 回调函数，接收emit触发所有参数
   * @param ctx 回调函数的执行上下文
   * @returns 返回this，支持链式调用
   */
  once(name: PLAY_EVENT, callback: (...args: any) => void): this
  /**
   * 解除绑定的自定义事件
   *  1.funcInQueue参数缺省时，该自定义事件对应的整个回调队列都会被清空，否则仅移除事件的回调队列中的某一函数；
   *
   * @param name 自定义事件名称，[事件列表](https://openact.ximalaya.com/web-jssdk/doc/#/content/player/event?id=%e4%ba%8b%e4%bb%b6%e5%88%97%e8%a1%a8)
   * @param funcInQueue 自定义事件对应的回调函数队列中某一函数
   * @returns 返回this，支持链式调用
   */
  off(name: PLAY_EVENT, funcInQueue?: (...args: any) => void): this
}

/**
 * API 请求参数
 */
interface RequestParams {
  /** 请求 url */
  url: string
  /** 请求类型 */
  type?: 'get' | 'post'
  /** 业务参数, 不需要公共参数 */
  params?: Record<string, any>
}

/**
 * API 返回值
 */
interface RequestResponse {
  /** 错误码，[详细介绍](https://openact.ximalaya.com/web-jssdk/doc/#/content/error-no) */
  code: number
  /** 错误提示 */
  message: string
  /** 返回数据 */
  data: any
}

export class XMLY {
  /**
   * 版本号
   */
  static version: string
  /**
   * 用于配置 XMLY 参数，与 config 初始化方法的参数完全相同，通常不需要这个方法
   */
  static setup(options: ConfigOptions): void
  /**
   * 和初始化参数完全相同
   * @param options
   */
  constructor(options: ConfigOptions)
  /**
   * 通用请求方法，支持开放平台对外输出的所有接口请求，[接口列表](https://open.ximalaya.com/doc/api)
   * @param opts 请求参数
   */
  request({ url, type, params }: RequestParams): Promise<RequestResponse>
  /**
   * 通用请求方法 - get，支持开放平台对外输出的所有接口请求，[接口列表](https://open.ximalaya.com/doc/api)
   * @param url 请求 url
   * @param params  业务参数
   */
  get(url: string, params?: Record<string, any>): Promise<RequestResponse>
  /**
   * 通用请求方法 - post，支持开放平台对外输出的所有接口请求，[接口列表](https://open.ximalaya.com/doc/api)
   * @param url 请求 url
   * @param params 业务参数
   */
  post(url: string, params?: Record<string, any>): Promise<RequestResponse>
  /**
   * 发起用户授权
   * @param opts 授权参数
   */
  authorize(opts: {
    /** 登录成功后的回跳地址（直接传地址字符串即可，不需要 encodeURIComponent，JSSDK 内部会自行处理）*/
    redirect_uri?: string
    /** 用户需要携带的数据（必须是字符串）*/
    state?: string
    /** 指定需要禁用的第三方登录方式，传true禁用全部，或者以数组形式传入qq、weixin、weibo中的一个或多个 */
    hide_third_login_modes?: string[] | false
  })
  /**
   * 下单购买
   * @param opts 下单参数
   */
  prepareOrder(opts: {
    /** 商品类型，目前支持：1-声音，2-专辑，3-会员，4-小喜马会员，5-小喜马付费专辑，6-企业版会员商品，7-实体商品， 8-亲子独立会员 */
    distribute_item_type: number
    /** 需要购买的声音 id 或专辑 id；多个 id 以英文逗号分隔 */
    pay_content: string
    /** 支付完成后的回跳地址（直接传地址字符串即可，不需要 encodeURIComponent，JSSDK 内部会自行处理） */
    return_url?: string
    /** 下单账户类型：1-access_token, 2-手机号，3-第三方账户（ JSSDK 暂不支持手机号、第三方账号下单） */
    access_account_type?: number
    /** 是否隐藏收银台巅峰会员引导，true：隐藏；false，不隐藏（默认） */
    hide_vip_guide?: boolean
    /** 促销活动ID */
    promotion_id?: string
    /** 促销活动类型 */
    promotion_type?: string
    /** 规定哪种方式打开收银台页面（_self、_blank、_parent、_top），同 a 标签的 target 值 */
    target?: string
  })
}
