// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'

import {IBlock} from '../blocks/block'
import {sendFlashMessage} from '../components/flashMessages'
import {WorkspaceComponent} from '../components/workspaceComponent'
import mutator from '../mutator'
import {OctoListener} from '../octoListener'
import {Utils} from '../utils'
import {BoardTree, MutableBoardTree} from '../viewModel/boardTree'
import {MutableWorkspaceTree, WorkspaceTree} from '../viewModel/workspaceTree'

type Props = {
    setLanguage: (lang: string) => void
}

type State = {
    boardId: string
    viewId: string
    workspaceTree: WorkspaceTree
    boardTree?: BoardTree
    readonly: boolean
}

export default class BoardPage extends React.Component<Props, State> {
    private workspaceListener = new OctoListener()

    constructor(props: Props) {
        super(props)
        const queryString = new URLSearchParams(window.location.search)
        const boardId = queryString.get('id') || ''
        const viewId = queryString.get('v') || ''
        const readonly = Boolean(queryString.get('r'))

        this.state = {
            boardId,
            viewId,
            workspaceTree: new MutableWorkspaceTree(),
            readonly,
        }

        Utils.log(`BoardPage. boardId: ${boardId}`)
    }

    shouldComponentUpdate(): boolean {
        return true
    }

    componentDidUpdate(prevProps: Props, prevState: State): void {
        Utils.log('componentDidUpdate')
        const board = this.state.boardTree?.board
        const prevBoard = prevState.boardTree?.board

        const activeView = this.state.boardTree?.activeView
        const prevActiveView = prevState.boardTree?.activeView

        if (board?.icon !== prevBoard?.icon) {
            Utils.setFavicon(board?.icon)
        }
        if (board?.title !== prevBoard?.title || activeView?.title !== prevActiveView?.title) {
            if (board) {
                let title = `${board.title}`
                if (activeView?.title) {
                    title += ` | ${activeView.title}`
                }
                document.title = title
            } else {
                document.title = 'OCTO'
            }
        }
    }

    private undoRedoHandler = async (e: KeyboardEvent) => {
        if (e.target !== document.body) {
            return
        }

        if (this.state.readonly) {
            return
        }

        if (e.keyCode === 90 && !e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Cmd+Z
            Utils.log('Undo')
            if (mutator.canUndo) {
                const description = mutator.undoDescription
                await mutator.undo()
                if (description) {
                    sendFlashMessage({content: `Undo ${description}`, severity: 'low'})
                } else {
                    sendFlashMessage({content: 'Undo', severity: 'low'})
                }
            } else {
                sendFlashMessage({content: 'Nothing to Undo', severity: 'low'})
            }
        } else if (e.keyCode === 90 && e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Shift+Cmd+Z
            Utils.log('Redo')
            if (mutator.canRedo) {
                const description = mutator.redoDescription
                await mutator.redo()
                if (description) {
                    sendFlashMessage({content: `Redo ${description}`, severity: 'low'})
                } else {
                    sendFlashMessage({content: 'Redu', severity: 'low'})
                }
            } else {
                sendFlashMessage({content: 'Nothing to Redo', severity: 'low'})
            }
        }
    }

    componentDidMount(): void {
        document.addEventListener('keydown', this.undoRedoHandler)
        if (this.state.boardId) {
            this.attachToBoard(this.state.boardId, this.state.viewId)
        } else {
            this.sync()
        }
    }

    componentWillUnmount(): void {
        Utils.log(`boardPage.componentWillUnmount: ${this.state.boardId}`)
        this.workspaceListener.close()
        document.removeEventListener('keydown', this.undoRedoHandler)
    }

    render(): JSX.Element {
        const {workspaceTree} = this.state

        Utils.log(`BoardPage.render ${this.state.boardTree?.board?.title}`)
        return (
            <div className='BoardPage'>
                <WorkspaceComponent
                    workspaceTree={workspaceTree}
                    boardTree={this.state.boardTree}
                    showView={(id, boardId) => {
                        this.showView(id, boardId)
                    }}
                    showBoard={(id) => {
                        this.showBoard(id)
                    }}
                    setSearchText={(text) => {
                        this.setSearchText(text)
                    }}
                    setLanguage={this.props.setLanguage}
                    readonly={this.state.readonly}
                />
            </div>
        )
    }

    private async attachToBoard(boardId?: string, viewId = '') {
        Utils.log(`attachToBoard: ${boardId}`)
        if (boardId) {
            this.sync(boardId, viewId)
        } else {
            // No board
            this.setState({
                boardTree: undefined,
                boardId: '',
                viewId: '',
            })
        }
    }

    private async sync(boardId: string = this.state.boardId, viewId: string | undefined = this.state.viewId) {
        Utils.log(`sync start: ${boardId}`)

        const workspaceTree = await MutableWorkspaceTree.sync()
        const boardIds = [...workspaceTree.boards.map((o) => o.id), ...workspaceTree.boardTemplates.map((o) => o.id)]
        this.setState({workspaceTree})

        // Listen to boards plus all blocks at root (Empty string for parentId)
        this.workspaceListener.open(
            ['', ...boardIds],
            async (blocks) => {
                Utils.log(`workspaceListener.onChanged: ${blocks.length}`)
                this.incrementalUpdate(blocks)
            },
            () => {
                Utils.log('workspaceListener.onReconnect')
                this.sync()
            },
        )

        if (boardId) {
            const boardTree = await MutableBoardTree.sync(boardId, viewId)

            if (boardTree && boardTree.board) {
                // Update url with viewId if it's different
                if (boardTree.activeView.id !== this.state.viewId) {
                    Utils.replaceUrlQueryParam('v', boardTree.activeView.id)
                }

                // TODO: Handle error (viewId not found)

                this.setState({
                    boardTree,
                    boardId,
                    viewId: boardTree.activeView!.id,
                })
                Utils.log(`sync complete: ${boardTree.board?.id} (${boardTree.board?.title})`)
            } else {
                // Board may have been deleted
                this.setState({
                    boardTree: undefined,
                    viewId: '',
                })
                Utils.log(`sync complete: board ${boardId} not found`)
            }
        }
    }

    private async incrementalUpdate(blocks: IBlock[]) {
        const {workspaceTree, boardTree, viewId} = this.state

        let newState = {workspaceTree, boardTree, viewId}

        const newWorkspaceTree = MutableWorkspaceTree.incrementalUpdate(workspaceTree, blocks)
        if (newWorkspaceTree) {
            newState = {...newState, workspaceTree: newWorkspaceTree}
        }

        let newBoardTree: BoardTree | undefined
        if (boardTree) {
            newBoardTree = MutableBoardTree.incrementalUpdate(boardTree, blocks)
        } else if (this.state.boardId) {
            // Corner case: When the page is viewing a deleted board, that is subsequently un-deleted on another client
            newBoardTree = await MutableBoardTree.sync(this.state.boardId, this.state.viewId)
        }

        if (newBoardTree) {
            newState = {...newState, boardTree: newBoardTree, viewId: newBoardTree.activeView.id}
        } else {
            newState = {...newState, boardTree: undefined}
        }

        // Update url with viewId if it's different
        if (newBoardTree && newBoardTree.activeView.id !== this.state.viewId) {
            Utils.replaceUrlQueryParam('v', newBoardTree?.activeView.id)
        }

        this.setState(newState)
    }

    // IPageController
    showBoard(boardId?: string): void {
        const {boardTree} = this.state

        if (boardTree?.board?.id === boardId) {
            return
        }

        let newUrl = window.location.protocol + '//' + window.location.host + window.location.pathname
        if (boardId) {
            newUrl += `?id=${encodeURIComponent(boardId)}`

            if (this.state.readonly) {
                newUrl += '&r=1'
            }
        }
        window.history.pushState({path: newUrl}, '', newUrl)

        this.attachToBoard(boardId)
    }

    showView(viewId: string, boardId: string = this.state.boardId): void {
        if (this.state.boardTree && this.state.boardId === boardId) {
            const newBoardTree = this.state.boardTree.copyWithView(viewId)
            this.setState({boardTree: newBoardTree, viewId})
        } else {
            this.attachToBoard(boardId, viewId)
        }

        let newUrl = window.location.protocol + '//' + window.location.host + window.location.pathname + `?id=${encodeURIComponent(boardId)}&v=${encodeURIComponent(viewId)}`
        if (this.state.readonly) {
            newUrl += '&r=1'
        }
        window.history.pushState({path: newUrl}, '', newUrl)
    }

    setSearchText(text?: string): void {
        if (!this.state.boardTree) {
            Utils.assertFailure('setSearchText: boardTree')
            return
        }

        const newBoardTree = this.state.boardTree.copyWithSearchText(text)
        this.setState({boardTree: newBoardTree})
    }
}
