import {animated, useSpring} from "@react-spring/web";
import {ReactElement, useMemo} from "react";
import {evaluateResourceBarStyles, ResourceBarColors, ResourceBarStyles} from "./resource_bar";
import {isDefined} from "./type_check";
import {SpringyValueInterpolatables, useSpringyValue} from "./SpringyValueHook";
import "./CharacterStatus.css";
import DefaultPortrait from "./default-portrait.svg";
import OverlayTrigger from "react-bootstrap/OverlayTrigger";
import Tooltip from "react-bootstrap/Tooltip";
import {to} from "@react-spring/web";

export enum CharacterHealth {
    Full = "Full",
    Healthy = "Healthy",
    Wounded = "Wounded",
    Crisis = "Crisis",
    Peril = "Peril",
    KO = "KO",
}

export function healthToColor(health: CharacterHealth | undefined): string {
    switch (health) {
        case CharacterHealth.Full:
            return "#cfc"
        case CharacterHealth.Healthy:
            return "#efe"
        case CharacterHealth.Crisis:
            return "#ffa"
        case CharacterHealth.Peril:
            return "#faa"
        case CharacterHealth.KO:
            return "#f66"
        case CharacterHealth.Wounded:
        default:
            return "#fff"
    }
}

export function healthToFraction(health: CharacterHealth | undefined): number {
    switch (health) {
        case CharacterHealth.Full:
            return 1
        case CharacterHealth.Healthy:
            return 0.95
        case CharacterHealth.Crisis:
            return 0.40
        case CharacterHealth.Peril:
            return 0.05
        case CharacterHealth.KO:
            return 0
        case CharacterHealth.Wounded:
        case undefined:
        default:
            return 0.75
    }
}

export function hpToHealth(hp: number | undefined, maxHp: number | undefined): CharacterHealth | undefined {
    if (!(isDefined(hp) && isDefined(maxHp)) || maxHp <= 0) {
        return undefined
    }
    if (Math.round(hp) >= maxHp) {
        return CharacterHealth.Full
    } else if (Math.round(hp) * 10 >= maxHp * 9) {
        return CharacterHealth.Healthy
    } else if (Math.round(hp) * 2 > maxHp) {
        return CharacterHealth.Wounded
    } else if (Math.round(hp) * 10 > maxHp) {
        return CharacterHealth.Crisis
    } else if (Math.round(hp) >= 1) {
        return CharacterHealth.Peril
    } else {
        return CharacterHealth.KO
    }
}

export function healthToBounds(health: CharacterHealth | undefined): string {
    switch (health) {
        case CharacterHealth.Full:
            return "100%"
        case CharacterHealth.Healthy:
            return "90-99%"
        case CharacterHealth.Wounded:
            return "51-99%"
        case CharacterHealth.Crisis:
            return "11-50%"
        case CharacterHealth.Peril:
            return "1-10%"
        case CharacterHealth.KO:
            return "0%"
        default:
            return "???"
    }
}

export enum CharacterTurnState {
    None = "None",
    Ready = "Ready",
    HighTurns = "HighTurns",
    Done = "Done",
    CantAct = "CantAct",
    Downed = "Downed",
    Active = "Active",
}

export function turnStateToTitle(state: CharacterTurnState): string {
    switch (state) {
        case CharacterTurnState.Active:
            return "Active"
        case CharacterTurnState.Ready:
            return "Ready"
        case CharacterTurnState.HighTurns:
            return "Multiple Turns"
        case CharacterTurnState.Done:
            return "Done"
        case CharacterTurnState.CantAct:
            return "Can't Act"
        case CharacterTurnState.Downed:
            return "Downed"
        case CharacterTurnState.None:
        default:
            return "None"
    }
}

export function turnStateToDescription(state: CharacterTurnState): string {
    switch (state) {
        case CharacterTurnState.Active:
            return "Currently taking a turn."
        case CharacterTurnState.Ready:
            return "Has not acted yet this round."
        case CharacterTurnState.HighTurns:
            return "Has %c% turns left out of %m% turns. Must still alternate with opponents."
        case CharacterTurnState.Done:
            return "Has finished acting this round."
        case CharacterTurnState.CantAct:
            return "Is currently unable to act."
        case CharacterTurnState.Downed:
            return "Has 0 HP. Is currently down and out of the action and unable to act."
        case CharacterTurnState.None:
        default:
            return "Cannot take turns."
    }
}

export enum SPType {
    UltimaPoints = "Ultima",
    FabulaPoints = "Fabula",
}

function spTypeToDescription(sp: SPType): string {
    switch (sp) {
        case SPType.UltimaPoints:
            return ("The number of Ultima Points. Ultima Points can be used to make a getaway, "
                + "recover MP and clear status effects, or perform special villainy moves.")
        case SPType.FabulaPoints:
            return ("The number of Fabula Points. Fabula Points can be used to buy rerolls by "
            + "invoking your Traits, boost your rolls by invoking your Bonds, or add elements to the story.")
    }
}

export interface StatusEffect {
    readonly name: string
    readonly count?: number
    readonly iconUrl: string
    readonly description?: string
}

export interface Character {
    readonly portraitUrl?: string
    readonly name?: string
    readonly level?: number
    readonly hp?: number
    readonly maxHp?: number
    readonly health?: CharacterHealth
    readonly mp?: number
    readonly maxMp?: number
    readonly ip?: number
    readonly maxIp?: number
    readonly sp?: number
    readonly spType?: SPType
    readonly turnsLeft?: number
    readonly turnsTotal?: number
    readonly canAct?: boolean
    readonly statuses?: readonly StatusEffect[]
}

const hpBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
    foreground: 'linear-gradient(to bottom, rgb(255, 255, 255, 0.1) 0%, rgb(0, 0, 0, 0) 30% 50%, rgb(0, 0, 0, 0.1) 80%, rgb(0, 0, 0, 0.2) 95%, rgb(0, 0, 0, 0.3) 100%)',
    barDirection: "to right",
    barColors: ({flashValue}: {flashValue: number}): ResourceBarColors => {
        return {
            emptiedColor: `rgb(${Math.min(1, Math.max(flashValue, 0)) * 55 + 55}, 55, 55)`,
            toEmptyColor: 'rgb(200, 0, 0)',
            toFillColor: 'rgb(150, 250, 250)',
            filledColor: 'rgb(0, 200, 0)',
        }
    },
}

const mpBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
    foreground: 'linear-gradient(to bottom, rgb(255, 255, 255, 0.1) 0%, rgb(0, 0, 0, 0) 30% 50%, rgb(0, 0, 0, 0.1) 80%, rgb(0, 0, 0, 0.2) 95%, rgb(0, 0, 0, 0.3) 100%)',
    barColors: ({flashValue}: {flashValue: number}): ResourceBarColors => {
        return {
            emptiedColor: `rgb(${Math.min(1, Math.max(flashValue, 0)) * 55 + 55}, 55, 55)`,
            toEmptyColor: 'rgb(250, 250, 60)',
            toFillColor: 'rgb(150, 250, 250)',
            filledColor: 'rgb(0, 150, 255)',
        }
    },
}

const ipBarStyle: SpringyValueInterpolatables<ResourceBarStyles> = {
    foreground: 'linear-gradient(to bottom, rgb(255, 255, 255, 0.1) 0%, rgb(0, 0, 0, 0) 30% 50%, rgb(0, 0, 0, 0.1) 80%, rgb(0, 0, 0, 0.2) 95%, rgb(0, 0, 0, 0.3) 100%)',
    barColors: ({flashValue}: {flashValue: number}): ResourceBarColors => {
        return {
            emptiedColor: `rgb(${Math.min(1, Math.max(flashValue, 0)) * 55 + 55}, 55, 55)`,
            toEmptyColor: 'rgb(160, 55, 195)',
            toFillColor: 'rgb(250, 250, 60)',
            filledColor: 'rgb(255, 150, 55)',
        }
    },
}

export function CharacterStatus({character, active}: {character: Character, active: boolean}): ReactElement {
    const {name, level, health, statuses} = character

    const {hp, maxHp} = character
    const effectiveMaxHp = maxHp ?? 100
    const effectiveHp = hp ?? (healthToFraction(health) * effectiveMaxHp)
    const {springs: [, , {v: hpRecentSpring}], flashSpring: {v: hpFlashSpring}, interpolate: hpInterpolate} = useSpringyValue({
        current: effectiveHp,
        max: effectiveMaxHp,
        flash: effectiveHp * 2 <= effectiveMaxHp && effectiveHp > 0,
    })
    const {hpText, hpTextStyleInterpolated, hpBarStyleInterpolated} = useMemo(() => {
        if ((isDefined(hp) && isDefined(maxHp)) || isDefined(health)) {
            return {
                hpText: isDefined(hp)
                    ? to([hpRecentSpring], recentValue => `${Math.round(recentValue)}`)
                    : to([hpRecentSpring], recentValue => hpToHealth(recentValue, maxHp) ?? "???"),
                hpBarStyleInterpolated: evaluateResourceBarStyles(hpBarStyle, hpInterpolate),
                hpTextStyleInterpolated: {
                    color: to([hpRecentSpring], recentValue => healthToColor(hpToHealth(recentValue, maxHp)))
                }
            }
        } else {
            return {}
        }
    }, [hp, health, maxHp, hpRecentSpring, hpInterpolate])

    const {mp, maxMp} = character
    const {springs: [, , {v: mpRecentSpring}], interpolate: mpInterpolate} = useSpringyValue({
        current: mp,
        max: maxMp,
        flash: false,
    })
    const {mpText, mpBarStyleInterpolated} = useMemo(() => {
        if (isDefined(mp) && isDefined(maxMp) && maxMp > 0) {
            return {
                mpText: to([mpRecentSpring], (recentValue) => `${Math.round(recentValue)}`),
                mpBarStyleInterpolated: evaluateResourceBarStyles(mpBarStyle, mpInterpolate),
            }
        } else {
            return {}
        }
    }, [mp, maxMp, mpRecentSpring, mpInterpolate])

    const {ip, maxIp} = character
    const {springs: [, , {v: ipRecentSpring}], interpolate: ipInterpolate} = useSpringyValue({
        current: ip,
        max: maxIp,
        flash: false,
    })
    const {ipText, ipBarStyleInterpolated} = useMemo(() => {
        if (isDefined(ip) && isDefined(maxIp) && maxIp > 0) {
            return {
                ipText: to([ipRecentSpring], recentValue => `${Math.round(recentValue)}`),
                ipBarStyleInterpolated: evaluateResourceBarStyles(ipBarStyle, ipInterpolate),
            }
        } else {
            return {}
        }
    }, [ip, maxIp, ipRecentSpring, ipInterpolate])

    const {sp, spType} = character
    const {springs: [, , {v: spRecentSpring}]} = useSpringyValue({
        current: sp,
        flash: isDefined(spType) && isDefined(sp) && sp > 0,
    })
    const {spText} = useMemo(() => {
        if (isDefined(sp) && isDefined(spType)) {
            return {
                spText: to([spRecentSpring], (recentValue) => recentValue.toFixed(0))
            }
        } else {
            return {}
        }
    }, [sp, spType, spRecentSpring])

    const {turnsLeft, turnsTotal, canAct} = character
    const {turnsState, turnsText} = useMemo(() => {
        if (!isDefined(turnsTotal) || !isDefined(turnsLeft)) {
            return {
                turnsState: CharacterTurnState.None
            }
        } else if (active) {
            return {
                turnsState: CharacterTurnState.Active,
                turnsText: "🞂",
            }
        } else if (hp === 0 && isDefined(maxHp) && maxHp > 0) {
            return {
                turnsState: CharacterTurnState.Downed,
                turnsText: (isDefined(turnsTotal) && turnsLeft === 0) ? "✓" : "",
            }
        } else if (canAct === false || turnsTotal === 0) {
            return {
                turnsState: CharacterTurnState.CantAct,
            }
        } else if (turnsLeft === 0) {
            return {
                turnsState: CharacterTurnState.Done,
                turnsText: "✓"
            }
        } else if (turnsTotal > 1) {
            return {
                turnsState: CharacterTurnState.HighTurns,
                turnsText: `${turnsLeft}`
            }
        } else {
            return {
                turnsState: CharacterTurnState.Ready,
            }
        }
    }, [active, hp, maxHp, canAct, turnsLeft, turnsTotal])

    const {portraitUrl} = character
    const effectivePortraitUrl = portraitUrl ?? DefaultPortrait
    const portraitFilterInterpolated = useMemo(() => {
        return to([hpRecentSpring], recentValue => {
            const filter = {
                color: 100,
                brightness: 100,
            }
            if (isDefined(effectiveMaxHp) && Math.round(recentValue) < 1 && effectiveMaxHp > 0) {
                filter.color *= 0.50
                filter.brightness *= 0.25
            }
            if (canAct === false || turnsTotal === 0) {
                filter.color *= 0.50
                filter.brightness *= 0.50
            }
            if (isDefined(turnsTotal) && turnsLeft === 0) {
                filter.color *= 0.75
                filter.brightness *= 0.75
            }
            return filter
        })
    }, [hpRecentSpring, effectiveMaxHp, turnsTotal, turnsLeft, canAct])
    const {brightness: brightnessSpring, grayscale: grayscaleSpring} = useSpring({
        grayscale: to([portraitFilterInterpolated], ({color}) => 100 - color),
        brightness: to([portraitFilterInterpolated], ({brightness}) => brightness),
    })
    const characterPortraitStyleInterpolated = useMemo(() => {
        return {
            backgroundImage: to([hpFlashSpring], (flashValue: number) => {
                return `radial-gradient(closest-side, rgb(75% 0% 0% / ${Math.round(100 * flashValue)}%), transparent), url("${effectivePortraitUrl}")`
            }),
            filter: to([brightnessSpring, grayscaleSpring],
                (brightness: number, grayscale: number) =>
                    `grayscale(${grayscale}%) brightness(${brightness}%)`)
        }
    }, [brightnessSpring, grayscaleSpring, hpFlashSpring, effectivePortraitUrl])

    const hpTooltip = <Tooltip>
        <div className={"characterHelpHeader"}>
            <span className={"characterHelpName"}>{isDefined(maxHp) && isDefined(hp) ? "Health Points" : health}</span>
            <span className={"characterHelpValue"}>{isDefined(maxHp) && isDefined(hp) ? `${hp}/${maxHp}` : (healthToBounds(health))}</span></div>
        {<div className={"characterHelpDescription"}>
            {isDefined(hp)
                ? "Health Points, or HP. HP represent a character's determination and will. "
                +  "HP are lost when taking damage. When a character's HP reach 0, they are defeated."
                : "The enemy's condition, giving a rough estimate of its current HP."}
        </div>}
    </Tooltip>
    const mpTooltip = <Tooltip>
        <div className={"characterHelpHeader"}>
            <span className={"characterHelpName"}>Mind Points</span>
            {isDefined(maxMp) && isDefined(mp) && <span className={"characterHelpValue"}>{mp}/{maxMp}</span>}
        </div>
        <div className={"characterHelpDescription"}>
            Mind Points, or MP. MP represent a character's focus and energy. MP are spent to use all manner of abilities.
        </div>
    </Tooltip>
    const ipTooltip = <Tooltip>
        <div className={"characterHelpHeader"}>
            <span className={"characterHelpName"}>Inventory Points</span>
            {isDefined(maxIp) && isDefined(ip) && <span className={"characterHelpValue"}>{ip}/{maxIp}</span>}
        </div>
        <div className={"characterHelpDescription"}>
            Inventory Points, or IP. IP represent a character's stock of prepared items. IP are spent to use items.
        </div>
    </Tooltip>

    return <div className="characterStatus">
        <animated.div className={"characterPortrait"} style={characterPortraitStyleInterpolated} />
        {isDefined(turnsState) &&
            <OverlayTrigger delay={{show: 750, hide: 0}} overlay={
                <Tooltip>
                    <div className={"characterHelpHeader"}>
                        <span className={"characterHelpName"}>{turnStateToTitle(turnsState)}</span>
                        {isDefined(turnsTotal) && isDefined(turnsLeft) && turnsTotal > 1 && <span className={"characterHelpValue"}>{turnsLeft}/{turnsTotal}</span>}</div>
                    {<div className={"characterHelpDescription"}>
                        {isDefined(turnsTotal) && isDefined(turnsLeft) &&
                            turnStateToDescription(turnsState)
                                .replaceAll("%c%", turnsLeft.toFixed(0))
                                .replaceAll("%m%", turnsTotal.toFixed(0))}
                    </div>}
                </Tooltip>
            } placement={"right"}>
                <div className={"characterTurns characterTurns" + turnsState}>{turnsText}</div>
            </OverlayTrigger>}
        <div className={"characterHeader"}>
            <div className="characterLevel">
                    <span className="characterLevelLabel">Lv</span>
                    <span className="characterLevelValue">{level ?? "??"}</span>
                </div>
            <div className={"characterName"}>{name ?? "???"}</div>
        </div>
        {isDefined(hpText) &&
            <div className={"characterHp"}>
                <OverlayTrigger delay={{show: 750, hide: 0}} overlay={hpTooltip} placement={"top"}>
                    <animated.div className={"characterHpBar"} style={hpBarStyleInterpolated} />
                </OverlayTrigger>
                <OverlayTrigger delay={{show: 750, hide: 0}} overlay={hpTooltip} placement={"top"}>
                    <animated.div
                        className={isDefined(hp) ? "characterHpValue" : "characterHealthText"}
                        style={hpTextStyleInterpolated}>{hpText}</animated.div>
                </OverlayTrigger>
            </div>}
        {isDefined(mpText) &&
            <div className={"characterMp"}>
                <OverlayTrigger delay={{show: 750, hide: 0}} overlay={mpTooltip} placement={"top"}>
                    <animated.div className={"characterMpBar"} style={mpBarStyleInterpolated} />
                </OverlayTrigger>
                <OverlayTrigger delay={{show: 750, hide: 0}} overlay={mpTooltip} placement={"top"}>
                    <animated.div className={"characterMpValue"}>{mpText}</animated.div>
                </OverlayTrigger>
            </div>}
        {isDefined(ipText) &&
                <div className={"characterIp"}>
                    <OverlayTrigger delay={{show: 750, hide: 0}} overlay={ipTooltip} placement={"top"}>
                        <animated.div className={"characterIpBar"} style={ipBarStyleInterpolated} />
                    </OverlayTrigger>
                    <OverlayTrigger delay={{show: 750, hide: 0}} overlay={ipTooltip} placement={"top"}>
                        <animated.div className={"characterIpValue"}>{ipText}</animated.div>
                    </OverlayTrigger>
                </div>
}
        {isDefined(spText) &&
            <OverlayTrigger delay={{show: 750, hide: 0}} overlay={
                <Tooltip>
                    <div className={"characterHelpHeader"}>
                        <span className={"characterHelpName"}>{spType} Points</span>
                        <span className={"characterHelpValue"}>{sp}</span></div>
                    {isDefined(spType) && <div className={"characterHelpDescription"}>
                        {spTypeToDescription(spType)}
                    </div>}
                </Tooltip>
            } placement={"right"}>
                <animated.div className={"characterSp characterSp" + spType}>
                    <animated.span className={"characterSpValue characterSpValue" + spType}>
                        {spText}</animated.span>
                </animated.div>
            </OverlayTrigger>}
        {isDefined(statuses) &&
            <div className={"characterStatuses"}>
                {statuses.map(({name, count, description, iconUrl}) =>
                    <OverlayTrigger key={iconUrl} delay={{show: 300, hide: 0}} overlay={
                        <Tooltip>
                            <div className={"characterStatusHeader"}>
                                <span className={"characterStatusName"}>{name}</span>
                                {isDefined(count) && <span className={"characterStatusCount"}>{count}</span>}</div>
                            {isDefined(description) && <div className={"characterStatusDescription"}>
                                {isDefined(count) ? description.replaceAll("%c%", count.toFixed(0)) : description}
                            </div>}
                        </Tooltip>
                    } placement={"bottom"}>
                        <div className={"characterStatusIcon"} style={{backgroundImage: `url("${iconUrl}")`}}><span className={"characterStatusIconCountBadge"}>{count}</span></div>
                    </OverlayTrigger>
                )}
            </div>}
    </div>
}
