// --- Shared TradingView Chart Order Lines (EVM) ---
// Runs in the page's main world. Listens for order data from the content script
// and draws horizontal price/MC lines on the active TradingView widget.
//
// NOTE: On EVM the `target` field from the backend is already the MC value
// in USD (unlike SOL where it's the per-token price). So mc = target.
;(function () {
  // Namespaced per extension so SOL and EVM can coexist when both are installed.
  if (window.__BLOOM_CHART_ORDERS_EVM__) return
  window.__BLOOM_CHART_ORDERS_EVM__ = true

  const LABEL_PREFIX = 'Bloom '
  const LABEL_PADDING = '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'
  const COLORS = { BUY: '#00C853', SELL: '#FF1744' }

  // chartValue: what the chart Y-axis represents
  //   'mc'    — market cap in USD (pass target directly, scaled by chartDivisor)
  //   'price' — raw token price in USD (pass target / supply)
  const PLATFORM_CONFIGS = {
    'axiom.trade': {
      tokenPagePattern: /\/meme\/[A-Za-z0-9]+/,
      chartValue: 'mc',
    },
    'backup.axiom.trade': {
      tokenPagePattern: /\/meme\/[A-Za-z0-9]+/,
      chartValue: 'mc',
    },
    'gmgn.ai': {
      tokenPagePattern: /\/[a-z]+\/token\/[^\/?#]+/,
      chartValue: 'mc',
    },
    'trade.padre.gg': {
      tokenPagePattern: /\/trade\/[a-z]+\/[^\/?#]+/,
      chartValue: 'mc',
      chartDivisor: 1e9,
    },
    'dexscreener.com': {
      tokenPagePattern: /\/[a-z]+\/[^\/?#]+/,
      chartValue: 'price',
    },
    'photon.tinyastro.io': {
      tokenPagePattern: /\/lp\/[^\/?#]+/,
      chartValue: 'price',
    },
    'photon-bnb.tinyastro.io': {
      tokenPagePattern: /\/lp\/[^\/?#]+/,
      chartValue: 'price',
    },
    'photon-base.tinyastro.io': {
      tokenPagePattern: /\/lp\/[^\/?#]+/,
      chartValue: 'price',
    },
  }

  function getPlatformConfig() {
    const host = window.location.hostname
    for (const [key, config] of Object.entries(PLATFORM_CONFIGS)) {
      if (host === key || host.endsWith('.' + key)) return config
    }
    return null
  }

  const platformConfig = getPlatformConfig()
  if (!platformConfig) return

  let cachedWidget = null
  let chartLinesInterval = null
  let drawnShapeIds = []
  let activeOrders = []
  let currentSupply = 0

  function formatUsdMc(n) {
    if (n >= 1e9) return `$${+(n / 1e9).toPrecision(3)}B`
    if (n >= 1e6) return `$${+(n / 1e6).toPrecision(3)}M`
    if (n >= 1e3) return `$${+(n / 1e3).toPrecision(3)}K`
    return `$${n.toFixed(0)}`
  }

  function getChartVisiblePriceRange(chart) {
    try {
      if (typeof chart.priceScale === 'function') {
        const scale = chart.priceScale()
        if (scale && typeof scale.getVisiblePriceRange === 'function') {
          const r = scale.getVisiblePriceRange()
          if (r?.from != null && r?.to != null) return r
        }
      }
    } catch {}
    try {
      const panes = chart.getPanes?.()
      for (const pane of panes || []) {
        const scale = pane.getMainSourcePriceScale?.() || pane.priceScale?.()
        if (scale && typeof scale.getVisiblePriceRange === 'function') {
          const r = scale.getVisiblePriceRange()
          if (r?.from != null && r?.to != null) return r
        }
      }
    } catch {}
    return null
  }

  function detectChartMode(chart) {
    try {
      let sym = null
      if (typeof chart.symbol === 'function') sym = chart.symbol()
      if (!sym && typeof chart.symbolExt === 'function') {
        const ext = chart.symbolExt()
        sym = ext?.full_name || ext?.symbol || ext?.name
      }
      if (typeof sym === 'string') {
        const s = sym.toUpperCase()
        if (s.includes('MCAP') || s.includes('MKTCAP') || s.endsWith('/MC'))
          return 'mc'
        if (s.includes('/PRICE') || s.endsWith('/USD') || s.endsWith('/SOL'))
          return 'price'
      }
    } catch {}

    const range = getChartVisiblePriceRange(chart)
    if (range) {
      const midVal = (Math.abs(range.from) + Math.abs(range.to)) / 2
      if (midVal > 1) return 'mc'
      if (midVal > 0) return 'price'
    }
    return null
  }

  function orderToChartLine(order, supply, chartMode) {
    const target = parseFloat(order.target)
    const side = order.direction === 'sell' ? 'SELL' : 'BUY'

    if (order.label === 'Timed') return null
    if (order.label === 'Dev Sell') return null
    if (order.label === 'Trailing Stop Loss') return null
    if (order.label === 'Claim') return null

    if (!target || isNaN(target)) return null

    // EVM: target IS the MC in USD directly.
    const mc = target
    const mode = chartMode || platformConfig.chartValue || 'mc'
    let price
    if (mode === 'price') {
      if (!supply) return null
      price = target / supply
    } else {
      price = mc / (platformConfig.chartDivisor || 1)
    }
    const mcLabel = formatUsdMc(mc)

    return { price, label: `${order.label} @ ${mcLabel} MC`, side }
  }

  document.addEventListener('bloom-chart-orders-evm', function (event) {
    const detail = event.detail
    if (!detail) return
    activeOrders = detail.orders || []
    currentSupply = detail.supply || 0
    redrawIfReady()
  })

  function getChartLines(chart) {
    const chartMode = chart ? detectChartMode(chart) : null
    const lines = []
    for (const order of activeOrders) {
      const line = orderToChartLine(order, currentSupply, chartMode)
      if (line) lines.push(line)
    }
    return lines
  }

  function isVisible(el) {
    let node = el
    while (node && node !== document.body) {
      if (parseFloat(getComputedStyle(node).opacity) === 0) return false
      node = node.parentElement
    }
    return true
  }

  function getChartFromWidget(obj) {
    if (!obj || typeof obj !== 'object') return null
    try {
      if (typeof obj.activeChart === 'function') {
        const c = obj.activeChart()
        if (c && typeof c.createShape === 'function') return c
      }
    } catch {}
    try {
      if (typeof obj.chart === 'function') {
        const c = obj.chart()
        if (c && typeof c.createShape === 'function') return c
      }
    } catch {}
    return null
  }

  function isWidgetCandidate(obj) {
    return getChartFromWidget(obj) != null
  }

  function scanFiberForWidget(startFiber) {
    const queue = [startFiber]
    const seen = new Set()
    let steps = 0
    while (queue.length && steps < 40) {
      const fiber = queue.shift()
      if (!fiber || seen.has(fiber)) continue
      seen.add(fiber)
      steps++

      try {
        if (isWidgetCandidate(fiber.stateNode)) return fiber.stateNode
      } catch {}

      try {
        let state = fiber.memoizedState
        let hops = 0
        while (state && hops < 50) {
          const candidates = [
            state.memoizedState?.current,
            state.memoizedState,
            state.baseState?.current,
            state.baseState,
          ]
          for (const c of candidates) {
            if (isWidgetCandidate(c)) return c
          }
          state = state.next
          hops++
        }
      } catch {}

      if (fiber.return) queue.push(fiber.return)
      if (fiber.child) queue.push(fiber.child)
      if (fiber.sibling) queue.push(fiber.sibling)
    }
    return null
  }

  function getFibersFromElement(el) {
    if (!el) return []
    return Object.keys(el)
      .filter((k) => k.startsWith('__reactFiber'))
      .map((k) => el[k])
      .filter(Boolean)
  }

  function scanAncestorsForWidget(startEl) {
    let el = startEl
    let depth = 0
    while (el && depth < 15) {
      for (const fiber of getFibersFromElement(el)) {
        const widget = scanFiberForWidget(fiber)
        if (widget) return widget
      }
      el = el.parentElement
      depth++
    }
    return null
  }

  function scanObjectForWidget(targetWindow) {
    const commonKeys = ['tvWidget', 'TradingView', '__tradingview_widget__']
    for (const k of commonKeys) {
      try {
        if (isWidgetCandidate(targetWindow[k])) return targetWindow[k]
      } catch {}
    }
    try {
      for (const k of Object.keys(targetWindow)) {
        if (commonKeys.includes(k)) continue
        let val
        try {
          val = targetWindow[k]
        } catch {
          continue
        }
        if (isWidgetCandidate(val)) return val
      }
    } catch {}
    return null
  }

  function scanIframesForWidget() {
    const iframes = document.querySelectorAll('iframe')
    for (const iframe of iframes) {
      if (!iframe.id || !/^tradingview_/.test(iframe.id)) continue
      let cw
      try {
        cw = iframe.contentWindow
      } catch {
        continue
      }
      if (!cw) continue
      const found = scanObjectForWidget(cw)
      if (found) return found
    }
    return null
  }

  let symbolSubscribed = false
  function subscribeToSymbolChanges(chart) {
    if (symbolSubscribed) return
    try {
      const subscription = chart.onSymbolChanged?.()
      if (subscription && typeof subscription.subscribe === 'function') {
        subscription.subscribe(null, () => redrawIfReady())
        symbolSubscribed = true
      }
    } catch {}
  }

  function findTradingViewWidget() {
    if (cachedWidget) {
      try {
        if (isWidgetCandidate(cachedWidget)) return cachedWidget
      } catch {}
      cachedWidget = null
      symbolSubscribed = false
    }

    const anchors = []
    const iframes = document.querySelectorAll('iframe')
    for (const iframe of iframes) {
      if (iframe.id && /^tradingview_/.test(iframe.id) && isVisible(iframe)) {
        anchors.push(iframe)
      }
    }
    for (const id of [
      'tv-chart-container',
      'trading-view-container',
      'chart_anchor_container_main',
      'chart_view_container_global_preload',
      'global-tv-overlay',
    ]) {
      const el = document.getElementById(id)
      if (el && isVisible(el)) anchors.push(el)
    }
    for (const cls of ['chart-anchor-main', 'chart-anchor-scope']) {
      const el = document.querySelector('.' + cls)
      if (el && isVisible(el)) anchors.push(el)
    }

    for (const anchor of anchors) {
      const widget = scanAncestorsForWidget(anchor)
      if (widget) {
        cachedWidget = widget
        return widget
      }
    }

    const fromWindow = scanObjectForWidget(window)
    if (fromWindow) {
      cachedWidget = fromWindow
      return fromWindow
    }

    const fromIframe = scanIframesForWidget()
    if (fromIframe) {
      cachedWidget = fromIframe
      return fromIframe
    }

    return null
  }

  function getAnchorTime(chart) {
    const nowSec = Math.floor(Date.now() / 1000)
    try {
      const range = chart.getVisibleRange()
      if (range?.from && range.from > 0) {
        return Math.min(Math.floor(range.from), nowSec)
      }
    } catch {}
    return nowSec
  }

  function removeAllLines(chart) {
    for (const id of drawnShapeIds) {
      try {
        chart.removeEntity(id)
      } catch {}
    }
    drawnShapeIds = []
  }

  function removeOrphanedBloomShapes(chart) {
    try {
      const allShapes = chart.getAllShapes()
      for (const shape of allShapes) {
        try {
          const props = chart.getShapeById(shape.id)?.getProperties()
          if (props?.text && props.text.startsWith(LABEL_PREFIX)) {
            chart.removeEntity(shape.id)
          }
        } catch {}
      }
    } catch {}
  }

  function waitForChartReady(widget) {
    if (!widget || typeof widget.onChartReady !== 'function')
      return Promise.resolve()
    return new Promise((resolve) => {
      try {
        widget.onChartReady(() => resolve())
      } catch {
        resolve()
      }
    })
  }

  async function drawOrderLines(widget, chart, lines) {
    await waitForChartReady(widget)
    subscribeToSymbolChanges(chart)
    removeOrphanedBloomShapes(chart)
    removeAllLines(chart)
    const time = getAnchorTime(chart)

    for (const line of lines) {
      try {
        const color = COLORS[line.side] || COLORS.BUY
        const result = chart.createShape(
          { price: line.price, time },
          {
            shape: 'horizontal_line',
            lock: true,
            disableSelection: true,
            disableSave: true,
            disableUndo: true,
            overrides: {
              linecolor: color,
              linewidth: 2,
              linestyle: 2,
              showLabel: true,
              text: `${LABEL_PREFIX}${line.label}${LABEL_PADDING}`,
              textcolor: color,
              fontsize: 12,
              horzLabelsAlign: 'right',
            },
          }
        )
        const shapeId = await Promise.resolve(result).catch(() => null)
        if (shapeId != null) drawnShapeIds.push(shapeId)
      } catch {}
    }
  }

  function redrawIfReady() {
    const widget = findTradingViewWidget()
    if (!widget) return
    const chart = getChartFromWidget(widget)
    if (!chart) return
    drawOrderLines(widget, chart, getChartLines(chart))
  }

  function tryDrawOrders() {
    const widget = findTradingViewWidget()
    if (!widget) return false
    const chart = getChartFromWidget(widget)
    if (!chart) return false
    drawOrderLines(widget, chart, getChartLines(chart))
    return true
  }

  let lastDetectedMode = null
  function startChartLines() {
    if (chartLinesInterval) return

    let drawn = false
    chartLinesInterval = setInterval(() => {
      if (!drawn) {
        drawn = tryDrawOrders()
        return
      }

      const widget = findTradingViewWidget()
      if (!widget) {
        drawn = false
        drawnShapeIds = []
        return
      }

      const chart = getChartFromWidget(widget)
      if (!chart) {
        drawn = false
        drawnShapeIds = []
        return
      }

      try {
        const currentMode = detectChartMode(chart)
        const modeChanged =
          currentMode != null && currentMode !== lastDetectedMode
        const expectedLineCount = getChartLines(chart).length
        const shapesMissing =
          drawnShapeIds.length > 0 && !chart.getShapeById(drawnShapeIds[0])
        const shouldRetry =
          expectedLineCount > 0 && drawnShapeIds.length < expectedLineCount

        if (modeChanged || shapesMissing || shouldRetry) {
          lastDetectedMode = currentMode
          drawnShapeIds = []
          drawOrderLines(widget, chart, getChartLines(chart))
        }
      } catch {
        drawn = false
        drawnShapeIds = []
      }
    }, 1500)
  }

  function stopChartLines() {
    if (chartLinesInterval) {
      clearInterval(chartLinesInterval)
      chartLinesInterval = null
    }
    drawnShapeIds = []
    cachedWidget = null
    activeOrders = []
    currentSupply = 0
    lastDetectedMode = null
    symbolSubscribed = false
  }

  function checkPage() {
    const isTokenPage = platformConfig.tokenPagePattern.test(
      window.location.pathname
    )
    if (isTokenPage) {
      startChartLines()
    } else {
      stopChartLines()
    }
  }

  const origPushState = history.pushState
  const origReplaceState = history.replaceState
  history.pushState = function (...args) {
    origPushState.apply(this, args)
    checkPage()
  }
  history.replaceState = function (...args) {
    origReplaceState.apply(this, args)
    checkPage()
  }
  window.addEventListener('popstate', checkPage)

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', checkPage)
  } else {
    checkPage()
  }
})()
