<template>
    <template v-if="!empty">
        <tr v-if="templates['groupheader'] && rowGroupMode === 'subheader' && shouldRenderRowGroupHeader" :class="cx('rowGroupHeader')" :style="rowGroupHeaderStyle" role="row" v-bind="ptm('rowGroupHeader')">
            <td :colspan="columnsLength - 1" v-bind="{ ...getColumnPT('bodycell'), ...ptm('rowGroupHeaderCell') }">
                <button v-if="expandableRowGroups" :class="cx('rowToggleButton')" @click="onRowGroupToggle" type="button" v-bind="ptm('rowToggleButton')">
                    <component v-if="templates['rowtoggleicon'] || templates['rowgrouptogglericon']" :is="templates['rowtoggleicon'] || templates['rowgrouptogglericon']" :expanded="isRowGroupExpanded" />
                    <template v-else>
                        <span v-if="isRowGroupExpanded && expandedRowIcon" :class="[cx('rowToggleIcon'), expandedRowIcon]" v-bind="ptm('rowToggleIcon')" />
                        <ChevronDownIcon v-else-if="isRowGroupExpanded && !expandedRowIcon" :class="cx('rowToggleIcon')" v-bind="ptm('rowToggleIcon')" />
                        <span v-else-if="!isRowGroupExpanded && collapsedRowIcon" :class="[cx('rowToggleIcon'), collapsedRowIcon]" v-bind="ptm('rowToggleIcon')" />
                        <ChevronRightIcon v-else-if="!isRowGroupExpanded && !collapsedRowIcon" :class="cx('rowToggleIcon')" v-bind="ptm('rowToggleIcon')" />
                    </template>
                </button>
                <component :is="templates['groupheader']" :data="rowData" :index="rowIndex" />
            </td>
        </tr>
        <tr
            v-if="expandableRowGroups ? isRowGroupExpanded : true"
            :class="rowClasses"
            :style="rowStyles"
            :tabindex="rowTabindex"
            role="row"
            :aria-selected="selectionMode ? isSelected : null"
            @click="onRowClick"
            @dblclick="onRowDblClick"
            @contextmenu="onRowRightClick"
            @touchend="onRowTouchEnd"
            @keydown.self="onRowKeyDown"
            @mousedown="onRowMouseDown"
            @dragstart="onRowDragStart"
            @dragover="onRowDragOver"
            @dragleave="onRowDragLeave"
            @dragend="onRowDragEnd"
            @drop="onRowDrop"
            v-bind="getBodyRowPTOptions('bodyRow')"
            :data-p-index="rowIndex"
            :data-p-selectable-row="selectionMode ? true : false"
            :data-p-selected="selection && isSelected"
            :data-p-selected-contextmenu="contextMenuSelection && isSelectedWithContextMenu"
        >
            <template v-for="(col, i) of columns">
                <DTBodyCell
                    v-if="shouldRenderBodyCell(col)"
                    :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i"
                    :rowData="rowData"
                    :column="col"
                    :rowIndex="rowIndex"
                    :index="i"
                    :selected="isSelected"
                    :frozenRow="frozenRow"
                    :rowspan="rowGroupMode === 'rowspan' ? calculateRowGroupSize(col) : null"
                    :editMode="editMode"
                    :editing="editMode === 'row' && isRowEditing"
                    :editingMeta="editingMeta"
                    :virtualScrollerContentProps="virtualScrollerContentProps"
                    :ariaControls="expandedRowId + '_' + rowIndex + '_expansion'"
                    :name="nameAttributeSelector"
                    :isRowExpanded="d_rowExpanded"
                    :expandedRowIcon="expandedRowIcon"
                    :collapsedRowIcon="collapsedRowIcon"
                    :editButtonProps="editButtonProps"
                    @radio-change="onRadioChange"
                    @checkbox-change="onCheckboxChange"
                    @row-toggle="onRowToggle"
                    @cell-edit-init="onCellEditInit"
                    @cell-edit-complete="onCellEditComplete"
                    @cell-edit-cancel="onCellEditCancel"
                    @row-edit-init="onRowEditInit"
                    @row-edit-save="onRowEditSave"
                    @row-edit-cancel="onRowEditCancel"
                    @editing-meta-change="onEditingMetaChange"
                    :unstyled="unstyled"
                    :pt="pt"
                />
            </template>
        </tr>
        <tr v-if="templates['expansion'] && expandedRows && d_rowExpanded" :id="expandedRowId + '_' + rowIndex + '_expansion'" :class="cx('rowExpansion')" role="row" v-bind="ptm('rowExpansion')">
            <td :colspan="columnsLength" v-bind="{ ...getColumnPT('bodycell'), ...ptm('rowExpansionCell') }">
                <component :is="templates['expansion']" :data="rowData" :index="rowIndex" />
            </td>
        </tr>
        <tr v-if="templates['groupfooter'] && rowGroupMode === 'subheader' && shouldRenderRowGroupFooter" :class="cx('rowGroupFooter')" role="row" v-bind="ptm('rowGroupFooter')">
            <td :colspan="columnsLength - 1" v-bind="{ ...getColumnPT('bodycell'), ...ptm('rowGroupFooterCell') }">
                <component :is="templates['groupfooter']" :data="rowData" :index="rowIndex" />
            </td>
        </tr>
    </template>
    <tr v-else :class="cx('emptyMessage')" role="row" v-bind="ptm('emptyMessage')">
        <td :colspan="columnsLength" v-bind="{ ...getColumnPT('bodycell'), ...ptm('emptyMessageCell') }">
            <component v-if="templates.empty" :is="templates.empty" />
        </td>
    </tr>
</template>

<script>
import { equals, isNotEmpty, resolveFieldData } from '@primeuix/utils/object';
import BaseComponent from '@primevue/core/basecomponent';
import { getVNodeProp } from '@primevue/core/utils';
import ChevronDownIcon from '@primevue/icons/chevrondown';
import ChevronRightIcon from '@primevue/icons/chevronright';
import { mergeProps } from 'vue';
import BodyCell from './BodyCell.vue';

export default {
    name: 'BodyRow',
    hostName: 'DataTable',
    extends: BaseComponent,
    emits: [
        'rowgroup-toggle',
        'row-click',
        'row-dblclick',
        'row-rightclick',
        'row-touchend',
        'row-keydown',
        'row-mousedown',
        'row-dragstart',
        'row-dragover',
        'row-dragleave',
        'row-dragend',
        'row-drop',
        'row-toggle',
        'radio-change',
        'checkbox-change',
        'cell-edit-init',
        'cell-edit-complete',
        'cell-edit-cancel',
        'row-edit-init',
        'row-edit-save',
        'row-edit-cancel',
        'editing-meta-change'
    ],
    props: {
        rowData: {
            type: Object,
            default: null
        },
        index: {
            type: Number,
            default: 0
        },
        value: {
            type: Array,
            default: null
        },
        columns: {
            type: null,
            default: null
        },
        frozenRow: {
            type: Boolean,
            default: false
        },
        empty: {
            type: Boolean,
            default: false
        },
        rowGroupMode: {
            type: String,
            default: null
        },
        groupRowsBy: {
            type: [Array, String, Function],
            default: null
        },
        expandableRowGroups: {
            type: Boolean,
            default: false
        },
        expandedRowGroups: {
            type: Array,
            default: null
        },
        first: {
            type: Number,
            default: 0
        },
        dataKey: {
            type: [String, Function],
            default: null
        },
        expandedRowIcon: {
            type: String,
            default: null
        },
        collapsedRowIcon: {
            type: String,
            default: null
        },
        expandedRows: {
            type: [Array, Object],
            default: null
        },
        selection: {
            type: [Array, Object],
            default: null
        },
        selectionKeys: {
            type: null,
            default: null
        },
        selectionMode: {
            type: String,
            default: null
        },
        contextMenu: {
            type: Boolean,
            default: false
        },
        contextMenuSelection: {
            type: Object,
            default: null
        },
        rowClass: {
            type: null,
            default: null
        },
        rowStyle: {
            type: null,
            default: null
        },
        rowGroupHeaderStyle: {
            type: null,
            default: null
        },
        editMode: {
            type: String,
            default: null
        },
        compareSelectionBy: {
            type: String,
            default: 'deepEquals'
        },
        editingRows: {
            type: Array,
            default: null
        },
        editingRowKeys: {
            type: null,
            default: null
        },
        editingMeta: {
            type: Object,
            default: null
        },
        templates: {
            type: null,
            default: null
        },
        scrollable: {
            type: Boolean,
            default: false
        },
        editButtonProps: {
            type: Object,
            default: null
        },
        virtualScrollerContentProps: {
            type: Object,
            default: null
        },
        isVirtualScrollerDisabled: {
            type: Boolean,
            default: false
        },
        expandedRowId: {
            type: String,
            default: null
        },
        nameAttributeSelector: {
            type: String,
            default: null
        }
    },
    data() {
        return {
            d_rowExpanded: false
        };
    },
    watch: {
        expandedRows: {
            deep: true,
            immediate: true,
            handler(newValue) {
                this.d_rowExpanded = this.dataKey ? newValue?.[resolveFieldData(this.rowData, this.dataKey)] !== undefined : newValue?.some((d) => this.equals(this.rowData, d));
            }
        },
        rowData(newValue) {
            this.d_rowExpanded = this.dataKey ? this.expandedRows?.[resolveFieldData(newValue, this.dataKey)] !== undefined : this.expandedRows?.some((d) => this.equals(newValue, d));
        }
    },
    methods: {
        columnProp(col, prop) {
            return getVNodeProp(col, prop);
        },
        //@todo - update this method
        getColumnPT(key) {
            const columnMetaData = {
                parent: {
                    instance: this,
                    props: this.$props,
                    state: this.$data
                }
            };

            return mergeProps(this.ptm(`column.${key}`, { column: columnMetaData }), this.ptm(`column.${key}`, columnMetaData), this.ptmo(this.columnProp({}, 'pt'), key, columnMetaData));
        },
        //@todo - update this method
        getBodyRowPTOptions(key) {
            const datatable = this.$parentInstance?.$parentInstance;

            return this.ptm(key, {
                context: {
                    index: this.rowIndex,
                    selectable: datatable?.rowHover || datatable?.selectionMode,
                    selected: this.isSelected,
                    stripedRows: datatable?.stripedRows || false
                }
            });
        },
        shouldRenderBodyCell(column) {
            const isHidden = this.columnProp(column, 'hidden');

            if (this.rowGroupMode && !isHidden) {
                const field = this.columnProp(column, 'field');

                if (this.rowGroupMode === 'subheader') {
                    return this.groupRowsBy !== field;
                } else if (this.rowGroupMode === 'rowspan') {
                    if (this.isGrouped(column)) {
                        let prevRowData = this.value[this.rowIndex - 1];

                        if (prevRowData) {
                            const currentRowFieldData = resolveFieldData(this.value[this.rowIndex], field);
                            const previousRowFieldData = resolveFieldData(prevRowData, field);

                            return currentRowFieldData !== previousRowFieldData;
                        } else {
                            return true;
                        }
                    } else {
                        return true;
                    }
                }
            } else {
                return !isHidden;
            }
        },
        calculateRowGroupSize(column) {
            if (this.isGrouped(column)) {
                let index = this.rowIndex;
                const field = this.columnProp(column, 'field');
                const currentRowFieldData = resolveFieldData(this.value[index], field);
                let nextRowFieldData = currentRowFieldData;
                let groupRowSpan = 0;

                if (this.d_rowExpanded) groupRowSpan++;

                while (currentRowFieldData === nextRowFieldData) {
                    groupRowSpan++;
                    let nextRowData = this.value[++index];

                    if (nextRowData) {
                        nextRowFieldData = resolveFieldData(nextRowData, field);
                    } else {
                        break;
                    }
                }

                return groupRowSpan === 1 ? null : groupRowSpan;
            } else {
                return null;
            }
        },
        isGrouped(column) {
            const field = this.columnProp(column, 'field');

            if (this.groupRowsBy && field) {
                if (Array.isArray(this.groupRowsBy)) return this.groupRowsBy.indexOf(field) > -1;
                else return this.groupRowsBy === field;
            } else {
                return false;
            }
        },
        findIndexInSelection(data) {
            return this.findIndex(data, this.selection);
        },
        findIndex(data, collection) {
            let index = -1;

            if (collection && collection.length) {
                for (let i = 0; i < collection.length; i++) {
                    if (this.equals(data, collection[i])) {
                        index = i;
                        break;
                    }
                }
            }

            return index;
        },
        equals(data1, data2) {
            return this.compareSelectionBy === 'equals' ? data1 === data2 : equals(data1, data2, this.dataKey);
        },
        onRowGroupToggle(event) {
            this.$emit('rowgroup-toggle', { originalEvent: event, data: this.rowData });
        },
        onRowClick(event) {
            this.$emit('row-click', { originalEvent: event, data: this.rowData, index: this.rowIndex });
        },
        onRowDblClick(event) {
            this.$emit('row-dblclick', { originalEvent: event, data: this.rowData, index: this.rowIndex });
        },
        onRowRightClick(event) {
            this.$emit('row-rightclick', { originalEvent: event, data: this.rowData, index: this.rowIndex });
        },
        onRowTouchEnd(event) {
            this.$emit('row-touchend', event);
        },
        onRowKeyDown(event) {
            this.$emit('row-keydown', { originalEvent: event, data: this.rowData, index: this.rowIndex });
        },
        onRowMouseDown(event) {
            this.$emit('row-mousedown', event);
        },
        onRowDragStart(event) {
            this.$emit('row-dragstart', { originalEvent: event, index: this.rowIndex });
        },
        onRowDragOver(event) {
            this.$emit('row-dragover', { originalEvent: event, index: this.rowIndex });
        },
        onRowDragLeave(event) {
            this.$emit('row-dragleave', event);
        },
        onRowDragEnd(event) {
            this.$emit('row-dragend', event);
        },
        onRowDrop(event) {
            this.$emit('row-drop', event);
        },
        onRowToggle(event) {
            this.d_rowExpanded = !this.d_rowExpanded;

            this.$emit('row-toggle', { ...event, expanded: this.d_rowExpanded });
        },
        onRadioChange(event) {
            this.$emit('radio-change', event);
        },
        onCheckboxChange(event) {
            this.$emit('checkbox-change', event);
        },
        onCellEditInit(event) {
            this.$emit('cell-edit-init', event);
        },
        onCellEditComplete(event) {
            this.$emit('cell-edit-complete', event);
        },
        onCellEditCancel(event) {
            this.$emit('cell-edit-cancel', event);
        },
        onRowEditInit(event) {
            this.$emit('row-edit-init', event);
        },
        onRowEditSave(event) {
            this.$emit('row-edit-save', event);
        },
        onRowEditCancel(event) {
            this.$emit('row-edit-cancel', event);
        },
        onEditingMetaChange(event) {
            this.$emit('editing-meta-change', event);
        },
        getVirtualScrollerProp(option, options) {
            options = options || this.virtualScrollerContentProps;

            return options ? options[option] : null;
        }
    },
    computed: {
        rowIndex() {
            const getItemOptions = this.getVirtualScrollerProp('getItemOptions');

            return getItemOptions ? getItemOptions(this.index).index : this.index;
        },
        rowStyles() {
            return this.rowStyle?.(this.rowData);
        },
        rowClasses() {
            let rowStyleClass = [];
            let columnSelectionMode = null;

            if (this.rowClass) {
                let rowClassValue = this.rowClass(this.rowData);

                if (rowClassValue) {
                    rowStyleClass.push(rowClassValue);
                }
            }

            if (this.columns) {
                for (let col of this.columns) {
                    let _selectionMode = this.columnProp(col, 'selectionMode');

                    if (isNotEmpty(_selectionMode)) {
                        columnSelectionMode = _selectionMode;
                        break;
                    }
                }
            }

            return [this.cx('row', { rowData: this.rowData, index: this.rowIndex, columnSelectionMode }), rowStyleClass];
        },
        rowTabindex() {
            if ((this.selection === null || (Array.isArray(this.selection) && this.selection.length === 0)) && (this.selectionMode === 'single' || this.selectionMode === 'multiple')) {
                return this.rowIndex === 0 ? 0 : -1;
            }

            return -1;
        },
        isRowEditing() {
            if (this.rowData && this.editingRows) {
                if (this.dataKey) return this.editingRowKeys ? this.editingRowKeys[resolveFieldData(this.rowData, this.dataKey)] !== undefined : false;
                else return this.findIndex(this.rowData, this.editingRows) > -1;
            }

            return false;
        },
        isRowGroupExpanded() {
            if (this.expandableRowGroups && this.expandedRowGroups) {
                const groupFieldValue = resolveFieldData(this.rowData, this.groupRowsBy);

                return this.expandedRowGroups.indexOf(groupFieldValue) > -1;
            }

            return false;
        },
        isSelected() {
            if (this.rowData && this.selection) {
                if (this.dataKey) {
                    return this.selectionKeys ? this.selectionKeys[resolveFieldData(this.rowData, this.dataKey)] !== undefined : false;
                } else {
                    if (this.selection instanceof Array) return this.findIndexInSelection(this.rowData) > -1;
                    else return this.equals(this.rowData, this.selection);
                }
            }

            return false;
        },
        isSelectedWithContextMenu() {
            if (this.rowData && this.contextMenuSelection) {
                return this.equals(this.rowData, this.contextMenuSelection, this.dataKey);
            }

            return false;
        },
        shouldRenderRowGroupHeader() {
            const currentRowFieldData = resolveFieldData(this.rowData, this.groupRowsBy);
            const prevRowData = this.value[this.rowIndex - 1];

            if (prevRowData) {
                const previousRowFieldData = resolveFieldData(prevRowData, this.groupRowsBy);

                return currentRowFieldData !== previousRowFieldData;
            } else {
                return true;
            }
        },
        shouldRenderRowGroupFooter() {
            if (this.expandableRowGroups && !this.isRowGroupExpanded) {
                return false;
            } else {
                let currentRowFieldData = resolveFieldData(this.rowData, this.groupRowsBy);
                let nextRowData = this.value[this.rowIndex + 1];

                if (nextRowData) {
                    let nextRowFieldData = resolveFieldData(nextRowData, this.groupRowsBy);

                    return currentRowFieldData !== nextRowFieldData;
                } else {
                    return true;
                }
            }
        },
        columnsLength() {
            if (this.columns) {
                let hiddenColLength = 0;

                this.columns.forEach((column) => {
                    if (this.columnProp(column, 'hidden')) hiddenColLength++;
                });

                return this.columns.length - hiddenColLength;
            }

            return 0;
        }
    },
    components: {
        DTBodyCell: BodyCell,
        ChevronDownIcon: ChevronDownIcon,
        ChevronRightIcon: ChevronRightIcon
    }
};
</script>
