edit(options: GuildOptions, reason?: string): Promise<Guild>;


editCommandPermissions(permissions: ApplicationCommandPermissions[]): Promise<GuildApplicationCommandPermissions[]>;


editDiscovery(options?: DiscoveryOptions): Promise<DiscoveryMetadata>;


editEmoji(emojiID: string, options: { name: string; roles?: string[] }, reason?: string): Promise<Emoji>;


editIntegration(integrationID: string, options: IntegrationOptions): Promise<void>;


editMember(memberID: string, options: MemberOptions, reason?: string): Promise<Member>;


/** @deprecated */
editNickname(nick: string): Promise<void>;


editRole(roleID: string, options: RoleOptions): Promise<Role>;


editSticker(stickerID: string, options?: EditStickerOptions, reason?: string): Promise<Sticker>;


editTemplate(code: string, options: GuildTemplateOptions): Promise<GuildTemplate>;


editVanity(code: string | null): Promise<GuildVanity>;


editVoiceState(options: VoiceStateOptions, userID?: string): Promise<void>;


editWelcomeScreen(options: WelcomeScreenOptions): Promise<WelcomeScreen>;


editWidget(options: Widget): Promise<Widget>;


fetchAllMembers(timeout?: number): Promise<number>;


fetchMembers(options?: FetchMembersOptions): Promise<Member[]>;


getActiveThreads(): Promise<ListedGuildThreads>;


getAuditLog(options?: GetGuildAuditLogOptions): Promise<GuildAuditLog>;


/** @deprecated */
getAuditLogs(limit?: number, before?: string, actionType?: number, userID?: string): Promise<GuildAuditLog>;


getBan(userID: string): Promise<{ reason?: string; user: User }>;


getBans(): Promise<{ reason?: string; user: User }[]>;


getCommandPermissions(): Promise<GuildApplicationCommandPermissions[]>;


getDiscovery(): Promise<DiscoveryMetadata>;


/** @deprecated */
getEmbed(): Promise<Widget>;


getIntegrations(): Promise<GuildIntegration>;


getInvites(): Promise<Invite[]>;


getPruneCount(options?: GetPruneOptions): Promise<number>;


getRESTChannels(): Promise<AnyGuildChannel[]>;


getRESTEmoji(emojiID: string): Promise<Emoji>;


getRESTEmojis(): Promise<Emoji[]>;


getRESTMember(memberID: string): Promise<Member>;


getRESTMembers(options?: GetRESTGuildMembersOptions): Promise<Member[]>;


/** @deprecated */
getRESTMembers(limit?: number, after?: string): Promise<Member[]>;


getRESTRoles(): Promise<Role[]>;


getRESTStickers(): Promise<Sticker[]>;


getTemplates(): Promise<GuildTemplate[]>;


getVanity(): Promise<GuildVanity>;


getVoiceRegions(): Promise<VoiceRegion[]>;


getWebhooks(): Promise<Webhook[]>;


getWelcomeScreen(): Promise<WelcomeScreen>;


getWidget(): Promise<WidgetData>;


getWidgetSettings(): Promise<Widget>;


kickMember(userID: string, reason?: string): Promise<void>;


leave(): Promise<void>;


leaveVoiceChannel(): void;


permissionsOf(memberID: string | Member | MemberRoles): Permission;


pruneMembers(options?: PruneMemberOptions): Promise<number>;


removeMemberRole(memberID: string, roleID: string, reason?: string): Promise<void>;


searchMembers(query: string, limit?: number): Promise<Member[]>;


syncIntegration(integrationID: string): Promise<void>;


syncTemplate(code: string): Promise<GuildTemplate>;


unbanMember(userID: string, reason?: string): Promise<void>;


delete(reason?: string): Promise<void>;


deletePermission(overwriteID: string, reason?: string): Promise<void>;


edit(options: Omit<EditChannelOptions, "icon" | "ownerID">, reason?: string): Promise<this>;


editPermission(
      overwriteID: string,
      allow: bigint | number,
      deny: bigint | number,
      type: PermissionType,
      reason?: string
    ): Promise<PermissionOverwrite>;


editPosition(position: number, options?: EditChannelPositionOptions): Promise<void>;


edit(options: IntegrationOptions): Promise<void>;


sync(): Promise<void>;


createGuild(name: string, icon?: string): Promise<Guild>;


delete(): Promise<GuildTemplate>;


edit(options: GuildTemplateOptions): Promise<GuildTemplate>;


sync(): Promise<GuildTemplate>;


acknowledge(): Promise<void>;


pong(): Promise<void>;


acknowledge(flags?: number): Promise<void>;


createFollowup(content: string | InteractionContent, file?: FileContent | FileContent[]): Promise<Message>;


createMessage(content: string | InteractionContent , file?: FileContent | FileContent[]): Promise<void>;


defer(flags?: number): Promise<void>;


deleteMessage(messageID: string): Promise<void>;


deleteOriginalMessage(): Promise<void>;


editMessage(messageID: string, content: string | InteractionEditContent, file?: FileContent | FileContent[]): Promise<Message>;


editOriginalMessage(content: string | InteractionEditContent, file?: FileContent | FileContent[]): Promise<Message>;


getOriginalMessage(): Promise<Message>


createMessage(content: string | InteractionContent, file?: FileContent | FileContent[]): Promise<void>;


deferUpdate(): Promise<void>;


editParent(content: InteractionEditContent, file?: FileContent | FileContent[]): Promise<void>;


acknowledge(choices: ApplicationCommandOptionChoice[]): Promise<void>;


result(choices: ApplicationCommandOptionChoice[]): Promise<void>;


acknowledge(data: InteractionOptions): Promise<void>;


addRole(roleID: string, reason?: string): Promise<void>;


ban(deleteMessageDays?: number, reason?: string): Promise<void>;


edit(options: MemberOptions, reason?: string): Promise<void>;


kick(reason?: string): Promise<void>;


removeRole(roleID: string, reason?: string): Promise<void>;


unban(reason?: string): Promise<void>;


addReaction(reaction: string): Promise<void>;


/** @deprecated */
addReaction(reaction: string, userID: string): Promise<void>;


createThreadWithMessage(messageID: string, options: CreateThreadOptions): Promise<NewsThreadChannel | PublicThreadChannel>;


crosspost(): Promise<T extends NewsChannel ? Message<NewsChannel> : never>;


deleteWebhook(token: string): Promise<void>;


edit(content: MessageContent): Promise<Message<T>>;


editWebhook(token: string, options: MessageWebhookContent): Promise<Message<T>>;


getReaction(reaction: string, options?: GetMessageReactionOptions): Promise<User[]>;


/** @deprecated */
getReaction(reaction: string, limit?: number, before?: string, after?: string): Promise<User[]>;


pin(): Promise<void>;


removeReaction(reaction: string, userID?: string): Promise<void>;


removeReactionEmoji(reaction: string): Promise<void>;


removeReactions(): Promise<void>;


unpin(): Promise<void>;


createInvite(options?: CreateInviteOptions, reason?: string): Promise<Invite<"withMetadata", NewsChannel>>;


createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise<Message<NewsChannel>>;


createThreadWithMessage(messageID: string, options: CreateThreadOptions): Promise<NewsThreadChannel>;


crosspostMessage(messageID: string): Promise<Message<NewsChannel>>;


editMessage(messageID: string, content: MessageContentEdit): Promise<Message<NewsChannel>>;


follow(webhookChannelID: string): Promise<ChannelFollow>;


getInvites(): Promise<(Invite<"withMetadata", NewsChannel>)[]>;


getMessage(messageID: string): Promise<Message<NewsChannel>>;


getMessages(options?: GetMessagesOptions): Promise<Message<NewsChannel>[]>;


/** @deprecated */
getMessages(limit?: number, before?: string, after?: string, around?: string): Promise<Message<NewsChannel>[]>;


getPins(): Promise<Message<NewsChannel>[]>;


createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise<Message<NewsThreadChannel>>;


edit(options: Pick<EditChannelOptions, "archived" | "autoArchiveDuration" | "locked" | "name" | "rateLimitPerUser">, reason?: string): Promise<this>;


editMessage(messageID: string, content: MessageContentEdit): Promise<Message<NewsThreadChannel>>;


getMessage(messageID: string): Promise<Message<NewsThreadChannel>>;


getMessages(options?: GetMessagesOptions): Promise<Message<NewsThreadChannel>[]>;


getPins(): Promise<Message<NewsThreadChannel>[]>;


has(permission: keyof Constants["Permissions"]): boolean;


addDataPacket(packet: unknown): void;


encode(source: string | Stream, options: VoiceResourceOptions): boolean;


getDataPacket(): Buffer;


resetPackets(): void;


setVolume(volume: number): void;


stop(e: Error, source: Duplex): void;


addMessageReaction(messageID: string, reaction: string): Promise<void>;


/** @deprecated */
addMessageReaction(messageID: string, reaction: string, userID: string): Promise<void>;


createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise<Message<PrivateChannel>>;


deleteMessage(messageID: string, reason?: string): Promise<void>;


editMessage(messageID: string, content: MessageContentEdit): Promise<Message<PrivateChannel>>;


getMessage(messageID: string): Promise<Message<PrivateChannel>>;


getMessageReaction(messageID: string, reaction: string, options?: GetMessageReactionOptions): Promise<User[]>;


/** @deprecated */
getMessageReaction(messageID: string, reaction: string, limit?: number, before?: string, after?: string): Promise<User[]>;


getMessages(options?: GetMessagesOptions): Promise<Message<PrivateChannel>[]>;


/** @deprecated */
getMessages(limit?: number, before?: string, after?: string, around?: string): Promise<Message<PrivateChannel>[]>;


getPins(): Promise<Message<PrivateChannel>[]>;


pinMessage(messageID: string): Promise<void>;


removeMessageReaction(messageID: string, reaction: string): Promise<void>;


/** @deprecated */
removeMessageReaction(messageID: string, reaction: string, userID: string): Promise<void>;


ring(recipient: string[]): void;


sendTyping(): Promise<void>;


syncCall(): void;


unpinMessage(messageID: string): Promise<void>;


unsendMessage(messageID: string): Promise<void>;


createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise<Message<PrivateThreadChannel>>;


edit(options: Pick<EditChannelOptions, "archived" | "autoArchiveDuration" | "invitable" | "locked" | "name" | "rateLimitPerUser">, reason?: string): Promise<this>;


editMessage(messageID: string, content: MessageContentEdit): Promise<Message<PrivateThreadChannel>>;


getMessage(messageID: string): Promise<Message<PrivateThreadChannel>>;


getMessages(options?: GetMessagesOptions): Promise<Message<PrivateThreadChannel>[]>;


getPins(): Promise<Message<PrivateThreadChannel>[]>;


createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise<Message<PublicThreadChannel>>;


editMessage(messageID: string, content: MessageContentEdit): Promise<Message<PublicThreadChannel>>;


getMessage(messageID: string): Promise<Message<PublicThreadChannel>>;


getMessages(options?: GetMessagesOptions): Promise<Message<PublicThreadChannel>[]>;


getPins(): Promise<Message<PublicThreadChannel>[]>;


globalUnblock(): void;


request(method: RequestMethod, url: string, auth?: boolean, body?: { [s: string]: unknown }, file?: FileContent, _route?: string, short?: boolean): Promise<unknown>;


routefy(url: string, method: RequestMethod): string;


edit(options: RoleOptions, reason?: string): Promise<Role>;


editPosition(position: number): Promise<void>;


check(override?: boolean): void;


queue(func: (cb: () => void) => void, short?: boolean): void;


checkReady(): void;


connect(): void;


createGuild(_guild: Guild): Guild;


disconnect(options?: { reconnect?: boolean | "auto" }, error?: Error): void;


// @ts-ignore: Method override
emit(event: string, ...args: any[]): void;


emit<K extends keyof ShardEvents>(event: K, ...args: ShardEvents[K]): boolean;


getGuildMembers(guildID: string, timeout: number): void;


hardReset(): void;


heartbeat(normal?: boolean): void;


identify(): void;


initializeWS(): void;


off<K extends keyof ShardEvents>(event: K, listener: (...args: ShardEvents[K]) => void): this;


once<K extends keyof ShardEvents>(event: K, listener: (...args: ShardEvents[K]) => void): this;


onPacket(packet: RawPacket): void;


requestGuildMembers(guildID: string, options?: RequestGuildMembersOptions): Promise<RequestGuildMembersReturn>;


requestGuildSync(guildID: string): void;


restartGuildCreateTimeout(): void;


sendStatusUpdate(): void;


sendWS(op: number, _data: Record<string, unknown>, priority?: boolean): void;


syncGuild(guildID: string): void;


wsEvent(packet: Required<RawPacket>): void;


on<K extends keyof ShardEvents>(event: K, listener: (...args: ShardEvents[K]) => void): this;


connect(shard: Shard): void;


spawn(id: number): void;


tryConnect(): void;


add(connection: VoiceConnection): void;


emit<K extends keyof StreamEvents>(event: K, ...args: StreamEvents[K]): boolean;


off<K extends keyof StreamEvents>(event: K, listener: (...args: StreamEvents[K]) => void): this;


once<K extends keyof StreamEvents>(event: K, listener: (...args: StreamEvents[K]) => void): this;


play(resource: ReadableStream | string, options?: VoiceResourceOptions): void;


remove(connection: VoiceConnection): void;


setSpeaking(value: boolean): void;


stopPlaying(): void;


on<K extends keyof StreamEvents>(event: K, listener: (...args: StreamEvents[K]) => void): this;


createInstance(options: StageInstanceOptions): Promise<StageInstance>;


deleteInstance(): Promise<void>;


editInstance(options: StageInstanceOptions): Promise<StageInstance>;


getInstance(): Promise<StageInstance>;


edit(options: StageInstanceOptions): Promise<StageInstance>;


update(data: BaseData): void;


createInvite(options?: CreateInviteOptions, reason?: string): Promise<Invite<"withMetadata", TextChannel>>;


createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise<Message<TextChannel>>;


createThreadWithMessage(messageID: string, options: CreateThreadOptions): Promise<PublicThreadChannel>;


createThreadWithoutMessage(options: CreateThreadWithoutMessageOptions): Promise<PrivateThreadChannel>;


createWebhook(options: { name: string; avatar?: string | null }, reason?: string): Promise<Webhook>;


deleteMessages(messageIDs: string[], reason?: string): Promise<void>;


editMessage(messageID: string, content: MessageContentEdit): Promise<Message<TextChannel>>;


/** @deprecated */
getActiveThreads(): Promise<ListedChannelThreads>;


getArchivedThreads(type: "private", options?: GetArchivedThreadsOptions): Promise<ListedChannelThreads<PrivateThreadChannel>>;


getArchivedThreads(type: "public", options?: GetArchivedThreadsOptions): Promise<ListedChannelThreads<PublicThreadChannel>>;


getInvites(): Promise<(Invite<"withMetadata", TextChannel>)[]>;


getJoinedPrivateArchivedThreads(options: GetArchivedThreadsOptions): Promise<ListedChannelThreads<PrivateThreadChannel>>;


getMessage(messageID: string): Promise<Message<TextChannel>>;


getMessages(options?: GetMessagesOptions): Promise<Message<TextChannel>[]>;


/** @deprecated */
getMessages(limit?: number, before?: string, after?: string, around?: string): Promise<Message<TextChannel>[]>;


getPins(): Promise<Message<TextChannel>[]>;


purge(options: PurgeChannelOptions): Promise<number>;


/** @deprecated */
purge(limit: number, filter?: (message: Message<this>) => boolean, before?: string, after?: string, reason?: string): Promise<number>;


removeMessageReaction(messageID: string, reaction: string, userID?: string): Promise<void>;


removeMessageReactionEmoji(messageID: string, reaction: string): Promise<void>;


removeMessageReactions(messageID: string): Promise<void>;


createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise<Message<ThreadChannel>>;


editMessage(messageID: string, content: MessageContentEdit): Promise<Message<ThreadChannel>>;


getMembers(): Promise<ThreadMember[]>;


getMessage(messageID: string): Promise<Message<ThreadChannel>>;


getMessages(options?: GetMessagesOptions): Promise<Message<ThreadChannel>[]>;


getPins(): Promise<Message<ThreadChannel>[]>;


join(userID?: string): Promise<void>;


leave(userID?: string): Promise<void>;


addRelationship(block?: boolean): Promise<void>;


deleteNote(): Promise<void>;


dynamicAvatarURL(format?: ImageFormat, size?: number): string;


editNote(note: string): Promise<void>;


getDMChannel(): Promise<PrivateChannel>;


getProfile(): Promise<UserProfile>;


removeRelationship(): Promise<void>;


createInvite(options?: CreateInviteOptions, reason?: string): Promise<Invite<"withMetadata", VoiceChannel>>;


getInvites(): Promise<(Invite<"withMetadata", VoiceChannel>)[]>;


join(options?: JoinVoiceChannelOptions): Promise<VoiceConnection>;


leave(): void;


connect(data: VoiceConnectData): NodeJS.Timer | void;


disconnect(error?: Error, reconnecting?: boolean): void;


emit<K extends keyof VoiceEvents>(event: K, ...args: VoiceEvents[K]): boolean;


heartbeat(): void;


off<K extends keyof VoiceEvents>(event: K, listener: (...args: VoiceEvents[K]) => void): this;


once<K extends keyof VoiceEvents>(event: K, listener: (...args: VoiceEvents[K]) => void): this;


receive(type: "opus" | "pcm"): VoiceDataStream;


registerReceiveEventHandler(): void;


sendWS(op: number, data: Record<string, unknown>): void;


switchChannel(channelID: string): void;


updateVoiceState(selfMute: boolean, selfDeaf: boolean): void;


on<K extends keyof VoiceEvents>(event: K, listener: (...args: VoiceEvents[K]) => void): this;


join(guildID: string, channelID: string, options: VoiceResourceOptions): Promise<VoiceConnection>;


leave(guildID: string): void;


switch(guildID: string, channelID: string): void;


voiceServerUpdate(data: VoiceServerUpdateData): void;


on(event: "data", listener: (data: Buffer, userID: string, timestamp: number, sequence: number) => void): this;


function assertTokens(tests: Array<{ input: string; tokens: Token[] }>) {
    let scannerState = ScannerState.WithinContent;
    for (const t of tests) {
      const scanner = createScanner(t.input, 0, scannerState);
      let tokenType = scanner.scan();
      const actual: Token[] = [];
      while (tokenType !== TokenType.EOS) {
        const actualToken: Token = {
          offset: scanner.getTokenOffset(),
          type: tokenType
        };
        if (
          tokenType === TokenType.StartTag ||
          tokenType === TokenType.EndTag
        ) {
          actualToken.content = t.input.substr(
            scanner.getTokenOffset(),
            scanner.getTokenLength()
          );
        }
        actual.push(actualToken);
        tokenType = scanner.scan();
      }
      assert.deepEqual(actual, t.tokens);
      scannerState = scanner.getScannerState();
    }
  }


() => {
    assertTokens([
      {
        input: '<abc',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<input',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'input' }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '< abc',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.Whitespace },
          { offset: 2, type: TokenType.StartTag, content: 'abc' }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '< abc>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.Whitespace },
          { offset: 2, type: TokenType.StartTag, content: 'abc' },
          { offset: 5, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: 'i <len;',
        tokens: [
          { offset: 0, type: TokenType.Content },
          { offset: 2, type: TokenType.StartTagOpen },
          { offset: 3, type: TokenType.StartTag, content: 'len' },
          { offset: 6, type: TokenType.Unknown }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<',
        tokens: [{ offset: 0, type: TokenType.StartTagOpen }]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '</a',
        tokens: [
          { offset: 0, type: TokenType.EndTagOpen },
          { offset: 2, type: TokenType.EndTag, content: 'a' }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc >',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<foo:bar>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'foo:bar' },
          { offset: 8, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '</abc>',
        tokens: [
          { offset: 0, type: TokenType.EndTagOpen },
          { offset: 2, type: TokenType.EndTag, content: 'abc' },
          { offset: 5, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '</abc  >',
        tokens: [
          { offset: 0, type: TokenType.EndTagOpen },
          { offset: 2, type: TokenType.EndTag, content: 'abc' },
          { offset: 5, type: TokenType.Whitespace },
          { offset: 7, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc />',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.StartTagSelfClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script type="text/javascript">var i= 10;</script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.Whitespace },
          { offset: 8, type: TokenType.AttributeName },
          { offset: 12, type: TokenType.DelimiterAssign },
          { offset: 13, type: TokenType.AttributeValue },
          { offset: 30, type: TokenType.StartTagClose },
          { offset: 31, type: TokenType.Script },
          { offset: 41, type: TokenType.EndTagOpen },
          { offset: 43, type: TokenType.EndTag, content: 'script' },
          { offset: 49, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script type="text/javascript">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.Whitespace },
          { offset: 8, type: TokenType.AttributeName },
          { offset: 12, type: TokenType.DelimiterAssign },
          { offset: 13, type: TokenType.AttributeValue },
          { offset: 30, type: TokenType.StartTagClose }
        ]
      },
      {
        input: 'var i= 10;',
        tokens: [{ offset: 0, type: TokenType.Script }]
      },
      {
        input: '</script>',
        tokens: [
          { offset: 0, type: TokenType.EndTagOpen },
          { offset: 2, type: TokenType.EndTag, content: 'script' },
          { offset: 8, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script type="text/javascript">var i= 10;',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.Whitespace },
          { offset: 8, type: TokenType.AttributeName },
          { offset: 12, type: TokenType.DelimiterAssign },
          { offset: 13, type: TokenType.AttributeValue },
          { offset: 30, type: TokenType.StartTagClose },
          { offset: 31, type: TokenType.Script }
        ]
      },
      {
        input: '</script>',
        tokens: [
          { offset: 0, type: TokenType.EndTagOpen },
          { offset: 2, type: TokenType.EndTag, content: 'script' },
          { offset: 8, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script type="text/javascript">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.Whitespace },
          { offset: 8, type: TokenType.AttributeName },
          { offset: 12, type: TokenType.DelimiterAssign },
          { offset: 13, type: TokenType.AttributeValue },
          { offset: 30, type: TokenType.StartTagClose }
        ]
      },
      {
        input: 'var i= 10;</script>',
        tokens: [
          { offset: 0, type: TokenType.Script },
          { offset: 10, type: TokenType.EndTagOpen },
          { offset: 12, type: TokenType.EndTag, content: 'script' },
          { offset: 18, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script type="text/plain">a\n<a</script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.Whitespace },
          { offset: 8, type: TokenType.AttributeName },
          { offset: 12, type: TokenType.DelimiterAssign },
          { offset: 13, type: TokenType.AttributeValue },
          { offset: 25, type: TokenType.StartTagClose },
          { offset: 26, type: TokenType.Script },
          { offset: 30, type: TokenType.EndTagOpen },
          { offset: 32, type: TokenType.EndTag, content: 'script' },
          { offset: 38, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script>a</script><script>b</script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.StartTagClose },
          { offset: 8, type: TokenType.Script },
          { offset: 9, type: TokenType.EndTagOpen },
          { offset: 11, type: TokenType.EndTag, content: 'script' },
          { offset: 17, type: TokenType.EndTagClose },
          { offset: 18, type: TokenType.StartTagOpen },
          { offset: 19, type: TokenType.StartTag, content: 'script' },
          { offset: 25, type: TokenType.StartTagClose },
          { offset: 26, type: TokenType.Script },
          { offset: 27, type: TokenType.EndTagOpen },
          { offset: 29, type: TokenType.EndTag, content: 'script' },
          { offset: 35, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script type="text/javascript"></script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.Whitespace },
          { offset: 8, type: TokenType.AttributeName },
          { offset: 12, type: TokenType.DelimiterAssign },
          { offset: 13, type: TokenType.AttributeValue },
          { offset: 30, type: TokenType.StartTagClose },
          { offset: 31, type: TokenType.EndTagOpen },
          { offset: 33, type: TokenType.EndTag, content: 'script' },
          { offset: 39, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script>var i= 10;</script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.StartTagClose },
          { offset: 8, type: TokenType.Script },
          { offset: 18, type: TokenType.EndTagOpen },
          { offset: 20, type: TokenType.EndTag, content: 'script' },
          { offset: 26, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script type="text/javascript" src="main.js"></script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.Whitespace },
          { offset: 8, type: TokenType.AttributeName },
          { offset: 12, type: TokenType.DelimiterAssign },
          { offset: 13, type: TokenType.AttributeValue },
          { offset: 30, type: TokenType.Whitespace },
          { offset: 31, type: TokenType.AttributeName },
          { offset: 34, type: TokenType.DelimiterAssign },
          { offset: 35, type: TokenType.AttributeValue },
          { offset: 44, type: TokenType.StartTagClose },
          { offset: 45, type: TokenType.EndTagOpen },
          { offset: 47, type: TokenType.EndTag, content: 'script' },
          { offset: 53, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script><!-- alert("<script></script>"); --></script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.StartTagClose },
          { offset: 8, type: TokenType.Script },
          { offset: 44, type: TokenType.EndTagOpen },
          { offset: 46, type: TokenType.EndTag, content: 'script' },
          { offset: 52, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script><!-- alert("<script></script>"); </script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.StartTagClose },
          { offset: 8, type: TokenType.Script },
          { offset: 41, type: TokenType.EndTagOpen },
          { offset: 43, type: TokenType.EndTag, content: 'script' },
          { offset: 49, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script><!-- alert("</script>"); </script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.StartTagClose },
          { offset: 8, type: TokenType.Script },
          { offset: 20, type: TokenType.EndTagOpen },
          { offset: 22, type: TokenType.EndTag, content: 'script' },
          { offset: 28, type: TokenType.EndTagClose },
          { offset: 29, type: TokenType.Content },
          { offset: 33, type: TokenType.EndTagOpen },
          { offset: 35, type: TokenType.EndTag, content: 'script' },
          { offset: 41, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<script> alert("<script></script>"); </script>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.StartTagClose },
          { offset: 8, type: TokenType.Script },
          { offset: 24, type: TokenType.EndTagOpen },
          { offset: 26, type: TokenType.EndTag, content: 'script' },
          { offset: 32, type: TokenType.EndTagClose },
          { offset: 33, type: TokenType.Content },
          { offset: 37, type: TokenType.EndTagOpen },
          { offset: 39, type: TokenType.EndTag, content: 'script' },
          { offset: 45, type: TokenType.EndTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo="bar">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.DelimiterAssign },
          { offset: 9, type: TokenType.AttributeValue },
          { offset: 14, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: "<abc foo='bar'>",
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.DelimiterAssign },
          { offset: 9, type: TokenType.AttributeValue },
          { offset: 14, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo="">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.DelimiterAssign },
          { offset: 9, type: TokenType.AttributeValue },
          { offset: 11, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo="bar" bar=\'foo\'>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.DelimiterAssign },
          { offset: 9, type: TokenType.AttributeValue },
          { offset: 14, type: TokenType.Whitespace },
          { offset: 15, type: TokenType.AttributeName },
          { offset: 18, type: TokenType.DelimiterAssign },
          { offset: 19, type: TokenType.AttributeValue },
          { offset: 24, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo=bar bar=help-me>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.DelimiterAssign },
          { offset: 9, type: TokenType.AttributeValue },
          { offset: 12, type: TokenType.Whitespace },
          { offset: 13, type: TokenType.AttributeName },
          { offset: 16, type: TokenType.DelimiterAssign },
          { offset: 17, type: TokenType.AttributeValue },
          { offset: 24, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo=bar/>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.DelimiterAssign },
          { offset: 9, type: TokenType.AttributeValue },
          { offset: 12, type: TokenType.StartTagSelfClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo=  "bar">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.DelimiterAssign },
          { offset: 9, type: TokenType.Whitespace },
          { offset: 11, type: TokenType.AttributeValue },
          { offset: 16, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo = "bar">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.Whitespace },
          { offset: 9, type: TokenType.DelimiterAssign },
          { offset: 10, type: TokenType.Whitespace },
          { offset: 11, type: TokenType.AttributeValue },
          { offset: 16, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo bar>',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.Whitespace },
          { offset: 9, type: TokenType.AttributeName },
          { offset: 12, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo!@#="bar">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 11, type: TokenType.DelimiterAssign },
          { offset: 12, type: TokenType.AttributeValue },
          { offset: 17, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input:
          '<abc #myinput (click)="bar" [value]="someProperty" *ngIf="someCondition">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 13, type: TokenType.Whitespace },
          { offset: 14, type: TokenType.AttributeName },
          { offset: 21, type: TokenType.DelimiterAssign },
          { offset: 22, type: TokenType.AttributeValue },
          { offset: 27, type: TokenType.Whitespace },
          { offset: 28, type: TokenType.AttributeName },
          { offset: 35, type: TokenType.DelimiterAssign },
          { offset: 36, type: TokenType.AttributeValue },
          { offset: 50, type: TokenType.Whitespace },
          { offset: 51, type: TokenType.AttributeName },
          { offset: 56, type: TokenType.DelimiterAssign },
          { offset: 57, type: TokenType.AttributeValue },
          { offset: 72, type: TokenType.StartTagClose }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<abc foo=">',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'abc' },
          { offset: 4, type: TokenType.Whitespace },
          { offset: 5, type: TokenType.AttributeName },
          { offset: 8, type: TokenType.DelimiterAssign },
          { offset: 9, type: TokenType.AttributeValue }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<!--a-->',
        tokens: [
          { offset: 0, type: TokenType.StartCommentTag },
          { offset: 4, type: TokenType.Comment },
          { offset: 5, type: TokenType.EndCommentTag }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<!--a>foo bar</a -->',
        tokens: [
          { offset: 0, type: TokenType.StartCommentTag },
          { offset: 4, type: TokenType.Comment },
          { offset: 17, type: TokenType.EndCommentTag }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<!--a>\nfoo \nbar</a -->',
        tokens: [
          { offset: 0, type: TokenType.StartCommentTag },
          { offset: 4, type: TokenType.Comment },
          { offset: 19, type: TokenType.EndCommentTag }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<!Doctype a>',
        tokens: [
          { offset: 0, type: TokenType.StartDoctypeTag },
          { offset: 9, type: TokenType.Doctype },
          { offset: 11, type: TokenType.EndDoctypeTag }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<!doctype a>',
        tokens: [
          { offset: 0, type: TokenType.StartDoctypeTag },
          { offset: 9, type: TokenType.Doctype },
          { offset: 11, type: TokenType.EndDoctypeTag }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '<!DOCTYPE a\n"foo" \'bar\'>',
        tokens: [
          { offset: 0, type: TokenType.StartDoctypeTag },
          { offset: 9, type: TokenType.Doctype },
          { offset: 23, type: TokenType.EndDoctypeTag }
        ]
      }
    ]);
  }


() => {
    assertTokens([
      {
        input: '    ',
        tokens: [{ offset: 0, type: TokenType.Content }]
      }
    ]);
    assertTokens([
      {
        input: '<!---   ',
        tokens: [
          { offset: 0, type: TokenType.StartCommentTag },
          { offset: 4, type: TokenType.Comment }
        ]
      }
    ]);
    assertTokens([
      {
        input: '<style>color:red',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'style' },
          { offset: 6, type: TokenType.StartTagClose },
          { offset: 7, type: TokenType.Styles }
        ]
      }
    ]);
    assertTokens([
      {
        input: '<script>alert("!!")',
        tokens: [
          { offset: 0, type: TokenType.StartTagOpen },
          { offset: 1, type: TokenType.StartTag, content: 'script' },
          { offset: 7, type: TokenType.StartTagClose },
          { offset: 8, type: TokenType.Script }
        ]
      }
    ]);
  }


interface Token {
    offset: number;
    type: TokenType;
    content?: string;
  }


@Injectable()
export class AdditionExerciseGenerator extends Generator<AdditionExercise, AdditionExplanationStep> {
  public generateExercise(): AdditionExerciseWithExplanation {
    const base = 2;  // TODO inject allowed bases
    const firstNumber = randomInt(1, 256);
    const secondNumber = randomInt(1, 256);

    const firstSummand = convert(firstNumber).toBase(base);
    const secondSummand = convert(secondNumber).toBase(base);
    const exercise: AdditionExercise = {
      base, firstSummand, secondSummand
    };

    const explanation = this.generateExplanationForExercise(exercise);

    return {explanation, exercise};
  }

  public generateExplanationForExercise(exercise: AdditionExercise) {
    const {firstSummand, secondSummand, base} = exercise;
    const resultLength = Math.max(firstSummand.length, secondSummand.length) + 2;
    const explanation: AdditionExplanation = {
      steps: []
    };
    const step: AdditionExplanationStep = {
      firstSummand: this.toDigitArray(resultLength, firstSummand),
      secondSummand: this.toDigitArray(resultLength, secondSummand),
      carry: this.toDigitArray(resultLength, ''),
      result: this.toDigitArray(resultLength, ''),
      hint: {done: false, calc: '', carry: ''},
    };

    explanation.steps.push(deepCopy(step));

    for (let index = resultLength - 1; index > 0; index--) {
      const firstDigit = step.firstSummand[index];
      const secondDigit = step.secondSummand[index];
      const carryDigit = step.carry[index];
      // skip the last step if there's nothing left and no carry.
      if (index === 1 && carryDigit.value === '') {
        continue;
      }
      const result = sum(firstDigit.value, secondDigit.value, carryDigit.value).withBase(base);
      firstDigit.cssClass = 'text-warning';
      secondDigit.cssClass = 'text-warning';
      carryDigit.cssClass = 'text-warning';

      if (result.length === 1) {
        step.result[index] = {value: result, isVisible: true, cssClass: 'text-success'};
      } else if (result.length === 2) {
        step.result[index] = {value: result[1], isVisible: true, cssClass: 'text-success'};
        step.carry[index - 1] = {value: result[0], isVisible: true, cssClass: 'text-danger'};
      } else {
        throw new Error(`Unexpected: ${firstDigit.value} + ${secondDigit.value} + ${carryDigit.value}` +
          `gave ${result} with length not equal to 1 or 2`);
      }

      step.hint = this.getHint(firstDigit.value, secondDigit.value, carryDigit.value, result);
      explanation.steps.push(deepCopy(step));

      // reset css classes so digits don't remain highlighted forever
      firstDigit.cssClass = '';
      secondDigit.cssClass = '';
      carryDigit.cssClass = '';
      step.result[index].cssClass = '';
      if (index > 0) {
        step.carry[index - 1].cssClass = '';
      }
    }

    // Add 'done' message to the last step.
    explanation.steps[explanation.steps.length - 1].hint.done = true;

    return explanation;
  }

  public getResult(exercise: AdditionExercise, answer: string): Result {
    const firstNumber = convert(exercise.firstSummand).fromBase(exercise.base).toNumber();
    const secondNumber = convert(exercise.secondSummand).fromBase(exercise.base).toNumber();
    const expectedNumber = firstNumber + secondNumber;
    const actualNumber = convert(answer).fromBase(exercise.base).toNumber();
    return {correct: expectedNumber === actualNumber};
  }

  private getHint(firstDigit: string, secondDigit: string, carry: string, result: string): AdditionExplanationHint {
    firstDigit = this.ensureDigit(firstDigit);
    secondDigit = this.ensureDigit(secondDigit);
    const hint: AdditionExplanationHint = {
      calc: `${firstDigit} + ${secondDigit} `,
      carry: '',
      done: false,
    };
    if (carry.length === 1) {
      hint.calc += `+ ${carry} `;
    }
    hint.calc += `= ${result} `;
    if (result.length === 2) {
      hint.carry = `carry the ${result[0]}`;
    }
    return hint;
  }
}


/**
 * Container for an exercise with explanation
 */
export interface AdditionExerciseWithExplanation {
  /** The exercise */
  exercise: AdditionExercise;
  /** The explanation */
  explanation: AdditionExplanation;
}


/**
 * An addition exercise that the user might solve
 */
export interface AdditionExercise {
  /** The base of the summands */
  base: number;
  /** The first summand in the correct base */
  firstSummand: string;
  /** The second summand in the correct base */
  secondSummand: string;
}


/**
 * The explanation of an addition exercise.
 */
export interface AdditionExplanation {
  /**
   *  Contains the steps in the explanation.
   * The user can navigate between the steps to better understand the process
   */
  steps: AdditionExplanationStep[];
}


/**
 * A single step in the explanation of an addition exercise
 */
export interface AdditionExplanationStep {
  /** The first summand */
  firstSummand: Digit[];
  /** The second summand */
  secondSummand: Digit[];
  /** The carried digits (so far) */
  carry: Digit[];
  /** The result (so far) */
  result: Digit[];
  /** A hint displayed for the current step */
  hint: AdditionExplanationHint;
}


/**
 * Provides hints for what happened in a single explanation step
 */
export interface AdditionExplanationHint {
  /** The digit-wise calculation performed in this step */
  calc: string;
  /** Optionally a message about what digit should be carried */
  carry: string;
  /** Indicates if the addition is done after this step */
  done: boolean;
}


public generateExercise(): AdditionExerciseWithExplanation {
    const base = 2;  // TODO inject allowed bases
    const firstNumber = randomInt(1, 256);
    const secondNumber = randomInt(1, 256);

    const firstSummand = convert(firstNumber).toBase(base);
    const secondSummand = convert(secondNumber).toBase(base);
    const exercise: AdditionExercise = {
      base, firstSummand, secondSummand
    };

    const explanation = this.generateExplanationForExercise(exercise);

    return {explanation, exercise};
  }


public generateExplanationForExercise(exercise: AdditionExercise) {
    const {firstSummand, secondSummand, base} = exercise;
    const resultLength = Math.max(firstSummand.length, secondSummand.length) + 2;
    const explanation: AdditionExplanation = {
      steps: []
    };
    const step: AdditionExplanationStep = {
      firstSummand: this.toDigitArray(resultLength, firstSummand),
      secondSummand: this.toDigitArray(resultLength, secondSummand),
      carry: this.toDigitArray(resultLength, ''),
      result: this.toDigitArray(resultLength, ''),
      hint: {done: false, calc: '', carry: ''},
    };

    explanation.steps.push(deepCopy(step));

    for (let index = resultLength - 1; index > 0; index--) {
      const firstDigit = step.firstSummand[index];
      const secondDigit = step.secondSummand[index];
      const carryDigit = step.carry[index];
      // skip the last step if there's nothing left and no carry.
      if (index === 1 && carryDigit.value === '') {
        continue;
      }
      const result = sum(firstDigit.value, secondDigit.value, carryDigit.value).withBase(base);
      firstDigit.cssClass = 'text-warning';
      secondDigit.cssClass = 'text-warning';
      carryDigit.cssClass = 'text-warning';

      if (result.length === 1) {
        step.result[index] = {value: result, isVisible: true, cssClass: 'text-success'};
      } else if (result.length === 2) {
        step.result[index] = {value: result[1], isVisible: true, cssClass: 'text-success'};
        step.carry[index - 1] = {value: result[0], isVisible: true, cssClass: 'text-danger'};
      } else {
        throw new Error(`Unexpected: ${firstDigit.value} + ${secondDigit.value} + ${carryDigit.value}` +
          `gave ${result} with length not equal to 1 or 2`);
      }

      step.hint = this.getHint(firstDigit.value, secondDigit.value, carryDigit.value, result);
      explanation.steps.push(deepCopy(step));

      // reset css classes so digits don't remain highlighted forever
      firstDigit.cssClass = '';
      secondDigit.cssClass = '';
      carryDigit.cssClass = '';
      step.result[index].cssClass = '';
      if (index > 0) {
        step.carry[index - 1].cssClass = '';
      }
    }

    // Add 'done' message to the last step.
    explanation.steps[explanation.steps.length - 1].hint.done = true;

    return explanation;
  }


public getResult(exercise: AdditionExercise, answer: string): Result {
    const firstNumber = convert(exercise.firstSummand).fromBase(exercise.base).toNumber();
    const secondNumber = convert(exercise.secondSummand).fromBase(exercise.base).toNumber();
    const expectedNumber = firstNumber + secondNumber;
    const actualNumber = convert(answer).fromBase(exercise.base).toNumber();
    return {correct: expectedNumber === actualNumber};
  }


private getHint(firstDigit: string, secondDigit: string, carry: string, result: string): AdditionExplanationHint {
    firstDigit = this.ensureDigit(firstDigit);
    secondDigit = this.ensureDigit(secondDigit);
    const hint: AdditionExplanationHint = {
      calc: `${firstDigit} + ${secondDigit} `,
      carry: '',
      done: false,
    };
    if (carry.length === 1) {
      hint.calc += `+ ${carry} `;
    }
    hint.calc += `= ${result} `;
    if (result.length === 2) {
      hint.carry = `carry the ${result[0]}`;
    }
    return hint;
  }


export interface ISuggestionsProps extends WithStyles<typeof styles> {
    suggestion: IProductCard;
    query: string;
    isHighlighted: boolean;
    clearSuggestion: (query: string) => void;
    containerRef: React.RefObject<HTMLDivElement>;
    classes?: any;
}


/** @hidden */
declare function throwIfFailed<Ts>(out: RunDetails<Ts>): void;


x => x.prim === 'storage'


x => x.prim === 'parameter'


export interface Transfer {
  user: string;
  balance: string;
  amout: string;
  pay: string;
}


export interface ListInput<T> {
    insert (item: T): void;
    insertAll (items: Array<T>): void;
    remove (item: T): void;
    removeAll (items: Array<T>): void;
    setAll (items: Array<T>): void;
    clear (): void;
}


type yago


export default function px2rem(opts: IOpts): any;


export interface IOpts {
    rootValue: number;
    unitPrecision: number;
    propWhiteList: any[];
    propBlackList: any[];
    exclude: boolean;
    selectorBlackList: any[];
    ignoreIdentifier: boolean;
    replace: boolean;
    mediaQuery: boolean;
    minPixelValue: number;
  }


// import { formatGitRepoUrl } from '../../lib/git';
export class GitCloneCommand extends Command {
  async getMetadata(): Promise<CommandMetadata> {
    return {
      name: 'clone',
      type: 'global',
      summary: 'Clones an Ionic app git repository to your computer',
      inputs: [
        {
          name: 'id',
          summary: 'The ID of the Ionic Appflow app to clone',
          validators: [validators.required],
        },
        {
          name: 'path',
          summary: 'The destination directory of the cloned app',
        },
      ],
      groups: [MetadataGroup.HIDDEN], // TODO: make part of start?
    };
  }

  async run(inputs: CommandLineInputs, options: CommandLineOptions): Promise<void> {
    // let [ id, destination ] = inputs;

    // const appLoader = new App(this.env.session.getUserToken(), this.env.client);
    // const app = await appLoader.load(id);
    // const remote = await formatGitRepoUrl(this.env.config, app.id);

    // if (!destination) {
    //   destination = app.slug ? app.slug : app.id;
    // }

    // destination = path.resolve(destination);

    // await this.env.shell.run('git', ['clone', '-o', 'ionic', remote, destination], { stdio: 'inherit' });

    // this.env.log.ok(`Your app has been cloned to ${strong(prettyPath(destination))}!`);
  }
}


async getMetadata(): Promise<CommandMetadata> {
    return {
      name: 'clone',
      type: 'global',
      summary: 'Clones an Ionic app git repository to your computer',
      inputs: [
        {
          name: 'id',
          summary: 'The ID of the Ionic Appflow app to clone',
          validators: [validators.required],
        },
        {
          name: 'path',
          summary: 'The destination directory of the cloned app',
        },
      ],
      groups: [MetadataGroup.HIDDEN], // TODO: make part of start?
    };
  }


async run(inputs: CommandLineInputs, options: CommandLineOptions): Promise<void> {
    // let [ id, destination ] = inputs;

    // const appLoader = new App(this.env.session.getUserToken(), this.env.client);
    // const app = await appLoader.load(id);
    // const remote = await formatGitRepoUrl(this.env.config, app.id);

    // if (!destination) {
    //   destination = app.slug ? app.slug : app.id;
    // }

    // destination = path.resolve(destination);

    // await this.env.shell.run('git', ['clone', '-o', 'ionic', remote, destination], { stdio: 'inherit' });

    // this.env.log.ok(`Your app has been cloned to ${strong(prettyPath(destination))}!`);
  }


() => {
    const iconSize = 44;
    const [currentMessage] = useSelector(selectSnackbarMessages);
    const hideSnackbar = useAction(overlayActions.hideSnackbar);

    useEffect(() => {
        setTimeout(() => {
            hideSnackbar(currentMessage.id);
        }, snackbarVisibilityTime);
    }, [currentMessage]);

    return (
        <Wrapper type={currentMessage.type}>
            <IconWrapper>
                <Icon
                    name={currentMessage.type}
                    width={iconSize}
                    height={iconSize}
                />
            </IconWrapper>
            <TextWrapper>
                <ErrorType>
                    <Text>{currentMessage.type}</Text>
                </ErrorType>
                <span>{' - '}</span>
                <Text>{currentMessage.messageKey}</Text>
            </TextWrapper>
        </Wrapper>


() => {
        setTimeout(() => {
            hideSnackbar(currentMessage.id);
        }, snackbarVisibilityTime);
    }


() => {
            hideSnackbar(currentMessage.id);
        }


({isButtonActive, ...rest}) => <Button {...rest}/>


props => !props.isButtonActive &&
      `border-color: #e2e6ec !important;
      background-color: #f9fafb !important;

      color: ${props.theme.textColorDark} !important;
      transition: all .15s ease-in-out;

      .anticon {
        color: #e2e6ec !important;
        transition: all .15s ease-in-out;
      }

      &:hover {
        background-color: #f5f5f5 !important;
        border-color: #e1e1e1 !important;

        .anticon {
          color: #e1e1e1 !important;
          transition: all .15s ease-in-out;
        }
      }`


(props) => {
  // jw: passing the rest of the properties through to StyledButton so that things like event listeners and children
  //     will work as expected. Discovered when I tried to use a Tooltip with this.
  const { isButtonActive, buttonType, onClick, isVoting, isDisabled, ...rest } = props;

  return (
    <StyledButton
      isButtonActive={isButtonActive}
      type={buttonType}
      loading={isVoting}
      disabled={isDisabled}
      size="large"
      block={true}
      onClick={onClick}
      {...rest}
    />


export interface VoteButtonProps {
  isButtonActive: boolean;
  buttonType: 'primary' | 'danger';
  onClick?: () => void;
  isVoting?: boolean;
  isDisabled?: boolean;
  className?: string;
}


async (card: Card): Promise<{ id: string }> =>
  stripe.createToken({ card })


async (
  token: string,
  amount: number,
  name: string
): Promise<any> => {
  const res = await fetch(`${host}/pay`, {
    body: JSON.stringify({
      token,
      name,
      amount,
    }),
    method: "POST",
  });
  if (res.status > 200) {
    // return Promise.reject();
    throw new Error("'something went wrong processing your payment'");
  }
  return res.json();
}


export function Criteria(field: string): any[] {
        return [field];
    }


export function isEmpty(c: any[]): boolean {
            return c == null ||
                c.length === 0 ||
                (c.length === 1 &&
                    typeof c[0] == "string" &&
                    c[0].length === 0);
        }


export function join(c1: any[], op: string, c2: any[]): any[] {
            if (C.isEmpty(c1))
                return c2;

            if (C.isEmpty(c2))
                return c1;

            return [c1, op, c2];
        }


export function paren(c: any[]): any[] {
            return C.isEmpty(c) ? c : ['()', c];
        }


export function and(c1: any[], c2: any[], ...rest: any[][]): any[] {
            var result = join(c1, 'and', c2);
            if (rest) {
                for (let k of rest)
                    result = join(result, 'and', k);
            }

            return result;
        }


export function or(c1: any[], c2: any[], ...rest: any[][]): any[] {
            var result = join(c1, 'or', c2);

            if (rest) {
                for (let k of rest)
                    result = join(result, 'or', k);
            }

            return result;
        }


@NgModule({
  imports: [
    FormsModule,
    CommonModule,
    RouterModule.forChild(preparationRoute),
  ],
  declarations: [PreparationComponent],
  providers: []
})
export class PreparationModule {

}


(chatState: "hidden" | "show") => {
  const [hasUnreadMessages, setHasUnreadMessages] = React.useState(false);
  const environment = useRelayEnvironment();
  const [playDiceRollSound] = useSound(diceRollSound, {
    volume: 0.5,
  });
  const [playNotificationSound] = useSound(notificationSound, {
    volume: 0.5,
  });
  const soundSettings = useSoundSettings();

  const refs = React.useRef({
    playDiceRollSound,
    playNotificationSound,
    chatState,
    soundSettings,
  });

  refs.current = {
    playDiceRollSound,
    playNotificationSound,
    chatState,
    soundSettings,
  };

  React.useEffect(() => {
    const subscription = requestSubscription<chatMessageSoundSubscription>(
      environment,
      {
        subscription: ChatMessageSoundSubscription,
        variables: {},
        onNext: (data) => {
          if (data) {
            let mode: "dice-roll-message" | "text-message" = "text-message";

            if (
              data.chatMessagesAdded.messages.some(
                (message) => message.containsDiceRoll === true
              )
            ) {
              mode = "dice-roll-message";
            }

            if (
              mode === "text-message" &&
              refs.current.soundSettings.value === "all"
            ) {
              refs.current.playNotificationSound();
            } else if (
              mode === "dice-roll-message" &&
              refs.current.soundSettings.value !== "none"
            ) {
              refs.current.playDiceRollSound();
            }

            if (refs.current.chatState === "hidden") {
              setHasUnreadMessages(true);
            }
          }
        },
      }
    );

    return () => subscription.dispose();
  }, [environment]);

  return [hasUnreadMessages, () => setHasUnreadMessages(false)] as [
    boolean,
    () => void
  ];
}


() => {
    const subscription = requestSubscription<chatMessageSoundSubscription>(
      environment,
      {
        subscription: ChatMessageSoundSubscription,
        variables: {},
        onNext: (data) => {
          if (data) {
            let mode: "dice-roll-message" | "text-message" = "text-message";

            if (
              data.chatMessagesAdded.messages.some(
                (message) => message.containsDiceRoll === true
              )
            ) {
              mode = "dice-roll-message";
            }

            if (
              mode === "text-message" &&
              refs.current.soundSettings.value === "all"
            ) {
              refs.current.playNotificationSound();
            } else if (
              mode === "dice-roll-message" &&
              refs.current.soundSettings.value !== "none"
            ) {
              refs.current.playDiceRollSound();
            }

            if (refs.current.chatState === "hidden") {
              setHasUnreadMessages(true);
            }
          }
        },
      }
    );

    return () => subscription.dispose();
  }


(data) => {
          if (data) {
            let mode: "dice-roll-message" | "text-message" = "text-message";

            if (
              data.chatMessagesAdded.messages.some(
                (message) => message.containsDiceRoll === true
              )
            ) {
              mode = "dice-roll-message";
            }

            if (
              mode === "text-message" &&
              refs.current.soundSettings.value === "all"
            ) {
              refs.current.playNotificationSound();
            } else if (
              mode === "dice-roll-message" &&
              refs.current.soundSettings.value !== "none"
            ) {
              refs.current.playDiceRollSound();
            }

            if (refs.current.chatState === "hidden") {
              setHasUnreadMessages(true);
            }
          }
        }


(message) => message.containsDiceRoll === true


() => subscription.dispose()


() => setHasUnreadMessages(false)


({ toggleShowDiceRollNotes }) => {
  const [mode, setMode] = React.useState<"chat" | "user" | "settings">("chat");

  const environment = useRelayEnvironment();
  React.useEffect(() => {
    const subscription = requestSubscription<chatSubscription>(environment, {
      subscription: AppSubscription,
      variables: {},
      updater: (store, data) => {
        const chat = ConnectionHandler.getConnection(
          store.getRoot(),
          "chatMessages_chat"
        );

        const records = store
          .getRootField("chatMessagesAdded")
          ?.getLinkedRecords("messages");
        if (!chat || !records) return;

        for (const chatMessage of records) {
          const edge = ConnectionHandler.createEdge(
            store,
            chat,
            chatMessage,
            "ChatMessage"
          );
          ConnectionHandler.insertEdgeAfter(chat, edge);
        }
      },
    });
    return () => subscription.dispose();
  }, [environment]);

  React.useEffect(() => {
    const subscription = requestSubscription<chatUserUpdateSubscription>(
      environment,
      {
        subscription: UserUpdateSubscription,
        variables: {},
        updater: (store) => {
          const users = ConnectionHandler.getConnection(
            store.getRoot(),
            "chatUserList_users"
          );

          const updateRecord = store.getRootField("userUpdate");
          const root = store.getRoot();

          if (!users || !updateRecord) return;

          // see https://github.com/relay-tools/relay-compiler-language-typescript/issues/186
          if (isAbstractGraphQLMemberType(updateRecord, "UserAddUpdate")) {
            const edge = ConnectionHandler.createEdge(
              store,
              users,
              updateRecord.getLinkedRecord("user"),
              "User"
            );
            ConnectionHandler.insertEdgeAfter(users, edge);
            root.setValue(updateRecord.getValue("usersCount"), "usersCount");
          } else if (
            isAbstractGraphQLMemberType(updateRecord, "UserRemoveUpdate")
          ) {
            const userId = updateRecord.getValue("userId");
            ConnectionHandler.deleteNode(users, userId);
            root.setValue(updateRecord.getValue("usersCount"), "usersCount");
          }
        },
      }
    );
    return () => subscription.dispose();
  }, [environment]);

  const retryRef = React.useRef<null | (() => void)>(null);

  return (
    <>
      <QueryRenderer<chatQuery>
        query={ChatQuery}
        environment={environment}
        variables={{}}


() => {
    const subscription = requestSubscription<chatSubscription>(environment, {
      subscription: AppSubscription,
      variables: {},
      updater: (store, data) => {
        const chat = ConnectionHandler.getConnection(
          store.getRoot(),
          "chatMessages_chat"
        );

        const records = store
          .getRootField("chatMessagesAdded")
          ?.getLinkedRecords("messages");
        if (!chat || !records) return;

        for (const chatMessage of records) {
          const edge = ConnectionHandler.createEdge(
            store,
            chat,
            chatMessage,
            "ChatMessage"
          );
          ConnectionHandler.insertEdgeAfter(chat, edge);
        }
      },
    });
    return () => subscription.dispose();
  }


(store, data) => {
        const chat = ConnectionHandler.getConnection(
          store.getRoot(),
          "chatMessages_chat"
        );

        const records = store
          .getRootField("chatMessagesAdded")
          ?.getLinkedRecords("messages");
        if (!chat || !records) return;

        for (const chatMessage of records) {
          const edge = ConnectionHandler.createEdge(
            store,
            chat,
            chatMessage,
            "ChatMessage"
          );
          ConnectionHandler.insertEdgeAfter(chat, edge);
        }
      }


() => {
    const subscription = requestSubscription<chatUserUpdateSubscription>(
      environment,
      {
        subscription: UserUpdateSubscription,
        variables: {},
        updater: (store) => {
          const users = ConnectionHandler.getConnection(
            store.getRoot(),
            "chatUserList_users"
          );

          const updateRecord = store.getRootField("userUpdate");
          const root = store.getRoot();

          if (!users || !updateRecord) return;

          // see https://github.com/relay-tools/relay-compiler-language-typescript/issues/186
          if (isAbstractGraphQLMemberType(updateRecord, "UserAddUpdate")) {
            const edge = ConnectionHandler.createEdge(
              store,
              users,
              updateRecord.getLinkedRecord("user"),
              "User"
            );
            ConnectionHandler.insertEdgeAfter(users, edge);
            root.setValue(updateRecord.getValue("usersCount"), "usersCount");
          } else if (
            isAbstractGraphQLMemberType(updateRecord, "UserRemoveUpdate")
          ) {
            const userId = updateRecord.getValue("userId");
            ConnectionHandler.deleteNode(users, userId);
            root.setValue(updateRecord.getValue("usersCount"), "usersCount");
          }
        },
      }
    );
    return () => subscription.dispose();
  }


(store) => {
          const users = ConnectionHandler.getConnection(
            store.getRoot(),
            "chatUserList_users"
          );

          const updateRecord = store.getRootField("userUpdate");
          const root = store.getRoot();

          if (!users || !updateRecord) return;

          // see https://github.com/relay-tools/relay-compiler-language-typescript/issues/186
          if (isAbstractGraphQLMemberType(updateRecord, "UserAddUpdate")) {
            const edge = ConnectionHandler.createEdge(
              store,
              users,
              updateRecord.getLinkedRecord("user"),
              "User"
            );
            ConnectionHandler.insertEdgeAfter(users, edge);
            root.setValue(updateRecord.getValue("usersCount"), "usersCount");
          } else if (
            isAbstractGraphQLMemberType(updateRecord, "UserRemoveUpdate")
          ) {
            const userId = updateRecord.getValue("userId");
            ConnectionHandler.deleteNode(users, userId);
            root.setValue(updateRecord.getValue("usersCount"), "usersCount");
          }
        }


(): string => {
  return generatePassword(32);
}


(token: string): string => {
  fs.writeFileSync(tokenPath, token);
  return tokenPath;
}


(): string => {
  return fs.readFileSync(tokenPath, "utf8");
}


(req: Request, res: Response, next: NextFunction) => {
  const allowlist = ["127.0.0.1", "::f", "::ffff:127.0.0.1"];

  if (allowlist.indexOf(req.connection.remoteAddress || "") >= 0) {
    // allow access from localhost
    next();
    return;
  }

  const tokenExists = fs.existsSync(tokenPath);

  if (tokenExists) {
    const token = readToken();

    if (req.session && token === req.session.token) {
      // token in session matches
      next();
      return;
    } else {
      req.session?.destroy(function () {
        res.redirect("/probot/setup/extended/authn");
      });

      return;
    }
  }

  res.writeHead(404);
  res.end();
}


export class ERModel extends SGraph {
    name: string
    notation: string
}


export class RelationEdge extends SEdge {
    routerKind = PolylineEdgeRouter.KIND;
    targetAnchorCorrection = Math.sqrt(5);
}


export class EntityNode extends RectangularNode {
    expanded: boolean

    canConnect(routable: SRoutableElement, role: string) {
        return true;
    }
}


export class NotationEdge extends SEdge {
    isSource: boolean
    showRelationship: boolean
    notation: String
    relationshipCardinality: String
}


/*
export class RelationshipNode extends DiamondNode {
    canConnect(routable: SRoutableElement, role: string) {
        return true;
    }
}
*/
export class CreateRelationPort extends RectangularPort implements CreatingOnDrag {
    createAction(id: string): Action {
        const edge: SEdgeSchema = {
            id,
            type: 'edge',
            sourceId: this.parent.id,
            targetId: this.id
        };
        return CreateElementAction.create(edge, { containerId: this.root.id });
    }
}


@injectable()
export class MultiplicityLabel extends SLabel implements EdgeLayoutable {
    edgePlacement = <EdgePlacement> {
        position: 0.5,
        side: 'top',
        rotate: false,
        offset: 5
    };
}


@injectable()
export class InheritanceEdge extends SEdge {
    public readonly targetAnchorCorrection = Math.sqrt(5);
    public readonly routerKind = ManhattanEdgeRouter.KIND;
}


canConnect(routable: SRoutableElement, role: string) {
        return true;
    }


createAction(id: string): Action {
        const edge: SEdgeSchema = {
            id,
            type: 'edge',
            sourceId: this.parent.id,
            targetId: this.id
        };
        return CreateElementAction.create(edge, { containerId: this.root.id });
    }


({ slug, data }) => {
  const { product } = (useAsyncProduct({ slug }) as unknown) as Product
  const [sku] = useSku<Item>(product)
  const { commercialOffer } = useBestSeller(sku)

  if (product === null || sku === null) {
    return null
  }

  console.log(data)

  return (
    <>
      <Box>
        <Offer variant="productDetails" commercialOffer={commercialOffer} />
        <Color />
        <Size />

        <Flex
          sx={{
            justifyContent: 'space-between',
            flexWrap: 'wrap',
            gap: 2,
            mt: '1rem',
            alignItems: 'center',
          }}
        >
          <Button
            sx={{
              width: ['100%', '70%', '37%'],
              fontSize: '14px',
              minWidth: '160px',
            }}
          >
            COMPRAR AGORA
          </Button>
          <BuyButton sku={sku} />
        </Flex>
        <Flex>
          <NumericStepper />
        </Flex>
        <SearchCEP />
      </Box>
    </>


interface Props {
  slug?: string
  data: NonNullable<OrderFormContext['value']>['items']
  varian: string
}


type Item = {
  itemId: string
  sellers: Array<{
    sellerId: string
    commercialOffer: {
      availableQuantity: number
      price: number
    }
  }>
}


type Product = {
  product: {
    productReference: string
    items: Item[]
  }
}


export class GithubSettings implements OAuth2Settings {
  accessTokenMethod: string;
  accountLinking: boolean;
  authorizeUrl: string;
  callbackUrl: string;
  clientId: string;
  clientSecret: string;
  finalRedirect: string;
  providerName: string;
  tokenUrl: string;
  responseType: string;

  constructor(grpcSdk: ConduitGrpcSdk, config: any, serverUrl: string) {
    this.providerName = 'github';
    this.clientId = config.github.clientId;
    this.clientSecret = config.github.clientSecret;
    this.callbackUrl = serverUrl + '/hook/authentication/github';
    this.responseType = 'code';
    this.accessTokenMethod = 'POST';
    this.finalRedirect = config.github.redirect_uri;
    this.accountLinking = config.github.accountLinking;
    this.authorizeUrl = 'https://github.com/login/oauth/authorize?';
    this.tokenUrl = 'https://github.com/login/oauth/access_token';
  }
}


@Component({
    selector: 'app-home',
    templateUrl: 'home.page.html',
    styleUrls: ['home.page.scss'],
})
export class HomePage {
    @ViewChild('predictImg') predictImg: ElementRef;
    imgSrc: string;
    model: GraphModel;
    isReady = false;
    breedPrediction: BreedPrediction;
    breedlist: string[] = [
        'Chihuahua',
        'Japanese_spaniel',
        'Maltese_dog',
        'Pekinese',
        'Shih-Tzu',
        'Blenheim_spaniel',
        'papillon',
        'toy_terrier',
        'Rhodesian_ridgeback',
        'Afghan_hound',
        'basset',
        'beagle',
        'bloodhound',
        'bluetick',
        'black-and-tan_coonhound',
        'Walker_hound',
        'English_foxhound',
        'redbone',
        'borzoi',
        'Irish_wolfhound',
        'Italian_greyhound',
        'whippet',
        'Ibizan_hound',
        'Norwegian_elkhound',
        'otterhound',
        'Saluki',
        'Scottish_deerhound',
        'Weimaraner',
        'Staffordshire_bullterrier',
        'American_Staffordshire_terrier',
        'Bedlington_terrier',
        'Border_terrier',
        'Kerry_blue_terrier',
        'Irish_terrier',
        'Norfolk_terrier',
        'Norwich_terrier',
        'Yorkshire_terrier',
        'wire-haired_fox_terrier',
        'Lakeland_terrier',
        'Sealyham_terrier',
        'Airedale',
        'cairn',
        'Australian_terrier',
        'Dandie_Dinmont',
        'Boston_bull',
        'miniature_schnauzer',
        'giant_schnauzer',
        'standard_schnauzer',
        'Scotch_terrier',
        'Tibetan_terrier',
        'silky_terrier',
        'soft-coated_wheaten_terrier',
        'West_Highland_white_terrier',
        'Lhasa',
        'flat-coated_retriever',
        'curly-coated_retriever',
        'golden_retriever',
        'Labrador_retriever',
        'Chesapeake_Bay_retriever',
        'German_short-haired_pointer',
        'vizsla',
        'English_setter',
        'Irish_setter',
        'Gordon_setter',
        'Brittany_spaniel',
        'clumber',
        'English_springer',
        'Welsh_springer_spaniel',
        'cocker_spaniel',
        'Sussex_spaniel',
        'Irish_water_spaniel',
        'kuvasz',
        'schipperke',
        'groenendael',
        'malinois',
        'briard',
        'kelpie',
        'komondor',
        'Old_English_sheepdog',
        'Shetland_sheepdog',
        'collie',
        'Border_collie',
        'Bouvier_des_Flandres',
        'Rottweiler',
        'German_shepherd',
        'Doberman',
        'miniature_pinscher',
        'Greater_Swiss_Mountain_dog',
        'Bernese_mountain_dog',
        'Appenzeller',
        'EntleBucher',
        'boxer',
        'bull_mastiff',
        'Tibetan_mastiff',
        'French_bulldog',
        'Great_Dane',
        'Saint_Bernard',
        'Eskimo_dog',
        'malamute',
        'Siberian_husky',
        'affenpinscher',
        'basenji',
        'pug',
        'Leonberg',
        'Newfoundland',
        'Great_Pyrenees',
        'Samoyed',
        'Pomeranian',
        'chow',
        'keeshond',
        'Brabancon_griffon',
        'Pembroke',
        'Cardigan',
        'toy_poodle',
        'miniature_poodle',
        'standard_poodle',
        'Mexican_hairless',
        'dingo',
        'dhole',
        'African_hunting_dog',
    ];

    constructor(private modalController: ModalController) {
        this.imgSrc = '/assets/test.jpg';
        this.presentModal();
        this.loadModel();
    }

    async presentModal() {
        const modal = await this.modalController.create({
            component: TutorialComponent,
        });
        return await modal.present();
    }

    async takePhoto(): Promise<void> {

        const capturedPhoto = await Plugins.Camera.getPhoto({
          resultType: CameraResultType.Base64,
          source: CameraSource.Camera,
          quality: 100
        });

        this.imgSrc = 'data:image/png;base64,' + capturedPhoto.base64String;

        const tensor = tf.browser.fromPixels(this.predictImg.nativeElement).expandDims(0).toFloat();
        const prediction = this.model.predict(tensor);
        // console.log('tensor', tensor, 'prediction', prediction);
        // @ts-ignore
        const score = tf.softmax(prediction);
        // console.log('score', score);
        const data = await score.data();
        // console.log('data', data);
        const all: Array<[string, number]> = [];
        for (let i = 0; i < this.breedlist.length; i++) {
            all.push([this.breedlist[i], data[i]]);
        }
        all.sort((a, b) => a[1] - b[1]).reverse();
        const result: BreedPrediction = {
            firstPlace: all[0],
            secondPlace: all[1],
            thirdPlace: all[2],
            all
        };
        this.breedPrediction = result;
    }

    async loadModel() {
        this.model = await tf.loadGraphModel('/assets/modeljs/model.json');
        this.isReady = true;
    }
}


async presentModal() {
        const modal = await this.modalController.create({
            component: TutorialComponent,
        });
        return await modal.present();
    }


async takePhoto(): Promise<void> {

        const capturedPhoto = await Plugins.Camera.getPhoto({
          resultType: CameraResultType.Base64,
          source: CameraSource.Camera,
          quality: 100
        });

        this.imgSrc = 'data:image/png;base64,' + capturedPhoto.base64String;

        const tensor = tf.browser.fromPixels(this.predictImg.nativeElement).expandDims(0).toFloat();
        const prediction = this.model.predict(tensor);
        // console.log('tensor', tensor, 'prediction', prediction);
        // @ts-ignore
        const score = tf.softmax(prediction);
        // console.log('score', score);
        const data = await score.data();
        // console.log('data', data);
        const all: Array<[string, number]> = [];
        for (let i = 0; i < this.breedlist.length; i++) {
            all.push([this.breedlist[i], data[i]]);
        }
        all.sort((a, b) => a[1] - b[1]).reverse();
        const result: BreedPrediction = {
            firstPlace: all[0],
            secondPlace: all[1],
            thirdPlace: all[2],
            all
        };
        this.breedPrediction = result;
    }


async loadModel() {
        this.model = await tf.loadGraphModel('/assets/modeljs/model.json');
        this.isReady = true;
    }


(accountsServer: AccountsServer) => async (
  _,
  args: {},
  ctx: IResolverContext
) => {
  const { user } = ctx;

  // Make sure user is logged in
  if (!(user && user.id)) {
    throw new Error('Unauthorized');
  }

  const password: any = accountsServer.getServices().password as AccountsPassword;

  if (!(typeof password.resetPassword === 'function')) {
    throw new Error('No service handle password modification.');
  }

  // https://github.com/speakeasyjs/speakeasy/blob/master/index.js#L517
  const secret = password.twoFactor.getNewAuthSecret();
  return secret;
}


async (
  _,
  args: {},
  ctx: IResolverContext
) => {
  const { user } = ctx;

  // Make sure user is logged in
  if (!(user && user.id)) {
    throw new Error('Unauthorized');
  }

  const password: any = accountsServer.getServices().password as AccountsPassword;

  if (!(typeof password.resetPassword === 'function')) {
    throw new Error('No service handle password modification.');
  }

  // https://github.com/speakeasyjs/speakeasy/blob/master/index.js#L517
  const secret = password.twoFactor.getNewAuthSecret();
  return secret;
}


(accountsServer: AccountsServer) => async (
  _,
  args: GQL.ITwoFactorSetOnMutationArguments,
  ctx: IResolverContext
) => {
  const { code, secret } = args;
  const { user } = ctx;

  // Make sure user is logged in
  if (!(user && user.id)) {
    throw new Error('Unauthorized');
  }

  const userId = user.id;
  const password: any = accountsServer.getServices().password as AccountsPassword;

  if (!(password && password.twoFactor && typeof password.twoFactor.set === 'function')) {
    throw new Error('No service handle set two factor.');
  }

  return password.twoFactor.set(userId, secret, code);
}


async (
  _,
  args: GQL.ITwoFactorSetOnMutationArguments,
  ctx: IResolverContext
) => {
  const { code, secret } = args;
  const { user } = ctx;

  // Make sure user is logged in
  if (!(user && user.id)) {
    throw new Error('Unauthorized');
  }

  const userId = user.id;
  const password: any = accountsServer.getServices().password as AccountsPassword;

  if (!(password && password.twoFactor && typeof password.twoFactor.set === 'function')) {
    throw new Error('No service handle set two factor.');
  }

  return password.twoFactor.set(userId, secret, code);
}


(accountsServer: AccountsServer) => async (
  _,
  args: GQL.ITwoFactorUnsetOnMutationArguments,
  ctx: IResolverContext
) => {
  const { code } = args;
  const { user } = ctx;

  // Make sure user is logged in
  if (!(user && user.id)) {
    throw new Error('Unauthorized');
  }

  const userId = user.id;
  const password: any = accountsServer.getServices().password as AccountsPassword;

  if (!(password && password.twoFactor && typeof password.twoFactor.unset === 'function')) {
    throw new Error('No service handle two factor.');
  }

  await password.twoFactor.unset(userId, code);
}


async (
  _,
  args: GQL.ITwoFactorUnsetOnMutationArguments,
  ctx: IResolverContext
) => {
  const { code } = args;
  const { user } = ctx;

  // Make sure user is logged in
  if (!(user && user.id)) {
    throw new Error('Unauthorized');
  }

  const userId = user.id;
  const password: any = accountsServer.getServices().password as AccountsPassword;

  if (!(password && password.twoFactor && typeof password.twoFactor.unset === 'function')) {
    throw new Error('No service handle two factor.');
  }

  await password.twoFactor.unset(userId, code);
}


@Component({
    selector: 'app-ngxd-benchmark',
    templateUrl: '../ngxd.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [ ItemsService ]
})
export class Ngxd100BenchmarkComponent extends NgxdBenchmarkComponent {
    constructor(resolver: ComponentResolver, items: ItemsService, measures: MeasureService) {
        super(resolver, items, measures);
    }
}


/// <reference types="node" />
export declare function sleep(ms: number): Promise<NodeJS.Timeout>;


/**
 * BatchGetTracesOutput shape
 */
export interface BatchGetTracesOutput extends __aws_sdk_types.MetadataBearer {
  /**
   *
Full traces for the specified requests.

   */
  Traces?: Array<_UnmarshalledTrace>;

  /**
   *
Trace IDs of requests that haven't been processed.

   */
  UnprocessedTraceIds?: Array<string>;

  /**
   *
Pagination token. Not used.

   */
  NextToken?: string;

  /**
   * Metadata about the response received, including the HTTP status code, HTTP headers, and any request identifiers recognized by the SDK.
   */
  $metadata: __aws_sdk_types.ResponseMetadata;
}


export interface productos{

    idArticulo?: number;
    nombreArticulo?: string;
    descripcion?: string;
    precio?: number;
    existencias?: string;
    imagen?: string;
    idCategoria?: number;
}

Edson Arley Sanabria Valencia/Gestion de usuarios/HappyShop/src/app/models/productos.ts


export interface persona{
    idPersona?: number;
    nombres?: string;
    apellidos?: string;
    cedula?: number;
    direccion?: string;
    telefono?: number;
    fechaNacimiento?: Date;
    email?: string;
    contrasena?: string;
    genero?: string;
    tipoCuenta?: string;
    idCiudad?: number;
    idPais?: number;
}


export interface cliente {
    idCliente?: number;
    direccionFacturacion?: string;
    idPersona?: number;
}


export interface ciudad {
    idCiudad?: number;
    nombreCiudad?: string;
    idPais?: number;
}


export interface pais {
    idPais?: number;
    nombrePais?: string;
}


export interface categoria {
    idCategoria?: number;
    nombre?: string;
    descripcion?: string;
}


export interface carrito {
    id?: number;
    cantidad?: number;
    idArticulo?: number;
    idCliente?: number;
}


export interface pedido{

    idPedido?: number;
    cantidad?: number;
    descuento?: number;
    fecha?: Date;
    total?: number;
    idCliente?: number;
}


export interface detallePedido {
    idDetallePedido?: number;
    metodoPago?: string;
    metodoEntrega?: string;
    idPedido?: number;
    idEmpresaTranspor?: number;
    idArticulo?: 0,
    cantidad?: 0;
}


export interface proveedorArticulo {
    id?: number;
    idProveedor?: number;
    idArticulo?: number;
}


export interface proveedor {
    idProveedor?: number;
    nit?: number;
    nombreProveedor?: string;
    telefono?: number;
    email?: string;
}


export interface empresaTransporte {
    idEmpresaTranspor?: number;
    nombre?: string;
    nit?: number;
    telefono?: number;
    email?: string;
}


// https://docs.synapse.org/rest/org/sagebionetworks/repo/model/FileEntity.html
(data: FileEntity) => {
          const fileHandleAssociationList = [
            {
              associateObjectId: synapseId,
              associateObjectType: FileHandleAssociateType.FileEntity,
              fileHandleId: data.dataFileHandleId
            }
          ]
          this.getSynapseFiles(fileHandleAssociationList, data.dataFileHandleId)
        }


(data: BatchFileResult) => {
        const { preSignedURL } = data.requestedFiles.filter(el => el.fileHandleId === id)[0] // this.matchToHandle(this.compareById(id, 'fileHandleId'), data.requestedFiles)
        this.setState({
          preSignedURL: preSignedURL
        })
      }


el => el.fileHandleId === id


err => {
        console.error('Error on getting image ', err)
      }


el => el.fileName === fileName


class SynapseImage extends React.Component<SynapseImageProps, SynapseImageState> {

  constructor(props: SynapseImageProps) {
    super(props)
    this.getEntity = this.getEntity.bind(this)
    this.getSynapseFiles = this.getSynapseFiles.bind(this)
    this.state = {
      preSignedURL: ''
    }
  }

  public getEntity() {
    const { token, synapseId } = this.props
    if (synapseId) {
      getEntity<FileEntity>(token, synapseId).then(
        // https://docs.synapse.org/rest/org/sagebionetworks/repo/model/FileEntity.html
        (data: FileEntity) => {
          const fileHandleAssociationList = [
            {
              associateObjectId: synapseId,
              associateObjectType: FileHandleAssociateType.FileEntity,
              fileHandleId: data.dataFileHandleId
            }
          ]
          this.getSynapseFiles(fileHandleAssociationList, data.dataFileHandleId)
        }
      )
    }
  }
  public getSynapseFiles(fileHandleAssociationList: FileHandleAssociation [], id: string) {
    // overload the method for two different use cases, one where
    // the image is attached to an entity and creates a list on the spot,
    // the other where list is passed in from componentDidMount in MarkdownSynapse
    const request: BatchFileRequest = {
      includeFileHandles: false,
      includePreSignedURLs: true,
      includePreviewPreSignedURLs: false,
      requestedFiles: fileHandleAssociationList
    }
    getFiles(request, this.props.token).then(
      (data: BatchFileResult) => {
        const { preSignedURL } = data.requestedFiles.filter(el => el.fileHandleId === id)[0] // this.matchToHandle(this.compareById(id, 'fileHandleId'), data.requestedFiles)
        this.setState({
          preSignedURL: preSignedURL
        })
      }
    ).catch(
      err => {
        console.error('Error on getting image ', err)
      }
    )
  }
  public componentDidMount() {
    if (!this.props.wikiId) {
      // Get file handle as external attachment
      this.getEntity()
    } else {
      // Can get presigned url right away from wiki association
      const { fileName, fileResults = [] } = this.props
      const { id } = fileResults.filter(el => el.fileName === fileName)[0]
      const fileHandleAssociationList: FileHandleAssociation [] = [
        {
          associateObjectId: this.props.wikiId,
          associateObjectType: FileHandleAssociateType.WikiAttachment,
          fileHandleId: id
        }
      ]
      this.getSynapseFiles(fileHandleAssociationList, id)
    }
  }

  public render() {
    const imgStyle: React.CSSProperties = {}
    const { params = {}} = this.props
    const { align = '' } = params
    const alignLowerCase = align.toLowerCase()
    let className = ''
    if (alignLowerCase === 'left') {
      className = 'floatLeft'
    }
    if (alignLowerCase === 'right') {
      className = 'floatright'
    }
    if (alignLowerCase === 'center') {
      className = 'align-center'
    }
    if (!this.state.preSignedURL) {
      return null
    }
    return (
      <React.Fragment>
        <img
          alt="synapse"
          className={"img-fluid  "  + className}
          src={this.state.preSignedURL}
          style={imgStyle}
        />
      </React.Fragment>
    )
  }
}


type SynapseImageProps = {
  wikiId?: string
  synapseId?: string
  token?: string
  fileName?: string
  fileResults?: FileHandle []
  params: any
}


type SynapseImageState = {
  preSignedURL: string
}


public getEntity() {
    const { token, synapseId } = this.props
    if (synapseId) {
      getEntity<FileEntity>(token, synapseId).then(
        // https://docs.synapse.org/rest/org/sagebionetworks/repo/model/FileEntity.html
        (data: FileEntity) => {
          const fileHandleAssociationList = [
            {
              associateObjectId: synapseId,
              associateObjectType: FileHandleAssociateType.FileEntity,
              fileHandleId: data.dataFileHandleId
            }
          ]
          this.getSynapseFiles(fileHandleAssociationList, data.dataFileHandleId)
        }
      )
    }
  }


public getSynapseFiles(fileHandleAssociationList: FileHandleAssociation [], id: string) {
    // overload the method for two different use cases, one where
    // the image is attached to an entity and creates a list on the spot,
    // the other where list is passed in from componentDidMount in MarkdownSynapse
    const request: BatchFileRequest = {
      includeFileHandles: false,
      includePreSignedURLs: true,
      includePreviewPreSignedURLs: false,
      requestedFiles: fileHandleAssociationList
    }
    getFiles(request, this.props.token).then(
      (data: BatchFileResult) => {
        const { preSignedURL } = data.requestedFiles.filter(el => el.fileHandleId === id)[0] // this.matchToHandle(this.compareById(id, 'fileHandleId'), data.requestedFiles)
        this.setState({
          preSignedURL: preSignedURL
        })
      }
    ).catch(
      err => {
        console.error('Error on getting image ', err)
      }
    )
  }


public componentDidMount() {
    if (!this.props.wikiId) {
      // Get file handle as external attachment
      this.getEntity()
    } else {
      // Can get presigned url right away from wiki association
      const { fileName, fileResults = [] } = this.props
      const { id } = fileResults.filter(el => el.fileName === fileName)[0]
      const fileHandleAssociationList: FileHandleAssociation [] = [
        {
          associateObjectId: this.props.wikiId,
          associateObjectType: FileHandleAssociateType.WikiAttachment,
          fileHandleId: id
        }
      ]
      this.getSynapseFiles(fileHandleAssociationList, id)
    }
  }


public render() {
    const imgStyle: React.CSSProperties = {}
    const { params = {}} = this.props
    const { align = '' } = params
    const alignLowerCase = align.toLowerCase()
    let className = ''
    if (alignLowerCase === 'left') {
      className = 'floatLeft'
    }
    if (alignLowerCase === 'right') {
      className = 'floatright'
    }
    if (alignLowerCase === 'center') {
      className = 'align-center'
    }
    if (!this.state.preSignedURL) {
      return null
    }
    return (
      <React.Fragment>
        <img
          alt="synapse"
          className={"img-fluid  "  + className}
          src={this.state.preSignedURL}
          style={imgStyle}
        />
      </React.Fragment>
    )
  }


({ router }) => {
    if (__VUEPRESS_SSR__) return
    useBaiduAnalytics(__BAIDU_ID__)
    if (__BAIDU_SPA__) {
      router.beforeEach((to) => {
        if (typeof _hmt !== 'undefined') {
          // eslint-disable-next-line no-undef
          _hmt.push(['_trackPageview', to.fullPath])
        }
      })
    }
  }


(to) => {
        if (typeof _hmt !== 'undefined') {
          // eslint-disable-next-line no-undef
          _hmt.push(['_trackPageview', to.fullPath])
        }
      }


function noop() { /* noop */ }


function applyMixin(thisObj: any, mixin: {[propertyName: string]: any}, propertiesToSkip: string[]) {
    Object.getOwnPropertyNames(mixin).forEach(name => {
        if (name !== 'constructor' && propertiesToSkip.indexOf(name) === -1) {
            assert(
                !(name in thisObj),
                `An object cannot have a method with the same name as one of its mixins: "${name}"`
            );
            thisObj[name] = mixin[name].bind(thisObj);
        }
    });
}


name => {
        if (name !== 'constructor' && propertiesToSkip.indexOf(name) === -1) {
            assert(
                !(name in thisObj),
                `An object cannot have a method with the same name as one of its mixins: "${name}"`
            );
            thisObj[name] = mixin[name].bind(thisObj);
        }
    }


(e: Types.SyntheticEvent) => {
        if (this._isTouchFeedbackApplicable()) {
            if (this.props.underlayColor) {
                if (this._hideTimeout) {
                    clearTimeout(this._hideTimeout);
                    this._hideTimeout = undefined;
                }
                this._showUnderlay();
            }

             // We do not want to animate opacity if underlayColour is provided. Unless an explicit activeOpacity is provided
            if (!this.props.disableTouchOpacityAnimation && (this.props.activeOpacity || !this.props.underlayColor)) {
                this._opacityActive(_activeOpacityAnimationDuration);
            }
        }

        if (!this.props.disabled && this.props.onPressIn) {
            this.props.onPressIn(e);
        }
    }


(e: Types.SyntheticEvent) => {
        if (this._isTouchFeedbackApplicable()) {
            if (this.props.underlayColor) {
                if (this._hideTimeout) {
                    clearTimeout(this._hideTimeout);
                }
                this._hideTimeout = Timers.setTimeout(this._hideUnderlay, _hideUnderlayTimeout);
            }

            if (!this.props.disableTouchOpacityAnimation && (this.props.activeOpacity || !this.props.underlayColor)) {
                this._opacityInactive(_inactiveOpacityAnimationDuration);
            }
        }

        if (!this.props.disabled && this.props.onPressOut) {
            this.props.onPressOut(e);
        }
    }


(e: Types.SyntheticEvent) => {
        UserInterface.evaluateTouchLatency(e);
        if (!this.props.disabled) {
            if (EventHelpers.isRightMouseButton(e)) {
                if (this.props.onContextMenu) {
                    this.props.onContextMenu(EventHelpers.toMouseEvent(e));
                }
            } else {
                if (this.props.onPress) {
                    this.props.onPress(EventHelpers.toMouseEvent(e));
                }
            }
        }
    }


(e: Types.SyntheticEvent) => {
        if (!this.props.disabled && !EventHelpers.isRightMouseButton(e) && this.props.onLongPress) {
            this.props.onLongPress(EventHelpers.toMouseEvent(e));
        }
    }


() => {
        return 20;
    }


() => {
        return {top: 20, left: 20, right: 20, bottom: 100};
    }


() => this.focus()


() => this._isMounted


(btn: any): void => {
        this._buttonElement = btn;
    }


(e: Types.SyntheticEvent) => {
        this._isMouseOver = true;
        this._onHoverStart(e);
    }


(e: Types.SyntheticEvent) => {
        this._isMouseOver = false;
        this._onHoverEnd(e);
    }


(e: Types.SyntheticEvent) => {
        if (!this._isHoverStarted && this._isMouseOver) {
            this._isHoverStarted = true;

            if (this.props.onHoverStart) {
                this.props.onHoverStart(e);
            }
        }
    }