<script setup>
import { computed } from 'vue'
import { RouterLink } from 'vue-router'
import Button from './Button.vue'
import Badge from './Badge.vue'

// In-app paths render as <RouterLink> so clicks stay inside the SPA —
// preserving Vue Router's scroll history (back-button restores y) and
// avoiding the flash of a full-document reload. External / anchor /
// hash-only links keep the plain <a> behaviour.
function isInternalPath(href) {
  return typeof href === 'string' && href.startsWith('/') && !href.startsWith('//')
}

const props = defineProps({
  eyebrow: { type: String, default: '' },
  headline: { type: String, default: '' },
  subheadline: { type: String, default: '' },
  image: { type: String, default: '' },
  imageAlt: { type: String, default: '' },
  badge: { type: String, default: '' },
  badgeVariant: {
    type: String,
    default: 'accent',
    validator: (v) =>
      ['neutral', 'brand', 'accent', 'subtle', 'success', 'warning', 'danger'].includes(v),
  },
  ctaLabel: { type: String, default: '' },
  ctaHref: { type: String, default: '' },
  secondaryLabel: { type: String, default: '' },
  secondaryHref: { type: String, default: '' },
  variant: {
    type: String,
    default: 'split',
    validator: (v) => ['split', 'centered'].includes(v),
  },
  tone: {
    type: String,
    default: 'cream',
    validator: (t) => ['cream', 'paper', 'brand'].includes(t),
  },
  /** Only affects the split variant. When true, the image sits on the left
   *  and copy on the right from `lg` up. Mobile order is unchanged (image
   *  on top, copy below) to preserve reading order. */
  reverse: { type: Boolean, default: false },
  /** Compact hero: smaller image cap + narrower media column from `lg` up.
   *  Used for secondary/supporting heroes (e.g. the cream "hundert
   *  Anwendungen" second-fold banner) so they read as companion content
   *  to the primary first-fold hero rather than a second centre-stage
   *  block. Mobile sizing is unchanged. */
  compact: { type: Boolean, default: false },
})

defineEmits(['cta', 'secondary'])

const tones = {
  cream: {
    surface: 'bg-cream',
    text: 'text-ink',
    sub: 'text-muted',
    disc: 'bg-accent-soft/60',
    glow: 'bg-accent/8',
  },
  paper: {
    surface: 'bg-paper',
    text: 'text-ink',
    sub: 'text-muted',
    disc: 'bg-cream',
    glow: 'bg-brand-soft-wash',
  },
  brand: {
    surface: 'bg-brand',
    text: 'text-cream',
    sub: 'text-cream/80',
    disc: 'bg-brand-soft/30',
    glow: 'bg-accent/5',
  },
}

const tone = computed(() => tones[props.tone])
const isBrandTone = computed(() => props.tone === 'brand')
const primaryVariant = computed(() => (isBrandTone.value ? 'accent' : 'primary'))

const layout = computed(() => {
  if (props.variant === 'centered') {
    return {
      root: 'flex flex-col items-center text-center gap-10 md:gap-12',
      copy: 'max-w-2xl mx-auto items-center text-center',
      actions: 'justify-center',
      media: 'mt-4 md:mt-6',
      mediaSize: 'w-full max-w-[287px] sm:max-w-[354px] md:max-w-[442px] lg:max-w-[530px]',
    }
  }
  // Split: image on top on mobile/tablet, text-left/image-right on desktop.
  // `order-*` flips the stacking order below lg without changing DOM order.
  // On lg, let the grid stretch items to the row height and then `justify-center`
  // the copy column so its headline/CTA block sits at the image's vertical midpoint.
  // `reverse` swaps the desktop columns so the image sits on the left.
  return {
    // `grid-cols-1` is load-bearing at mobile. Without it, the grid
    // has no column template below `lg`, so `grid-auto-columns: auto`
    // sizes the implicit column to max-content of its widest child —
    // which is the image column's declared width. That max-content
    // column then forces the copy column to the same width, overflowing
    // narrow viewports and clipping the headline against the section
    // edge. `grid-cols-1` emits `minmax(0, 1fr)`, constraining the
    // column to available width and letting children actually shrink.
    root: props.reverse
      ? 'grid grid-cols-1 gap-10 md:gap-14 lg:grid-cols-[1fr_1.05fr]'
      : 'grid grid-cols-1 gap-10 md:gap-14 lg:grid-cols-[1.05fr_1fr]',
    copy: [
      'order-2 max-w-xl mx-auto lg:mx-0 items-center text-center lg:justify-center',
      props.reverse ? 'lg:order-2 lg:items-start lg:text-left' : 'lg:order-1 lg:items-start lg:text-left',
    ].join(' '),
    actions: 'justify-center lg:justify-start',
    media: props.reverse ? 'order-1 lg:order-1' : 'order-1 lg:order-2',
    // Media cell: use max-width rather than fixed width below `lg` so
    // the column can shrink below the image's preferred size on narrow
    // phones. A fixed `w-[373px]` forced the single-column mobile grid
    // to 373px, which overflowed a 375px viewport minus section padding
    // and pushed the headline text past the section edge (clipped).
    // Compact heroes cap the desktop media width tighter so the image
    // doesn't dominate; mobile/tablet sizing is identical.
    // Non-compact caps were knocked back ~20% in May '26 — the main
    // home hero felt too image-heavy on desktop, eating headroom for
    // the headline and CTAs.
    mediaSize: props.compact
      ? 'w-full max-w-[373px] sm:max-w-[489px] md:max-w-[603px] lg:max-w-[460px] mx-auto'
      : 'w-full max-w-[300px] sm:max-w-[390px] md:max-w-[480px] lg:max-w-[600px] mx-auto',
  }
})

// Max image height per viewport step. Compact drops the desktop cap
// from 78svh → 48svh so the secondary hero reads as a companion,
// not a second main stage. Mobile/tablet caps are shared because
// phone screens shouldn't scale content down below its legible size.
// Image max-height steps. Default is the Hero's original scale —
// the jar is meant to be the focal element of the first fold.
// Compact shrinks it for secondary banners (e.g. the cream
// "hundert Anwendungen" band) so the second-fold section reads as
// a companion rather than a second main stage.
// Non-compact height caps were knocked back ~20% in May '26 alongside
// the matching width caps — the image was still hitting the height
// ceiling on tall desktop viewports even after the width reduction,
// so the silhouette wasn't visibly smaller.
const imageHeightClass = computed(() =>
  props.compact
    ? 'max-h-[44svh] md:max-h-[61svh] lg:max-h-[48svh]'
    : 'max-h-[35svh] md:max-h-[49svh] lg:max-h-[62svh]',
)
</script>

<template>
  <!-- overflow-clip (not overflow-hidden) contains the media halo/disc
       decor WITHOUT establishing a new scrollport — so the headline's
       italic-overhang can't be clipped against the section edge at
       narrow widths. Historically this was `overflow-hidden`, which
       made the section a scrolling ancestor and sheared the last
       italic letter off the hero h1 at phone-sim widths.
       The `hero-section` scoped class carries the overflow-clip-margin
       value — up to 1rem of content (including italic overhang) is
       allowed past the section edge before clipping kicks in. -->
  <section
    :class="[
      'hero-section relative overflow-clip',
      'px-6 py-10 sm:px-8 sm:py-12 md:px-12 md:py-16 lg:px-16 lg:py-20',
      tone.surface,
      tone.text,
    ]"
  >
    <div :class="['relative mx-auto w-full max-w-6xl', layout.root]">
      <!-- Copy.
           `min-w-0` is load-bearing: the copy column is a flex/grid
           child whose default `min-width: auto` resolves to its
           longest word's intrinsic width, which lets the headline
           push the column wider than the viewport. Overriding to 0
           lets the headline actually respect its own max width so
           the font-size clamp and wrapping can take effect. -->
      <div :class="['relative z-[1] flex flex-col gap-6 min-w-0', layout.copy]">
        <p v-if="eyebrow" class="eyebrow">{{ eyebrow }}</p>

        <!-- Headline sizing uses responsive Tailwind arbitrary values
             so each breakpoint compiles to a plain media-query
             `font-size` rule — no scoped-CSS hash, no HMR edge
             cases. Italic Fraunces at ≥2.25rem overhangs the
             line-box at ≤400px and gets clipped by the section's
             `overflow-hidden` (needed for the halo/disc decor);
             capping at 1.625rem on phones keeps the whole phrase
             inside the content width. `break-words` + the inline
             `overflow-wrap` is belt-and-braces for any long word
             (e.g. German compounds) that still can't fit. -->
        <h1
          class="hero-headline font-display font-normal leading-[1.04] tracking-tight hyphens-auto break-words text-[1.625rem] sm:text-[2.25rem] lg:text-[3rem] xl:text-[3.75rem] 2xl:text-[4.5rem]"
        >
          <slot name="headline">{{ headline }}</slot>
        </h1>

        <p
          v-if="subheadline || $slots.subheadline"
          :class="['text-lg leading-relaxed max-w-xl', tone.sub]"
        >
          <slot name="subheadline">{{ subheadline }}</slot>
        </p>

        <div
          v-if="ctaLabel || secondaryLabel || $slots.actions"
          :class="['mt-2 flex flex-wrap items-center gap-3', layout.actions]"
        >
          <slot name="actions">
            <RouterLink v-if="ctaLabel && ctaHref && isInternalPath(ctaHref)" :to="ctaHref" class="inline-flex">
              <Button :variant="primaryVariant" size="lg">
                <slot name="cta">{{ ctaLabel }}</slot>
              </Button>
            </RouterLink>
            <a v-else-if="ctaLabel && ctaHref" :href="ctaHref" class="inline-flex">
              <Button :variant="primaryVariant" size="lg">
                <slot name="cta">{{ ctaLabel }}</slot>
              </Button>
            </a>
            <Button
              v-else-if="ctaLabel"
              :variant="primaryVariant"
              size="lg"
              @click="$emit('cta')"
            >
              <slot name="cta">{{ ctaLabel }}</slot>
            </Button>

            <template v-if="secondaryLabel">
              <!-- Brand tone needs a cream-outlined pill; Button's ghost/secondary
                   render dark-on-dark on the brand green. Internal paths use
                   RouterLink so SPA scroll history survives the navigation. -->
              <RouterLink
                v-if="isBrandTone && secondaryHref && isInternalPath(secondaryHref)"
                :to="secondaryHref"
                class="inline-flex items-center justify-center rounded-pill border border-cream/50 px-[34px] py-[17px] text-[16px] font-semibold tracking-label text-cream transition-colors duration-base hover:border-cream hover:bg-cream-wash-strong"
              >{{ secondaryLabel }}</RouterLink>
              <component
                v-else-if="isBrandTone"
                :is="secondaryHref ? 'a' : 'button'"
                :type="secondaryHref ? undefined : 'button'"
                :href="secondaryHref || undefined"
                class="inline-flex items-center justify-center rounded-pill border border-cream/50 px-[34px] py-[17px] text-[16px] font-semibold tracking-label text-cream transition-colors duration-base hover:border-cream hover:bg-cream-wash-strong"
                @click="secondaryHref ? null : $emit('secondary')"
              >{{ secondaryLabel }}</component>
              <RouterLink
                v-else-if="secondaryHref && isInternalPath(secondaryHref)"
                :to="secondaryHref"
                class="inline-flex"
              >
                <Button variant="secondary" size="lg">{{ secondaryLabel }}</Button>
              </RouterLink>
              <a v-else-if="secondaryHref" :href="secondaryHref" class="inline-flex">
                <Button variant="secondary" size="lg">{{ secondaryLabel }}</Button>
              </a>
              <Button
                v-else
                variant="secondary"
                size="lg"
                @click="$emit('secondary')"
              >{{ secondaryLabel }}</Button>
            </template>
          </slot>
        </div>
      </div>

      <!-- Media -->
      <div
        v-if="image || $slots.media"
        :class="['relative flex items-center justify-center', layout.media]"
      >
        <!-- Soft halo behind the disc. Kept low-opacity + blur-xl (not
             blur-2xl) so the gradient is shallow enough that it doesn't
             quantize into visible rings on 6-bit panels. Tuning: halve
             the alpha or drop to blur-lg if banding returns on a specific
             surface. -->
        <div
          aria-hidden="true"
          :class="[
            'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',
            'w-[80%] aspect-square rounded-full blur-xl',
            tone.glow,
          ]"
        />
        <!-- Decorative disc — solid fill, no blur. -->
        <div
          aria-hidden="true"
          :class="[
            'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',
            'w-[70%] aspect-square rounded-full',
            tone.disc,
          ]"
        />
        <div :class="['relative', layout.mediaSize]">
          <Badge
            v-if="badge"
            :variant="badgeVariant"
            class="absolute -top-3 -left-3 z-[1] shadow-sm"
          >{{ badge }}</Badge>
          <slot name="media">
            <img
              :src="image"
              :alt="imageAlt || headline"
              loading="eager"
              decoding="async"
              :class="[
                'relative mx-auto w-auto max-w-full object-contain drop-shadow-[0_20px_40px_rgba(28,58,40,0.18)]',
                imageHeightClass,
              ]"
            />
          </slot>
          <!-- Page-level extension point for mobile category shortcuts
               etc. Sits under the image within the media column so it
               tracks the hero's vertical rhythm. Empty by default. -->
          <slot name="afterMedia" />
        </div>
      </div>
    </div>
  </section>
</template>

<style scoped>
/* overflow-clip (vs overflow-hidden) contains the decorative halo and
   disc behind the product image WITHOUT establishing a new scrollport.
   The margin lets up to 1rem of content — including Fraunces italic
   overhang — extend past the section edge before being clipped. No
   Tailwind utility covers this pair, so it lives here rather than as
   inline CSS on every call site. */
.hero-section {
  overflow-clip-margin: 1rem;
}

/* Headline wrap rules. `overflow-wrap: anywhere` is stronger than
   `break-words` — the latter only breaks between words, the former
   will break inside a word if it can't otherwise fit the line-box.
   Paired with `word-break: normal` so regular CJK / soft-hyphen
   behaviour isn't altered. Together these guarantee a long German
   compound can never overflow the column. */
.hero-headline {
  overflow-wrap: anywhere;
  word-break: normal;
}
</style>

