import {GenderTable} from "../database/genders";

export interface Pronouns {
    id: string
    defaultGenderMarker: GenderMarker
    name: string
    displayOrder: number
    usePlural: boolean
    subjective: string
    adjective: string
    possessive: string
    reflexive: string
    objective: string
}

export interface GenderMarker {
    emoji: string
    defaultGenderName: string
    displayOrder: number
    defaultPronouns: Pronouns|null
}

export class GenderManager {
    private cachedPronouns: {[key: string]: Pronouns} = {}
    private cachedGenderMarkers: {[key: string]: GenderMarker} = {}
    private cachedPronounList: Pronouns[] = []
    private cachedMarkerList: GenderMarker[] = []
    private refreshPromise: Promise<void>|null = null

    private readonly _table: GenderTable

    constructor({genders}: { genders: GenderTable }) {
        this._table = genders
    }

    async refreshCache(): Promise<void> {
        if (this.refreshPromise !== null) {
            await this.refreshPromise
            return
        }

        this.refreshPromise = (async () => {
            const {pronouns, markers} = await this._table.getPronounsAndMarkers()

            const pronounSet = new Set<string>()
            const markerSet = new Set<string>()
            this.cachedPronounList = []
            this.cachedMarkerList = []

            for (const pronounData of pronouns) {
                const pronoun: Pronouns = this.cachedPronouns[pronounData.id] ?? {}
                pronoun.id = pronounData.id
                pronoun.name = pronounData.name
                pronoun.displayOrder = pronounData.display_order
                pronoun.subjective = pronounData.subjective
                pronoun.adjective = pronounData.adjective
                pronoun.possessive = pronounData.possessive
                pronoun.reflexive = pronounData.reflexive
                pronoun.objective = pronounData.objective
                this.cachedPronouns[pronoun.id] = pronoun
                pronounSet.add(pronoun.id)
                this.cachedPronounList.push(pronoun)
            }

            for (const key of Object.keys(this.cachedPronouns)) {
                if (!pronounSet.has(key)) {
                    delete this.cachedPronouns[key]
                }
            }

            for (const markerData of markers) {
                const marker: GenderMarker = this.cachedGenderMarkers[markerData.emoji] ?? {}
                marker.emoji = markerData.emoji
                marker.displayOrder = markerData.display_order
                marker.defaultGenderName = markerData.default_name
                this.cachedGenderMarkers[marker.emoji] = marker
                markerSet.add(marker.emoji)
                this.cachedMarkerList.push(marker)
            }

            for (const key of Object.keys(this.cachedGenderMarkers)) {
                if (!markerSet.has(key)) {
                    delete this.cachedGenderMarkers[key]
                }
            }

            this.cachedPronounList.sort((a, b) => a.displayOrder - b.displayOrder)
            this.cachedMarkerList.sort((a, b) => a.displayOrder - b.displayOrder)

            for (const pronounData of pronouns) {
                if (!Object.hasOwn(this.cachedPronouns, pronounData.id)
                    || !Object.hasOwn(this.cachedGenderMarkers, pronounData.default_marker)) {
                    throw Error("invalid marker: can't find " + pronounData.id + " and " + pronounData.default_marker)
                }
                this.cachedPronouns[pronounData.id].defaultGenderMarker = this.cachedGenderMarkers[pronounData.default_marker]
            }

            for (const markerData of markers) {
                this.cachedGenderMarkers[markerData.emoji].defaultPronouns =
                    this.cachedPronounList.find((p) => p.defaultGenderMarker.emoji === markerData.emoji) ?? null
            }
        })()

        try {
            await this.refreshPromise
        } finally {
            this.refreshPromise = null
        }
    }

    async getPronouns(): Promise<Pronouns[]> {
        if (this.refreshPromise !== null) {
            await this.refreshPromise
        }
        return this.cachedPronounList
    }

    async getPronounsById(id: string): Promise<Pronouns> {
        if (this.refreshPromise !== null) {
            await this.refreshPromise
        }
        const result = this.cachedPronouns[id] ?? null
        if (result === null) {
            throw Error("No battle type found with id: " + id)
        }
        return result
    }

    async getPronounsByName(name: string): Promise<Pronouns> {
        if (this.refreshPromise !== null) {
            await this.refreshPromise
        }
        const result = this.cachedPronounList.find(t => t.name === name) ?? null
        if (result === null) {
            throw Error("No pronouns found with name: " + name)
        }
        return result
    }

    async getGenderMarkers(): Promise<GenderMarker[]> {
        if (this.refreshPromise !== null) {
            await this.refreshPromise
        }
        return this.cachedMarkerList
    }

    async getGenderMarkerByEmoji(emoji: string): Promise<GenderMarker> {
        if (this.refreshPromise !== null) {
            await this.refreshPromise
        }
        const result = this.cachedGenderMarkers[emoji] ?? null
        if (result === null) {
            throw Error("No marker found with emoji: " + emoji)
        }
        return result
    }

    async getDefaultGenderMarkerForGender(gender: string): Promise<GenderMarker> {
        if (this.refreshPromise !== null) {
            await this.refreshPromise
        }
        const result = this.cachedMarkerList.find(t => t.defaultGenderName === gender) ?? this.cachedMarkerList[0] ?? null
        if (result === null) {
            throw Error("No markers found")
        }
        return result
    }
}