import {FixedNumber} from "ethers";
import {usePrefStore, useStore} from "@/store/store.js";
import {token} from "@/blockchain/token.js";
import Color from "color";
import {DateTime} from "luxon";
import {dateString} from "@/common.js";
import { v4 as uuidv4 } from 'uuid';

const QUOTE_SYMBOLS = [
    // in order of preference
    // todo put this in metadata.json using addrs
    'USDT',
    'USDC',
    'USDC.e',
    'BTC',
    'WBTC',
    'ETH',
    'WETH',
    'ARB',
]

export class SingletonCoroutine {
    constructor(f, delay = 10) {
        this.f = f
        this.delay = delay
        this.timeout = null
        this.args = null
    }

    pending() {
        return this.timeout !== null
    }

    invoke(/*arguments*/) {
        this.args = arguments
        // console.log('invoke', arguments)
        if (this.timeout === null)
            // noinspection JSCheckFunctionSignatures
this.timeout = setTimeout(this.onTimeout, this.delay, this)
    }

    async onTimeout(self) {
        try {
            await self.f(...self.args)
        } catch (e) {
            if (self.retry) {
                console.log('retrying', this.f, 'due to error', e)
                self.timeout = null
                self.invoke(self.args)
            } else
                console.error(e)
        } finally {
            self.timeout = null
        }
    }

}

export const vAutoSelect = {
    beforeMount: (el) => {
        const input = el.querySelector('input')
        input.onfocus = () => setTimeout(() => input.select(), 0)
    }
}
export const uint32max = 4294967295
export const uint64max = 18446744073709551615n

export function tokenNumber(token, balance) {
    const dec = token ? token.decimals : 0
    console.log('token dec', dec, balance)
    return FixedNumber.fromValue(balance, dec, {decimals: dec, width: 256})
}

export function tokenFloat(token, balance) {
    return tokenNumber(token, balance).toUnsafeFloat()
}

export function routeInverted(route) {
    const s = useStore()
    return route && (route.token0 === s.tokenA) === s.inverted
}

export function intervalString(seconds) {
    if (seconds < 1)
        return 'now'
    else if (seconds < 60)
        return `${seconds} seconds`
    else if (seconds < 3600)
        return `${(seconds / 60).toFixed(1)} minutes`
    else if (seconds < 86400)
        return `${(seconds / 3600).toFixed(1)} hours`
    else
        return `${(seconds / 86400).toFixed(1)} days`
}

export function timestampString(seconds) {
    const date = DateTime.fromSeconds(seconds).setZone(usePrefStore().timezone)
    return dateString(date)
}


export function pairKey(chainId, tokenA, tokenB) {
    const token0 = tokenA.a < tokenB.a ? tokenA.a : tokenB.a
    const token1 = tokenA.a > tokenB.a ? tokenA.a : tokenB.a
    return [chainId, token0, token1];
}


export function pairPriceAddr(chainId, baseTokenAddr, quoteTokenAddr, price) {
    const baseToken = token(chainId, baseTokenAddr)
    const quoteToken = token(chainId, quoteTokenAddr)
    if (!baseToken || !quoteToken)
        return null
    return pairPrice(chainId, baseToken, quoteToken, price)
}


export function flipInversionPreference(chainId, base, quote) {
    const inverted = base.a > quote.a
    const token0 = !inverted ? base.a : quote.a
    const token1 = inverted ? base.a : quote.a
    const k = [chainId, token0, token1];
    const prefs = usePrefStore()
    prefs.inverted[k] = !prefs.inverted[k]
}


export function inversionPreference(chainId, base, quote) {
    // returns the user preference for whether to invert this pair or not
    const inputInverted = base.a > quote.a
    const token0 = !inputInverted ? base.a : quote.a
    const token1 = inputInverted ? base.a : quote.a
    const key = [chainId, token0, token1]; // todo chainId shouldn't matter
    const prefs = usePrefStore()
    if (!(key in prefs.inverted)) {
        // todo prefer stablecoins as the quote asset
        let preferInverted = false;
        for (const q of QUOTE_SYMBOLS) {
            if (quote.s === q)
                break // definitely not inverted
            if (base.s === q) {
                preferInverted = true
                break // definitely inverted
            }
        }
        prefs.inverted[key] = preferInverted
    }
    return prefs.inverted[key] !== inputInverted
}

export const sleep = ms => new Promise(r => setTimeout(r, ms))

export function uuid() {return uuidv4()}

export function lightenColor(color, lightness = 85, alpha = null) {
    let c = new Color(color).hsl()
    if (useStore().theme === 'light')
        c = c.lightness(lightness)
    else
        c = c.lightness(100-lightness)
    c = c.rgb()
    if (alpha !== null)
        c = c.alpha(alpha)
    return c.string()
}

export function lightenColor2(color, alpha = null) {
    return lightenColor(color, 98, alpha)
}


const colorRanges = {
    buy: ['#00CC33', '#3300CC'],
    sell: ['#CC0033', '#CCCC33'],
}

export function sideColor(buy, index=0) {
    const range = buy ? colorRanges.buy : colorRanges.sell
    const a = new Color(range[0]).rgb()
    const b = new Color(range[1]).rgb()
    // const z2 = Math.PI * Math.PI / 6 - 1 // ζ(2) modulo 1
    const z2 = 1 - 1/Math.E // ζ(2) modulo 1
    const kk = index * z2;
    const k = kk - Math.trunc(kk)
    const mix = (x, y) => (1 - k) * x + k * y
    const c = new Color([mix(a.red(), b.red()), mix(a.green(), b.green()), mix(a.blue(), b.blue())])
    // console.log('default color', c.string())
    return c.string()
}


export function unique(arr) {
    const u = {}, a = [];
    for(let i = 0, l = arr.length; i < l; ++i){
        if(!u.hasOwnProperty(arr[i])) {
            a.push(arr[i]);
            u[arr[i]] = 1;
        }
    }
    return a;
}

export function linspace(a, b, n) {
    if (n===1) return [(a+b)/2]  // single line
    const spacing = (b - a) / (n - 1)
    // console.log('spacing', a, b)
    const result = []
    for (let i = 0; i < n; i++)
        result.push(a + i * spacing)
    return result;
}

export function intervalToSeconds(interval) {
    if (interval.endsWith('T'))
        throw Error('Tick intervals not supported')
    const unit = interval.endsWith('M') ? 30 * 24 * 60 * 60
               : interval.endsWith('W') ?  7 * 24 * 60 * 60
               : interval.endsWith('D') ?      24 * 60 * 60
               : interval.endsWith('S') ?                 1
               :                                         60  // if no unit char, then it's minutes
    const result = parseInt(/\d+/.exec(interval)[0]) * unit
    // console.log('intervalToSeconds', interval, result)
    return result
}


export function secondsToInterval(seconds) {
    const units = [
        [30 * 24 * 60 * 60, 'M'],
        [7 * 24 * 60 * 60, 'W'],
        [24 * 60 * 60, 'D'],
        [60, ''],
        [1, 'S'],
    ]
    for( const [unit, suffix] of units)
        if (seconds % unit === 0)
            return `${seconds / unit}${suffix}`
    throw Error(`invalid secondsToInterval ${seconds}`)
}


export function interpolate(a, b, zeroToOne) {
    const d = (b-a)
    return a + d * zeroToOne
}

export 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 defined(v) {
    return v !== undefined && v !== null
}

export function toPrecision(value, significantDigits = 3) {
    if (!isFinite(value)) return value.toString(); // Handle Infinity and NaN
    if (value === 0) return "0"; // Special case for 0
    const magnitude = Math.floor(Math.log10(Math.abs(value)));
    const decimalsNeeded = Math.max(0, significantDigits - 1 - magnitude);
    return value.toFixed(decimalsNeeded); // Use toFixed to completely avoid scientific notation
}

export function toPrecisionOrNull(value, significantDigits = 3) {
    if (value===null) return null
    if (value===undefined) return undefined
    return toPrecision(value, significantDigits)
}

export function toHuman(value, significantDigits = 2) {
    if (!isFinite(value)) return value.toString(); // Handle Infinity and NaN
    let suffix = ''
    if (value >= 1_000_000_000) {
        value /= 1_000_000_000
        suffix = 'B'
    }
    else if (value >= 1_000_000) {
        value /= 1_000_000
        suffix = 'M'
    }
    else if (value >= 1_000) {
        value /= 1_000
        suffix = 'K'
    }
    return toPrecision(value, significantDigits) + suffix
}

export function errorSuggestsMissingVault(e) {
    return e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION';
}
