<template>
 <div class="interactive_list_container">
    <!-- <div id="widthMeasure" style="width:100%"></div> -->

    <transition name="fade">
        <li 
            class="table-header" 
            v-if="(!!columns) && (!initing) && !isMobile" 
            :style="rowColumnStyle+';top:' + headerPositionTop +'px;'" 
        >
            <div 
                :class="`header_cell clickable title col-${hind}`"
                v-for="(h, hind) in columns" :key="h"
                @click.stop.prevent="onToggleFilter"
                :style="`padding: 0px ${columnGapPx}px`"
            >
                {{h}}
            </div>

        </li>
    </transition>

     <div class="no_data" style="font-size:14px;font-weight:300;" v-if="rendering > 0">
         {{filterList(displayList).filter(checkSearch).length > 0 ? '列表刷新中' : '暂无数据' }}
     </div>

     <div class="no_data" style="font-size:14px;font-weight:300;" v-if="(rendering <1) && dataList.length == 0">
         正在加载数据...
     </div>

    <li class="table_filters" v-if="(rendering<1) && dataList.length > 0 &&  !isMobile" 
        :style="rowColumnStyle+';top:' + (headerPositionTop + 25) +'px;' 
                + 'height:' + ( showFilters ? 35 : 0) + 'px;'
                + `overflow:${ showFilters ? 'visible' : 'hidden'};`"             
    >
        
        <!-- <img src="/images/filter_clear.png" class="filter_img" alt="" @click="onClickClear"> -->

        <div 
            :class="`header_cell title col-${hind}`"
            v-for="(h, hind) in columns" :key="h"
            :style="`padding: 0px ${columnGapPx}px`"
        >
                <a-input
                    v-if="(!emptyColumns.includes(hind)) && filterCongfigs[hind]=='input'"
                    class="filter_input"
                    style='background-color:white;opacity:1;'
                    @change="e=>onChangeSearch(e, hind)"
                />
                <a-select
                    v-if="(!emptyColumns.includes(hind)) && filterCongfigs[hind]=='select'"
                    @change="e=>onChangeFilter(e, hind)"
                    class="filter_input"
                    mode="tags"
                    :value="filters[hind]"
                    :showSearch="true"
                    :getPopupContainer="triggerNode => triggerNode.parentNode"
                >
                    <a-select-option value="">
                        全选
                    </a-select-option>
                    <a-select-option :value="opt" v-for="(opt, oind) in selectOptions[hind]" :key="oind">
                    {{opt}}
                    </a-select-option>
                </a-select>
                <div :class="{
                    sortButtons: true, 
                    flipped: currSort.col == hind && currSort.desc
                }">
                    <div class="clickable"
                        @click.stop.prevent="changeSort(hind)">
                        ^
                    </div>
                </div>
                &nbsp;&nbsp;
        </div>
    </li>

    <!-- {{displayList}} {{ isMobile}} -->
    <!-- widescreen view -->
    <transition name="fade">
        <div class="_content enter_animated" v-if="(!initing) && !isMobile">
            <li 
                :class="'list-group-item ' + nodeName"
                :style="rowColumnStyle" 
                v-for="(row, ind) in filterList(displayList).filter(checkSearch)" 
                :key="ind"
            >
                <div :class="`display_cell title ${clickables.includes(columns[cind]) ? '_clickable' : ''} col-${cind}`"
                    v-for="(cell, cind) in row.slice(0, columns.length) "
                    :key="cind"
                    :title="cell"
                    :style="`padding: 0px ${columnGapPx}px`"
                    @click.stop.prevent="onClilckCell(row, cind)"
                    v-html="displayCell(cell, cind)"
                >
                </div>
            </li>
        </div>
    </transition>
    <!-- mobile view -->
    <div class="_content enter_animated" v-if="isMobile">
        <div class="mobile_block" 
            v-for="row in filterList(displayList).filter(checkSearch)" 
            @click.stop.prevent="emit('clickCell', row[row.length-1])"
            :key="row[row.length-1]"
        >   
        <div 
            :class="`cell_title ${clickables.includes(columns[titleIndex]) ? '_clickable' : ''}`" 
            v-html="displayCell(row[titleIndex], true)">
        </div>

        <div class="cell_body">
            <div :class="`mobile_cell title`"
                v-for="(cell, cind) in row.filter((r,rind)=>rind != titleIndex).slice(0, columns.length-1)"
                :key="cind"
                v-html="displayCell(cell, cind == columns.length-2)"
                :style="`padding: 0px ${columnGapPx}px`"
                @click.stop.prevent="emit('clickCell', row[row.length-1])"
            >
            </div>
        </div>
        </div>
    </div>

 </div>
</template>

<script lang="ts">
import { ref, onMounted, watch,defineComponent, nextTick, onBeforeUnmount } from "vue";
import { makeExcel } from './makeExcel';
import checkNumbers from './checkNumbers'
import dummyData from './dummyData'
import workerURL from './workerURL'

import _ from 'lodash'

export default defineComponent({
    name:'InteractiveList',
    props: {
        isMobile:{
            default: false,
        },
        lazy:{
            default: true,
        },
        headerPositionTop:{
            default:0
        },
        search:{
            default:[] as string[]
        },
        titleIndex:{
            default:0
        },
        headerWidths:{
            default:[] as number[]
        },
        headerWidthsUnit:{
            default:'%' as 'em' | '%'
        },
        headers:{
            default:[
                "项目简称",
                "业主",
                "开工年份",
                "预计完工",
                ]
        },
        clickables:{
            default:[
                "项目简称",
            ]
        },
        tableName:{
            default:''
        },
        dataType: {
            default: 'lists' as 'lists' | 'objects'
        },
        dataObjects:{
            default: dummyData
        },
        dataList:{
            default:[
                ['项目1', '深圳市第一大业主公司','2021', '2022'],
                ['项目2', '深圳市第二大业主公司','2022', '2023'],
                ['项目3', '深圳市第三大业主公司','2023', '2025'],
            ]
        },
        hideEmpty: {
            default: true
        },
        columnGapPx:{
            default: 5
        },
        nodeName:{
            default:''
        }, 
        defaultFilters : {
            default:{} as {[key:number]: string[]}
        },
        defaultShowFilters:{
            default:false
        },
        defaultSortBy:{
            default:0
        }
    },
    setup(props, {emit}){

        const selectOptions = ref([] as string[][])
        const filterCongfigs = ref([] as string[])

        // proxy data
        const displayList = ref([] as any[])
        const filters = ref(props.defaultFilters as any)
        const columns = ref([] as string[])
        
        // handle data, set column widths
        const computeWidths = (_dataList:string[][], columns:string[], uniques:string[][]) => {
            rendering.value = 1
            displayList.value = _dataList //.filter(x=>!!x[0])
            if(_dataList.length == 0){
                return []
            }
            const _headerTypes = [] as string[]
            const _columnUniqueValues = [] as string[][]
            const _columnTextWidths = [] as number[]

            // handle clickables
            emptyColumns.value = []

            // iterate over columns
            for(let i = 0; i < columns.length; i++){
                const columnData = uniques[i]
                                        .map(x=>(String(x)||'').trim())
                                        .filter(x=>!['', 'undefined', 'null'].includes(x))
                // hide all-empty columns
                if(columnData.length == 0 && props.hideEmpty){
                    emptyColumns.value.push(i)
                }

                const currUniqueValues = columnData as string[]
                let numCount = 0
                let textWidth = 0
                if(!props.hideEmpty){
                    currUniqueValues.push(columns[i])
                    columnData.unshift(columns[i])
                }
                columnData.forEach(cell=>{
                    textWidth += String(cell).split('').reduce((a,b)=>'0123456789.,'.includes(b)?a+0.7:a+1, 0)
                    if(!Number.isNaN(Number(cell.trim()))){
                        numCount ++
                    }
                })
                if(currUniqueValues.length == 1 && currUniqueValues[0]==''){continue}
                
                _columnUniqueValues.push(currUniqueValues)

                const cellAverageWidth = textWidth/Math.max(columnData.filter(x=>String(x).length > 0).length, 1)

                _columnTextWidths.push(

                    (props.hideEmpty && cellAverageWidth == 0) ? 0 

                    : Math.min(
                        Math.max(
                            columns[i].split('').reduce((a,b)=>'0123456789.,'.includes(b)?a+0.7:a+1, 0),
                            cellAverageWidth
                        )
                        ,  10
                    )
                )
                
                if(numCount > _dataList.length/2){
                    _headerTypes.push('number')
                }else{
                    _headerTypes.push('text')
                }
            }

            if(props.headerWidths.length > 0){
                if(props.headerWidthsUnit == '%'){
                    const tot = props.headerWidths.reduce((a,b)=>a+b, 0)
                    const columns = props.headerWidths.map(h=>{
                        return `${100*h/tot}%`
                    }).join(' ')
                    rowColumnStyle.value =`
                        display:grid;
                        grid-template-columns: ${columns};
                    `
                }else if(['em', 'px'].includes(props.headerWidthsUnit)){
                    const columns = props.headerWidths.map(h=>{
                        return `${h}${props.headerWidthsUnit}`
                    }).join(' ')
                    rowColumnStyle.value =`
                        display:grid;
                        grid-template-columns: ${columns};
                    `
                }
            }else{
                const totalWidth = _columnTextWidths.reduce((a,b)=>a+b, 0)
                rowColumnStyle.value = `
                    display:grid;
                    grid-template-columns: ${_columnTextWidths.map(x=> Math.floor(x*100/totalWidth) + '% ').join(' ')};
                `
            }
            headerTypes.value = _headerTypes
            columnUniqueValues.value = _columnUniqueValues

            nextTick(()=>{
                renderColumns()
                rendering.value = 0
            })
        }
        // compute select options
        const initing = ref(false)
        const init = () => {
            initing.value = true
            if(window.Worker){
                const selectOptionsWorker = new Worker(
                    new URL(workerURL, import.meta.url)
                )
    
                if(props.dataType == 'lists'){
                    columns.value = props.headers
                    const processDataList = ()=>{
                        selectOptions.value = []
                        selectOptionsWorker.postMessage({
                            message:'makeOptions',
                            data: JSON.stringify(props.dataList)
                        })
                    }
                    onMounted(processDataList)
                    watch(()=>props.dataList, processDataList, {deep:true})
                    selectOptionsWorker.onmessage = ((message)=>{
                        selectOptions.value = message.data
                        filterCongfigs.value = []
                        for(let i = 0; i < selectOptions.value.length; i++){
                            if(selectOptions.value[i].length > 5){
                                filterCongfigs.value[i] = 'input'
                            }else{
                                filterCongfigs.value[i] = 'select'
                            }
                        }
                        initing.value = false
                    })
                }else if(props.dataType == 'objects'){
                    // join keys into columns
                    columns.value = []
                    for(let r of props.dataObjects){
                        for(let k of Object.keys(r)){
                            if(!columns.value.includes(k)){
                                columns.value.push(k)
                            }
                        }
                    }

                    displayList.value = props.dataObjects.map(r=>{
                        return columns.value.map(c=>r[c] || '')
                    })
    
                    const processDataList = ()=>{
                        selectOptions.value = []
                        selectOptionsWorker.postMessage({
                            message:'makeOptions',
                            data: JSON.stringify(displayList.value)
                        })
                    }
                    processDataList()
                    selectOptionsWorker.onmessage = ((message)=>{
                        computeWidths(displayList.value, columns.value, message.data)
                        selectOptions.value = message.data
                        filterCongfigs.value = []
                        for(let i = 0; i < selectOptions.value.length; i++){
                            if(selectOptions.value[i].length > 5){
                                filterCongfigs.value[i] = 'input'
                            }else{
                                filterCongfigs.value[i] = 'select'
                            }
                        }
                        initing.value = false
                    })
                }
            }
        }
        onMounted(init)
        watch(()=>props.dataObjects, init, {deep:true})
        if(props.dataType == 'objects'){
            watch(()=>props.dataObjects, init, {deep: true})
        }else{
            watch(()=>props.dataList, init, {deep: true})
        }

        const rendering = ref(0)
    

        // column unique values; data categories
        const headerTypes = ref([] as string[])
        const columnUniqueValues = ref([] as string[][])
        const rowColumnStyle = ref("opacity:1;display:flex;flex-direction:row;justify-content:space-evenly")
        const emptyColumns = ref([] as number[])

        // filter control
        const showFilters = ref(props.defaultShowFilters)
        const onToggleFilter = () => {
            if(!showFilters.value){
                showFilters.value = true
            }else{
                showFilters.value = false
                onClickClear()
                filters.value = {}
            }
        }
        const onClickClear = () => {
                filters.value = {}
        }

        const filterList = (dataList:any[][]) => {
            const out = dataList.filter((x, xind)=>{
                for(let i = 0; i < headerTypes.value.length - 1; i++){
                    if(!filters.value[i]) continue

                    if(!x[i]) return false
                    if(filterCongfigs.value[i] == 'select'){
                        let matched = false
                        if(filters.value[i].length == 0){
                            return true
                        }
                        for(let kw of filters.value[i]){
                            if(kw == '' || x[i] == kw){
                                matched = true
                            }
                        }
                        return matched
                    }else{
                        if(headerTypes.value[i] == 'text' && (!x[i].includes(filters.value[i]))){
                            return false
                        }
                    }
                    if(headerTypes.value[i] == 'number'){
                        // if(!checkNumbers(String(x[i]), String(filters.value[i]))){
                        //     return false
                        // }
                        if(!(x[i].includes(filters.value[i]))){
                            return false
                        }
                    }
                }
                return true
            })
            return out
        }


        const renderColumns = () => {
            const startT = Date.now()
            
            for(let i=0; i<columns.value.length; i++){
                const selector = `.col-${i}`
                const colCells = Array.from(document.querySelectorAll(selector)) as HTMLElement[]
                if(!(colCells && colCells.length > 0)) continue
                colCells[0].style.width = '100%'
                nextTick(()=>{
                    const standard = colCells[0].getBoundingClientRect()
                    colCells.forEach(cell=>{
                        cell.style.width = (standard.width - props.columnGapPx * 2) + 'px';
                        // cell.style.paddingRight = props.columnGapPx + 'px';
                        // cell.style.paddingLeft = props.columnGapPx + 'px';
                    })
                })
            }
        }

        let Live = 0
        
        onBeforeUnmount(()=>Live = 0)

        window.addEventListener('resize', ()=>{
            if(Live && props.dataList){
                renderColumns()
            }
        })

        // sorting
        const currSort = ref({
            col:-1,
            desc:false
        })
        const changeSort = (c:number, ) => {
            if(currSort.value.col == c){
                currSort.value.desc = !currSort.value.desc
            }else{
                currSort.value.col = c
                currSort.value.desc = true
            }
            const desc = currSort.value.desc ? 1 : -1
            
            displayList.value = displayList.value.sort((a,b)=>{

                if(!a[c]) return 0
                if(!a[c].match) return 0
                if(!b[c]) return 0
                if(!b[c].match) return 0

                if(a[c] && String(a[c]).trim()==''){ 
                    return 1 * desc
                }else if(String(b[c]).trim()==''){ 
                    return -1 * desc
                }else if(
                    a[c] && a[c].match(/[0-9]{4}((-[0-9]{1,2})){0,2}/)
                    &&
                    b[c] && b[c].match(/[0-9]{4}((-[0-9]{1,2})){0,2}/)
                )
                {
                    // dates
                    const aSplit = a[c].split('-')
                    const bSplit = b[c].split('-')
                    const len = Math.max(aSplit.length, bSplit.length)
                    for(let i = 0; i < len; i++){
                        if(aSplit[i] != bSplit[i]){
                            return (Number(bSplit[i]) - Number(aSplit[i])) * desc
                        }
                        if(!aSplit[i]){return 1 * desc}
                        if(!bSplit[i]){return -1 * desc}
                    }
                    return 0
                }else if(!(Number.isNaN(Number(a[c].trim())) || 
                            Number.isNaN(Number(b[c].trim())))){
                    return (Number(b[c]) - Number(a[c])) * desc
                }else{
                    return String(a[c].trim()).localeCompare(String(b[c].trim())) * desc
                }
            })
        }

        // onMounted(()=>{
        //     changeSort(props.defaultSortBy)
        // })
        watch(()=>props.dataList, ()=>changeSort(props.defaultSortBy), {deep: true})

        // handle redirects
        const gotoView = (article:any) => {
        }

        // export excel
        const exportExcel = () => {
            makeExcel(displayList.value)
        }

        // search
        const checkSearch = (inData:any) => {
            if(props.search.join('').trim().length == 0) return true
            for(let s of props.search){
                let found = false
                for(let v of Object.values(inData)){
                    if(String(v).includes(s)){
                        found = true
                    }
                }
                if(!found) return false
            }
            return true
        }
        const displayCell = (_c:string, cind:string) => {
            // if(props.search.join('').trim().length == 0) {
            //     return c
            // }
            const c = String(_c)
            if(!(typeof c == typeof 'string')) {
                return ''
            }
            if(c=='') {
                return ''
            }
            let out = c
            for(let s of props.search){
                if(s=='')continue
                if(c.includes(s)){
                    const delim = `<span class="red">${s}</span>`
                    out = c.split(s).join(delim)
                }
            }

            // if(props.isMobile && !noBar){
            //     out += '<span>&nbsp;|&nbsp</span>'
            // }

            return out
        }

        const onChangeSearch =  _.debounce((e, hind) => {
            filters.value[hind] = e.target.value
        })
        const onChangeFilter = _.debounce((e, hind) => {
            filters.value[hind] = e
        })


        const onClilckCell = (row:any, col:any) => {
            const out = {}
            for(let cind in columns.value){
                const c = columns.value[cind]
                out[c] = row[cind]
            }
            emit('clickRow', out)
            emit('clickCell', row[row.length-1])
        }


        return {
            rendering,
            emit,

            filters,
            columns,


            changeSort,

            displayList,

            gotoView,
            exportExcel,

            rowColumnStyle,
            emptyColumns,
            headerTypes,

            filterList,

            showFilters,
            onToggleFilter,
            onChangeFilter,
            onClickClear,

            checkSearch,
            displayCell,

            selectOptions,
            filterCongfigs,
            onChangeSearch,

            currSort,
            onClilckCell,
            initing

        }
    },
})
</script>

<style scoped>
.interactive_list_container{
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* min-height:101vh; */
    text-align: left;
  --content-min-width:90%;  
}
.bg{
  position: fixed;
  width: 100%;
  height: 100%;
  opacity:0.5;
  background-image: url(https://huamei2001.oss-cn-shenzhen.aliyuncs.com/hm2001/backgrounds/stkl.png);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

._control{
  width: 100%;
  ;
  ;
  z-index: 110;
  display:flex;
  flex-direction: column;
  align-items: center;
  transition: all 0.3s ease-in-out;
  position:sticky;
  top:-1px;
}
.page_title{
  z-index: 1100;
  color: rgb(70, 70, 70);
  font-size: 18px;
}
._content{
  width: 100%;
  margin-bottom: 50px;
}

.draft::after{
  content: "草稿";
  font-size: 14px;
  padding-left: 5px;
  color:rgb(67, 0, 0)
}
.article_expander{
  transition: all linear 0.3s;
}
.list-group-item,.table-header{
    position: relative;
    width:100%;
    font-size: 14px;
    font-weight: 300;
    display:grid;
    transition: all linear 0.3s;
    color: rgb(80, 80, 80);
    word-wrap:normal;
}
.table-header{
    color: gray;
    /* user-select: none; */
    position:sticky;
    background-color:white;
    z-index: 10;
    height:25px;
    margin:3px 0px 3px 0px;
    width:100%;
    margin-top: 10px;
    margin-bottom: 5px;
    border-bottom: 1px solid #eee;
    padding-bottom: 28px;
}
.table_filters{
  background-color:white;
  z-index: 10;
  position:sticky;
  transition: all linear 0.2s;
  width:100%;
  height: 50px;
}
.list-group-item{
    cursor: pointer;
    padding-top: 5px;
    padding-bottom: 5px;
}
.list-group-item:hover{
    background-color:#eee;
}
.title{
  /* padding-right: 30px !important; */
  /* position:relative; */
  /* width: 300px !important; */
  /* white-space: nowrap; */
  /* overflow: hidden; */
  /* text-overflow: ellipsis !important; */
}  
.datetime{
  justify-self: left;
  padding-right: 30%;
  /* word-break: normal; */
  white-space: nowrap;
}

.edit-buttons {
  position: sticky;
  top: 10px;
  right:20px;
  width: 100px;
  height:0px;
  transform:translate(calc(50vw - 30px));
  z-index: 1000;
  padding-top: 20px;
  padding-right: 20px;
  display: flex;
  flex-direction: column;;
  justify-content: space-between;
}

.property-services{
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  padding-left: 15px;
}

.ps-item{
  width: fit-content;
  /* white-space: nowrap; */
  padding-right: 5px;
}
.display_cell{
  display:inline-block;
  overflow-x: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}
.page{
  margin-bottom: 10px;
  margin-top: 10px;
  z-index: 100;
  display: flex;
  flex-direction: column;
  justify-content:center;
  align-items: center;
}
.page_explain{
  width: 100%;
  font-size: 14px;
  font-weight: 300;
  margin-bottom: 10px;
}
.filter_img{
    position:absolute;
    left: calc(100% -  16px);
    top: 5px;
    width:15px;
    height:15px;
    opacity: 0.3;
    cursor: pointer;
    transition: all 0.2s ease-in-out;
}
.filter_img:hover{
    opacity: 0.8
}
.filter_input{
    width:90%;
    outline-width: 0;
    color: gray;
    font-size: 14px;
    font-weight: 300;
}
.header_cell{
  cursor: pointer;
  font-size: 14px;
  white-space: nowrap;
  overflow: visible;
  background-color:white;
  display: flex;
  flex-direction: row;
  text-align: left;
}
.arrow_img{
  height: 15px;
  opacity: 0.3;
  margin-left: 7px;
  /* transition: all 0.5s ease-in-out; */
}
.arrow_img.flipped{
  transform: rotate(180deg);
}


.reward{
  font-size: 14px;
  line-height: 14px;
  font-weight: 350;
  display:flex;
  flex-direction: row;
  align-items: center;
  user-select:text;
}
.bean_image{
  margin-left: 5px;
  width: 17px;
  height: 17px;
  opacity: 1;
}

.control_btn{
  padding-right: 10px;
}

.mobile_info{
  display: flex;
  flex-direction: row;
}
.mobile_block{
  margin-bottom: 25px;
  cursor: pointer;
}
.mobile_cell{
  color: gray;
  font-size: 14px;
  font-weight: 300;
}
.divider{
  padding-right: 5px;
  padding-left: 5px;
  color: rgb(80, 80, 80);
}



._clickable{
	color:#1680FF;
	cursor: pointer;
	transition: color 0.2s linear;
    user-select: none;
}
._clickable:hover, ._clickable.active{
	color:#334250
}

.cell_title{
    font-size: 14px;
    font-weight: 300;
}
.cell_body{
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content:flex-start;
}
.mobile_cell{
    /* min-width: 30vw; */

}

.clickable{
	color:#328bdf;
	cursor: pointer;
	transition: color 0.2s linear;
    user-select: none;
}
.clickable:hover, .clickable.active{
	color:#334250
}


.sortButtons{
    width:25px;
    font-size: 25px;
    line-height: 12px;
    text-align: center;
    display:flex;
    flex-direction: column;
    justify-content:center;
    align-items: center;
    transform:rotate(0.5turn) translateY(5px);
    transition: all 0.2s linear;
}
.sortButtons.flipped{
    transform:rotate(1turn) translateY(5px)
}
</style>

<style >
.ant-select-item-option{
    font-weight: 250;
    font-size: 14px;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s ease-in-out;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>