import {computeInterceptSlope, uuid} from "@/misc.js";
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
import {useOrderStore, useStore} from "@/store/store.js";
import {encodeIEE754, timestamp} from "@/common.js";
import {defineStore} from "pinia";
import {computed, ref} from "vue";
import Color from "color";


// Builders are data objects which store a configuration state
// the component name must match a corresponding Vue component in the BuilderFactory.vue component, which is responsible
// for instantiating the UI component for a given builder dictionary, based on its builder.component field.
export function newBuilder( component, options = {}) {
    const id = uuid()
    return {
        id, component, options,
        allocation: 1.0, points: {}, shapes: {}, props: {}
    }
}

// Orders hold an amount and builders
// noinspection JSUnusedLocalSymbols
const Order = {
    id: uuid(),
    amount: 0,
    builders: [],
}


// the key is order.id and the value is a function() that returns an order
export const orderFuncs = {}

// the key is order.builder.id and the value is a function() that returns an array of tranches
// if null is returned, the builder inputs are not valid and the place order button will be disabled
export const builderFuncs = {}

function newDefaultOrder() {
    return { id:uuid(), valid: false, amount:null, amountIsTokenA: true, buy: true, builders:[] }
}

export const useChartOrderStore = defineStore('chart_orders', () => {
    const chartReady = ref(false)

    const showPoolSelection = ref(false)  // if true, the pool information / fee choosing dialog is shown
    const defaultOrder = newDefaultOrder()
    const orders = ref([defaultOrder])  // order models in UI format
    const selectedOrder = ref(null)
    const selectedSymbol = ref(null)
    const intervalSecs = ref(0)
    const baseToken = computed(()=>selectedSymbol.value === null ? null : selectedSymbol.value.base)
    const quoteToken = computed(()=>selectedSymbol.value === null ? null : selectedSymbol.value.quote)
    const price = computed(() => {
        if (!selectedSymbol.value)
            return null
        const s = useStore()
        const key = [s.chainId, selectedSymbol.value.address];
        let result = s.poolPrices[key]
        if (selectedSymbol.value.inverted)
            result = 1 / result
        return result
    })

    const meanRange = ref(1)

    const drawing = ref(false)
    const drew = ref(true) // true if at least one of the points has been drawn already

    function newOrder() {
        const order = newDefaultOrder()
        orders.value.push(order)
        selectedOrder.value = order
    }

    function removeOrder(order) {
        let index = orders.value.findIndex((o)=>o.id===order.id)
        if (index === -1) return
        const result = orders.value.filter((o)=>o.id!==order.id)
        if (result.length === 0) {
            const order = newDefaultOrder()
            result.push(order)
            selectedOrder.value = order
        }
        else
            selectedOrder.value = result[Math.max(0,index-1)] // select the order above the removed one
        orders.value = result
    }

    function resetOrders() {
        const order = newDefaultOrder()
        orders.value = [order]
        selectedOrder.value = order
    }

    return {
        chartReady, selectedSymbol, intervalSecs, baseToken, quoteToken, price,
        orders, drawing, drew, newOrder, removeOrder, resetOrders, meanRange,
        showPoolSelection,
    }
})


export function applyLimitOld(tranche, price = null, isMinimum = null) {
    // todo deprecate.  used by old forms-based components.
    if (price === null) {
        const os = useOrderStore()
        price = os.limitPrice
        if (!price)
            return
    }

    applyLineOld(tranche, price, 0, isMinimum)
}


export function linePointsValue(time0, price0, time1, price1, unixTime = null) {
    if (unixTime === null)
        unixTime = timestamp()
    try {
        const [intercept, slope] = computeInterceptSlope(time0, price0, time1, price1)
        return intercept + unixTime * slope
    } catch (e) {
        console.log('error computing line', e)
        return null
    }
}


export function applyLinePoint(tranche, symbol, buy, price0, breakout=false) {
    applyLine(tranche, symbol, buy, price0, 0, breakout)
}


export function applyLinePoints(tranche, symbol, buy, time0, price0, time1, price1, breakout=false) {
    const [intercept, slope] = computeInterceptSlope(time0, price0, time1, price1);
    applyLine(tranche, symbol, buy, intercept, slope, breakout)
}


function applyLine(tranche, symbol, buy, intercept, slope=0, breakout=false) {
    const scale = 10 ** -symbol.decimals
    let m = slope * scale
    let b = intercept * scale
    m = encodeIEE754(m)
    b = encodeIEE754(b)
    const isMax = buy !== breakout  // buy XOR inverted XOR breakout
    // console.log('apply line point isMax?', buy?'buy':'sell', symbol.inverted, breakout, isMax)
    // console.log('applyLine current line value', isMax?'max':'min', slope*timestamp()+intercept, 1./(slope*timestamp()+intercept))
    if (isMax) {
        tranche.maxLine.intercept = b;
        tranche.maxLine.slope = m;
    } else {
        tranche.minLine.intercept = b;
        tranche.minLine.slope = m;
    }
    tranche.marketOrder = false;
}


export function timesliceTranches() {
    const ts = []
    const os = useOrderStore()
    const n = os.tranches // num tranches
    const interval = os.interval;
    const timeUnitIndex = os.timeUnitIndex;
    let duration =
        timeUnitIndex === 0 ? interval * 60 :      // minutes
            timeUnitIndex === 1 ? interval * 60 * 60 : // hours
                interval * 24 * 60 * 60;                   // days
    let window
    if (!os.intervalIsTotal) {
        window = duration
        duration *= n // duration is the total time for all tranches
    } else {
        window = Math.round(duration / n)
    }
    if (window < 60)
        window = 60  // always allow at least one minute for execution
    const amtPerTranche = Math.ceil(MAX_FRACTION / n)
    duration -= 15  // subtract 15 seconds so the last tranche completes before the deadline
    for (let i = 0; i < n; i++) {
        const start = Math.floor(i * (duration / Math.max((n - 1), 1)))
        const end = start + window
        ts.push(newTranche({
            marketOrder: true,
            fraction: amtPerTranche,
            startTimeIsRelative: true,
            startTime: start,
            endTimeIsRelative: true,
            endTime: end,
        }))
    }
    return ts
}

export function builderDefaults(builder, defaults) {
    for (const k in defaults)
        if (!Object.prototype.hasOwnProperty.call(builder, k)) {
            builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k]
        }
}

export function linearWeights(num, skew) {
    if (num === 1) return [1]
    const result = []
    if (skew === 0) {
        // equal weighted
        for (let i = 0; i < num; i++)
            result.push(1 / num)
    } else if (skew === 1) {
        result.push(1)
        for (let i = 1; i < num; i++)
            result.push(0)
    } else if (skew === -1) {
        for (let i = 1; i < num; i++)
            result.push(0)
        result.push(1)
    } else {
        for (let i = 0; i < num; i++)
            result.push((1 - skew * (2 * i / (num - 1) - 1)) / num)
    }
    // console.log('weights', result)
    return result
}

export function weightColors(weights, color) {
    const c = new Color(color).rgb()
    const max = Math.max(...weights)
    const ns = weights.map((w) => w / max) // set largest weight to 100%
    const adj = ns.map((w) => c.alpha(Math.pow(w, 0.67))) // https://en.wikipedia.org/wiki/Stevens's_power_law
    return adj.map((a) => a.string())
}

export function deleteBuilder(order, builder) {
    order.builders = order.builders.filter((b) => b !== builder)
} // Fields must be defined in order to be reactive
