// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"

// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
//     import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
//     import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.

// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import { Socket } from "phoenix"
import { LiveSocket } from "phoenix_live_view"
import { hooks as colocatedHooks } from "phoenix-colocated/towerops"
import topbar from "../vendor/topbar"
import Chart from "../vendor/chart"
import type { ChartData, ChartDataset, ChartDataPoint } from "../vendor/chart"
import { WebAuthnRegister, WebAuthnLogin } from "./webauthn"
import "./passkey_settings"
import "./passkey_login"
import "./passkey_discoverable"
import type { SensorChartHook } from "./types/liveview"

// Generic Sensor Chart Hook (for CPU, Memory, Storage, etc.)
const SensorChart: SensorChartHook = {
  mounted() {
    this.createChart()
  },

  updated() {
    // Destroy and recreate chart to properly handle scale changes
    if (this.chart) {
      this.chart.destroy()
    }
    this.createChart()
  },

  destroyed() {
    if (this.chart) {
      this.chart.destroy()
    }
  },

  createChart() {
    if (!this.el) return

    const dataAttr = this.el.dataset.chart
    if (!dataAttr) return

    const data: ChartData = JSON.parse(dataAttr)
    const canvas = this.el.querySelector('canvas')
    if (!canvas) return

    const ctx = canvas.getContext('2d')
    if (!ctx) return

    const unit = this.el.dataset.unit || '%'
    const autoScale = this.el.dataset.autoScale === 'true' || this.el.dataset.autoScale === 'true'
    const showZeroLine = this.el.dataset.showZeroLine === 'true' || this.el.dataset.showZeroLine === 'true'
    const isTrafficChart = unit === 'bps'

    // Generate colors for each dataset
    const colors = [
      'rgb(59, 130, 246)',   // blue
      'rgb(239, 68, 68)',    // red
      'rgb(34, 197, 94)',    // green
      'rgb(234, 179, 8)',    // yellow
      'rgb(168, 85, 247)',   // purple
      'rgb(236, 72, 153)',   // pink
      'rgb(14, 165, 233)',   // sky
      'rgb(249, 115, 22)',   // orange
    ]

    const datasets: ChartDataset[] = data.datasets.map((dataset, index) => {
      const color = colors[index % colors.length]
      const baseConfig: ChartDataset = {
        ...dataset,
        borderColor: color,
        backgroundColor: color.replace('rgb', 'rgba').replace(')', ', 0.2)'),
        borderWidth: 2,
        tension: 0.4,
        pointRadius: 0,
        pointHoverRadius: 4,
      }

      // Add fill for traffic charts to create area effect
      if (isTrafficChart) {
        baseConfig.fill = 'origin'  // Fill to the zero line
      } else {
        baseConfig.fill = false  // No fill for CPU/memory/storage charts
      }

      return baseConfig
    })

    // Format bits per second into human-readable units
    const formatBps = (bps: number): string => {
      // Use absolute value for display (inbound is negative, but we show it as positive)
      const absBps = Math.abs(bps)
      if (absBps >= 1_000_000_000) {
        return (absBps / 1_000_000_000).toFixed(2) + ' Gbps'
      } else if (absBps >= 1_000_000) {
        return (absBps / 1_000_000).toFixed(2) + ' Mbps'
      } else if (absBps >= 1_000) {
        return (absBps / 1_000).toFixed(2) + ' Kbps'
      } else {
        return absBps.toFixed(2) + ' bps'
      }
    }

    // For traffic graphs, calculate symmetric scale to keep zero in the middle
    let yAxisConfig: any = {}
    if (autoScale) {
      if (showZeroLine && unit === 'bps') {
        // Find max absolute value to make symmetric scale
        let maxAbsValue = 0
        datasets.forEach(dataset => {
          if (dataset.data && dataset.data.length > 0) {
            dataset.data.forEach((point: ChartDataPoint) => {
              if (point && typeof point.y === 'number') {
                maxAbsValue = Math.max(maxAbsValue, Math.abs(point.y))
              }
            })
          }
        })
        // Add 10% padding, ensure minimum of 1000 (1 Kbps)
        maxAbsValue = Math.max(maxAbsValue * 1.1, 1000)

        yAxisConfig = {
          min: -maxAbsValue,
          max: maxAbsValue,
          ticks: {
            callback: function(value: number) {
              return formatBps(value)
            }
          },
          grid: {
            color: function(context: any) {
              // Make the zero line more prominent
              if (context.tick.value === 0) {
                return 'rgba(0, 0, 0, 0.3)'
              }
              return 'rgba(0, 0, 0, 0.05)'
            },
            lineWidth: function(context: any) {
              // Make the zero line thicker
              if (context.tick.value === 0) {
                return 2
              }
              return 1
            }
          }
        }
      } else {
        yAxisConfig = {
          beginAtZero: showZeroLine,
          ticks: {
            callback: function(value: number) {
              if (unit === 'bps') {
                return formatBps(value)
              }
              return value + unit
            }
          },
          grid: {
            color: function(context: any) {
              // Make the zero line more prominent
              if (showZeroLine && context.tick.value === 0) {
                return 'rgba(0, 0, 0, 0.3)'
              }
              return 'rgba(0, 0, 0, 0.05)'
            },
            lineWidth: function(context: any) {
              // Make the zero line thicker
              if (showZeroLine && context.tick.value === 0) {
                return 2
              }
              return 1
            }
          }
        }
      }
    } else {
      yAxisConfig = {
        min: 0,
        max: 100,
        ticks: {
          callback: function(value: number) {
            if (unit === 'bps') {
              return formatBps(value)
            }
            return value + unit
          }
        },
        grid: {
          color: 'rgba(0, 0, 0, 0.05)'
        }
      }
    }

    // Calculate 24-hour time range for consistent x-axis
    const now = Date.now()
    const twentyFourHoursAgo = now - (24 * 60 * 60 * 1000)

    this.chart = new Chart(ctx, {
      type: 'line',
      data: { datasets },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        interaction: {
          mode: 'index',
          intersect: false,
        },
        plugins: {
          legend: {
            position: 'bottom',
            labels: {
              usePointStyle: true,
              padding: 15,
            }
          },
          tooltip: {
            callbacks: {
              title: function(context: any) {
                const timestamp = context[0].parsed.x
                const date = new Date(timestamp)
                return date.toLocaleString('en-US', {
                  month: 'short',
                  day: 'numeric',
                  hour: '2-digit',
                  minute: '2-digit',
                  hour12: false
                })
              },
              label: function(context: any) {
                if (unit === 'bps') {
                  return context.dataset.label + ': ' + formatBps(context.parsed.y)
                }
                return context.dataset.label + ': ' + context.parsed.y.toFixed(1) + unit
              }
            }
          }
        },
        scales: {
          x: {
            type: 'linear',
            min: twentyFourHoursAgo,
            max: now,
            ticks: {
              callback: function(value: number) {
                const date = new Date(value)
                return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false })
              },
              maxTicksLimit: 12
            },
            grid: {
              display: false
            }
          },
          y: yAxisConfig
        }
      }
    })
  }
}

// LiveView hook for copying to clipboard
const CopyToClipboard = {
  mounted() {
    this.el.addEventListener("click", (e) => {
      e.preventDefault()
      const targetId = this.el.dataset.target
      if (!targetId) {
        console.error("No data-target attribute found on copy button")
        return
      }

      const targetEl = document.querySelector(targetId) as HTMLInputElement | HTMLTextAreaElement | HTMLElement
      if (!targetEl) {
        console.error(`Target element not found: ${targetId}`)
        return
      }

      let text = ""
      if (targetEl.tagName === "INPUT" || targetEl.tagName === "TEXTAREA") {
        text = (targetEl as HTMLInputElement | HTMLTextAreaElement).value
      } else {
        text = targetEl.innerText || targetEl.textContent || ""
      }

      if (text) {
        navigator.clipboard.writeText(text).then(() => {
          console.log('Copied to clipboard successfully')
          // Optional: Show visual feedback
          const originalText = this.el.innerHTML
          this.el.innerHTML = '<svg class="h-3 w-3 inline" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg> Copied!'
          setTimeout(() => {
            this.el.innerHTML = originalText
          }, 2000)
        }).catch(err => {
          console.error('Failed to copy:', err)
        })
      } else {
        console.error('No text found to copy')
      }
    })
  }
}

// LiveView hook for scrolling to top
const ScrollToTop = {
  mounted() {
    this.handleEvent("scroll_to_top", () => {
      window.scrollTo({ top: 0, behavior: "smooth" })
    })
  }
}

// LiveView hook for auto-dismissing flash messages
const AutoDismissFlash = {
  timeout: null as ReturnType<typeof setTimeout> | null,
  mounted(this: any) {
    const dismissAfter = parseInt(this.el.dataset.dismissAfter || "30000", 10)
    this.timeout = setTimeout(() => {
      this.el.click()
    }, dismissAfter)
  },
  destroyed(this: any) {
    if (this.timeout) {
      clearTimeout(this.timeout)
      this.timeout = null
    }
  }
}

const csrfToken = document.querySelector<HTMLMetaElement>("meta[name='csrf-token']")?.getAttribute("content")
if (!csrfToken) {
  throw new Error('CSRF token meta tag not found')
}

const liveSocket = new LiveSocket("/live", Socket, {
  longPollFallbackMs: 2500,
  params: { _csrf_token: csrfToken },
  hooks: { ...colocatedHooks, SensorChart, WebAuthnRegister, WebAuthnLogin, CopyToClipboard, ScrollToTop, AutoDismissFlash },
})

// Show progress bar on live navigation and form submits
topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" })
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())

// Handle clipboard copy events
window.addEventListener("phx:copy", (event: any) => {
  // When using JS.dispatch with 'to:', the event is dispatched to that element
  // and event.target will be that element
  const el = event.target as HTMLElement

  let text = ""

  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
    text = (el as HTMLInputElement | HTMLTextAreaElement).value
  } else {
    text = el.innerText || el.textContent || ""
  }

  if (text) {
    navigator.clipboard.writeText(text).then(() => {
      console.log('Copied to clipboard:', text.substring(0, 50) + (text.length > 50 ? '...' : ''))
    }).catch(err => {
      console.error('Failed to copy text:', err)
    })
  } else {
    console.warn('No text found to copy from element:', el)
  }
})

// connect if there are any LiveViews on the page
liveSocket.connect()

// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000)  // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
declare global {
  interface Window {
    liveSocket: LiveSocket
    liveReloader?: any
  }
}

window.liveSocket = liveSocket

// The lines below enable quality of life phoenix_live_reload
// development features:
//
//     1. stream server logs to the browser console
//     2. click on elements to jump to their definitions in your code editor
//
if (process.env.NODE_ENV === "development") {
  window.addEventListener("phx:live_reload:attached", ({ detail: reloader }: any) => {
    // Enable server log streaming to client.
    // Disable with reloader.disableServerLogs()
    reloader.enableServerLogs()

    // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component
    //
    //   * click with "c" key pressed to open at caller location
    //   * click with "d" key pressed to open at function component definition location
    let keyDown: string | null = null
    window.addEventListener("keydown", e => keyDown = e.key)
    window.addEventListener("keyup", _e => keyDown = null)
    window.addEventListener("click", e => {
      if (keyDown === "c") {
        e.preventDefault()
        e.stopImmediatePropagation()
        reloader.openEditorAtCaller(e.target)
      } else if (keyDown === "d") {
        e.preventDefault()
        e.stopImmediatePropagation()
        reloader.openEditorAtDef(e.target)
      }
    }, true)

    window.liveReloader = reloader
  })
}
