import * as React from 'react'
import type { StoryFn, Meta } from '@storybook/react-webpack5'
import { ListGrid } from './index'
import type { ListGridProps } from './types'
import type {
    GridActionsMenuFullProps,
    GridConfirmPayload,
    GridRowDropInfo,
    GridRowMeta,
} from '../../types'
import { GridCellButton, GridCellDefault } from '../../components'
import { Cancel, Edit } from '@planview/pv-icons'
import { useGridRow } from '../../context/grid-context'
import { GridEditorInput, GridEditorSwitch } from '../../editors'
import { ListItem, ListItemDivider, Menu } from '@planview/pv-uikit'
import GridMeta from '../grid/index.stories'

export default {
    title: 'pv-grid/Components/ListGrid',
    component: ListGrid,
    parameters: {
        badges: ['intl'],
    },
    argTypes: {
        rows: GridMeta.argTypes.rows,
        columns: GridMeta.argTypes.columns,
        loading: GridMeta.argTypes.loading,
        emptyContent: GridMeta.argTypes.emptyContent,
        expandedRows: GridMeta.argTypes.expandedRows,
        onExpandedRowsChange: GridMeta.argTypes.onExpandedRowsChange,
        rowDrag: GridMeta.argTypes.rowDrag,
        onCellChange: GridMeta.argTypes.onCellChange,
        ref: GridMeta.argTypes.ref,
        actionsMenu: {
            control: { type: 'radio' },
            options: ['no actions menu', 'actions menu', 'actions menu async'],
            mapping: {
                'no actions menu': undefined,
                'actions menu': () => (
                    <>
                        <ListItem label="Edit column details" />
                        <ListItem label="Deactivate column" />
                        <ListItemDivider />
                        <ListItem label="Remove column" />
                    </>
                ),
                'actions menu async': {
                    Menu({ row, menuProps }: GridActionsMenuFullProps<any>) {
                        const [loading, setLoading] = React.useState(true)

                        React.useEffect(() => {
                            const t = setTimeout(() => setLoading(false), 500)
                            return () => clearTimeout(t)
                        }, [])

                        return (
                            <Menu {...menuProps} loading={loading} alignRight>
                                <>
                                    <ListItem label="Edit column details" />
                                    <ListItem label="Deactivate column" />
                                    <ListItemDivider />
                                    <ListItem label="Remove column" />
                                </>
                            </Menu>
                        )
                    },
                },
            },
            table: {
                category: 'Context Menu',
            },
        },
    },
} satisfies Meta<typeof ListGrid>

type KpiCategory = {
    id: string
    label: string
    isIntegrated?: boolean
    inUse?: boolean
}

export const Default: StoryFn<ListGridProps<KpiCategory>> = ({
    columns: _columns,
    rows: _rows,
    rowDrag: _rowDrag,
    ...rest
}) => {
    const [sortedRows, setSortedRows] = React.useState<KpiCategory[]>([
        { id: '1', label: 'Financial' },
        { id: '2', label: 'Customer', inUse: true },
        { id: '3', label: 'Internal Process' },
        { id: '4', label: 'Learning and Growth' },
        { id: '5', label: 'System', isIntegrated: true },
    ])
    return (
        <ListGrid
            columns={[
                {
                    id: 'label',
                    label: 'Label',
                },
            ]}
            rows={sortedRows}
            rowDrag={{
                previewColumnId: 'label',
                onDrop({ resultIds }) {
                    if (resultIds) {
                        setSortedRows((rows) =>
                            resultIds.map(
                                (id) => rows.find((row) => row.id === id)!
                            )
                        )
                    }
                },
            }}
            {...rest}
        />
    )
}

export const ActionsMenu: StoryFn<ListGridProps<KpiCategory>> = ({
    columns: _columns,
    rows: _rows,
    ...rest
}) => {
    const [sortedRows, setSortedRows] = React.useState<KpiCategory[]>([
        { id: '1', label: 'Financial' },
        { id: '2', label: 'Customer', inUse: true },
        { id: '3', label: 'Internal Process' },
        { id: '4', label: 'Learning and Growth' },
        { id: '5', label: 'System', isIntegrated: true },
    ])
    return (
        <ListGrid
            columns={[
                {
                    id: 'label',
                    label: 'Label',
                },
            ]}
            rows={sortedRows}
            rowDrag={{
                previewColumnId: 'label',
                onDrop({ resultIds }) {
                    if (resultIds) {
                        setSortedRows((rows) =>
                            resultIds.map(
                                (id) => rows.find((row) => row.id === id)!
                            )
                        )
                    }
                },
            }}
            actionsMenu={() => (
                <>
                    <ListItem label="Edit column details" />
                    <ListItem label="Deactivate column" />
                    <ListItemDivider />
                    <ListItem label="Remove column" />
                </>
            )}
        />
    )
}

type KpiTreeCategory = {
    id: string
    label: string
    isIntegrated?: boolean
    inUse?: boolean
    parentId: string | null
    index: number
}

function sortByIndex(a: KpiTreeCategory, b: KpiTreeCategory) {
    return a.index - b.index
}

export const Tree: StoryFn<ListGridProps<KpiTreeCategory>> = () => {
    const [sourceRows, setSourceRows] = React.useState<KpiTreeCategory[]>([
        { id: '1', label: 'Financial', parentId: null, index: 0 },
        { id: '2', label: 'Customer', inUse: true, parentId: '1', index: 0 },
        { id: '3', label: 'Internal Process', parentId: null, index: 1 },
        { id: '4', label: 'Learning and Growth', parentId: '1', index: 1 },
        {
            id: '5',
            label: 'System',
            isIntegrated: true,
            parentId: null,
            index: 2,
        },
        {
            id: '6',
            label: 'System Subsection',
            isIntegrated: true,
            parentId: '5',
            index: 0,
        },
    ])

    const rows = React.useMemo(() => {
        // These three items are needed by the grid
        const ids: string[] = []
        const data = new Map<string, KpiTreeCategory>()
        const meta = new Map<string, GridRowMeta<KpiTreeCategory>>()

        const sortedRows = sourceRows.concat()
        sortedRows.sort(sortByIndex)

        sortedRows.forEach((row) => {
            // Populate the raw data map
            data.set(row.id, row)
            if (row.parentId === null) {
                // If there is no parentId, this row is at the root level
                // so add it to the root `ids` array.
                ids.push(row.id)
            } else {
                // This is a child, so fetch its parent's meta data if it exists
                // and add it to the `children` array
                const children = meta.get(row.parentId)?.children ?? []
                children.push(row.id)
                meta.set(row.parentId, { type: 'tree', children })
            }
        })

        return { ids, data, meta }
    }, [sourceRows])

    const onDrop = React.useCallback(
        (info: GridRowDropInfo<KpiTreeCategory>) => {
            const draggedIds = new Set(info.draggedRowIds)
            const impactedParentIds = new Set(
                info.draggedRows.map((d) => d.row.parentId)
            )

            // This is where you could kick off an API request to move the
            // impacted items on the server. The code below would be needed
            // regardless to provide an optimistic change to the user
            // immediately. In the event of a server error, you could revert
            // the activities to their original state

            setSourceRows((prev) => {
                // Make deep copy of activities, to avoid mutating in-memory
                const newRows = structuredClone(prev)

                // Find rows in their original order
                // You can't go off of raw index order since absolute position
                // in the list is influenced by parent nesting.
                const draggedRows = info.draggedRowIds.map(
                    (id) => newRows.find((d) => d.id === id)!
                )

                // Steps 1 & 2 – Remove from parents and heal siblings.
                // parentId may be `null` indicating root
                impactedParentIds.forEach((parentId) => {
                    const oldSiblings = newRows.filter(
                        (r) => r.parentId === parentId && !draggedIds.has(r.id)
                    )
                    oldSiblings.sort(sortByIndex)
                    oldSiblings.forEach((sibling, index) => {
                        sibling.index = index
                    })
                })

                // Step 3 - Insert dragged items in the correct location
                // and adjust sibling indexes
                const newSiblings = newRows.filter(
                    (r) =>
                        r.parentId === info.targetParentId &&
                        !draggedIds.has(r.id)
                )
                newSiblings.sort(sortByIndex)
                newSiblings.splice(
                    // If rows were dropped on a parent and not in a specific
                    // location, the targetIndex will be null. In this case
                    // they are inserted at the end
                    info.targetIndex == null
                        ? newSiblings.length
                        : info.targetIndex,
                    0,
                    ...draggedRows
                )

                // Step 4 - Adjust indexes of siblings and dropped rows
                // This also sets the parentId for the dropped rows as the other
                // siblings already have this value set
                newSiblings.forEach((sibling, index) => {
                    sibling.index = index
                    sibling.parentId = info.targetParentId
                })
                return newRows
            })
        },
        []
    )

    return (
        <ListGrid
            rowHeight="small"
            columns={[
                {
                    id: 'label',
                    tree: true,
                    label: 'Label',
                    cell: {
                        colSpan({ row }) {
                            if (row.id === '1' || row.id === '5') {
                                return ['label']
                            }
                            return ['label', 'editAction']
                        },
                        editable: true,
                        Editor: GridEditorInput,
                    },
                },
                {
                    id: 'editAction',
                    label: 'Edit',
                    width: 36,

                    cell: {
                        Renderer({ tabIndex, rowId }) {
                            if (rowId === '1' || rowId === '5') {
                                return (
                                    <GridCellButton
                                        tabIndex={tabIndex}
                                        icon={<Edit />}
                                        tooltip="Delete category"
                                        onClick={() => {
                                            // void deleteRow(rowId)
                                        }}
                                    />
                                )
                            }
                            return (
                                <GridCellDefault tabIndex={tabIndex} label="" />
                            )
                        },
                    },
                },
                {
                    id: 'actions',
                    label: 'Actions',
                    width: 36,

                    cell: {
                        Renderer({ tabIndex, rowId }) {
                            return (
                                <GridCellButton
                                    tabIndex={tabIndex}
                                    icon={<Cancel />}
                                    tooltip="Delete category"
                                    onClick={() => {
                                        // void deleteRow(rowId)
                                    }}
                                />
                            )
                        },
                    },
                },
            ]}
            rows={rows}
            rowDrag={{
                enableLeafConversion: true,
                onDrop,
            }}
        />
    )
}

export const ReorderableAndEditable: StoryFn<
    ListGridProps<KpiCategory>
> = () => {
    const [sortedRows, setSortedRows] = React.useState<KpiCategory[]>([
        { id: '1', label: 'Financial' },
        { id: '2', label: 'Customer', inUse: true },
        { id: '3', label: 'Internal Process' },
        { id: '4', label: 'Learning and Growth' },
        { id: '5', label: 'System', isIntegrated: true },
    ])
    return (
        <ListGrid
            columns={[
                {
                    id: 'label',
                    label: 'Label',
                    cell: {
                        editable({ row }) {
                            return !row.isIntegrated
                        },
                        Editor: GridEditorInput,
                    },
                },
                {
                    id: 'actions',
                    label: 'Actions',
                    width: 36,

                    cell: {
                        Renderer({ tabIndex, rowId }) {
                            const row = useGridRow<KpiCategory>(rowId)
                            if (row.inUse || row.isIntegrated) {
                                return (
                                    <GridCellDefault
                                        tabIndex={tabIndex}
                                        label=""
                                    />
                                )
                            }

                            return (
                                <GridCellButton
                                    tabIndex={tabIndex}
                                    icon={<Cancel />}
                                    tooltip="Delete category"
                                    onClick={() => {
                                        // void deleteRow(rowId)
                                    }}
                                />
                            )
                        },
                    },
                },
            ]}
            rows={sortedRows}
            rowDrag={{
                previewColumnId: 'label',
                onDrop({ resultIds }) {
                    if (resultIds) {
                        setSortedRows((rows) =>
                            resultIds.map(
                                (id) => rows.find((row) => row.id === id)!
                            )
                        )
                    }
                },
            }}
            onCellChange={(payload: GridConfirmPayload) => {
                setSortedRows((rows) =>
                    rows.map((row) => {
                        if (row.id === payload.rowId) {
                            return {
                                ...row,
                                [payload.columnId]: payload.nextValue,
                            }
                        }
                        return row
                    })
                )
            }}
        />
    )
}

export const ActiveAndEditable: StoryFn<ListGridProps<KpiCategory>> = () => {
    const [sortedRows, setSortedRows] = React.useState([
        { id: '1', label: 'Row 1', enabled: false },
        { id: '2', label: 'Row 2', enabled: true },
        { id: '3', label: 'Row 3', enabled: false },
    ])
    return (
        <ListGrid
            flexColumnId="label"
            columns={[
                {
                    id: 'enabled',
                    label: 'Enabled',
                    width: 56,
                    cell: {
                        hybridEditor: true,
                        editable: true,
                        Editor: GridEditorSwitch,
                    },
                },
                {
                    id: 'label',
                    label: 'Label',
                    cell: {
                        editable: true,
                        Editor: GridEditorInput,
                    },
                },
                {
                    id: 'actions',
                    label: 'Actions',
                    width: 36,

                    cell: {
                        Renderer({ tabIndex }) {
                            return (
                                <GridCellButton
                                    tabIndex={tabIndex}
                                    icon={<Cancel />}
                                />
                            )
                        },
                    },
                },
            ]}
            rows={sortedRows}
            rowDrag={{
                previewColumnId: 'label',
                onDrop({ resultIds }) {
                    if (resultIds) {
                        setSortedRows((rows) =>
                            resultIds.map(
                                (id) => rows.find((row) => row.id === id)!
                            )
                        )
                    }
                },
            }}
            onCellChange={(payload: GridConfirmPayload) => {
                setSortedRows((rows) =>
                    rows.map((row) => {
                        if (row.id === payload.rowId) {
                            return {
                                ...row,
                                [payload.columnId]: payload.nextValue,
                            }
                        }
                        return row
                    })
                )
            }}
        />
    )
}
