class AspectRatioSliderController {
    constructor(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod) {
        //References
        this.widthSlider = new SliderComponentController(widthSlider);
        this.heightSlider = new SliderComponentController(heightSlider);
        this.ratioSource = new DropdownComponentController(ratioSource);
        this.roundingSource = new CheckboxComponentController(roundingSource);
        this.roundingMethod = new RadioComponentController(roundingMethod);
        this.roundingIndicatorBadge = document.createElement("div");
        // Badge implementation
        this.roundingIndicatorBadge.innerText = "📐";
        this.roundingIndicatorBadge.classList.add("rounding-badge");
        this.ratioSource.element.appendChild(this.roundingIndicatorBadge);
        // Check initial value of ratioSource to set badge visbility
        let initialRatio = this.ratioSource.getVal();
        if (!initialRatio.includes(":")) {
            this.roundingIndicatorBadge.style.display = "none";
        }
        //Adjust badge icon if rounding is on
        if (this.roundingSource.getVal()) {
            this.roundingIndicatorBadge.classList.add("active");
            this.roundingIndicatorBadge.innerText = "⚠️";
        }
        //Make badge clickable to toggle setting
        this.roundingIndicatorBadge.addEventListener("click", () => {
            this.roundingSource.setVal(!this.roundingSource.getVal());
        });
        //Make rounding setting toggle badge text and style if setting changes
        this.roundingSource.child.addEventListener("change", () => {
            if (this.roundingSource.getVal()) {
                this.roundingIndicatorBadge.classList.add("active");
                this.roundingIndicatorBadge.innerText = "⚠️";
            }
            else {
                this.roundingIndicatorBadge.classList.remove("active");
                this.roundingIndicatorBadge.innerText = "📐";
            }
            this.adjustStepSize();
        });
        //Other event listeners
        this.widthSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); });
        this.widthSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("width"); });
        this.heightSlider.childRangeField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); });
        this.heightSlider.childNumField.addEventListener("change", (e) => { e.preventDefault(); this.resize("height"); });
        this.ratioSource.childSelector.addEventListener("change", (e) => {
            e.preventDefault();
            //Check and toggle display of badge conditionally on dropdown selection
            if (!this.ratioSource.getVal().includes(":")) {
                this.roundingIndicatorBadge.style.display = 'none';
            }
            else {
                this.roundingIndicatorBadge.style.display = 'block';
            }
            this.adjustStepSize();
        });
    }
    resize(dimension) {
        //For moving slider or number field
        let val = this.ratioSource.getVal();
        if (!val.includes(":")) {
            return;
        }
        let [width, height] = val.split(":").map(Number);
        let ratio = width / height;
        if (dimension == 'width') {
            let newHeight = parseInt(this.widthSlider.getVal()) / ratio;
            if (this.roundingSource.getVal()) {
                switch (this.roundingMethod.getVal()) {
                    case 'Round':
                        newHeight = Math.round(newHeight / 8) * 8;
                        break;
                    case 'Ceiling':
                        newHeight = Math.ceil(newHeight / 8) * 8;
                        break;
                    case 'Floor':
                        newHeight = Math.floor(newHeight / 8) * 8;
                        break;
                }
            }
            this.heightSlider.setVal(newHeight.toString());
        }
        else if (dimension == "height") {
            let newWidth = parseInt(this.heightSlider.getVal()) * ratio;
            if (this.roundingSource.getVal()) {
                switch (this.roundingMethod.getVal()) {
                    case 'Round':
                        newWidth = Math.round(newWidth / 8) * 8;
                        break;
                    case 'Ceiling':
                        newWidth = Math.ceil(newWidth / 8) * 8;
                        break;
                    case 'Floor':
                        newWidth = Math.floor(newWidth / 8) * 8;
                        break;
                }
            }
            this.widthSlider.setVal(newWidth.toString());
        }
    }
    adjustStepSize() {
        /* Sets scales/precision/rounding steps;*/
        let val = this.ratioSource.getVal();
        if (!val.includes(":")) {
            //If ratio unlocked
            this.widthSlider.childRangeField.step = "8";
            this.widthSlider.childRangeField.min = "64";
            this.widthSlider.childNumField.step = "8";
            this.widthSlider.childNumField.min = "64";
            this.heightSlider.childRangeField.step = "8";
            this.heightSlider.childRangeField.min = "64";
            this.heightSlider.childNumField.step = "8";
            this.heightSlider.childNumField.min = "64";
            return;
        }
        //Format string and calculate step sizes
        let [width, height] = val.split(":").map(Number);
        let decimalPlaces = (width.toString().split(".")[1] || []).length;
        //keep upto 6 decimal points of precision of ratio
        //euclidean gcd does not support floats, so we scale it up 
        decimalPlaces = decimalPlaces > 6 ? 6 : decimalPlaces;
        let gcd = this.gcd(width * 10 ** decimalPlaces, height * 10 ** decimalPlaces) / 10 ** decimalPlaces;
        let stepSize = 8 * height / gcd;
        let stepSizeOther = 8 * width / gcd;
        if (this.roundingSource.getVal()) {
            //If rounding is on set/keep default stepsizes
            this.widthSlider.childRangeField.step = "8";
            this.widthSlider.childRangeField.min = "64";
            this.widthSlider.childNumField.step = "8";
            this.widthSlider.childNumField.min = "64";
            this.heightSlider.childRangeField.step = "8";
            this.heightSlider.childRangeField.min = "64";
            this.heightSlider.childNumField.step = "8";
            this.heightSlider.childNumField.min = "64";
        }
        else {
            //if rounding is off, set step sizes so they enforce snapping
            //min is changed, because it offsets snap positions
            this.widthSlider.childRangeField.step = stepSizeOther.toString();
            this.widthSlider.childRangeField.min = stepSizeOther.toString();
            this.widthSlider.childNumField.step = stepSizeOther.toString();
            this.widthSlider.childNumField.min = stepSizeOther.toString();
            this.heightSlider.childRangeField.step = stepSize.toString();
            this.heightSlider.childRangeField.min = stepSize.toString();
            this.heightSlider.childNumField.step = stepSize.toString();
            this.heightSlider.childNumField.min = stepSize.toString();
        }
        let currentWidth = parseInt(this.widthSlider.getVal());
        //Rounding treated kinda like pythons divmod
        let stepsTaken = Math.round(currentWidth / stepSizeOther);
        //this snaps it to closest rule matches (rules being html step points, and ratio)
        let newWidth = stepsTaken * stepSizeOther;
        this.widthSlider.setVal(newWidth.toString());
        this.heightSlider.setVal(Math.round(newWidth / (width / height)).toString());
    }
    gcd(a, b) {
        //euclidean gcd
        if (b === 0) {
            return a;
        }
        return this.gcd(b, a % b);
    }
    static observeStartup(widthSliderId, heightSliderId, ratioSourceId, roundingSourceId, roundingMethodId) {
        let observer = new MutationObserver(() => {
            let widthSlider = document.querySelector("gradio-app").shadowRoot.getElementById(widthSliderId);
            let heightSlider = document.querySelector("gradio-app").shadowRoot.getElementById(heightSliderId);
            let ratioSource = document.querySelector("gradio-app").shadowRoot.getElementById(ratioSourceId);
            let roundingSource = document.querySelector("gradio-app").shadowRoot.getElementById(roundingSourceId);
            let roundingMethod = document.querySelector("gradio-app").shadowRoot.getElementById(roundingMethodId);
            if (widthSlider && heightSlider && ratioSource && roundingSource && roundingMethod) {
                observer.disconnect();
                new AspectRatioSliderController(widthSlider, heightSlider, ratioSource, roundingSource, roundingMethod);
            }
        });
        observer.observe(gradioApp(), { childList: true, subtree: true });
    }
}
document.addEventListener("DOMContentLoaded", () => {
    //Register mutation observer for self start-up;
    AspectRatioSliderController.observeStartup("txt2img_width", "txt2img_height", "txt2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method");
    AspectRatioSliderController.observeStartup("img2img_width", "img2img_height", "img2img_ratio", "setting_aspect_ratios_rounding", "setting_aspect_ratios_rounding_method");
});
