import helpers from "/scratchpad/_lib/line.asset.mjs"
import palette from "/scratchpad/_lib/palette.asset.mjs"
import * as random from "/scratchpad/_lib/random.asset.mjs"

const ease = (t, p = 5) =>
  t <= 0.5 ? Math.pow(t * 2, p) / 2 : 1 - Math.pow(2 - t * 2, p) / 2

const colors = {
  bg: palette.dark,
  fg: palette.canvas,
}

const connect = (ctx, points) => {
  points.forEach(({ x, y }, i) => {
    ctx[i === 0 ? "moveTo" : "lineTo"](x, y)
  })
}

const setup = (canvas) => {
  canvas.width = canvas.height = canvas.offsetWidth * window.devicePixelRatio
}

const pose = (w, h) => {
  const eye = (dx, dy) => {
    const [x, y] = [random.wobble(dx, w * 0.04), random.wobble(dy, w * 0.05)]
    const f = w * 0.007
    const o = w * 0.006
    return [
      [x + random.wobble(o, f), y + random.wobble(o, f)],
      [x + random.wobble(-o, f), y + random.wobble(o, f)],
      [x + random.wobble(-o, f), y + random.wobble(-o, f)],
      [x + random.wobble(o, f), y + random.wobble(-o, f)],
    ]
  }

  const moustache = () => {
    const l = [
      random.wobble(w * 0.37, w * 0.05),
      random.wobble(h * 0.61, h * 0.04),
    ]
    const r = [
      random.wobble(w * 0.63, w * 0.05),
      random.wobble(h * 0.61, h * 0.04),
    ]

    return [
      [l[0], l[1] - random.wobble(h * 0.07, h * 0.03)],
      l,
      [random.wobble(w * 0.5, w * 0.05), random.wobble(h * 0.66, h * 0.05)],
      r,
      [r[0], r[1] - random.wobble(h * 0.07, h * 0.03)],
      [random.wobble(w * 0.5, w * 0.05), random.wobble(h * 0.53, h * 0.02)],
    ]
  }

  const paw = (x, y) => {
    const l = [
      x - random.wobble(w * 0.1, w * 0.05),
      y + random.wobble(0, h * 0.04),
    ]
    const r = [
      x + random.wobble(w * 0.1, w * 0.05),
      y + random.wobble(0, h * 0.04),
    ]

    return [
      [l[0], l[1] - random.wobble(h * 0.04, h * 0.01)],
      [l[0], l[1] + random.wobble(h * 0.04, h * 0.01)],
      [r[0], r[1] + random.wobble(h * 0.04, h * 0.01)],
      [r[0], r[1] - random.wobble(h * 0.04, h * 0.01)],
      [random.wobble(x, w * 0.05), y - random.wobble(h * 0.07, h * 0.01)],
    ].reverse()
  }

  return {
    body: [
      // Left cheek
      [random.wobble(w * 0.21, w * 0.1), random.wobble(h * 0.67, h * 0.07)],
      // Crown
      [random.wobble(w * 0.26, w * 0.08), random.wobble(h * 0.15, h * 0.05)],
      [random.wobble(w * 0.74, w * 0.08), random.wobble(h * 0.15, h * 0.05)],
      // Right cheek
      [random.wobble(w * 0.79, w * 0.1), random.wobble(h * 0.67, h * 0.07)],
      // Chin
      [random.wobble(w * 0.5, w * 0.1), random.wobble(h * 0.81, h * 0.05)],
    ],

    eyeL: eye(w * 0.34, h * 0.4),
    eyeR: eye(w * 0.66, h * 0.4),
    handR: paw(
      w * 0.15,
      h * 0.6 + (random.maybe(0.3) ? 0 : random.number(-h * 0.33))
    ),
    handL: paw(
      w * 0.85,
      h * 0.6 + (random.maybe(0.3) ? 0 : random.number(-h * 0.33))
    ),
    footR: paw(w * 0.35 - (random.maybe(0.1) ? w * 0.2 : 0), h * 0.9),
    footL: paw(w * 0.65 + (random.maybe(0.1) ? w * 0.2 : 0), h * 0.9),
    moustache: moustache(),
    mouth: random.maybe(0.3)
      ? eye(w * 0.5, h * 0.66)
      : [
          [
            random.wobble(w * 0.43, w * 0.05),
            random.wobble(h * 0.66, h * 0.01),
          ],
          [random.wobble(w * 0.5, w * 0.05), random.wobble(h * 0.68, h * 0.01)],
          [
            random.wobble(w * 0.57, w * 0.05),
            random.wobble(h * 0.66, h * 0.01),
          ],
          [random.wobble(w * 0.5, w * 0.05), random.wobble(h * 0.69, h * 0.01)],
        ],
  }
}

const interpolate = (f1, f2, t) =>
  Object.fromEntries(
    Object.entries(f1).map(([key, val]) => [
      key,
      val.map(([x, y], i) => [
        x + (f2[key][i][0] - x) * t,
        y + (f2[key][i][1] - y) * t,
      ]),
    ])
  )

const draw = (ctx, head, t) => {
  const { width: w, height: h } = ctx.canvas

  ctx.clearRect(0, 0, w, h)
  ctx.save()

  Object.entries(head).forEach(([key, shape]) => {
    shape.push(shape[0])
    shape.push(shape[1])
    shape.push(shape[2])
    head[key] = helpers.spline(helpers.objectify(shape)).slice(1, -1)
  })

  const y = Math.sin(Math.PI * t) * h * 0.04

  ctx.translate(0, y)
  connect(ctx, head.body)
  connect(ctx, head.handL)
  connect(ctx, head.handR)
  ctx.translate(0, -y)

  connect(ctx, head.footR)
  connect(ctx, head.footL)
  ctx.fillStyle = colors.fg
  ctx.fill()

  ctx.translate(0, y)
  ctx.beginPath()
  connect(ctx, head.eyeL)
  connect(ctx, head.eyeR)

  ctx.lineJoin = "round"
  ctx.lineCap = "round"
  ctx.strokeStyle = colors.bg
  ctx.lineWidth = w * 0.05
  ctx.stroke()

  ctx.beginPath()
  ctx.fillStyle = colors.bg
  connect(ctx, head.moustache)
  ctx.translate(w * 0.2, h * 0.3)
  ctx.scale(0.6, 0.6)
  connect(ctx, head.moustache)
  ctx.fill()
  ctx.restore(0, -y)
}

export default (canvas) => {
  setup(canvas)

  let playing = true
  let start = Date.now()
  let duration = random.maybe(0.2) ? 280 : 420
  let { width, height } = canvas
  let poses = [pose(width, height), pose(width, height)]

  const ctx = canvas.getContext("2d")

  ;(function loop() {
    if (playing) {
      const t = Math.min((Date.now() - start) / duration, 1)

      draw(ctx, interpolate(...poses, ease(t, 1.5)), t)
      window.requestAnimationFrame(loop)

      if (t === 1) {
        start = Date.now()
        poses.shift()
        poses.push(pose(width, height))
      }
    }
  })()

  return {
    cancel: () => {
      playing = false
    },
  }
}