<script setup lang="ts">
import { useElementSize } from '@vueuse/core'
import * as d3 from "d3";
import type { PlotMargin } from "../types/plot";
import type { StructureOperonGene, StructureOperonGeneWithCoordinate, StructureOperonGeneWithImg } from "../types/structure"
import { useStructuresBasket } from '~/stores/structuresBasket';


interface Props {
    genes: StructureOperonGeneWithImg[] | null
}
const structureBasket = useStructuresBasket()

const props = withDefaults(defineProps<Props>(), {
    genes: null
});
const height = ref<number>(200)
const svgRef = ref<SVGElement | null>(null)
const margin = ref<PlotMargin>({
    marginTop: 50,
    marginRight: 7,
    marginBottom: 1,
    marginLeft: 7,
})

const snackbar = ref(false)
const color = d3.scaleOrdinal(d3.schemeCategory10);
const plotHeight = computed(() => {
    const { marginTop, marginBottom } = toValue(margin)
    return toValue(height) + marginTop + marginBottom
})

const domain = computed(() => {
    const genes = toValue(computedGenes)
    return genes?.map(d => { return d.gene })
})

const innerPadding = ref<number>(5)

const totalGeneLength = computed(() => {
    const genes = toValue(computedGenes)
    return genes.reduce((acc, curr) => {
        return acc + (curr?.size ?? 10) + innerPadding.value
    }, 0)
})


const domainGenes = computed(() => {
    return [0, totalGeneLength.value]
})
const xScale = computed(() => {
    return d3.scaleBand()
        .paddingInner(0)
        .domain(toValue(domain))
        .range([0, computedPlotWidth.value])
})


const xScaleGenes = computed(() => {
    return d3.scaleLinear()
        .domain(toValue(domainGenes))
        .range([0, computedPlotWidth.value])
})

const yScale = ref(d3.scaleBand()
    .domain(['img', 'buff', 'buff2', 'gene'])
    .range([toValue(margin).marginTop, toValue(height)]));
const gbContainer = ref(null)
const computedContainerWidth = computed(() => {
    return useElementSize(gbContainer, { width: 500, height: 0 }, { box: 'border-box' }).width.value
})
const computedPlotWidth = computed(() => {
    const { marginLeft, marginRight } = toValue(margin)
    return computedContainerWidth.value - marginLeft - marginRight
})



const computedGenes = computed<StructureOperonGene[]>(() => {
    const genes = toValue(props.genes)
    if (genes !== null) {
        let currentSumSize = 0
        return genes.map(d => {
            const size = d?.size ?? 10
            const position = currentSumSize
            currentSumSize = position + size + innerPadding.value
            return {
                ...d,
                size,
                position
            }
        })
    }
    else { return [] }
})


const structureVersion = computed(() => {
    const genesVal = toValue(computedGenes)
    if (genesVal?.length > 0) {
        return genesVal[0].version
    }
    else {
        return undefined
    }
})


const genesWithCoord = computed<StructureOperonGeneWithCoordinate[]>(() => {
    const genes = toValue(computedGenes)
    const xScaleVal = toValue(xScaleGenes)
    const yScaleVal = toValue(yScale)
    if (genes !== null) {
        return genes.map(d => {
            return {
                ...d,
                width: xScaleVal(d.size),
                x: xScaleVal(d.position),
                y: yScaleVal('gene'),
                height: yScaleVal.bandwidth()
            }
        })
    }
    else { return [] }
})
onMounted(() => {
    draw()
})

watch(genesWithCoord, () => {
    draw()
})

function createOrSelect(container: d3.Selection<SVGElement, any, HTMLElement | null, any>, tag: string, selectionClass: string) {
    let selection = container.select(`${tag}.${selectionClass}`)
    if (selection.empty()) {
        selection = container.append(tag).classed(selectionClass, true)
    }
    return selection
}

function draw() {

    if (svgRef.value !== null) {
        const svg = d3.select<SVGElement, undefined>(svgRef.value);
        const { marginLeft, marginTop } = toValue(margin)
        const xAxis = d3.axisTop(xScale.value)
        const gx = createOrSelect(svg, 'g', 'x-axis')
        gx
            .attr("transform", `translate(${marginLeft},${marginTop})`)
            .call(xAxis)

        gx.call(g => g.select(".domain")
            .remove())
            .selectAll("text")
            .attr("transform", 'rotate(20)')
            .attr("text-anchor", "start")

        let gGenes = createOrSelect(svg, "g", "genes")
        gGenes
            .attr("transform", `translate(${marginLeft},0)`)
            .call(drawGenes, xScale, yScale)
    }
}

function drawGenes(genesGroup: d3.Selection<SVGElement, any, SVGElement, any>) {
    const data = toValue(genesWithCoord)
    const genesSelection = genesGroup
        .selectAll("g.operon-item") // get all "existing" lines in svg
        .data<StructureOperonGeneWithCoordinate>(data) // sync them with our data
        .join(
            enter => {
                const gOperonItem = enter.append("g")
                    .classed("operon-item", true);

                // gene grp
                const gGene = gOperonItem.append("g")
                    .classed("gene-grp", true)
                gGene
                    .append("path")
                    .classed("gene", true)

                // img group
                gOperonItem
                    .append("g").classed("img", true)
                    .append("image")
                    .on("mouseover", function (event) {
                        const target = d3.select(event.srcElement.parentElement)
                        target
                            // .attr("stroke-width", 4)
                            // .attr("stroke", "darkred")
                            .attr("cursor", "pointer")
                    })
                    .on("mouseout", function (event) {
                        const target = d3.select(event.srcElement.parentElement)
                        target
                            // .attr("stroke-width", 0)
                            // .attr("stroke", null)
                            .attr("cursor", "unset")
                    })


                gOperonItem.append("text")
                    // .attr("fill", "white")
                    .classed("gene-label", true)
                    .attr("fill", "currentColor")
                    .attr("dominant-baseline", "middle")


                gOperonItem.append("line")
                gOperonItem.append("title")
                return gOperonItem
            },
            update => update,
            exit => exit.remove()
        )
    genesSelection.select("g.gene-grp").attr("transform", d => `translate(${d.x}, 0)`)
    genesSelection.select("g.img")
        .attr("transform", d => `translate(${xScale.value(d.gene)})`)
        .select("image")
        .attr("transform", d => `translate(0, ${toValue(yScale)("img")})`)
        .attr("href", d => d?.structImg ?? null)
        .attr("width", toValue(xScale).bandwidth())
        .attr("height", toValue(yScale).step() * 3)
        .attr("preserveAspectRatio", "xMidYMid meet")
        .on("click", function (event) {
            const data = d3.select<SVGElement, StructureOperonGeneWithCoordinate>(this).data()
            structureBasket.set(data.map(s => s?.structPath ?? ''))

        })


    genesSelection.select("g.gene-grp").select("path.gene")
        .attr("transform", d => `translate(0, ${d.y})`)
        .attr("fill", d => color(d.system))
        .attr("d", d => drawGene(d).toString())

    genesSelection.select("line")
        //  x1="0" y1="80" x2="100" y2="20" stroke="black"
        .attr("x1", d => xScale.value(d.gene) + xScale.value.bandwidth() / 2)
        .attr("y1", d => yScale.value("buff2") + yScale.value.bandwidth() / 3)
        .attr("x2", d => xScaleGenes.value(d.position) + xScaleGenes.value(d.size / 2))
        .attr("y2", d => yScale.value("gene") - 2)
        .attr("stroke", "currentColor")

}

function drawGene({ width, height }) {
    const context = d3.path()
    context.moveTo(0, 0)
    context.lineTo(width, 0)
    context.lineTo(width, height)
    context.lineTo(0, height)
    context.closePath()

    return context
}

</script>
<template>
    <div ref="gbContainer">
        <v-card flat color="transparent">
            <v-card-item>
                <v-card-title>Operon structure <v-btn size="x-small" density="comfortable" variant="tonal"
                        icon="mdi-help" @click="snackbar = true">
                    </v-btn></v-card-title>
                <v-card-subtitle>defenseFinder model version : {{ structureVersion }}</v-card-subtitle>
            </v-card-item>

            <svg ref="svgRef" :width="computedContainerWidth" :height="plotHeight">
                <g class="x-axis" />
            </svg>
            <v-snackbar v-model="snackbar">
                Click on the structure image to
                visualize it.
                <template v-slot:actions>
                    <v-btn color="info" variant="text" @click="snackbar = false">
                        Close
                    </v-btn>
                </template>
            </v-snackbar>
        </v-card>
    </div>
</template>