<script setup lang="ts">
// Dispatcher central : transforme une Page du portfolio en props de template
// (§9.2) — médias résolus à la bonne résolution, texte effectif, libellés.
// Mêmes composants en aperçu (basse rés.) et en print (haute rés.).
import { marked } from 'marked'
import type { Page, Portfolio, Project, ResolvedMedia } from '~~/types'

const props = withDefaults(
  defineProps<{
    portfolio: Portfolio
    page: Page
    index: number // position 0-based dans portfolio.pages
    projects: Record<string, Project>
    renderMode?: 'preview' | 'print'
    /** id du portfolio si le rendu doit lire le snapshot figé (§7.3) */
    frozen?: string
    /** base des URLs médias (route publique en vue publique) */
    mediaBase?: string
  }>(),
  { renderMode: 'preview', mediaBase: '/api/media' },
)

// Clic sur une entrée du sommaire (aperçu) → défilement vers la page cible
defineEmits<{ navigate: [pageNumber: number] }>()

const pageInfo = computed(() => ({ index: props.index + 1, total: props.portfolio.pages.length }))
// En print, chaque image est demandée à ~2× sa taille d'affichage (spec §10.1)
const scale = computed(() => (props.renderMode === 'print' ? 2 : 1))

// Largeur utile de la page A3 (1587,4px − 2 × 30,2px de marge)
const INNER_W = 1527

function resolve(projectId: string, filename: string, width: number, type: 'image' | 'video' = 'image'): ResolvedMedia {
  const w = Math.min(Math.round(width * scale.value), 3200)
  const base = mediaUrl(projectId, filename, undefined, props.mediaBase)
  const frozenParam = props.frozen ? `&frozen=${props.frozen}` : ''
  return {
    filename,
    type,
    url: type === 'image' ? `${base}?w=${w}${frozenParam}` : `${base}${frozenParam ? `?${frozenParam.slice(1)}` : ''}`,
    posterUrl: type === 'video' ? `${base}?poster=1&w=${w}${frozenParam}` : undefined,
  }
}

const project = computed(() => {
  const p = props.page
  return p.kind === 'project' || p.kind === 'project-grid' ? props.projects[p.projectId] : undefined
})

// ---- Page couverture de projet ----
const projectCoverImage = computed<ResolvedMedia | undefined>(() => {
  if (props.page.kind !== 'project' || !project.value) return undefined
  const filename = props.page.coverImage || project.value.coverImage
  if (!filename) return undefined
  return resolve(project.value.id, filename, 1588)
})

const projectText = computed(() => {
  if (props.page.kind !== 'project' || !project.value) return ''
  // `textOverride` undefined = suit Grav (bodyHtml) ; toute chaîne définie
  // (y compris vide) est un texte figé à l'édition et rendue telle quelle.
  const override = props.page.textOverride
  const html = override === undefined
    ? project.value.bodyHtml
    : (marked.parse(override) as string)
  return stripTargetParam(html)
})

// Ligne d'info de la couverture de projet : type de travail + année.
// Type par défaut = taxonomy.category de Grav (« Site web », « Édition »…),
// c.-à-d. Project.categories — surchargée par subtitleOverride si renseignée.
const projectWorkType = computed(() => {
  if (props.page.kind !== 'project') return ''
  return props.page.subtitleOverride?.trim() || project.value?.categories.join(', ') || ''
})

const projectYear = computed(() => {
  if (props.page.kind !== 'project') return ''
  if (props.portfolio.showYears === false) return '' // réglage document : années masquées
  if (props.page.year?.trim()) return props.page.year.trim()
  const iso = project.value?.date
  const y = iso ? new Date(iso).getFullYear() : NaN
  return Number.isNaN(y) ? '' : String(y)
})

const projectLink = computed(() => {
  if (props.page.kind !== 'project') return ''
  return stripTargetParam(props.page.link?.trim() || project.value?.externalUrl || '')
})

const projectBadge = computed(() => {
  const c = project.value?.category
  return c ? CATEGORY_BADGE[c] ?? c : undefined
})

// ---- Page grille de projet ----
// Filet et mode clair hérités de la page couverture du même projet.
const gridCover = computed(() => {
  if (props.page.kind !== 'project-grid') return undefined
  const projectId = props.page.projectId
  return props.portfolio.pages.find(
    (p): p is Extract<Page, { kind: 'project' }> => p.kind === 'project' && p.projectId === projectId,
  )
})
// Le filet des grilles hérite de la couleur du bandeau de la couverture du projet.
const gridStripOverride = computed(() => gridCover.value?.bandColor)

// Orientation dominante des images posées : la grille adaptative en dépend
const gridOrientation = computed(() => {
  if (props.page.kind !== 'project-grid' || !project.value) return 'mixed' as const
  const byName = new Map(project.value.media.map(m => [m.filename, m]))
  return dominantOrientation(
    props.page.media.map((f) => {
      const a = byName.get(f)
      return a?.width && a?.height ? a.width / a.height : 0
    }),
  )
})

const gridMedia = computed<ResolvedMedia[]>(() => {
  if (props.page.kind !== 'project-grid' || !project.value) return []
  const byName = new Map(project.value.media.map(m => [m.filename, m]))
  const rects = gridCellRects(gridDef(props.page.grid, props.page.media.length, gridOrientation.value))
  return props.page.media
    .map((filename, i) => {
      const asset = byName.get(filename)
      if (!asset || asset.type !== 'image') return null // vidéos exclues des grilles
      const cellW = Math.round((rects[i]?.w ?? 1) * INNER_W)
      const media = resolve(project.value!.id, filename, cellW, 'image')
      if (asset.width && asset.height) media.ratio = asset.width / asset.height
      return media
    })
    .filter((m): m is ResolvedMedia => m !== null)
})

// Texte libre optionnel sous la phrase d'intro de la couverture (markdown).
const coverBody = computed(() =>
  props.portfolio.cover.body?.trim()
    ? stripTargetParam(marked.parse(props.portfolio.cover.body) as string)
    : '',
)

// Filet de la couverture du dossier — définit aussi celui du sommaire.
const coverBandColor = computed(() => {
  const cover = props.portfolio.pages.find(
    (p): p is Extract<Page, { kind: 'cover' }> => p.kind === 'cover',
  )
  return cover?.bandColor
})

// Sommaire : entrées générées depuis les pages couverture de projet
const tocEntries = computed(() =>
  props.portfolio.pages
    .map((p, i) => ({ p, pageNumber: i + 1 }))
    .filter(({ p }) => p.kind === 'project')
    .map(({ p, pageNumber }) => {
      const proj = props.projects[(p as Extract<Page, { kind: 'project' }>).projectId]
      return { title: proj?.title ?? '—', subtitle: proj?.categories.join(' · '), category: proj?.category, pageNumber }
    }),
)

// Page libre : colonnes de texte (markdown) ou d'images. Repli sur les champs
// legacy `body`/`media` pour les pages non encore migrées (lues depuis le disque).
interface ResolvedColumn {
  id: string
  kind: 'text' | 'media'
  heading?: string
  html?: string
  media?: ResolvedMedia[]
}

function resolveFreeMedia(refs: string[], colCount: number): ResolvedMedia[] {
  const width = Math.round(INNER_W / colCount)
  return refs.map((ref) => {
    const idx = ref.lastIndexOf('/')
    const projectId = ref.slice(0, idx)
    const media = resolve(projectId, ref.slice(idx + 1), width)
    media.caption = props.projects[projectId]?.title // titre du projet en légende
    return media
  })
}

// Page équipe : membres cochés, résolus depuis le roster statique (nom +
// portrait) avec rôle/présentation surchargés le cas échéant.
const teamMembers = computed(() => {
  if (props.page.kind !== 'team') return []
  return props.page.members
    .filter(m => m.enabled)
    .map((m) => {
      const r = TEAM_ROSTER.find(x => x.id === m.id)
      return {
        id: m.id,
        name: r?.name ?? m.id,
        portraitUrl: portraitUrl(r?.portrait ?? ''),
        role: m.role ?? r?.role,
        bio: m.bio ?? r?.bio,
      }
    })
})

const freeColumns = computed<ResolvedColumn[]>(() => {
  if (props.page.kind !== 'free') return []
  const page = props.page
  // Colonnes explicites, sinon synthèse depuis les champs legacy.
  const cols = page.columns?.length
    ? page.columns
    : [
        ...(page.body?.trim() ? [{ id: 'legacy-text', kind: 'text' as const, body: page.body }] : []),
        ...(page.media?.length ? [{ id: 'legacy-media', kind: 'media' as const, media: page.media }] : []),
      ]
  const list = cols.length ? cols : [{ id: 'empty', kind: 'text' as const, body: '' }]
  const count = list.length
  return list.map(col => col.kind === 'media'
    ? { id: col.id, kind: 'media' as const, heading: col.heading, media: resolveFreeMedia(col.media ?? [], count) }
    : { id: col.id, kind: 'text' as const, heading: col.heading, html: col.body?.trim() ? stripTargetParam(marked.parse(col.body) as string) : '' },
  )
})

</script>

<template>
  <CoverTemplate
    v-if="page.kind === 'cover'"
    :title="portfolio.cover.title"
    :body="coverBody"
    :date="portfolio.cover.date"
    :site-url="portfolio.cover.siteUrl"
    :contact-email="portfolio.cover.contactEmail"
    :address="portfolio.cover.address"
    :background="page.background"
    :band-color="page.bandColor"
    :render-mode="renderMode"
  />

  <OutroTemplate
    v-else-if="page.kind === 'outro'"
    :title="page.title"
    :site-url="portfolio.cover.siteUrl"
    :contact-email="portfolio.cover.contactEmail"
    :address="portfolio.cover.address"
    :background="page.background"
    :band-color="page.bandColor ?? coverBandColor"
    :render-mode="renderMode"
  />

  <TeamTemplate
    v-else-if="page.kind === 'team'"
    :title="page.title"
    :members="teamMembers"
    :background="page.background"
    :band-color="page.bandColor ?? coverBandColor"
    :page="pageInfo"
    :render-mode="renderMode"
  />

  <TocTemplate
    v-else-if="page.kind === 'toc'"
    :title="page.title"
    :entries="tocEntries"
    :background="page.background"
    :band-color="coverBandColor"
    :page="pageInfo"
    :render-mode="renderMode"
    @navigate="$emit('navigate', $event)"
  />

  <FreeTextPage
    v-else-if="page.kind === 'free'"
    :title="page.title"
    :columns="freeColumns"
    :background="page.background"
    :band-color="page.bandColor ?? coverBandColor"
    :text-scale="page.textScale"
    :page="pageInfo"
    :render-mode="renderMode"
  />

  <template v-else-if="page.kind === 'project'">
    <div v-if="!project" class="fl-page flex items-center justify-center bg-white text-neutral-400">
      Projet introuvable : {{ page.projectId }}
    </div>
    <ProjectCoverPage
      v-else
      :title="project.title"
      :category="project.category"
      :badge="projectBadge"
      :work-type="projectWorkType"
      :year="projectYear"
      :subtitle="page.subtitle"
      :link="projectLink"
      :text="projectText"
      :image="projectCoverImage"
      :image-fit="page.coverFit"
      :image-align="page.coverAlign"
      :title-scale="page.titleScale"
      :band-height="page.bandHeight"
      :band-color="page.bandColor"
      :ink="page.ink"
      :background="page.background"
      :layout="page.layout"
    />
  </template>

  <template v-else-if="page.kind === 'project-grid'">
    <div v-if="!project" class="fl-page flex items-center justify-center bg-white text-neutral-400">
      Projet introuvable : {{ page.projectId }}
    </div>
    <ProjectGridPage
      v-else
      :media="gridMedia"
      :grid="page.grid"
      :orientation="gridOrientation"
      :band-color="gridStripOverride"
      :background="page.background"
    />
  </template>
</template>
