var editorControlsLeft = document.getElementById("image-editor-controls-left")

const IMAGE_EDITOR_MAX_SIZE = 800

const IMAGE_EDITOR_BUTTONS = [
    {
        name: "Cancel",
        icon: "fa-regular fa-circle-xmark",
        handler: (editor) => {
            editor.hide()
        },
    },
    {
        name: "Save",
        icon: "fa-solid fa-floppy-disk",
        handler: (editor) => {
            editor.saveImage()
        },
    },
]

const defaultToolBegin = (editor, ctx, x, y, is_overlay = false) => {
    ctx.beginPath()
    ctx.moveTo(x, y)
}
const defaultToolMove = (editor, ctx, x, y, is_overlay = false) => {
    ctx.lineTo(x, y)
    if (is_overlay) {
        ctx.clearRect(0, 0, editor.width, editor.height)
        ctx.stroke()
    }
}
const defaultToolEnd = (editor, ctx, x, y, is_overlay = false) => {
    ctx.stroke()
    if (is_overlay) {
        ctx.clearRect(0, 0, editor.width, editor.height)
    }
}
const toolDoNothing = (editor, ctx, x, y, is_overlay = false) => {}

const IMAGE_EDITOR_TOOLS = [
    {
        id: "draw",
        name: "Draw",
        icon: "fa-solid fa-pencil",
        cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer",
        begin: defaultToolBegin,
        move: defaultToolMove,
        end: defaultToolEnd,
        hotkey: "d",
    },
    {
        id: "erase",
        name: "Erase",
        icon: "fa-solid fa-eraser",
        cursor: "url(/media/images/fa-eraser.svg) 0 14, pointer",
        begin: defaultToolBegin,
        move: (editor, ctx, x, y, is_overlay = false) => {
            ctx.lineTo(x, y)
            if (is_overlay) {
                ctx.clearRect(0, 0, editor.width, editor.height)
                ctx.globalCompositeOperation = "source-over"
                ctx.globalAlpha = 1
                ctx.filter = "none"
                ctx.drawImage(editor.canvas_current, 0, 0)
                editor.setBrush(editor.layers.overlay)
                ctx.stroke()
                editor.canvas_current.style.opacity = 0
            }
        },
        end: (editor, ctx, x, y, is_overlay = false) => {
            ctx.stroke()
            if (is_overlay) {
                ctx.clearRect(0, 0, editor.width, editor.height)
                editor.canvas_current.style.opacity = ""
            }
        },
        setBrush: (editor, layer) => {
            layer.ctx.globalCompositeOperation = "destination-out"
        },
        hotkey: "e",
    },
    {
        id: "fill",
        name: "Fill",
        icon: "fa-solid fa-fill",
        cursor: "url(/media/images/fa-fill.svg) 20 6, pointer",
        begin: (editor, ctx, x, y, is_overlay = false) => {
            if (!is_overlay) {
                var color = hexToRgb(ctx.fillStyle)
                color.a = parseInt(ctx.globalAlpha * 255) // layer.ctx.globalAlpha
                flood_fill(editor, ctx, parseInt(x), parseInt(y), color)
            }
        },
        move: toolDoNothing,
        end: toolDoNothing,
        hotkey: "f",
    },
    {
        id: "colorpicker",
        name: "Picker",
        icon: "fa-solid fa-eye-dropper",
        cursor: "url(/media/images/fa-eye-dropper.svg) 0 24, pointer",
        begin: (editor, ctx, x, y, is_overlay = false) => {
            if (!is_overlay) {
                var img_rgb = editor.layers.background.ctx.getImageData(x, y, 1, 1).data
                var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data
                var drawn_opacity = drawn_rgb[3] / 255
                editor.custom_color_input.value = rgbToHex({
                    r: drawn_rgb[0] * drawn_opacity + img_rgb[0] * (1 - drawn_opacity),
                    g: drawn_rgb[1] * drawn_opacity + img_rgb[1] * (1 - drawn_opacity),
                    b: drawn_rgb[2] * drawn_opacity + img_rgb[2] * (1 - drawn_opacity),
                })
                editor.custom_color_input.dispatchEvent(new Event("change"))
            }
        },
        move: toolDoNothing,
        end: toolDoNothing,
        hotkey: "p",
    },
]

const IMAGE_EDITOR_ACTIONS = [
    {
        id: "load_mask",
        name: "Load mask from file",
        className: "load_mask",
        icon: "fa-regular fa-folder-open",
        handler: (editor) => {
            let el = document.createElement("input")
            el.setAttribute("type", "file")
            el.addEventListener("change", function() {
                if (this.files.length === 0) {
                    return
                }

                let reader = new FileReader()
                let file = this.files[0]

                reader.addEventListener("load", function(event) {
                    let maskData = reader.result

                    editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height)
                    var image = new Image()
                    image.onload = () => {
                        editor.layers.drawing.ctx.drawImage(image, 0, 0, editor.width, editor.height)
                    }
                    image.src = maskData
                })

                if (file) {
                    reader.readAsDataURL(file)
                }
            })

            el.click()
        },
        trackHistory: true,
    },
    {
        id: "fill_all",
        name: "Fill all",
        icon: "fa-solid fa-paint-roller",
        handler: (editor) => {
            editor.ctx_current.globalCompositeOperation = "source-over"
            editor.ctx_current.rect(0, 0, editor.width, editor.height)
            editor.ctx_current.fill()
            editor.setBrush()
        },
        trackHistory: true,
    },
    {
        id: "clear",
        name: "Clear",
        icon: "fa-solid fa-xmark",
        handler: (editor) => {
            editor.ctx_current.clearRect(0, 0, editor.width, editor.height)
            imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas
        },
        trackHistory: true,
    },
    {
        id: "undo",
        name: "Undo",
        icon: "fa-solid fa-rotate-left",
        handler: (editor) => {
            editor.history.undo()
        },
        trackHistory: false,
    },
    {
        id: "redo",
        name: "Redo",
        icon: "fa-solid fa-rotate-right",
        handler: (editor) => {
            editor.history.redo()
        },
        trackHistory: false,
    },
]

var IMAGE_EDITOR_SECTIONS = [
    {
        name: "tool",
        title: "Tool",
        default: "draw",
        options: Array.from(IMAGE_EDITOR_TOOLS.map((t) => t.id)),
        initElement: (element, option) => {
            var tool_info = IMAGE_EDITOR_TOOLS.find((t) => t.id == option)
            element.className = "image-editor-button button"
            var sub_element = document.createElement("div")
            var icon = document.createElement("i")
            tool_info.icon.split(" ").forEach((c) => icon.classList.add(c))
            sub_element.appendChild(icon)
            var label_element = document.createElement("div")
            label_element.classList.add("image-editor-button-label")
            label_element.textContent=tool_info.name
            sub_element.appendChild(label_element)
            element.appendChild(sub_element)
        },
    },
    {
        name: "color",
        title: "Color",
        default: "#f1c232",
        options: [
            "custom",
            "#ea9999",
            "#e06666",
            "#cc0000",
            "#990000",
            "#660000",
            "#f9cb9c",
            "#f6b26b",
            "#e69138",
            "#b45f06",
            "#783f04",
            "#ffe599",
            "#ffd966",
            "#f1c232",
            "#bf9000",
            "#7f6000",
            "#b6d7a8",
            "#93c47d",
            "#6aa84f",
            "#38761d",
            "#274e13",
            "#a4c2f4",
            "#6d9eeb",
            "#3c78d8",
            "#1155cc",
            "#1c4587",
            "#b4a7d6",
            "#8e7cc3",
            "#674ea7",
            "#351c75",
            "#20124d",
            "#d5a6bd",
            "#c27ba0",
            "#a64d79",
            "#741b47",
            "#4c1130",
            "#ffffff",
            "#c0c0c0",
            "#838383",
            "#525252",
            "#000000",
        ],
        initElement: (element, option) => {
            if (option == "custom") {
                var input = document.createElement("input")
                input.type = "color"
                element.appendChild(input)
                var span = document.createElement("span")
                span.textContent = "Custom"
                span.onclick = function(e) {
                    input.click()
                }
                element.appendChild(span)
            } else {
                element.style.background = option
            }
        },
        getCustom: (editor) => {
            var input = editor.popup.querySelector(".image_editor_color input")
            return input.value
        },
    },
    {
        name: "brush_size",
        title: "Brush Size",
        default: 48,
        options: [6, 12, 16, 24, 30, 40, 48, 64],
        initElement: (element, option) => {
            element.parentElement.style.flex = option
            element.style.width = option + "px"
            element.style.height = option + "px"
            element.style["margin-right"] = "2px"
            element.style["border-radius"] = (option / 2).toFixed() + "px"
        },
    },
    {
        name: "opacity",
        title: "Opacity",
        default: 0,
        options: [0, 0.2, 0.4, 0.6, 0.8],
        initElement: (element, option) => {
            element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px`
        },
    },
    {
        name: "sharpness",
        title: "Sharpness",
        default: 0,
        options: [0, 0.05, 0.1, 0.2, 0.3],
        initElement: (element, option) => {
            var size = 32
            var blur_amount = parseInt(option * size)
            var sub_element = document.createElement("div")
            sub_element.style.background = `var(--background-color3)`
            sub_element.style.filter = `blur(${blur_amount}px)`
            sub_element.style.width = `${size - 2}px`
            sub_element.style.height = `${size - 2}px`
            sub_element.style["border-radius"] = `${size}px`
            element.style.background = "none"
            element.appendChild(sub_element)
        },
    },
]

class EditorHistory {
    constructor(editor) {
        this.editor = editor
        this.events = [] // stack of all events (actions/edits)
        this.current_edit = null
        this.rewind_index = 0 // how many events back into the history we've rewound to. (current state is just after event at index 'length - this.rewind_index - 1')
    }
    push(event) {
        // probably add something here eventually to save state every x events
        if (this.rewind_index != 0) {
            this.events = this.events.slice(0, 0 - this.rewind_index)
            this.rewind_index = 0
        }
        var snapshot_frequency = 20 // (every x edits, take a snapshot of the current drawing state, for faster rewinding)
        if (this.events.length > 0 && this.events.length % snapshot_frequency == 0) {
            event.snapshot = this.editor.layers.drawing.ctx.getImageData(0, 0, this.editor.width, this.editor.height)
        }
        this.events.push(event)
    }
    pushAction(action) {
        this.push({
            type: "action",
            id: action,
        })
    }
    editBegin(x, y) {
        this.current_edit = {
            type: "edit",
            id: this.editor.getOptionValue("tool"),
            options: Object.assign({}, this.editor.options),
            points: [{ x: x, y: y }],
        }
    }
    editMove(x, y) {
        if (this.current_edit) {
            this.current_edit.points.push({ x: x, y: y })
        }
    }
    editEnd(x, y) {
        if (this.current_edit) {
            this.push(this.current_edit)
            this.current_edit = null
        }
    }
    clear() {
        this.events = []
    }
    undo() {
        this.rewindTo(this.rewind_index + 1)
    }
    redo() {
        this.rewindTo(this.rewind_index - 1)
    }
    rewindTo(new_rewind_index) {
        if (new_rewind_index < 0 || new_rewind_index > this.events.length) {
            return // do nothing if target index is out of bounds
        }

        var ctx = this.editor.layers.drawing.ctx
        ctx.clearRect(0, 0, this.editor.width, this.editor.height)

        var target_index = this.events.length - 1 - new_rewind_index
        var snapshot_index = target_index
        while (snapshot_index > -1) {
            if (this.events[snapshot_index].snapshot) {
                break
            }
            snapshot_index--
        }

        if (snapshot_index != -1) {
            ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0)
        }

        for (var i = snapshot_index + 1; i <= target_index; i++) {
            var event = this.events[i]
            if (event.type == "action") {
                var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == event.id)
                action.handler(this.editor)
            } else if (event.type == "edit") {
                var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == event.id)
                this.editor.setBrush(this.editor.layers.drawing, event.options)

                var first_point = event.points[0]
                tool.begin(this.editor, ctx, first_point.x, first_point.y)
                for (var point_i = 1; point_i < event.points.length; point_i++) {
                    tool.move(this.editor, ctx, event.points[point_i].x, event.points[point_i].y)
                }
                var last_point = event.points[event.points.length - 1]
                tool.end(this.editor, ctx, last_point.x, last_point.y)
            }
        }

        // re-set brush to current settings
        this.editor.setBrush(this.editor.layers.drawing)

        this.rewind_index = new_rewind_index
    }
}

class ImageEditor {
    constructor(popup, inpainter = false) {
        this.inpainter = inpainter
        this.popup = popup
        this.history = new EditorHistory(this)
        if (inpainter) {
            this.popup.classList.add("inpainter")
        }
        this.drawing = false
        this.temp_previous_tool = null // used for the ctrl-colorpicker functionality
        this.container = popup.querySelector(".editor-controls-center > div")
        this.layers = {}
        var layer_names = ["background", "drawing", "overlay"]
        layer_names.forEach((name) => {
            let canvas = document.createElement("canvas")
            canvas.className = `editor-canvas-${name}`
            this.container.appendChild(canvas)
            this.layers[name] = {
                name: name,
                canvas: canvas,
                ctx: canvas.getContext("2d"),
            }
        })

        // add mouse handlers
        this.container.addEventListener("mousedown", this.mouseHandler.bind(this))
        this.container.addEventListener("mouseup", this.mouseHandler.bind(this))
        this.container.addEventListener("mousemove", this.mouseHandler.bind(this))
        this.container.addEventListener("mouseout", this.mouseHandler.bind(this))
        this.container.addEventListener("mouseenter", this.mouseHandler.bind(this))

        this.container.addEventListener("touchstart", this.mouseHandler.bind(this))
        this.container.addEventListener("touchmove", this.mouseHandler.bind(this))
        this.container.addEventListener("touchcancel", this.mouseHandler.bind(this))
        this.container.addEventListener("touchend", this.mouseHandler.bind(this))

        // initialize editor controls
        this.options = {}
        this.optionElements = {}
        IMAGE_EDITOR_SECTIONS.forEach((section) => {
            section.id = `image_editor_${section.name}`
            var sectionElement = document.createElement("div")
            sectionElement.className = section.id

            var title = document.createElement("h4")
            title.innerText = section.title
            sectionElement.appendChild(title)

            var optionsContainer = document.createElement("div")
            optionsContainer.classList.add("editor-options-container")

            this.optionElements[section.name] = []
            section.options.forEach((option, index) => {
                var optionHolder = document.createElement("div")
                var optionElement = document.createElement("div")
                optionHolder.appendChild(optionElement)
                section.initElement(optionElement, option)
                optionElement.addEventListener("click", (target) => this.selectOption(section.name, index))
                optionsContainer.appendChild(optionHolder)
                this.optionElements[section.name].push(optionElement)
            })
            this.selectOption(section.name, section.options.indexOf(section.default))

            sectionElement.appendChild(optionsContainer)

            this.popup.querySelector(".editor-controls-left").appendChild(sectionElement)
        })

        this.custom_color_input = this.popup.querySelector(`input[type="color"]`)
        this.custom_color_input.addEventListener("change", () => {
            this.custom_color_input.parentElement.style.background = this.custom_color_input.value
            this.selectOption("color", 0)
        })

        if (this.inpainter) {
            this.selectOption("color", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "color").options.indexOf("#ffffff"))
            this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "opacity").options.indexOf(0.4))
        }

        // initialize the right-side controls
        var buttonContainer = document.createElement("div")
        IMAGE_EDITOR_BUTTONS.forEach((button) => {
            var element = document.createElement("div")
            var icon = document.createElement("i")
            element.className = "image-editor-button button"
            icon.className = button.icon
            element.appendChild(icon)
            element.append(button.name)
            buttonContainer.appendChild(element)
            element.addEventListener("click", (event) => button.handler(this))
        })
        var actionsContainer = document.createElement("div")
        var actionsTitle = document.createElement("h4")
        actionsTitle.textContent = "Actions"
        actionsContainer.appendChild(actionsTitle)
        IMAGE_EDITOR_ACTIONS.forEach((action) => {
            var element = document.createElement("div")
            var icon = document.createElement("i")
            element.className = "image-editor-button button"
            if (action.className) {
                element.className += " " + action.className
            }
            icon.className = action.icon
            element.appendChild(icon)
            element.append(action.name)
            actionsContainer.appendChild(element)
            element.addEventListener("click", (event) => this.runAction(action.id))
        })
        this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer)
        this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer)

        this.keyHandlerBound = this.keyHandler.bind(this)

        this.setSize(512, 512)
    }
    show() {
        this.popup.classList.add("active")
        document.addEventListener("keydown", this.keyHandlerBound, true)
        document.addEventListener("keyup", this.keyHandlerBound, true)
    }
    hide() {
        this.popup.classList.remove("active")
        document.removeEventListener("keydown", this.keyHandlerBound, true)
        document.removeEventListener("keyup", this.keyHandlerBound, true)
    }
    setSize(width, height) {
        if (width == this.width && height == this.height) {
            return
        }

        if (width > height) {
            var max_size = Math.min(parseInt(window.innerWidth * 0.9), width, 768)
            var multiplier = max_size / width
            width = (multiplier * width).toFixed()
            height = (multiplier * height).toFixed()
        } else {
            var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768)
            var multiplier = max_size / height
            width = (multiplier * width).toFixed()
            height = (multiplier * height).toFixed()
        }
        this.width = parseInt(width)
        this.height = parseInt(height)

        this.container.style.width = width + "px"
        this.container.style.height = height + "px"

        Object.values(this.layers).forEach((layer) => {
            layer.canvas.width = width
            layer.canvas.height = height
        })

        if (this.inpainter) {
            this.saveImage() // We've reset the size of the image so inpainting is different
        }
        this.setBrush()
        this.history.clear()
    }
    get tool() {
        var tool_id = this.getOptionValue("tool")
        return IMAGE_EDITOR_TOOLS.find((t) => t.id == tool_id)
    }
    loadTool() {
        this.drawing = false
        this.container.style.cursor = this.tool.cursor
    }
    setImage(url, width, height) {
        this.setSize(width, height)
        this.layers.background.ctx.clearRect(0, 0, this.width, this.height)
        if (!(url && this.inpainter)) {
            this.layers.drawing.ctx.clearRect(0, 0, this.width, this.height)
        }
        if (url) {
            var image = new Image()
            image.onload = () => {
                this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height)
            }
            image.src = url
        } else {
            this.layers.background.ctx.fillStyle = "#ffffff"
            this.layers.background.ctx.beginPath()
            this.layers.background.ctx.rect(0, 0, this.width, this.height)
            this.layers.background.ctx.fill()
        }
        this.history.clear()
    }
    saveImage() {
        if (!this.inpainter) {
            // This is not an inpainter, so save the image as the new img2img input
            this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height)
            var base64 = this.layers.background.canvas.toDataURL()
            initImagePreview.src = base64 // this will trigger the rest of the app to use it
        } else {
            // This is an inpainter, so make sure the toggle is set accordingly
            var is_blank = !this.layers.drawing.ctx
                .getImageData(0, 0, this.width, this.height)
                .data.some((channel) => channel !== 0)
            maskSetting.checked = !is_blank
            maskSetting.dispatchEvent(new Event("change"))
        }
        this.hide()
    }
    getImg() {
        // a drop-in replacement of the drawingboard version
        return this.layers.drawing.canvas.toDataURL()
    }
    setImg(dataUrl) {
        // a drop-in replacement of the drawingboard version
        var image = new Image()
        image.onload = () => {
            var ctx = this.layers.drawing.ctx
            ctx.clearRect(0, 0, this.width, this.height)
            ctx.globalCompositeOperation = "source-over"
            ctx.globalAlpha = 1
            ctx.filter = "none"
            ctx.drawImage(image, 0, 0, this.width, this.height)
            this.setBrush(this.layers.drawing)
        }
        image.src = dataUrl
    }
    runAction(action_id) {
        var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == action_id)
        if (action.trackHistory) {
            this.history.pushAction(action_id)
        }
        action.handler(this)
    }
    setBrush(layer = null, options = null) {
        if (options == null) {
            options = this.options
        }
        if (layer) {
            layer.ctx.lineCap = "round"
            layer.ctx.lineJoin = "round"
            layer.ctx.lineWidth = options.brush_size
            layer.ctx.fillStyle = options.color
            layer.ctx.strokeStyle = options.color
            var sharpness = parseInt(options.sharpness * options.brush_size)
            layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)`
            layer.ctx.globalAlpha = 1 - options.opacity
            layer.ctx.globalCompositeOperation = "source-over"
            var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == options.tool)
            if (tool && tool.setBrush) {
                tool.setBrush(editor, layer)
            }
        } else {
            Object.values(["drawing", "overlay"])
                .map((name) => this.layers[name])
                .forEach((l) => {
                    this.setBrush(l)
                })
        }
    }
    get ctx_overlay() {
        return this.layers.overlay.ctx
    }
    get ctx_current() {
        // the idea is this will help support having custom layers and editing each one
        return this.layers.drawing.ctx
    }
    get canvas_current() {
        return this.layers.drawing.canvas
    }
    keyHandler(event) {
        // handles keybinds like ctrl+z, ctrl+y
        if (!this.popup.classList.contains("active")) {
            document.removeEventListener("keydown", this.keyHandlerBound)
            document.removeEventListener("keyup", this.keyHandlerBound)
            return // this catches if something else closes the window but doesnt properly unbind the key handler
        }

        // keybindings
        if (event.type == "keydown") {
            if ((event.key == "z" || event.key == "Z") && event.ctrlKey) {
                if (!event.shiftKey) {
                    this.history.undo()
                } else {
                    this.history.redo()
                }
                event.stopPropagation()
                event.preventDefault()
            }
            else if (event.key == "y" && event.ctrlKey) {
                this.history.redo()
                event.stopPropagation()
                event.preventDefault()
            }
            else if (event.key === "Escape") {
                this.hide()
                event.stopPropagation()
                event.preventDefault()
            } else {
                let toolIndex = IMAGE_EDITOR_TOOLS.findIndex( t => t.hotkey ==event.key )
                if (toolIndex != -1) {
                    this.selectOption("tool", toolIndex)
                    event.stopPropagation()
                    event.preventDefault()
                }
            }
        }

        // dropper ctrl holding handler stuff
        var dropper_active = this.temp_previous_tool != null
        if (dropper_active && !event.ctrlKey) {
            this.selectOption(
                "tool",
                IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == this.temp_previous_tool)
            )
            this.temp_previous_tool = null
        } else if (!dropper_active && event.ctrlKey) {
            this.temp_previous_tool = this.getOptionValue("tool")
            this.selectOption(
                "tool",
                IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == "colorpicker")
            )
        }
    }
    mouseHandler(event) {
        var bbox = this.layers.overlay.canvas.getBoundingClientRect()
        var x = (event.clientX || 0) - bbox.left
        var y = (event.clientY || 0) - bbox.top
        var type = event.type
        var touchmap = {
            touchstart: "mousedown",
            touchmove: "mousemove",
            touchend: "mouseup",
            touchcancel: "mouseup",
        }
        if (type in touchmap) {
            type = touchmap[type]
            if (event.touches && event.touches[0]) {
                var touch = event.touches[0]
                var x = (touch.clientX || 0) - bbox.left
                var y = (touch.clientY || 0) - bbox.top
            }
        }
        event.preventDefault()
        // do drawing-related stuff
        if (type == "mousedown" || (type == "mouseenter" && event.buttons == 1)) {
            this.drawing = true
            this.tool.begin(this, this.ctx_current, x, y)
            this.tool.begin(this, this.ctx_overlay, x, y, true)
            this.history.editBegin(x, y)
        }
        if (type == "mouseup" || type == "mousemove") {
            if (this.drawing) {
                if (x > 0 && y > 0) {
                    this.tool.move(this, this.ctx_current, x, y)
                    this.tool.move(this, this.ctx_overlay, x, y, true)
                    this.history.editMove(x, y)
                }
            }
        }
        if (type == "mouseup" || type == "mouseout") {
            if (this.drawing) {
                this.drawing = false
                this.tool.end(this, this.ctx_current, x, y)
                this.tool.end(this, this.ctx_overlay, x, y, true)
                this.history.editEnd(x, y)
            }
        }
    }
    getOptionValue(section_name) {
        var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name)
        return this.options && section_name in this.options ? this.options[section_name] : section.default
    }
    selectOption(section_name, option_index) {
        var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name)
        var value = section.options[option_index]
        this.options[section_name] = value == "custom" ? section.getCustom(this) : value

        this.optionElements[section_name].forEach((element) => element.classList.remove("active"))
        this.optionElements[section_name][option_index].classList.add("active")

        // change the editor
        this.setBrush()
        if (section.name == "tool") {
            this.loadTool()
        }
    }
}

const imageEditor = new ImageEditor(document.getElementById("image-editor"))
const imageInpainter = new ImageEditor(document.getElementById("image-inpainter"), true)

imageEditor.setImage(null, 512, 512)
imageInpainter.setImage(null, 512, 512)

document.getElementById("init_image_button_draw").addEventListener("click", () => {
    imageEditor.show()
})
document.getElementById("init_image_button_inpaint").addEventListener("click", () => {
    imageInpainter.show()
})

img2imgUnload() // no init image when the app starts

function rgbToHex(rgb) {
    function componentToHex(c) {
        var hex = parseInt(c).toString(16)
        return hex.length == 1 ? "0" + hex : hex
    }
    return "#" + componentToHex(rgb.r) + componentToHex(rgb.g) + componentToHex(rgb.b)
}

function hexToRgb(hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
    return result
        ? {
              r: parseInt(result[1], 16),
              g: parseInt(result[2], 16),
              b: parseInt(result[3], 16),
          }
        : null
}

function pixelCompare(int1, int2) {
    return Math.abs(int1 - int2) < 4
}

// adapted from https://ben.akrin.com/canvas_fill/fill_04.html
// May 2023 - look at using a library instead of custom code: https://github.com/shaneosullivan/example-canvas-fill
function flood_fill(editor, the_canvas_context, x, y, color) {
    pixel_stack = [{ x: x, y: y }]
    pixels = the_canvas_context.getImageData(0, 0, editor.width, editor.height)
    var linear_cords = (y * editor.width + x) * 4
    var original_color = {
        r: pixels.data[linear_cords],
        g: pixels.data[linear_cords + 1],
        b: pixels.data[linear_cords + 2],
        a: pixels.data[linear_cords + 3],
    }

    var opacity = color.a / 255
    var new_color = {
        r: parseInt(color.r * opacity + original_color.r * (1 - opacity)),
        g: parseInt(color.g * opacity + original_color.g * (1 - opacity)),
        b: parseInt(color.b * opacity + original_color.b * (1 - opacity)),
    }

    if (
        pixelCompare(new_color.r, original_color.r) &&
        pixelCompare(new_color.g, original_color.g) &&
        pixelCompare(new_color.b, original_color.b)
    ) {
        return // This color is already the color we want, so do nothing
    }
    var max_stack_size = editor.width * editor.height
    while (pixel_stack.length > 0 && pixel_stack.length < max_stack_size) {
        new_pixel = pixel_stack.shift()
        x = new_pixel.x
        y = new_pixel.y

        linear_cords = (y * editor.width + x) * 4
        while (
            y-- >= 0 &&
            pixelCompare(pixels.data[linear_cords], original_color.r) &&
            pixelCompare(pixels.data[linear_cords + 1], original_color.g) &&
            pixelCompare(pixels.data[linear_cords + 2], original_color.b)
        ) {
            linear_cords -= editor.width * 4
        }
        linear_cords += editor.width * 4
        y++

        var reached_left = false
        var reached_right = false
        while (
            y++ < editor.height &&
            pixelCompare(pixels.data[linear_cords], original_color.r) &&
            pixelCompare(pixels.data[linear_cords + 1], original_color.g) &&
            pixelCompare(pixels.data[linear_cords + 2], original_color.b)
        ) {
            pixels.data[linear_cords] = new_color.r
            pixels.data[linear_cords + 1] = new_color.g
            pixels.data[linear_cords + 2] = new_color.b
            pixels.data[linear_cords + 3] = 255

            if (x > 0) {
                if (
                    pixelCompare(pixels.data[linear_cords - 4], original_color.r) &&
                    pixelCompare(pixels.data[linear_cords - 4 + 1], original_color.g) &&
                    pixelCompare(pixels.data[linear_cords - 4 + 2], original_color.b)
                ) {
                    if (!reached_left) {
                        pixel_stack.push({ x: x - 1, y: y })
                        reached_left = true
                    }
                } else if (reached_left) {
                    reached_left = false
                }
            }

            if (x < editor.width - 1) {
                if (
                    pixelCompare(pixels.data[linear_cords + 4], original_color.r) &&
                    pixelCompare(pixels.data[linear_cords + 4 + 1], original_color.g) &&
                    pixelCompare(pixels.data[linear_cords + 4 + 2], original_color.b)
                ) {
                    if (!reached_right) {
                        pixel_stack.push({ x: x + 1, y: y })
                        reached_right = true
                    }
                } else if (reached_right) {
                    reached_right = false
                }
            }

            linear_cords += editor.width * 4
        }
    }
    the_canvas_context.putImageData(pixels, 0, 0)
}