import {useChartOrderStore} from "@/orderbuild.js";
import {invokeCallbacks, prototype} from "@/common.js";
import {DataFeed, defaultSymbol, feelessTickerKey, getAllSymbols, lookupSymbol} from "@/charts/datafeed.js";
import {intervalToSeconds, secondsToInterval, SingletonCoroutine, toHuman, toPrecision} from "@/misc.js";
import {usePrefStore, useStore} from "@/store/store.js";
import {tvCustomThemes} from "../../theme.js";

export let widget = null
export let chart = null
export let crosshairPoint = null
export let defaultShapeHandler = null // if set, then TV events that dont have a registered shape handler get passed directly to this function
let symbolChangedCbs = [] // callbacks for TV's chart.onSymbolChanged()


const s = useStore()
const co = useChartOrderStore()
const prefs = usePrefStore()

export function addSymbolChangedCallback(cb) {
    symbolChangedCbs.push(cb)
}

export function removeSymbolChangedCallback(cb) {
    symbolChangedCbs = symbolChangedCbs.filter((i)=>i!==cb)
}

function symbolChanged(symbol) {
    const info = symbol===null ? (defaultSymbol===null?'default':defaultSymbol) : lookupSymbol(symbol.ticker)
    co.selectedSymbol = info
    // console.log('setting prefs ticker', info.ticker)
    prefs.selectedTicker = info.ticker
    symbolChangedCbs.forEach((cb) => cb(info))
    updateFeeDropdown()
    // console.log('symbol changed', info)
}


export async function setSymbol(symbol, interval=null) {
    if (interval===null)
        interval = widget.symbolInterval().interval
    await new Promise(resolve => {
        if (co.selectedSymbol?.ticker !== symbol.ticker)
            widget.setSymbol(symbol.ticker, interval, ()=>setTimeout(resolve,0))
        else
            resolve()
    })
}


export async function setSymbolTicker(ticker) {
    const found = getAllSymbols()[ticker]
    if (!found) {
        console.error('No symbol for ticker', ticker)
        return
    }
    await setSymbol(found)
}


function changeInterval(interval) {
    const secs = intervalToSeconds(interval)
    co.intervalSecs = secs
    prefs.selectedTimeframe = interval
    DataFeed.intervalChanged(secs)
}


export function changeIntervalSecs(secs) {
    const interval = secondsToInterval(secs);
    co.intervalSecs = secs
    prefs.selectedTimeframe = interval
    DataFeed.intervalChanged(secs)
}

function dataLoaded() {
    const range = chartMeanRange()
    // console.log('new mean range', range,)
    co.meanRange = range
}


/*  TradingView event keystrings
const subscribeEvents = [
    'toggle_sidebar', 'indicators_dialog', 'toggle_header', 'edit_object_dialog', 'chart_load_requested',
    'chart_loaded', 'mouse_down', 'mouse_up', 'drawing', 'study', 'undo', 'redo', 'undo_redo_state_changed',
    'reset_scales', 'compare_add', 'add_compare', 'load_study_template', 'onTick', 'onAutoSaveNeeded',
    'onScreenshotReady', 'onMarkClick', 'onPlusClick', 'onTimescaleMarkClick', 'onSelectedLineToolChanged',
    'layout_about_to_be_changed', 'layout_changed', 'activeChartChanged', 'series_event', 'study_event',
    'drawing_event', 'study_properties_changed', 'series_properties_changed', 'panes_height_changed',
    'panes_order_changed',
]
*/


let poolButtonTextElement = null

function initFeeDropdown() {
    const button = widget.createButton()
    button.setAttribute('title', 'See Pool Info and Choose Fee');
    button.addEventListener('click', function () {
        co.showPoolSelection = true
    });
    button.id = 'pool-button'

    button.style.height = '34px';
    button.style.display = 'flex';
    button.style.alignItems = 'center';
    button.addEventListener('mouseover', () => {
        button.style.backgroundColor = 'rgb(60,60,60)';
    });
    button.addEventListener('mouseout', () => {
        button.style.backgroundColor = '';
    });
    button.style.margin = '2px 0';
    button.style.borderRadius = '4px';
    button.classList.add('pool-button')

    let img = document.createElement('img');
    img.src = '/arbitrum-logo.svg';
    img.style.width = '1em';
    img.style.marginRight = '0.2em';
    button.appendChild(img);

    img = document.createElement('img');
    img.src = '/uniswap-logo.svg';
    img.style.height = '1.25em';
    img.style.marginRight = '0.2em';
    img.style.backgroundColor = 'white';
    img.style.borderRadius = '50%';
    button.appendChild(img);

    const span = document.createElement('span');
    span.style.marginY = 'auto';
    button.appendChild(span);
    poolButtonTextElement = span

    updateFeeDropdown()
}

export function updateFeeDropdown() {
    if (poolButtonTextElement===null) return
    const symbolItem = useChartOrderStore().selectedSymbol
    let text = ''
    text += (symbolItem.fee / 10000).toFixed(2) + '%'
    const index = symbolItem.feeGroup.findIndex((p) => p[1] === symbolItem.fee)
    if (symbolItem.liquiditySymbol) {
        const liq = symbolItem.liquidities[index]
        if (symbolItem.liquiditySymbol === 'USD')
            text += ` $${toHuman(liq)}`
        else
            text = ` ${toHuman(liq)} ${symbolItem.liquiditySymbol}`
    }
    poolButtonTextElement.textContent = text
}

export function initTVButtons() {
    initFeeDropdown();
}

export function initWidget(el) {
    getAllSymbols()
    const symbol = prefs.selectedTicker === null ? 'default' : prefs.selectedTicker
    const interval = prefs.selectedTimeframe === null ? '15' : prefs.selectedTimeframe
    widget = window.tvWidget = new TradingView.widget({

        // Widget Options
        // https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.ChartingLibraryWidgetOptions
        library_path: "/charting_library/",
        // debug: true,
        autosize: true,
        symbol,
        interval,
        container: el,
        datafeed: DataFeed, // use this for ohlc
        locale: "en",
        disabled_features: ['main_series_scale_menu','display_market_status',],
        enabled_features: ['saveload_separate_drawings_storage','snapshot_trading_drawings','show_exchange_logos','show_symbol_logos',],
        // drawings_access: {type: 'white', tools: [],}, // show no tools
        custom_themes: tvCustomThemes,
        theme: useStore().theme,
        timezone: prefs.timezone,

        // Chart Overrides
        // https://www.tradingview.com/charting-library-docs/latest/customization/overrides/chart-overrides
        overrides: {
            "mainSeriesProperties.priceAxisProperties.log": false,
        }

    });

    // debug dump all events
    // for( const event of subscribeEvents )
    //     widget.subscribe(event, ()=>console.log('event', event, arguments))

    widget.subscribe('drawing_event', handleDrawingEvent)
    widget.subscribe('onSelectedLineToolChanged', onSelectedLineToolChanged)
    widget.subscribe('mouse_down', mouseDown)
    widget.subscribe('mouse_up', mouseUp)
    widget.headerReady().then(()=>initTVButtons())
    widget.onChartReady(initChart)
    console.log('tv widget initialized')
}


export function onChartReady(f) {
    if (co.chartReady)
        f(widget, chart)
    else
        chartInitCbs.push(f)
}

let chartInitCbs = []


function initChart() {
    // console.log('init chart')
    chart = widget.activeChart()
    const themeName = useStore().theme;
    widget.changeTheme(themeName).catch((e)=>console.warn(`Could not change theme to ${themeName}`, e))
    chart.crossHairMoved().subscribe(null, (point)=>setTimeout(()=>handleCrosshairMovement(point),0) )
    chart.onSymbolChanged().subscribe(null, symbolChanged)
    chart.onIntervalChanged().subscribe(null, changeInterval)
    chart.onDataLoaded().subscribe(null, dataLoaded)
    const tzapi = chart.getTimezoneApi();
    tzapi.onTimezoneChanged().subscribe(null, (tz)=>{if (tz==='exchange') tz='Etc/UTC'; s.timeZone=tz})
    s.timeZone = tzapi.getTimezone().id
    // chart.onHoveredSourceChanged().subscribe(null, ()=>console.log('hovered source changed', arguments))
    // chart.selection().onChanged().subscribe(null, s => console.log('selection', chart.selection().allSources()));
    const symbolExt = chart.symbolExt();
    // console.log('symbolExt', symbolExt);
    if(symbolExt) {
        symbolChanged(symbolExt)
    }
    else {
        symbolChanged(null)
    }
    changeInterval(widget.symbolInterval().interval)
    co.chartReady = true
    setTimeout(()=>{
        for (const cb of chartInitCbs)
            cb(widget, chart)
        chartInitCbs = []
    }, 1)
    // console.log('chart ready')
}


// noinspection JSUnusedLocalSymbols
export const ShapeCallback = {
    onDraw: () => {},  // start drawing a new shape for the builder
    onRedraw: () => {},  // the mouse moved while in drawing mode
    onUndraw: () => {},  // drawing was canceled by clicking on a different tool
    onAddPoint: () => {},  // the user clicked a point while drawing (that point is added to the points list)
    onCreate: (shapeId, shape, points, props) => {}, // the user has finished creating all the control points.  drawing mode is exited and the initial shape is created.
    onPoints: (shapeId, shape, points) => {}, // the control points of an existing shape were changed
    onProps: (shapeId, shape, props) => {},  // the display properties of an existing shape were changed
    onMove: (shapeId, shape, points) => {},  // the entire shape was moved without dragging control points
    onDrag: (shapeId, shape, points) => {},  // the shape is being dragged: this gets called on every mouse movement update during a drag
    onHide: (shapeId, shape, props) => {},
    onShow: (shapeId, shape, props) => {},
    onClick: (shapeId, shape) => {},   // the shape was selected
    onDelete: (shapeId) => {},
}


// noinspection JSUnusedLocalSymbols
export const VerboseCallback = prototype(ShapeCallback, {
    onDraw: ()=>{console.log('onDraw')},  
    // onRedraw: ()=>{console.log('onRedraw')},
    onUndraw: ()=>{console.log('onUndraw')},
    onAddPoint: ()=>{console.log('onAddPoint')},
    onCreate: (shapeId, shape, points, props)=>{console.log('onCreate')},
    onPoints: (shapeId, shape, points)=>{console.log('onPoints')},
    onProps: (shapeId, shape, props)=>{console.log('onProps')},
    onMove: (shapeId, shape, points)=>{console.log('onMove')},
    onDrag: (shapeId, shape, points) => {console.log('onDrag')},
    onHide: (shapeId, shape, props)=>{console.log('onHide')},
    onShow: (shapeId, shape, props)=>{console.log('onShow')},
    onClick: (shapeId, shape)=>{console.log('onClick')},
    onDelete: (shapeId)=>{console.log('onDelete')},
})

let drawingTool = null
let previousDrawingTool = null
let drawingCallbacks = null

export function drawShape(shapeType, ...callbacks) {
    // puts the chart into a line-drawing mode for a new shape
    // console.log('drawShape', callbacks, shapeType.name, shapeType.code)
    if( drawingCallbacks )
        invokeCallbacks(drawingCallbacks, 'onUndraw')
    drawingCallbacks = callbacks
    drawingTool = null
    previousDrawingTool = widget.selectedLineTool()
    co.drawing = true
    co.drew = false
    widget.selectLineTool(shapeType.code)
    invokeCallbacks(callbacks, 'onDraw')
}


export function createShape(shapeType, points, options={}, ...callbacks) {
    const chart = widget.activeChart()
    drawingCallbacks = null
    cancelDrawing()
    if (typeof shapeType === 'string' )
        options.shape = shapeType
    else
        options.shape = shapeType.code
    // programatically adds a shape to the chart
    let shapeId
    try {
        // console.log('creating shape', points, options)
        if (points.time || points.price)
            shapeId = chart.createShape(points, options)
        else if (points.length === 1)
            shapeId = chart.createShape(points[0], options)
        else
            shapeId = chart.createMultipointShape(points, options)
    }
    catch (e) {
        console.error('tvShape could not create', shapeType, points, options, e)
        return null
    }
    if (shapeId===null) {
        console.error('could not create shape', points, options)
    }
    allShapeIds.push(shapeId)
    if( callbacks.length )
        shapeCallbacks[shapeId] = callbacks
    const shape = chart.getShapeById(shapeId)
    // console.log('tvShape created', shapeId)
    shape.bringToFront()
    const props = shape.getProperties()
    invokeCallbacks(callbacks, 'onCreate', shapeId, shape, points, props)
    return shapeId
}


export let allShapeIds = []

export function cancelDrawing() {
    if (co.drawing) {
        co.drawing = false
        widget.selectLineTool(previousDrawingTool)
        invokeCallbacks(drawingCallbacks, 'onUndraw')
    }
}


const shapeCallbacks = {}

function onSelectedLineToolChanged() {
    const tool = widget.selectedLineTool();
    // console.log('line tool changed', tool)
    if (drawingTool===null)
        drawingTool = tool
    else if (tool!==drawingTool && co.drawing)
        cancelDrawing();
}

export let dragging = false
export let draggingShapeIds = []
function mouseDown() {
    // console.log('mouseDown')
    // todo push into drawing event queue instead, then set dragging there
    dragging = true
}

function mouseUp() {
    // console.log('mouseUp')
    dragging = false
    draggingShapeIds = []
}


function handleCrosshairMovement(point) {
    crosshairHandler.invoke(point) // delayed invocation to await selection to register later in the tv loop
}


const crosshairHandler = new SingletonCoroutine(doHandleCrosshairMovement, 0)


function doHandleCrosshairMovement(point) {
    // console.log('crosshair moved')
    crosshairPoint = point
    if (co.drawing)
        invokeCallbacks(drawingCallbacks, 'onRedraw')
    else if (dragging) {
        const selection = chart.selection().allSources();
        if (selection.length)
            draggingShapeIds = selection
        // console.log('dragging selected', draggingShapeIds)
        for (const shapeId of draggingShapeIds) {
            let shape
            try {
                shape = chart.getShapeById(shapeId);
            }
            catch (e) {
                continue
            }
            const points = structuredClone(shape.getPoints());
            const lpbe = shape._model._linePointBeingEdited
            points[lpbe===null?0:lpbe] = point
            // console.log('calling onDrag', points, shape)
            invokeCallbacks(shapeCallbacks[shapeId], 'onDrag', shapeId, shape, points)
        }
    }
    else if (draggingShapeIds.length > 0) {
        draggingShapeIds = []
    }
}


let drawingEventQueue = []
let eventLock = false // this is set to true before events are processed, in order to avoid any loops
let propsEvents = {}

function handleDrawingEvent(id, event) {
    if (eventLock && event !== 'properties_changed' && event !== 'remove') { // properties has its own locking mechanism
        console.log('ignoring event', id, event)
        return
    }
    // it's important to decouple our actions from the TV thread.  we must wait until the TV loop is completed
    // before interacting with the chart
    if (drawingEventQueue.length===0)
        setTimeout(drawingEventWorker,0)
    drawingEventQueue.push([id,event])
}


function drawingEventWorker() {
    eventLock = true
    try {
        const queue = drawingEventQueue
        drawingEventQueue = []
        propsEvents = {}
        for (const [id, event] of queue) {
            if (event === 'properties_changed')
                propsEvents[id] = event
            else
                doHandleDrawingEvent(id, event)
        }
        for (const [k,v] of Object.entries(propsEvents))
            doHandleDrawingEvent(k, v)
    }
    finally {
        eventLock = false
        // console.log('events unlocked')
    }
}

function doHandleDrawingEvent(id, event) {
    // console.log('drawing event', id, event)
    let shape
    if (event==='remove')
        shape = null
    else {
        try {
            shape = chart.getShapeById(id)
        }
        catch {
            return
        }
    }
    if (event === 'create') {
        allShapeIds.push(id)
        const callbacks = drawingCallbacks
        // console.log('drawing callbacks', callbacks)
        if (callbacks !== null) {
            shapeCallbacks[id] = callbacks
            co.drawing = false
            const points = shape.getPoints()
            const props = shape.getProperties()
            invokeCallbacks(shapeCallbacks[id], 'onCreate', id, shape, points, props)
            invokeCallbacks(shapeCallbacks[id], 'onPoints', id, shape, points)
            invokeCallbacks(shapeCallbacks[id], 'onProps', id, shape, props)
        }
    } else if (event === 'points_changed') {
        if (dragging) return
        if (id in shapeCallbacks) {
            const points = shape.getPoints()
            // console.log('points', id, points, dragStartPoints)
            invokeCallbacks(shapeCallbacks[id], 'onPoints', id, shape, points)
        }
    } else if (event === 'properties_changed') {
        const props = shape.getProperties()
        if (id in shapeCallbacks)
            invokeCallbacks(shapeCallbacks[id], 'onProps', id, shape, props)
        else {
            // otherwise it's an event on a shape we don't "own" that could be being drawn
            co.drew = true
            console.log('warning: ignoring setProperties on TV shape', id, props)
        }
    } else if (event === 'move') {
        if (id in shapeCallbacks) {
            invokeCallbacks(shapeCallbacks[id], 'onMove', id, shape)
        }
    } else if (event === 'remove') {
        allShapeIds = allShapeIds.filter((v)=>v!==id)
        if (id in shapeCallbacks) {
            invokeCallbacks(shapeCallbacks[id], 'onDelete', id)
        }
    } else if (event === 'click') {
        if (id in shapeCallbacks) {
            invokeCallbacks(shapeCallbacks[id], 'onClick', id, shape)
        }
    } else if (event === 'hide') {
        if (id in shapeCallbacks) {
            invokeCallbacks(shapeCallbacks[id], 'onHide', id, shape)
        }
    } else if (event === 'showDialog') {
        if (id in shapeCallbacks) {
            invokeCallbacks(shapeCallbacks[id], 'onShow', id, shape)
        }
    } else
        console.log('unknown drawing event', event)
}

export function deleteShapeId(id) {
    if( id in shapeCallbacks )
        delete shapeCallbacks[id]
    // console.log('removing entity', id)
    chart.removeEntity(id)
}

const MEAN_RANGE_MULTIPLIER = 3

function chartMeanRange() {
    let range = 0
    const series = chart.getSeries()
    const bars = series.data().bars();
    const final = Math.max(bars.size() - 50, 0)
    let count = 0
    for (let barIndex = bars.size() - 1; barIndex >= final; barIndex--) {
        count++
        const [_time, _open, high, low, _close, _volume, _ms] = bars.valueAt(barIndex)
        range += Math.abs(high - low)
    }
    if (count > 0)
        range /= count
    else
        range = 1
    return range * MEAN_RANGE_MULTIPLIER
}
