import chroma, { Color, Scale } from "chroma-js"
import {
    ColorScheme,
    Layer,
    Player,
    RampSet,
    Style,
    Styles,
    StyleSet,
    ThemeSyntax,
} from "./colorScheme"

export function colorRamp(color: Color): Scale {
    let endColor = color.desaturate(1).brighten(5)
    let startColor = color.desaturate(1).darken(4)
    return chroma.scale([startColor, color, endColor]).mode("lab")
}

export function createColorScheme(
    name: string,
    isLight: boolean,
    colorRamps: { [rampName: string]: Scale },
    syntax?: ThemeSyntax
): ColorScheme {
    // Chromajs scales from 0 to 1 flipped if isLight is true
    let ramps: RampSet = {} as any

    // Chromajs mutates the underlying ramp when you call domain. This causes problems because
    // we now store the ramps object in the theme so that we can pull colors out of them.
    // So instead of calling domain and storing the result, we have to construct new ramps for each
    // theme so that we don't modify the passed in ramps.
    // This combined with an error in the type definitions for chroma js means we have to cast the colors
    // function to any in order to get the colors back out from the original ramps.
    if (isLight) {
        for (var rampName in colorRamps) {
            ;(ramps as any)[rampName] = chroma.scale(
                colorRamps[rampName].colors(100).reverse()
            )
        }
        ramps.neutral = chroma.scale(colorRamps.neutral.colors(100).reverse())
    } else {
        for (var rampName in colorRamps) {
            ;(ramps as any)[rampName] = chroma.scale(
                colorRamps[rampName].colors(100)
            )
        }
        ramps.neutral = chroma.scale(colorRamps.neutral.colors(100))
    }

    let lowest = lowestLayer(ramps)
    let middle = middleLayer(ramps)
    let highest = highestLayer(ramps)

    let popoverShadow = {
        blur: 4,
        color: ramps
            .neutral(isLight ? 7 : 0)
            .darken()
            .alpha(0.2)
            .hex(), // TODO used blend previously. Replace with something else
        offset: [1, 2],
    }

    let modalShadow = {
        blur: 16,
        color: ramps
            .neutral(isLight ? 7 : 0)
            .darken()
            .alpha(0.2)
            .hex(), // TODO used blend previously. Replace with something else
        offset: [0, 2],
    }

    let players = {
        "0": player(ramps.blue),
        "1": player(ramps.green),
        "2": player(ramps.magenta),
        "3": player(ramps.orange),
        "4": player(ramps.violet),
        "5": player(ramps.cyan),
        "6": player(ramps.red),
        "7": player(ramps.yellow),
    }

    return {
        name,
        isLight,

        ramps,

        lowest,
        middle,
        highest,

        popoverShadow,
        modalShadow,

        players,
        syntax,
    }
}

function player(ramp: Scale): Player {
    return {
        selection: ramp(0.5).alpha(0.24).hex(),
        cursor: ramp(0.5).hex(),
    }
}

function lowestLayer(ramps: RampSet): Layer {
    return {
        base: buildStyleSet(ramps.neutral, 0.2, 1),
        variant: buildStyleSet(ramps.neutral, 0.2, 0.7),
        on: buildStyleSet(ramps.neutral, 0.1, 1),
        accent: buildStyleSet(ramps.blue, 0.1, 0.5),
        positive: buildStyleSet(ramps.green, 0.1, 0.5),
        warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
        negative: buildStyleSet(ramps.red, 0.1, 0.5),
    }
}

function middleLayer(ramps: RampSet): Layer {
    return {
        base: buildStyleSet(ramps.neutral, 0.1, 1),
        variant: buildStyleSet(ramps.neutral, 0.1, 0.7),
        on: buildStyleSet(ramps.neutral, 0, 1),
        accent: buildStyleSet(ramps.blue, 0.1, 0.5),
        positive: buildStyleSet(ramps.green, 0.1, 0.5),
        warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
        negative: buildStyleSet(ramps.red, 0.1, 0.5),
    }
}

function highestLayer(ramps: RampSet): Layer {
    return {
        base: buildStyleSet(ramps.neutral, 0, 1),
        variant: buildStyleSet(ramps.neutral, 0, 0.7),
        on: buildStyleSet(ramps.neutral, 0.1, 1),
        accent: buildStyleSet(ramps.blue, 0.1, 0.5),
        positive: buildStyleSet(ramps.green, 0.1, 0.5),
        warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
        negative: buildStyleSet(ramps.red, 0.1, 0.5),
    }
}

function buildStyleSet(
    ramp: Scale,
    backgroundBase: number,
    foregroundBase: number,
    step: number = 0.08
): StyleSet {
    let styleDefinitions = buildStyleDefinition(
        backgroundBase,
        foregroundBase,
        step
    )

    function colorString(indexOrColor: number | Color): string {
        if (typeof indexOrColor === "number") {
            return ramp(indexOrColor).hex()
        } else {
            return indexOrColor.hex()
        }
    }

    function buildStyle(style: Styles): Style {
        return {
            background: colorString(styleDefinitions.background[style]),
            border: colorString(styleDefinitions.border[style]),
            foreground: colorString(styleDefinitions.foreground[style]),
        }
    }

    return {
        default: buildStyle("default"),
        hovered: buildStyle("hovered"),
        pressed: buildStyle("pressed"),
        active: buildStyle("active"),
        disabled: buildStyle("disabled"),
        inverted: buildStyle("inverted"),
    }
}

function buildStyleDefinition(
    bgBase: number,
    fgBase: number,
    step: number = 0.08
) {
    return {
        background: {
            default: bgBase,
            hovered: bgBase + step,
            pressed: bgBase + step * 1.5,
            active: bgBase + step * 2.2,
            disabled: bgBase,
            inverted: fgBase + step * 6,
        },
        border: {
            default: bgBase + step * 1,
            hovered: bgBase + step,
            pressed: bgBase + step,
            active: bgBase + step * 3,
            disabled: bgBase + step * 0.5,
            inverted: bgBase - step * 3,
        },
        foreground: {
            default: fgBase,
            hovered: fgBase,
            pressed: fgBase,
            active: fgBase + step * 6,
            disabled: bgBase + step * 4,
            inverted: bgBase + step * 2,
        },
    }
}
