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


export const MIN_EXECUTION_TIME = 60  // give at least one full minute for each tranche to trigger


function unimplemented() { throw Error('Unimplemented') }

// 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: {}, valid: false,
    }
}

// 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 defaultOrder = newDefaultOrder()
    const orders = ref([defaultOrder])  // order models in UI format
    const selectedOrder = ref(null)
    const selectedSymbol = ref(null)
    const selectedPool = 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 (!selectedPool.value || !selectedSymbol.value)
            return null
        const s = useStore()
        let result = s.poolPrices[[s.chainId, selectedPool.value[0]]]
        if (selectedSymbol.value.inverted)
            result = 1 / result
        return result
    })


    const drawing = ref(false)
    const drawingCallbacks = ref(null)  // only during draw mode

    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, selectedPool, intervalSecs, baseToken, quoteToken, price,
        orders, drawing, drawingCallbacks, newOrder, removeOrder, resetOrders,
    }
})


export function applyLimit(tranche, price = null, isMinimum = null) {
    if (price === null) {
        const os = useOrderStore()
        price = os.limitPrice
        if (!price)
            return
    }

    applyLine(tranche, price, 0, isMinimum)
}


function computeInterceptSlope(time0, price0, time1, price1) {
    if (!time0 || !price0 && price0 !== 0 || !time1 || !price1 && price1 !== 0)
        throw Error(`invalid line points data ${time0} ${price0} ${time1} ${price1}`)
    const t0 = time0
    const t1 = time1
    if (t0 === t1)
        throw Error("line points' times must be different")
    const slope = (price1 - price0) / (t1 - t0)
    const intercept = price1 - slope * t1
    return [intercept, slope]
}


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 applyLinePoints(tranche, time0, price0, time1, price1, isMinimum = null) {
    const [intercept, slope] = computeInterceptSlope(time0, price0, time1, price1);
    applyLine(tranche, intercept, slope, isMinimum)
}


export function applyLine(tranche, intercept, slope, isMinimum = null) {
    console.log('intercept, slope', intercept, slope)
    // intercept and slope are still in "human" units of decimal-adjusted prices
    const os = useOrderStore()
    const route = os.route
    const inverted = routeInverted(route)
    const scale = 10 ** (os.tokenA.d - os.tokenB.d)
    let m = inverted ? -scale / slope : slope / scale
    let b = inverted ? scale / intercept : intercept / scale
    const cur = b + timestamp() * m
    console.log('inverted b, m, cur', inverted, b, m, cur)
    m = encodeIEE754(m)
    b = encodeIEE754(b)
    if (isMinimum === null)
        isMinimum = os.limitIsMinimum
    console.log('limit is minimum', isMinimum)
    if (isMinimum) {
        tranche.minIntercept = b;
        tranche.minSlope = m;
    } else {
        tranche.maxIntercept = b;
        tranche.maxSlope = m;
    }
    tranche.marketOrder = false;
}


export function applyLine2(tranche, isMinimum, intercept, slope, poolDecimals, inverted) {
    console.log('intercept, slope', intercept, slope)
    // intercept and slope are still in "human" units of decimal-adjusted prices
    const scale = 10 ** -poolDecimals
    let m = slope === 0 ? 0 : inverted ? -scale / slope : scale * slope
    let b = inverted ? scale / intercept : scale * intercept
    const cur = b + timestamp() * m
    console.log('inverted b, m, cur', inverted, b, m, cur)
    m = encodeIEE754(m)
    b = encodeIEE754(b)
    if (inverted)
        isMinimum = !isMinimum
    if (isMinimum) {
        tranche.minIntercept = b;
        tranche.minSlope = m;
    } else {
        tranche.maxIntercept = b;
        tranche.maxSlope = 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({
            fraction: amtPerTranche,
            startTimeIsRelative: true,
            startTime: start,
            endTimeIsRelative: true,
            endTime: end,
        }))
    }
    return ts
}

export function builderDefaults(builder, defaults) {
    for (const k in defaults)
        if (builder[k] === undefined)
            builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k]
}

export function linearWeights(n, s) {
    if (n === 1) return [1]
    const result = []
    if (s === 0) {
        // equal weighted
        for (let i = 0; i < n; i++)
            result.push(1 / n)
    } else if (s === 1) {
        result.push(1)
        for (let i = 1; i < n; i++)
            result.push(0)
    } else if (s === -1) {
        for (let i = 1; i < n; i++)
            result.push(0)
        result.push(1)
    } else {
        for (let i = 0; i < n; i++)
            result.push((1 - s * (2 * i / (n - 1) - 1)) / n)
    }
    // 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 allocationText(weight, amount, symbol) {
    let text = ''
    const hasWeight = weight!==null && weight!==undefined
    if (hasWeight)
        text += `${(weight * 100).toFixed(1)}%`
    const hasAmount = amount!==null && amount!==undefined
    const hasSymbol = symbol!==null && symbol!==undefined
    if (hasAmount && hasSymbol) {
        if (hasWeight)
            text += ' = '
        text += `${amount.toLocaleString('fullwide')} ${symbol}`
    }
    return text
}

export function deleteBuilder(order, builder) {
    order.builders = order.builders.filter((b) => b !== builder)
    // if (props.deleteShapes)  // todo is this redundant?
    //   props.deleteShapes()
}