import { memo, useCallback, useState, useEffect, useMemo } from 'react'
import { Form, Button, Popconfirm } from 'antd'
import { EditOutlined, SaveOutlined, PlusOutlined, CloseCircleOutlined, DeleteOutlined } from '@ant-design/icons'

import { invokeWebApiWrapperAsync } from '@components/factory'
import { hasPermission } from '@utils'

import { Table } from '.'
import { EditableCell } from './EditableCell'

const newRowKeyValue = 'newRow'

const actions = {
    insert: (data, idWell) => [idWell, data],
    insertRange: (data, idWell) => [idWell, [data].flat(1)],
    update: (data, idWell, idRecord) => [idWell, idRecord && data.id, data],
    delete: (data, idWell) => [idWell, data.id],
}

export const makeTableAction = ({
    service,
    permission,
    action,
    actionName,
    recordParser,
    idWell,
    idRecord = false,
    setLoader,
    errorMsg = 'Не удалось выполнить операцию',
    onComplete,
}) => hasPermission(permission) && service && action && (
    (record) => invokeWebApiWrapperAsync(
        async () => {
            const data = recordParser?.(record) ?? record
            const params = actions[action]?.(data, idWell, idRecord).filter(Boolean)
            if (params?.length > 0)
                await service[action](...params)
            await onComplete?.()
        },
        setLoader,
        errorMsg,
        actionName
    )
)

export const tryAddKeys = (items) => {
    if (!items?.length || !items[0])
        return []
    if (items[0].key)
        return items
    return items.map((item, index) => ({...item, key: item.key ?? item.id ?? index }))
}

const EditableTableComponents = { body: { cell: EditableCell }}

/**
 * @param onChange - Метод вызывается со всем dataSource с измененными элементами после любого действия
 * @param onRowAdd - Метод вызывается с новой добавленной записью. Если метод не определен, то кнопка добавления строки не показывается
 * @param onRowEdit - Метод вызывается с новой отредактированной записью. Если метод не поределен, то кнопка редактирования строки не показывается
 * @param onRowDelete - Метод вызывается с удаленной записью. Если метод не поределен, то кнопка удаления строки не показывается
 */
export const EditableTable = memo(({
    columns,
    dataSource,
    onChange,
    onRowAdd,
    onRowEdit,
    onRowDelete,
    additionalButtons,
    buttonsWidth,
    ...otherTableProps
}) => {

    const [form] = Form.useForm()
    const [data, setData] = useState(tryAddKeys(dataSource))
    const [editingKey, setEditingKey] = useState('')

    const onAdd = useMemo(() => typeof onRowAdd === 'function' ? onRowAdd : makeTableAction(onRowAdd), [onRowAdd])
    const onEdit = useMemo(() => typeof onRowEdit === 'function' ? onRowEdit : makeTableAction(onRowEdit), [onRowEdit])
    const onDelete = useMemo(() => typeof onRowDelete === 'function' ? onRowDelete : makeTableAction(onRowDelete), [onRowDelete])

    const isEditing = useCallback((record) => record?.key === editingKey, [editingKey])

    const edit = useCallback((record) => {
        form.setFieldsValue({...record})
        setEditingKey(record.key)
    }, [form])

    const cancel = useCallback(() => {
        if (editingKey === newRowKeyValue) {
            const newData = [...data]
            const index = newData.findIndex((item) => newRowKeyValue === item.key)
            newData.splice(index, 1)
            setData(newData)
        }
        setEditingKey('')
    }, [data, editingKey])

    const addNewRow = useCallback(async () => {
        let newRow = {
            ...form.initialValues,
            key:newRowKeyValue
        }

        const newData = [newRow, ...data]
        setData(newData)
        edit(newRow)
    }, [data, edit, form.initialValues])

    const save = useCallback(async (record) => {
        try {
            const row = await form.validateFields()
            const newData = [...data]
            const index = newData.findIndex((item) => record.key === item.key)
            const item = newData[index]
            const newItem = { ...item, ...row }

            newData.splice(index, 1, newItem)

            if (item.key === newRowKeyValue)
                item.key = newRowKeyValue + newData.length

            const isAdding = editingKey === newRowKeyValue
            setEditingKey('')
            setData(newData)

            if (isAdding)
                try {
                    onAdd(newItem)
                } catch (err) {
                    console.log('callback onRowAdd fault:', err)
                }
            else
                try {
                    onEdit(newItem)
                } catch (err) {
                    console.log('callback onRowEdit fault:', err)
                }

            try {
                onChange?.(newData)
            } catch (err) {
                console.log('callback onChange fault:', err)
            }
        } catch (errInfo) {
            console.log('Validate Failed:', errInfo)
        }
    }, [data, editingKey, form, onChange, onAdd, onEdit])

    const deleteRow = useCallback((record) => {
        const newData = [...data]
        const index = newData.findIndex((item) => record.key === item.key)

        newData.splice(index, 1)
        setData(newData)

        onDelete(record)
        onChange?.(newData)
    }, [data, onChange, onDelete])

    const handleColumn = useCallback((col) => {
        if (col.children)
            col.children = col.children.map(handleColumn)

        if (!col.editable)
            return col

        return {
            ...col,
            onCell: (record) => ({
                ...col.onCell?.(record),
                editing: isEditing(record),
                record,
                dataIndex: col.dataIndex ?? col.key,
                key: col.key ?? col.dataIndex,
                input: col.input,
                isRequired: col.isRequired,
                title: col.title,
                datatype: col.datatype,
                formItemClass: col.formItemClass,
                formItemRules: col.formItemRules,
                initialValue: col.initialValue,
            }),
        }
    }, [isEditing])

    const operationColumn = useMemo(() => ({
        width: buttonsWidth ?? 82,
        title: !!onAdd && (
            <Button
                onClick={addNewRow}
                disabled={editingKey !== ''}
                icon={<PlusOutlined/>}
            />
        ),
        dataIndex: 'operation',
        render: (_, record) => isEditing(record) ? (
            <span>
                <Button onClick={() => save(record)} icon={<SaveOutlined/>}/>
                <Button onClick={cancel} icon={<CloseCircleOutlined/>}/>
                {additionalButtons?.(record, editingKey)}
            </span>
        ) : (
            <span>
                {onEdit && (
                    <Button
                        disabled={editingKey !== ''}
                        onClick={() => edit(record)}
                        icon={<EditOutlined/>}
                    />
                )}
                {onDelete && (
                    <Popconfirm title={'Удалить?'} onConfirm={() => deleteRow(record)}>
                        <Button icon={<DeleteOutlined/>}/>
                    </Popconfirm>
                )}
                {additionalButtons?.(record, editingKey)}
            </span>
        ),
    }), [onAdd, onEdit, onDelete, isEditing, editingKey, save, cancel, edit, deleteRow])

    const mergedColumns = useMemo(() => [...columns.map(handleColumn), operationColumn], [columns, handleColumn, operationColumn])

    useEffect(() => {
        setData(tryAddKeys(dataSource))
    }, [dataSource])

    return (
        <Form form={form}>
            <Table
                components={EditableTableComponents}
                columns={mergedColumns}
                dataSource={data}
                {...otherTableProps}
            />
        </Form>
    )
})
