import {wrapIn, toggleMark} from "prosemirror-commands"
import {wrapInList} from "prosemirror-schema-list"
import {undo, redo, undoDepth, redoDepth} from "prosemirror-history"

import {CitationDialog, FigureDialog, LinkDialog, MathDialog, TableDialog} from "../../dialogs"
import {READ_ONLY_ROLES, COMMENT_ONLY_ROLES} from "../.."
import {setBlockType} from "../../keymap"
import {checkProtectedInSelection} from "../../state_plugins"

const BLOCK_LABELS = {
    'paragraph': gettext('Normal Text'),
    'heading1': gettext('1st Heading'),
    'heading2': gettext('2nd Heading'),
    'heading3': gettext('3rd Heading'),
    'heading4': gettext('4th Heading'),
    'heading5': gettext('5th Heading'),
    'heading6': gettext('6th Heading'),
    'code_block': gettext('Code'),
    'figure': gettext('Figure')
}

// from https://github.com/ProseMirror/prosemirror-tables/blob/master/src/util.js
const findTable = function(state) {
    const $head = state.selection.$head
    for (let d = $head.depth; d > 0; d--) {
        if ($head.node(d).type.spec.tableRole == "table") {
            return $head.node(d)
        }
    }
    return false
}

function elementAvailable(editor, elementName) {
    let elementInDocParts = false
    editor.view.state.doc.firstChild.forEach(docPart => {
        if (docPart.attrs.elements && docPart.attrs.elements.includes(elementName)) {
            elementInDocParts = true
        }
    })
    return (
        editor.view.state.doc.firstChild.attrs.footnote_elements.includes(elementName) ||
          elementInDocParts
    )
}

export function elementDisabled(editor, elementName) {
    if (editor.currentView === editor.view) {
        // main editor
        const anchorDocPart = editor.currentView.state.selection.$anchor.node(2),
            headDocPart = editor.currentView.state.selection.$head.node(2)

        return !anchorDocPart ||
            headDocPart !== anchorDocPart ||
            !anchorDocPart.attrs.elements ||
            !anchorDocPart.attrs.elements.includes(elementName) ||
            checkProtectedInSelection(editor.view.state)
    } else {
        // footnote editor
        const anchorFootnote = editor.currentView.state.selection.$anchor.node(1),
            headFootnote = editor.currentView.state.selection.$head.node(1)

        return !anchorFootnote ||
            headFootnote !== anchorFootnote ||
            !editor.view.state.doc.firstChild.attrs.footnote_elements.includes(elementName)
    }

}

function markAvailable(editor, markName) {
    let markInDocParts = false
    editor.view.state.doc.firstChild.forEach(docPart => {
        if (docPart.attrs.elements && docPart.attrs.marks.includes(markName)) {
            markInDocParts = true
        }
    })
    return (
        editor.view.state.doc.firstChild.attrs.footnote_marks.includes(markName) ||
          markInDocParts
    )
}

function markDisabled(editor, markName) {
    if (editor.currentView === editor.view) {
        // main editor
        const anchorDocPart = editor.currentView.state.selection.$anchor.node(2),
            headDocPart = editor.currentView.state.selection.$head.node(2)

        return !anchorDocPart ||
            headDocPart !== anchorDocPart ||
            !anchorDocPart.attrs.marks ||
            !anchorDocPart.attrs.marks.includes(markName) ||
            checkProtectedInSelection(editor.view.state)
    } else {
        // footnote editor
        const anchorFootnote = editor.currentView.state.selection.$anchor.node(1),
            headFootnote = editor.currentView.state.selection.$head.node(1)

        return !anchorFootnote ||
            headFootnote !== anchorFootnote ||
            !editor.view.state.doc.firstChild.attrs.footnote_marks.includes(markName)
    }

}

export const toolbarModel = () => ({
    openMore: false, // whether 'more' menu is opened.
    content: [
        {
            type: 'button',
            title: gettext('Open/close header menu'),
            icon: editor => {
                if (editor.menu.headerbarModel.open) {
                    return 'angle-double-up'
                } else {
                    return 'angle-double-down'
                }
            },
            action: editor => {
                editor.menu.headerbarModel.open = !editor.menu.headerbarModel.open
                if (editor.menu.headerView) {
                    editor.menu.headerView.update()
                }
            },
            class: editor => {
                if (editor.menu.headerbarModel.open) {
                    return 'no-border'
                } else {
                    return 'no-border header-closed'
                }
            },
            order: 0
        },
        {
            type: 'info',
            show: editor => {
                let title = ""
                if (editor.currentView !== editor.view) {
                    return gettext('Footnote')
                } else if (
                    editor.currentView.state.selection.$anchor.node(2) &&
                    editor.currentView.state.selection.$anchor.node(2) ===
                        editor.currentView.state.selection.$head.node(2)
                ) {
                    title = editor.currentView.state.selection.$anchor.node(2).attrs.title || gettext('Title')
                    return title.length > 20 ? title.slice(0, 20) + "..." : title
                } else if (
                    editor.currentView.state.selection.$anchor.depth === 1 &&
                    editor.currentView.state.selection.from ===
                        editor.currentView.state.selection.to
                ) {
                    title = editor.currentView.state.selection.$anchor.nodeAfter.attrs.title
                    return title.length > 20 ? title.slice(0, 20) + "..." : title
                } else if (
                    editor.currentView.state.selection.jsonID === 'node' &&
                    editor.currentView.state.selection.node.isBlock &&
                    editor.currentView.state.selection.node.attrs.title
                ) {
                    title = editor.currentView.state.selection.node.attrs.title
                    return title.length > 20 ? title.slice(0, 20) + "..." : title
                } else {
                    return ''
                }
            },
            order: 1

        },
        {
            type: 'menu',
            show: editor => {
                if (
                    editor.currentView.state.selection.$anchor.node(2) &&
                    !editor.view.state.selection.$anchor.node(2).attrs.elements
                ) {
                    return ''
                }
                if (
                    editor.currentView.state.selection.jsonID === 'node' &&
                    editor.currentView.state.selection.node.isBlock
                ) {
                    const selectedNode = editor.currentView.state.selection.node
                    return BLOCK_LABELS[selectedNode.type.name] ? BLOCK_LABELS[selectedNode.type.name] : ''
                }
                const startElement = editor.currentView.state.selection.$anchor.parent,
                    endElement = editor.currentView.state.selection.$head.parent
                if (!startElement || !endElement) {
                    return ''
                } else if (startElement === endElement) {
                    const blockNodeType = startElement.type.name
                    return BLOCK_LABELS[blockNodeType] ? BLOCK_LABELS[blockNodeType] : ''
                } else {
                    let blockNodeType = true
                    editor.currentView.state.doc.nodesBetween(
                        editor.currentView.state.selection.from,
                        editor.currentView.state.selection.to,
                        node => {
                            if (node.isTextblock) {
                                const nextBlockNodeType = node.type.name
                                if (blockNodeType === true) {
                                    blockNodeType = nextBlockNodeType
                                }
                                if (blockNodeType !== nextBlockNodeType) {
                                    blockNodeType = false
                                }
                            }
                        }
                    )

                    if (blockNodeType) {
                        return BLOCK_LABELS[blockNodeType] ? BLOCK_LABELS[blockNodeType] : ''
                    } else {
                        return ''
                    }
                }

            },
            disabled: editor =>
                READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    !editor.currentView.state.selection.$anchor.node(2) ||
                    !editor.currentView.state.selection.$anchor.node(2).attrs.elements ||
                    (
                        editor.currentView.state.selection.jsonID === 'node' &&
                        editor.currentView.state.selection.node.isBlock &&
                        !editor.currentView.state.selection.node.isTextblock
                    ) ||
                        editor.currentView.state.selection.jsonID === 'gapcursor',
            content: [
                {
                    title: BLOCK_LABELS['paragraph'],
                    action: editor => {
                        const view = editor.currentView
                        setBlockType(view.state.schema.nodes.paragraph)(view.state, view.dispatch)
                    },
                    available: editor => elementAvailable(editor, 'paragraph'),
                    disabled: editor => elementDisabled(editor, 'paragraph'),
                    order: 0
                },
                {
                    title: BLOCK_LABELS['heading1'],
                    action: editor => {
                        const view = editor.currentView
                        setBlockType(view.state.schema.nodes.heading1)(view.state, view.dispatch)
                    },
                    available: editor => elementAvailable(editor, 'heading1'),
                    disabled: editor => elementDisabled(editor, 'heading1'),
                    order: 1
                },
                {
                    title: BLOCK_LABELS['heading2'],
                    action: editor => {
                        const view = editor.currentView
                        setBlockType(view.state.schema.nodes.heading2)(view.state, view.dispatch)
                    },
                    available: editor => elementAvailable(editor, 'heading2'),
                    disabled: editor => elementDisabled(editor, 'heading2'),
                    order: 2
                },
                {
                    title: BLOCK_LABELS['heading3'],
                    action: editor => {
                        const view = editor.currentView
                        setBlockType(view.state.schema.nodes.heading3)(view.state, view.dispatch)
                    },
                    available: editor => elementAvailable(editor, 'heading3'),
                    disabled: editor => elementDisabled(editor, 'heading3'),
                    order: 3
                },
                {
                    title: BLOCK_LABELS['heading4'],
                    action: editor => {
                        const view = editor.currentView
                        setBlockType(view.state.schema.nodes.heading4)(view.state, view.dispatch)
                    },
                    available: editor => elementAvailable(editor, 'heading4'),
                    disabled: editor => elementDisabled(editor, 'heading4'),
                    order: 4
                },
                {
                    title: BLOCK_LABELS['heading5'],
                    action: editor => {
                        const view = editor.currentView
                        setBlockType(view.state.schema.nodes.heading5)(view.state, view.dispatch)
                    },
                    available: editor => elementAvailable(editor, 'heading5'),
                    disabled: editor => elementDisabled(editor, 'heading5'),
                    order: 5
                },
                {
                    title: BLOCK_LABELS['heading6'],
                    action: editor => {
                        const view = editor.currentView
                        setBlockType(view.state.schema.nodes.heading6)(view.state, view.dispatch)
                    },
                    available: editor => elementAvailable(editor, 'heading6'),
                    disabled: editor => elementDisabled(editor, 'heading6'),
                    order: 6
                },
                {
                    title: BLOCK_LABELS['code_block'],
                    action: editor => {
                        const view = editor.currentView
                        setBlockType(view.state.schema.nodes.code_block)(view.state, view.dispatch)
                    },
                    available: editor => elementAvailable(editor, 'code_block'),
                    disabled: editor => elementDisabled(editor, 'code_block'),
                    order: 7
                }
            ],
            order: 2
        },
        {
            type: 'button',
            title: gettext('Strong'),
            icon: 'bold',
            action: editor => {
                const mark = editor.currentView.state.schema.marks['strong']
                const command = toggleMark(mark)
                command(editor.currentView.state, tr => editor.currentView.dispatch(tr))
            },
            available: editor => markAvailable(editor, 'strong'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    editor.currentView.state.selection.jsonID === 'gapcursor' ||
                    markDisabled(editor, 'strong')
                ) {
                    return true
                }
            },
            selected: editor => {
                const storedMarks = editor.currentView.state.storedMarks
                if (
                    storedMarks?.some(mark => mark.type.name === 'strong') ||
                    editor.currentView.state.selection.$head.marks().some(mark => mark.type.name === 'strong')
                ) {
                    return true
                } else {
                    return false
                }

            },
            order: 3
        },
        {
            type: 'button',
            title: gettext('Emphasis'),
            icon: 'italic',
            action: editor => {
                const mark = editor.currentView.state.schema.marks['em']
                const command = toggleMark(mark)
                command(editor.currentView.state, tr => editor.currentView.dispatch(tr))
            },
            available: editor => markAvailable(editor, 'em'),
            disabled: editor =>
                READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    editor.currentView.state.selection.jsonID === 'gapcursor' ||
                    markDisabled(editor, 'em'),
            selected: editor => {
                const storedMarks = editor.currentView.state.storedMarks
                if (
                    storedMarks?.some(mark => mark.type.name === 'em') ||
                    editor.currentView.state.selection.$head.marks().some(mark => mark.type.name === 'em')
                ) {
                    return true
                } else {
                    return false
                }

            },
            order: 4
        },
        {
            type: 'button',
            title: gettext('Underline'),
            icon: 'underline',
            action: editor => {
                const mark = editor.currentView.state.schema.marks['underline']
                const command = toggleMark(mark)
                command(editor.currentView.state, tr => editor.currentView.dispatch(tr))
            },
            available: editor => markAvailable(editor, 'underline'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    editor.currentView.state.selection.jsonID === 'gapcursor' ||
                    markDisabled(editor, 'underline')
                ) {
                    return true
                }
            },
            selected: editor => {
                const storedMarks = editor.currentView.state.storedMarks
                if (
                    storedMarks?.some(mark => mark.type.name === 'underline') ||
                    editor.currentView.state.selection.$head.marks().some(mark => mark.type.name === 'underline')
                ) {
                    return true
                } else {
                    return false
                }

            },
            order: 5
        },
        {
            type: 'button',
            title: gettext('Numbered list'),
            icon: 'list-ol',
            action: editor => {
                const node = editor.currentView.state.schema.nodes['ordered_list']
                const command = wrapInList(node)
                command(editor.currentView.state, tr => editor.currentView.dispatch(tr))
            },
            available: editor => elementAvailable(editor, 'ordered_list'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    elementDisabled(editor, 'ordered_list')
                ) {
                    return true
                }
            },
            order: 6
        },
        {
            type: 'button',
            title: gettext('Bullet list'),
            icon: 'list-ul',
            action: editor => {
                const node = editor.currentView.state.schema.nodes['bullet_list']
                const command = wrapInList(node)
                command(editor.currentView.state, tr => editor.currentView.dispatch(tr))
            },
            available: editor => elementAvailable(editor, 'bullet_list'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    elementDisabled(editor, 'bullet_list')
                ) {
                    return true
                }
            },
            order: 7
        },
        {
            type: 'button',
            title: gettext('Blockquote'),
            icon: 'quote-right',
            action: editor => {
                const node = editor.currentView.state.schema.nodes['blockquote']
                const command = wrapIn(node)
                command(editor.currentView.state, tr => editor.currentView.dispatch(tr))
            },
            available: editor => elementAvailable(editor, 'blockquote'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    elementDisabled(editor, 'blockquote')
                ) {
                    return true
                }
            },
            order: 8
        },
        {
            id: 'link',
            type: 'button',
            title: gettext('Link'),
            icon: 'link',
            action: editor => {
                const dialog = new LinkDialog(editor)
                dialog.init()
            },
            available: editor => markAvailable(editor, 'link'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    (
                        markDisabled(editor, 'link') &&
                        elementDisabled(editor, 'cross_reference')
                    )
                ) {
                    return true
                }
            },
            selected: editor => editor.currentView.state.selection.$head.marks().some(mark => mark.type.name === 'link'),
            order: 9
        },
        {
            type: 'button',
            title: gettext('Footnote'),
            icon: 'asterisk',
            action: editor => {
                const node = editor.view.state.schema.nodes['footnote']
                const tr = editor.view.state.tr.replaceSelectionWith(node.createAndFill())
                editor.view.dispatch(tr)
                return false
            },
            available: editor => elementAvailable(editor, 'footnote'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    editor.view !== editor.currentView || // we don't allow footnotes in footnotes
                    elementDisabled(editor, 'footnote')
                ) {
                    return true
                }
            },
            order: 10
        },
        {
            type: 'button',
            title: gettext('Cite'),
            icon: 'book',
            action: editor => {
                const dialog = new CitationDialog(editor)
                dialog.init()
                return false
            },
            available: editor => elementAvailable(editor, 'citation'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    elementDisabled(editor, 'citation') ||
                    !['text', 'node'].includes(editor.currentView.state.selection.jsonID) ||
                    (
                        editor.currentView.state.selection.jsonID === 'node' &&
                        editor.currentView.state.selection.node.type.name !== 'citation'
                    )
                ) {
                    return true
                }
            },
            order: 11
        },
        {
            type: 'button',
            title: gettext('Horizontal line'),
            icon: 'minus',
            action: editor => {
                const view = editor.currentView,
                    state = view.state
                view.dispatch(
                    state.tr.replaceSelectionWith(state.schema.node("horizontal_rule"))
                )
            },
            available: editor => elementAvailable(editor, 'horizontal_rule'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    elementDisabled(editor, 'horizontal_rule')
                ) {
                    return true
                }
            },
            order: 12
        },
        {
            type: 'button',
            title: gettext('Math'),
            icon: 'percent',
            action: editor => {
                const dialog = new MathDialog(editor)
                dialog.init()
            },
            available: editor => elementAvailable(editor, 'equation'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    elementDisabled(editor, 'equation') ||
                    !['text', 'node'].includes(editor.currentView.state.selection.jsonID) ||
                    (
                        editor.currentView.state.selection.jsonID === 'node' &&
                        editor.currentView.state.selection.node.type.name !== 'equation'
                    )
                ) {
                    return true
                }
            },
            order: 13
        },
        {
            type: 'button',
            title: gettext('Figure'),
            icon: 'image',
            action: editor => {
                const dialog = new FigureDialog(editor)
                dialog.init()
                return false
            },
            available: editor => elementAvailable(editor, 'figure'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    elementDisabled(editor, 'figure')
                ) {
                    return true
                }
            },
            order: 14
        },
        {
            type: 'button',
            title: gettext('Table'),
            tooltip: gettext('Insert a table into the document.'),
            icon: 'table',
            action: editor => {
                const dialog = new TableDialog(editor)
                dialog.init()
                return false
            },
            available: editor => elementAvailable(editor, 'table'),
            disabled: editor => {
                if (
                    READ_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    COMMENT_ONLY_ROLES.includes(editor.docInfo.access_rights) ||
                    elementDisabled(editor, 'table') ||
                    findTable(editor.currentView.state)
                ) {
                    return true
                }
            },
            order: 15
        },
        {
            type: 'button',
            title: gettext('Undo'),
            icon: 'undo',
            action: editor => undo(editor.currentView.state, tr => editor.currentView.dispatch(tr.setMeta('inputType', 'historyUndo'))),
            disabled: editor => undoDepth(editor.currentView.state) === 0,
            order: 16
        },
        {
            type: 'button',
            title: gettext('Redo'),
            icon: 'redo',
            action: editor => redo(editor.currentView.state, tr => editor.currentView.dispatch(tr.setMeta('inputType', 'historyRedo'))),
            disabled: editor => redoDepth(editor.currentView.state) === 0,
            order: 17
        }
    ]
})
