import { test } from 'node:test'
import assert from 'node:assert/strict'
import {
  heroBgStack,
  buildOverlay,
  heroBackground,
  parseLayout,
  defaultLayout,
  DEFAULT_HERO_IMAGE,
} from '../src/lib/heroLayout.js'

// Unit-test the hero-layout helpers shared by the public portal and the admin
// editor. The high-value logic: parseLayout must reject anything malformed or of
// the wrong version (a bad stored value must never render as a broken hero), and
// heroBackground must take the untouched-default branch only when there is no
// custom image.

// ── parseLayout: the version/shape guard ────────────────────────────────
test('parseLayout accepts a well-formed v1 layout', () => {
  const layout = { version: 1, elements: [] }
  assert.deepEqual(parseLayout(JSON.stringify(layout)), layout)
})

test('parseLayout returns null for missing, malformed, wrong-version, or wrong-shape input', () => {
  assert.equal(parseLayout(null), null)
  assert.equal(parseLayout(''), null)
  assert.equal(parseLayout('{ not json'), null)
  assert.equal(parseLayout(JSON.stringify({ version: 2, elements: [] })), null) // wrong version
  assert.equal(parseLayout(JSON.stringify({ version: 1, elements: 'nope' })), null) // elements not an array
  assert.equal(parseLayout(JSON.stringify({ version: 1 })), null) // no elements
})

// ── heroBackground: default vs custom branch ────────────────────────────
test('heroBackground uses the contained-emblem default stack only when default AND no custom image', () => {
  const style = heroBackground({ background: {} }, { isDefault: true })
  assert.match(style.backgroundImage, /runic-emblem\.png/)
  assert.equal(style.backgroundSize, 'cover, min(74vh, 640px, 86vw)') // the two-layer default size
})

test('heroBackground threads a per-instance defaultImage into the default stack', () => {
  const style = heroBackground({ background: {} }, { isDefault: true, defaultImage: '/brand/hero.jpg' })
  assert.match(style.backgroundImage, /\/brand\/hero\.jpg/)
})

test('heroBackground composes overlay + custom image once a custom image is set', () => {
  const style = heroBackground(
    { background: { image_url: '/uploads/hero.png', position_x: 'right', position_y: 'top', size: 'contain' }, overlay: { opacity: 0.5 } },
    { isDefault: true }, // still leaves the default branch because a custom image_url is present
  )
  assert.match(style.backgroundImage, /\/uploads\/hero\.png/)
  assert.match(style.backgroundImage, /rgba\(11,15,20,0\.5\)/) // overlay opacity threaded in
  assert.equal(style.backgroundPosition, 'right top')
  assert.equal(style.backgroundSize, 'contain')
})

test('heroBackground defaults the overlay opacity to 0.72 when unset', () => {
  const style = heroBackground({ background: { image_url: '/x.png' } }, {})
  assert.match(style.backgroundImage, /rgba\(11,15,20,0\.72\)/)
})

// ── smaller helpers ─────────────────────────────────────────────────────
test('heroBgStack falls back to the default emblem when no image is given', () => {
  assert.match(heroBgStack(), new RegExp(DEFAULT_HERO_IMAGE.replace(/[/.]/g, '\\$&')))
  assert.match(heroBgStack('/custom.png'), /\/custom\.png/)
})

test('buildOverlay scales both gradient stops from the opacity', () => {
  const overlay = buildOverlay(0.8)
  assert.match(overlay, /rgba\(11,15,20,0.12\)/) // 0.8 * 0.15 top stop
  assert.match(overlay, /rgba\(11,15,20,0.8\)/) // bottom stop
})

// ── defaultLayout: the fallback hero ────────────────────────────────────
test('defaultLayout builds a valid v1 layout that threads the teaser and shard name', () => {
  const layout = defaultLayout('Come play with us', 'My Shard')
  assert.equal(parseLayout(JSON.stringify(layout)) !== null, true, 'the default is itself a valid layout')
  assert.equal(layout.version, 1)
  const textLines = layout.elements.find((e) => e.id === 'default-text').props.lines
  assert.ok(textLines.some((l) => l.text === 'My Shard'), 'shard name rendered as the h1')
  assert.ok(textLines.some((l) => l.text === 'Come play with us'), 'teaser threaded in')
})
