Clone
// 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/pi_day"
import topbar from "../vendor/topbar"

const AVATAR_SYMBOLS = {
  pi: "\u03C0", sigma: "\u03A3", delta: "\u0394", omega: "\u03A9",
  theta: "\u03B8", lambda: "\u03BB", phi: "\u03C6", psi: "\u03C8",
  epsilon: "\u03B5", zeta: "\u03B6"
}

const AVATAR_COLORS = {
  pi: "#06b6d4", sigma: "#8b5cf6", delta: "#f59e0b", omega: "#ef4444",
  theta: "#22c55e", lambda: "#ec4899", phi: "#3b82f6", psi: "#f97316",
  epsilon: "#14b8a6", zeta: "#a855f7"
}

const STATIONS = [
  { x: 130, y: 130, label: "Pi Memory\nSprint", icon: "\u{1F9E0}", color: "#06b6d4" },
  { x: 670, y: 130, label: "Monte Carlo\nPi", icon: "\u{1F3AF}", color: "#8b5cf6" },
  { x: 130, y: 460, label: "Slice\nthe Pi", icon: "\u{1FA93}", color: "#f59e0b" },
  { x: 670, y: 460, label: "Pi Trivia\nBlitz", icon: "\u{1F4A1}", color: "#ec4899" },
  { x: 400, y: 300, label: "Projectile\nPi", icon: "\u{1F680}", color: "#22c55e" },
]

const SpectateHub = {
  mounted() {
    const canvas = this.el.querySelector("canvas")
    const ctx = canvas.getContext("2d")
    this.players = []

    const resize = () => {
      const rect = this.el.getBoundingClientRect()
      canvas.width = 800
      canvas.height = 600
    }
    resize()
    window.addEventListener("resize", resize)

    this.handleEvent("hub_players", ({ players }) => {
      this.players = players
      this.draw(ctx, canvas)
    })

    // Initial draw
    this.draw(ctx, canvas)
  },

  draw(ctx, canvas) {
    const W = 800, H = 600
    ctx.clearRect(0, 0, W, H)

    // Background
    ctx.fillStyle = "#0a0a2e"
    ctx.fillRect(0, 0, W, H)

    // Grid
    ctx.strokeStyle = "rgba(30, 27, 75, 0.3)"
    ctx.lineWidth = 1
    for (let x = 0; x <= W; x += 40) {
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke()
    }
    for (let y = 0; y <= H; y += 40) {
      ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke()
    }

    // Border
    ctx.strokeStyle = "rgba(99, 102, 241, 0.3)"
    ctx.lineWidth = 2
    ctx.strokeRect(20, 20, 760, 560)

    // "3.14" watermark
    ctx.fillStyle = "rgba(30, 27, 75, 0.3)"
    ctx.font = "bold 120px monospace"
    ctx.textAlign = "center"
    ctx.textBaseline = "middle"
    ctx.fillText("3.14", 400, 300)

    // Stations
    STATIONS.forEach(s => {
      ctx.beginPath()
      ctx.arc(s.x, s.y, 35, 0, Math.PI * 2)
      ctx.fillStyle = s.color + "99"
      ctx.fill()
      ctx.strokeStyle = s.color
      ctx.lineWidth = 2
      ctx.stroke()

      ctx.font = "24px serif"
      ctx.textAlign = "center"
      ctx.textBaseline = "middle"
      ctx.fillStyle = "white"
      ctx.fillText(s.icon, s.x, s.y - 3)

      ctx.font = "11px monospace"
      ctx.fillStyle = "#e2e8f0"
      const lines = s.label.split("\n")
      lines.forEach((line, i) => {
        ctx.fillText(line, s.x, s.y + 48 + i * 14)
      })
    })

    // Title
    ctx.font = "bold 24px monospace"
    ctx.fillStyle = "#a78bfa"
    ctx.textAlign = "center"
    ctx.fillText("Pi Station", 400, 30)

    // Players
    this.players.forEach(p => {
      const color = AVATAR_COLORS[p.avatar_key] || "#888888"
      const symbol = AVATAR_SYMBOLS[p.avatar_key] || "?"

      // Shadow
      ctx.beginPath()
      ctx.ellipse(p.x, p.y + 12, 15, 5, 0, 0, Math.PI * 2)
      ctx.fillStyle = "rgba(0,0,0,0.3)"
      ctx.fill()

      // Body
      ctx.beginPath()
      ctx.arc(p.x, p.y, 18, 0, Math.PI * 2)
      ctx.fillStyle = color
      ctx.fill()
      ctx.strokeStyle = "rgba(255,255,255,0.4)"
      ctx.lineWidth = 2
      ctx.stroke()

      // Symbol
      ctx.font = "18px serif"
      ctx.fillStyle = "white"
      ctx.textAlign = "center"
      ctx.textBaseline = "middle"
      ctx.fillText(symbol, p.x, p.y)

      // Name
      ctx.font = "11px monospace"
      const nameWidth = ctx.measureText(p.name).width
      ctx.fillStyle = "rgba(0,0,0,0.5)"
      ctx.fillRect(p.x - nameWidth / 2 - 4, p.y - 35, nameWidth + 8, 16)
      ctx.fillStyle = "white"
      ctx.fillText(p.name, p.x, p.y - 27)

      // Status badge
      if (p.status && p.status !== "hub") {
        ctx.font = "9px monospace"
        ctx.fillStyle = "#22d3ee"
        ctx.fillText("Playing: " + p.status, p.x, p.y + 28)
      }
    })

    // Player count
    ctx.font = "12px monospace"
    ctx.fillStyle = "#a78bfa"
    ctx.textAlign = "left"
    ctx.fillText("Online: " + this.players.length, 30, 580)
  }
}

const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {
  longPollFallbackMs: 2500,
  params: {_csrf_token: csrfToken},
  hooks: {...colocatedHooks, SpectateHub},
})

// 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())

// 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()
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}) => {
    // 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
    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
  })
}