import type { PJ } from '@lastres/pj'
import type { Location } from '@lastres/location'
import type InputPacket from '@lastres/input-packet'
import type { LogLine } from '@lastres/log-line'
import type { ActionHash } from '@lastres/action'
import InputPacketInfo from '@lastres/input-packet/info'
import InputPacketPong from '@lastres/input-packet/pong'

type SetTeamPJs = (set: PJ[] | null) => void
type SetIsBattling = (set: boolean | null) => void
type SetEnemyTeamPJs = (set: PJ[] | null) => void
type SetCurrentLocation = (set: Location | null) => void
type SetConnectedLocations = (set: Location[] | null) => void
type DispatchHash = Record<string, any>
type SetLogLinesCallback = (set: LogLine[] | null) => LogLine[]
type SetLogLines = (set: LogLine[] | SetLogLinesCallback | null) => void
type SetError = (set: string | null) => void
type SetScrollLogCallback = (set: number | null) => number | null
type SetScrollLog = (set: number | null | SetScrollLogCallback) => void
type LogPresentationRef = React.RefObject<HTMLDivElement>
type SetMovingTo = (set: Location | null) => void
type SetRemainingFrames = (set: number | null) => void
type SetActionHash = (set: ActionHash | null) => void

interface Packet {
    command: string
    data: any
}
type LogHash = Record<string, LogLine>

export default class InputPackets {
    setTeamPJs: SetTeamPJs
    setEnemyTeamPJs: SetEnemyTeamPJs
    setIsBattling: SetIsBattling
    setCurrentLocation: SetCurrentLocation
    setConnectedLocations: SetConnectedLocations
    setLogLines: SetLogLines
    logLines: LogLine[] | null
    cachedHash: DispatchHash | null = null
    cachedArray: InputPacket[] | null = null
    setError: SetError
    setScrollLog: SetScrollLog
    logPresentationRef: LogPresentationRef
    setMovingTo: SetMovingTo
    setRemainingFrames: SetRemainingFrames
    setActionHash: SetActionHash
    constructor (setTeamPJs: SetTeamPJs,
        setEnemyTeamPJs: SetEnemyTeamPJs,
        setIsBattling: SetIsBattling,
        setCurrentLocation: SetCurrentLocation,
        setConnectedLocations: SetConnectedLocations,
        logLines: LogLine[] | null,
        setLogLines: SetLogLines,
        setError: SetError,
        setScrollLog: SetScrollLog,
        logPresentationRef: LogPresentationRef,
        setMovingTo: SetMovingTo,
        setRemainingFrames: SetRemainingFrames,
        setActionHash: SetActionHash) {
        this.setTeamPJs = setTeamPJs
        this.setEnemyTeamPJs = setEnemyTeamPJs
        this.setCurrentLocation = setCurrentLocation
        this.setConnectedLocations = setConnectedLocations
        this.logLines = logLines
        this.setLogLines = setLogLines
        this.setError = setError
        this.setScrollLog = setScrollLog
        this.logPresentationRef = logPresentationRef
        this.setMovingTo = setMovingTo
        this.setRemainingFrames = setRemainingFrames
        this.setIsBattling = setIsBattling
        this.setActionHash = setActionHash
    }

    handle (packet: Packet): void {
        const hash = this.hashAvailablePackets()
        const identifier = packet.command
        const inputPacket = hash[identifier]
        inputPacket.recv(packet)
    }

    listAvailablePackets (): InputPacket[] {
        let firstTime = true
        if (this.cachedArray === null) {
            const infoPacket = new InputPacketInfo()
            const pongPacket = new InputPacketPong()
            infoPacket.onReceive((data) => {
                const logPresentationRef = this.logPresentationRef
                let scrollData: number[] = []
                function saveScroll (): void {
                    if (logPresentationRef.current === null) {
                        return
                    }
                    scrollData = [logPresentationRef.current.scrollHeight, logPresentationRef.current.scrollTop, logPresentationRef.current.offsetHeight]
                }
                function applyScroll (): void {
                    if (scrollData.length < 3) {
                        return
                    }
                    if (logPresentationRef.current === null) {
                        console.log('Not defined')
                        return
                    }
                    const logPresentation = logPresentationRef.current
                    const [scrollHeight, scrollTop, offsetHeight] = scrollData
                    if (firstTime) {
                        firstTime = false
                        logPresentation.scrollTo(0, logPresentation.scrollHeight)
                        return
                    }
                    if (scrollHeight <= scrollTop + offsetHeight * (3 / 2)) {
                        logPresentation.scrollTo(0, logPresentation.scrollHeight)
                    }
                }
                if (data.error !== undefined) {
                    this.setError(data.error)
                    return
                }
                if (data.team_pjs !== undefined) {
                    this.setTeamPJs(data.team_pjs)
                }
                if (data.enemy_team_pjs !== undefined) {
                    this.setEnemyTeamPJs(data.enemy_team_pjs)
                }
                if (data.is_battling !== undefined) {
                    this.setIsBattling(data.is_battling)
                }
                if (data.location_data !== undefined) {
                    console.log(data.location_data)
                    if (data.location_data.connected_places !== undefined) {
                        this.setConnectedLocations(data.location_data.connected_places)
                    }
                    if (data.location_data.current !== undefined) {
                        this.setCurrentLocation(data.location_data.current)
                    }
                    if (data.location_data.moving_to !== undefined) {
                        this.setMovingTo(data.location_data.moving_to)
                    } else {
                        this.setMovingTo(null)
                    }
                }
                if (data.remaining_frames !== undefined) {
                    this.setRemainingFrames(data.remaining_frames)
                }
                if (data.set_log !== undefined) {
                    saveScroll()
                    this.setLogLines(data.set_log)
                    window.setTimeout(() => {
                        applyScroll()
                    }, 10)
                }
                if (data.append_log !== undefined) {
                    const logHash: LogHash = {}
                    saveScroll()
                    this.setLogLines((logLines: LogLine[] | null): LogLine[] => {
                        if (logLines !== null) {
                            for (const item of logLines) {
                                logHash[item.uuid] = item
                            }
                            logHash[data.append_log.uuid] = data.append_log
                            const outputLog: LogLine[] = Object.keys(logHash).map((item, i): LogLine => {
                                return logHash[item]
                            })
                            return outputLog
                        }
                        return []
                    })
                    window.setTimeout(() => {
                        applyScroll()
                    }, 10)
                }
                if (data.available_actions !== undefined) {
                    this.setActionHash(data.available_actions)
                }
            })
            this.cachedArray = [infoPacket, pongPacket]
        }
        return this.cachedArray
    }

    hashAvailablePackets (): DispatchHash {
        if (this.cachedHash === null) {
            this.cachedHash = {}
            for (const inputPacket of this.listAvailablePackets()) {
                this.cachedHash[inputPacket.identifier()] = inputPacket
            }
        }
        return this.cachedHash
    }
}
