/**
 * installTitle — the five-second onboarding card + the audio gate.
 *
 * Full-screen overlay on boot: bold CSS bubble-letter BLOBBO wordmark with
 * rainbow paint-drip accents, a one-line pitch, a three-row controls card, and
 * a fat PLAY button. If the world is still booting (a `ready` promise was
 * handed in), PLAY is replaced by a wobbling "loading…" blob dot until it
 * resolves.
 *
 * On the first click / keypress / gamepad button it emits **`ui:play`** exactly
 * once (synchronously, inside the user gesture, so a listening AudioContext can
 * unlock), fades the overlay out and never shows it again.
 *
 * No external fonts or assets — everything is inline CSS. Readable on any
 * background thanks to the dimming backdrop.
 */
import type { World } from '../contracts'
import { PALETTE } from '../contracts'
import { ghostTime, hasGhost } from '../ghost/format'

export interface TitleOptions {
  /** Resolves when Rapier/world finished booting. While pending: loading state. */
  ready?: Promise<unknown>
}

export interface TitleHandle {
  /** Resolves once the player has started (ui:play fired). */
  done: Promise<void>
  /** Force-remove the overlay (teardown / tests). Does not emit ui:play. */
  remove(): void
}

const STYLE_ID = 'blobbo-title-style'
const DRIP_COLORS = [PALETTE.red, PALETTE.yellow, PALETTE.green, PALETTE.blue, PALETTE.pink]

function injectStyle(): void {
  if (document.getElementById(STYLE_ID)) return
  const el = document.createElement('style')
  el.id = STYLE_ID
  el.textContent = `
#blobbo-title{position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:26px;padding:24px;box-sizing:border-box;
  background:radial-gradient(120% 120% at 50% 30%,rgba(24,30,54,.72),rgba(10,12,24,.92));
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  font-family:'Comic Sans MS','Chalkboard SE','Baloo 2',system-ui,sans-serif;
  color:#fff;text-align:center;opacity:1;transition:opacity .4s ease}
#blobbo-title.hide{opacity:0;pointer-events:none}
.blobbo-word{display:flex;gap:.01em;line-height:1;
  font-weight:900;font-size:clamp(58px,15vw,168px);filter:drop-shadow(0 8px 14px rgba(0,0,0,.45))}
.blobbo-word span{position:relative;color:#fff;
  -webkit-text-stroke:.085em #16264a;paint-order:stroke fill;
  text-shadow:0 .05em 0 #16264a,0 .11em .02em rgba(0,0,0,.4);
  transform:rotate(var(--rot,0deg));
  animation:blobbo-pop .5s cubic-bezier(.2,1.5,.4,1) both;animation-delay:var(--d,0s)}
.blobbo-drip{position:absolute;left:50%;bottom:.04em;width:.15em;height:.2em;
  transform:translateX(-50%);background:var(--c,#fff);border-radius:0 0 55% 55%/0 0 70% 70%;
  box-shadow:0 .05em 0 var(--c,#fff);z-index:-1;
  animation:blobbo-drip 2.8s ease-in-out infinite;animation-delay:var(--dd,0s)}
@keyframes blobbo-pop{from{transform:translateY(.25em) rotate(var(--rot,0deg)) scale(.6);opacity:0}
  to{transform:translateY(0) rotate(var(--rot,0deg)) scale(1);opacity:1}}
@keyframes blobbo-drip{0%,100%{transform:translateX(-50%) scaleY(1)}55%{transform:translateX(-50%) scaleY(1.7)}}
.blobbo-pitch{font-size:clamp(17px,3.2vw,26px);font-weight:700;letter-spacing:.02em;
  color:#eaf2ff;text-shadow:0 2px 6px rgba(0,0,0,.6);margin-top:-6px}
.blobbo-controls{display:flex;flex-wrap:wrap;gap:10px 18px;justify-content:center;
  background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.16);border-radius:14px;
  padding:12px 18px;font-family:ui-monospace,Menlo,monospace;font-size:15px;font-weight:600}
.blobbo-controls .row{display:flex;align-items:center;gap:8px;white-space:nowrap}
.blobbo-controls .key{display:inline-block;min-width:20px;padding:2px 8px;border-radius:6px;
  background:#33436b;color:#fff;box-shadow:0 2px 0 rgba(0,0,0,.4);text-align:center}
.blobbo-best{font-family:ui-monospace,Menlo,monospace;font-size:15px;font-weight:700;
  color:#ffd60a;min-height:1.2em}
.blobbo-play{cursor:pointer;border:none;font-family:inherit;font-weight:900;
  font-size:clamp(24px,5vw,40px);color:#16264a;letter-spacing:.04em;
  padding:.35em 1.3em;border-radius:999px;
  background:linear-gradient(180deg,#fff,#ffe14d);
  box-shadow:0 8px 0 #b98a00,0 12px 24px rgba(0,0,0,.4);
  transform:translateY(0);transition:transform .08s ease,box-shadow .08s ease;
  animation:blobbo-breathe 2.2s ease-in-out infinite}
.blobbo-play:hover{transform:translateY(-2px)}
.blobbo-play:active{transform:translateY(6px);box-shadow:0 2px 0 #b98a00,0 4px 10px rgba(0,0,0,.4)}
@keyframes blobbo-breathe{0%,100%{scale:1}50%{scale:1.05}}
.blobbo-loading{display:flex;flex-direction:column;align-items:center;gap:12px;
  font-family:ui-monospace,Menlo,monospace;font-size:16px;font-weight:700;color:#eaf2ff}
.blobbo-dot{width:34px;height:34px;border-radius:50%;
  background:radial-gradient(circle at 35% 30%,#fff,#bfe6ff 60%,#7ab6e6);
  animation:blobbo-wobble 1s ease-in-out infinite}
@keyframes blobbo-wobble{0%,100%{transform:translateY(0) scale(1,1)}
  25%{transform:translateY(-10px) scale(.92,1.1)}
  55%{transform:translateY(0) scale(1.15,.85)}
  75%{transform:translateY(-4px) scale(.96,1.05)}}
.blobbo-hint{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:rgba(234,242,255,.65)}
`
  document.head.appendChild(el)
}

function buildWordmark(): HTMLElement {
  const word = document.createElement('div')
  word.className = 'blobbo-word'
  word.setAttribute('aria-label', 'BLOBBO')
  const letters = 'BLOBBO'.split('')
  // deterministic-ish tilts + a few drips so it reads hand-made, not random.
  const tilts = [-5, 4, -3, 6, -4, 3]
  const dripAt = new Set([0, 2, 4]) // under B, O, B
  letters.forEach((ch, i) => {
    const span = document.createElement('span')
    span.textContent = ch
    span.style.setProperty('--rot', `${tilts[i]}deg`)
    span.style.setProperty('--d', `${i * 0.06}s`)
    if (dripAt.has(i)) {
      const drip = document.createElement('span')
      drip.className = 'blobbo-drip'
      drip.style.setProperty('--c', DRIP_COLORS[i % DRIP_COLORS.length])
      drip.style.setProperty('--dd', `${i * 0.35}s`)
      span.appendChild(drip)
    }
    word.appendChild(span)
  })
  return word
}

function buildControls(): HTMLElement {
  const box = document.createElement('div')
  box.className = 'blobbo-controls'
  const rows: Array<[string, string]> = [
    ['WASD / stick', 'move'],
    ['Space / A', 'jump'],
    ['R / Start', 'restart'],
  ]
  for (const [keys, action] of rows) {
    const row = document.createElement('div')
    row.className = 'row'
    const k = document.createElement('span')
    k.className = 'key'
    k.textContent = keys
    const a = document.createElement('span')
    a.textContent = action
    row.append(k, a)
    box.appendChild(row)
  }
  return box
}

export function installTitle(world: World, opts: TitleOptions = {}): TitleHandle {
  injectStyle()

  const overlay = document.createElement('div')
  overlay.id = 'blobbo-title'

  overlay.appendChild(buildWordmark())

  const pitch = document.createElement('div')
  pitch.className = 'blobbo-pitch'
  pitch.textContent = 'Race. Get painted. Paint is power.'
  overlay.appendChild(pitch)

  overlay.appendChild(buildControls())

  // saved best + ghost status
  const best = document.createElement('div')
  best.className = 'blobbo-best'
  if (hasGhost()) {
    const t = ghostTime()
    best.textContent = t != null ? `best ${t.toFixed(2)}s  ·  👻 ghost ready` : '👻 ghost ready'
  }
  overlay.appendChild(best)

  // action slot: either the loading blob or the PLAY button
  const slot = document.createElement('div')
  overlay.appendChild(slot)

  const hint = document.createElement('div')
  hint.className = 'blobbo-hint'
  overlay.appendChild(hint)

  document.body.appendChild(overlay)

  // ---- lifecycle -----------------------------------------------------------
  let armed = false // becomes true once boot completes → input can start play
  let started = false
  let resolveDone: () => void = () => {}
  const done = new Promise<void>((res) => (resolveDone = res))

  const showLoading = (): void => {
    slot.innerHTML = ''
    const box = document.createElement('div')
    box.className = 'blobbo-loading'
    const dot = document.createElement('div')
    dot.className = 'blobbo-dot'
    const label = document.createElement('div')
    label.textContent = 'loading…'
    box.append(dot, label)
    slot.appendChild(box)
  }

  const playBtn = document.createElement('button')
  playBtn.className = 'blobbo-play'
  playBtn.type = 'button'
  playBtn.textContent = 'PLAY'

  const showPlay = (): void => {
    slot.innerHTML = ''
    slot.appendChild(playBtn)
    hint.textContent = 'click PLAY · or press any key · or a gamepad button'
  }

  // ---- the single start path ----------------------------------------------
  const start = (): void => {
    if (!armed || started) return
    started = true
    cleanup()
    // Emit synchronously within the user gesture so audio can unlock here.
    world.events.emit('ui:play')
    overlay.classList.add('hide')
    resolveDone()
    // remove from DOM after the fade so it can never reappear or catch input
    window.setTimeout(() => overlay.remove(), 450)
  }

  // input wiring (removed on start so ui:play can only fire once)
  const onKey = (e: KeyboardEvent): void => {
    if (e.repeat) return
    start()
  }
  const onClick = (): void => start()
  let padRaf = 0
  const pollPad = (): void => {
    if (started) return
    const pads = typeof navigator !== 'undefined' && navigator.getGamepads ? navigator.getGamepads() : null
    if (pads) {
      for (const p of pads) {
        if (!p || !p.connected) continue
        if (p.buttons.some((b) => b.pressed)) {
          start()
          return
        }
        break
      }
    }
    padRaf = requestAnimationFrame(pollPad)
  }

  const cleanup = (): void => {
    window.removeEventListener('keydown', onKey)
    playBtn.removeEventListener('click', onClick)
    if (padRaf) cancelAnimationFrame(padRaf)
  }

  playBtn.addEventListener('click', onClick)

  const arm = (): void => {
    if (armed) return
    armed = true
    showPlay()
    window.addEventListener('keydown', onKey)
    padRaf = requestAnimationFrame(pollPad)
  }

  if (opts.ready) {
    showLoading()
    hint.textContent = 'waking up the physics…'
    opts.ready.then(arm, arm) // arm even if boot rejects; the game decides what to do
  } else {
    arm()
  }

  return {
    done,
    remove(): void {
      cleanup()
      overlay.remove()
    },
  }
}
