import { DesugaringStep } from "./Conversion"
import SpecialVisualizations from "../../../UI/SpecialVisualizations"
import { Translatable } from "../Json/Translatable"
import { TagConfigJson } from "../Json/TagConfigJson"
import {
    MappingConfigJson,
    QuestionableTagRenderingConfigJson,
} from "../Json/QuestionableTagRenderingConfigJson"

export default class AddPrefixToTagRenderingConfig extends DesugaringStep<QuestionableTagRenderingConfigJson> {
    private readonly _prefix: string

    constructor(prefix: string) {
        super(
            "AddPrefixToTagRenderingConfig",
            "Adds `prefix` to _all_ keys. Used to add information about a subamenity withing a bigger amenity (e.g. toilets in a restaurant, a sauna in a water park, ...)"
        )
        this._prefix = prefix
    }

    /**
     *
     * const edit = new AddPrefixToTagRenderingConfig("PREFIX")
     * edit.updateString("Some string") // => "Some string"
     * edit.updateString("Some string {key0}") // => "Some string {PREFIX:key0}"
     *
     * // Should prefix a key in a special visualisation
     * new AddPrefixToTagRenderingConfig("PREFIX").updateString("{opening_hours_table(opening_hours)}") // => "{opening_hours_table(PREFIX:opening_hours,,)}"
     *
     * // Should prefix the default key in a special visualisation
     * new AddPrefixToTagRenderingConfig("PREFIX").updateString("{opening_hours_table()}") // => "{opening_hours_table(PREFIX:opening_hours,,)}"
     */
    private updateString(str: string): string {
        const parsed = SpecialVisualizations.constructSpecification(str)
        const fixedSpec: string[] = []
        for (const spec of parsed) {
            if (typeof spec === "string") {
                const part = spec.replace(/{([a-zA-Z0-9:_-]+)}/g, `{${this._prefix}:$1}`)
                fixedSpec.push(part)
            } else {
                const newArgs: string[] = []
                for (let i = 0; i < spec.func.args.length; i++) {
                    const argDoc = spec.func.args[i]
                    const argV = spec.args[i]
                    if (argDoc.type === "key") {
                        newArgs.push(this._prefix + ":" + (argV ?? argDoc.defaultValue ?? ""))
                    } else {
                        newArgs.push(argV ?? "")
                    }
                }
                fixedSpec.push("{" + spec.func.funcName + "(" + newArgs.join(",") + ")}")
            }
        }
        return fixedSpec.join("")
    }

    private updateTranslatable(val: Translatable | undefined): Translatable | undefined {
        if (!val) {
            return val
        }
        if (typeof val === "string") {
            return this.updateString(val)
        }
        const newTranslations: Record<string, string> = {}
        for (const lng in val) {
            newTranslations[lng] = this.updateString(val[lng])
        }
        return newTranslations
    }

    private updateTag(tags: string): string
    private updateTag(tags: TagConfigJson): TagConfigJson
    private updateTag(tags: TagConfigJson): TagConfigJson {
        if (!tags) {
            return tags
        }
        if (tags["and"]) {
            return { and: this.updateTags(tags["and"]) }
        }
        if (tags["or"]) {
            return { or: this.updateTags(tags["or"]) }
        }
        return this._prefix + ":" + tags
    }

    private updateTags(tags: ReadonlyArray<string>): string[] {
        return tags?.map((tag) => this.updateTag(tag))
    }

    private updateMapping(mapping: Readonly<MappingConfigJson>): MappingConfigJson {
        return {
            ...mapping,
            addExtraTags: this.updateTags(mapping.addExtraTags),
            if: this.updateTag(mapping.if),
            then: this.updateTranslatable(mapping.then),
            alsoShowIf: this.updateTag(mapping.alsoShowIf),
            ifnot: this.updateTag(mapping.ifnot),
            priorityIf: this.updateTag(mapping.priorityIf),
            hideInAnswer:
                mapping.hideInAnswer === true || mapping.hideInAnswer === false
                    ? mapping.hideInAnswer
                    : this.updateTag(mapping.hideInAnswer),
        }
    }

    public convert(
        json: Readonly<QuestionableTagRenderingConfigJson>
    ): QuestionableTagRenderingConfigJson {
        let freeform = json.freeform
        if (freeform) {
            const ff = json.freeform
            freeform = {
                ...ff,
                key: this._prefix + ":" + ff.key,
                addExtraTags: this.updateTags(ff.addExtraTags),
            }
        }

        return <QuestionableTagRenderingConfigJson>{
            ...json,
            id: this._prefix.replace(/[^a-zA-Z0-9_]/, "_") + "_" + json.id,

            question: this.updateTranslatable(json.question),
            questionHint: this.updateTranslatable(json.questionHint),

            render: this.updateTranslatable(<Translatable>json.render),
            freeform,
            editButtonAriaLabel: json.editButtonAriaLabel,
            onSoftDelete: this.updateTags(json.onSoftDelete),
            invalidValues: this.updateTag(json.invalidValues),
            mappings: json.mappings?.map((mapping) => this.updateMapping(mapping)),

            condition: this.updateTag(json.condition),
            metacondition: json.metacondition, // no update here
            filter: json.filter === true, // We break references to filters, as those references won't have the updated tags
            _appliedPrefix: this._prefix,
        }
    }
}
