<commit_before>import {
    EntityCode,
    EntityId,
    EntityName,
    OwidTableSlugs,
} from "../../coreTable/OwidTableConstants"
import { EntityUrlBuilder } from "../core/EntityUrlBuilder"
import { difference, mapBy } from "../../clientUtils/Util"
import { isPresent } from "../../clientUtils/isPresent"
import { action, computed, observable } from "mobx"

interface Entity {
    entityName: EntityName
    entityId?: EntityId
    entityCode?: EntityCode
}

export class SelectionArray {
    constructor(
        selectedEntityNames: EntityName[] = [],
        availableEntities: Entity[] = [],
        entityType = "country"
    ) {
        this.selectedEntityNames = selectedEntityNames.slice()
        this.availableEntities = availableEntities.slice()
        this.entityType = entityType
    }

    @observable entityType: string
    @observable selectedEntityNames: EntityName[]
    @observable private availableEntities: Entity[]

    @computed get availableEntityNames() {
        return this.availableEntities.map((entity) => entity.entityName)
    }

    @computed get availableEntityNameSet() {
        return new Set(this.availableEntityNames)
    }

    private mapBy(col: keyof Entity, val: keyof Entity) {
        return mapBy(
            this.availableEntities,
            (v) => v[col],
            (v) => v[val]
        )
    }

    @computed get entityNameToIdMap() {
        return this.mapBy(OwidTableSlugs.entityName, OwidTableSlugs.entityId)
    }

    @computed get entityCodeToNameMap() {
        return this.mapBy(OwidTableSlugs.entityCode, OwidTableSlugs.entityName)
    }

    @computed get entityIdToNameMap() {
        return this.mapBy(OwidTableSlugs.entityId, OwidTableSlugs.entityName)
    }

    @computed get hasSelection() {
        return this.selectedEntityNames.length > 0
    }

    @computed get unselectedEntityNames() {
        return difference(this.availableEntityNames, this.selectedEntityNames)
    }

    @computed get numSelectedEntities() {
        return this.selectedEntityNames.length
    }

    @computed get selectedSet() {
        return new Set<EntityName>(this.selectedEntityNames)
    }

    @computed get allSelectedEntityIds(): EntityId[] {
        const map = this.entityNameToIdMap
        return this.selectedEntityNames
            .map((name) => map.get(name))
            .filter(isPresent)
    }

    // Clears and sets selected entities
    @action.bound setSelectedEntities(entityNames: EntityName[]) {
        this.clearSelection()
        return this.addToSelection(entityNames)
    }

    @action.bound addToSelection(entityNames: EntityName[]) {
        this.selectedEntityNames = this.selectedEntityNames.concat(entityNames)
        return this
    }

    @action.bound addAvailableEntityNames(entities: Entity[]) {
        this.availableEntities.push(...entities)
        return this
    }

    @action.bound setSelectedEntitiesByCode(entityCodes: EntityCode[]) {
        const map = this.entityCodeToNameMap
        const codesInData = entityCodes.filter((code) => map.has(code))
        return this.setSelectedEntities(
            codesInData.map((code) => map.get(code)!)
        )
    }

    @action.bound setSelectedEntitiesByEntityId(entityIds: EntityId[]) {
        const map = this.entityIdToNameMap
        return this.setSelectedEntities(entityIds.map((id) => map.get(id)!))
    }

    @action.bound selectAll() {
        return this.addToSelection(this.unselectedEntityNames)
    }

    @action.bound clearSelection() {
        this.selectedEntityNames = []
    }

    @action.bound toggleSelection(entityName: EntityName) {
        return this.selectedSet.has(entityName)
            ? this.deselectEntity(entityName)
            : this.selectEntity(entityName)
    }

    @computed get numAvailableEntityNames() {
        return this.availableEntityNames.length
    }

    @action.bound selectEntity(entityName: EntityName) {
        return this.addToSelection([entityName])
    }<commit_msg>fix(selection): prevent `SelectionArray.selectEntity` from adding the same entity multiple items<commit_after>import {
    EntityCode,
    EntityId,
    EntityName,
    OwidTableSlugs,
} from "../../coreTable/OwidTableConstants"
import { EntityUrlBuilder } from "../core/EntityUrlBuilder"
import { difference, mapBy } from "../../clientUtils/Util"
import { isPresent } from "../../clientUtils/isPresent"
import { action, computed, observable } from "mobx"

interface Entity {
    entityName: EntityName
    entityId?: EntityId
    entityCode?: EntityCode
}

export class SelectionArray {
    constructor(
        selectedEntityNames: EntityName[] = [],
        availableEntities: Entity[] = [],
        entityType = "country"
    ) {
        this.selectedEntityNames = selectedEntityNames.slice()
        this.availableEntities = availableEntities.slice()
        this.entityType = entityType
    }

    @observable entityType: string
    @observable selectedEntityNames: EntityName[]
    @observable private availableEntities: Entity[]

    @computed get availableEntityNames() {
        return this.availableEntities.map((entity) => entity.entityName)
    }

    @computed get availableEntityNameSet() {
        return new Set(this.availableEntityNames)
    }

    private mapBy(col: keyof Entity, val: keyof Entity) {
        return mapBy(
            this.availableEntities,
            (v) => v[col],
            (v) => v[val]
        )
    }

    @computed get entityNameToIdMap() {
        return this.mapBy(OwidTableSlugs.entityName, OwidTableSlugs.entityId)
    }

    @computed get entityCodeToNameMap() {
        return this.mapBy(OwidTableSlugs.entityCode, OwidTableSlugs.entityName)
    }

    @computed get entityIdToNameMap() {
        return this.mapBy(OwidTableSlugs.entityId, OwidTableSlugs.entityName)
    }

    @computed get hasSelection() {
        return this.selectedEntityNames.length > 0
    }

    @computed get unselectedEntityNames() {
        return difference(this.availableEntityNames, this.selectedEntityNames)
    }

    @computed get numSelectedEntities() {
        return this.selectedEntityNames.length
    }

    @computed get selectedSet() {
        return new Set<EntityName>(this.selectedEntityNames)
    }

    @computed get allSelectedEntityIds(): EntityId[] {
        const map = this.entityNameToIdMap
        return this.selectedEntityNames
            .map((name) => map.get(name))
            .filter(isPresent)
    }

    // Clears and sets selected entities
    @action.bound setSelectedEntities(entityNames: EntityName[]) {
        this.clearSelection()
        return this.addToSelection(entityNames)
    }

    @action.bound addToSelection(entityNames: EntityName[]) {
        this.selectedEntityNames = this.selectedEntityNames.concat(entityNames)
        return this
    }

    @action.bound addAvailableEntityNames(entities: Entity[]) {
        this.availableEntities.push(...entities)
        return this
    }

    @action.bound setSelectedEntitiesByCode(entityCodes: EntityCode[]) {
        const map = this.entityCodeToNameMap
        const codesInData = entityCodes.filter((code) => map.has(code))
        return this.setSelectedEntities(
            codesInData.map((code) => map.get(code)!)
        )
    }

    @action.bound setSelectedEntitiesByEntityId(entityIds: EntityId[]) {
        const map = this.entityIdToNameMap
        return this.setSelectedEntities(entityIds.map((id) => map.get(id)!))
    }

    @action.bound selectAll() {
        return this.addToSelection(this.unselectedEntityNames)
    }

    @action.bound clearSelection() {
        this.selectedEntityNames = []
    }

    @action.bound toggleSelection(entityName: EntityName) {
        return this.selectedSet.has(entityName)
            ? this.deselectEntity(entityName)
            : this.selectEntity(entityName)
    }

    @computed get numAvailableEntityNames() {
        return this.availableEntityNames.length
    }

    @action.bound selectEntity(entityName: EntityName) {
        if (!this.selectedSet.has(entityName))
            return this.addToSelection([entityName])
        return this
    }
{'fraction_non_alphanumeric': 0.10268076733890802, 'fraction_numerical': 0.00024594195769798326, 'mean_word_length': 2.9007194244604317, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4920261', 'n_tokens_mistral': 2263, 'n_tokens_neox': 2251, 'n_words': 433}