/**
 * Décline les photographies sources de /public en AVIF + WebP responsives.
 *
 *   pnpm images
 *
 * - sources : toutes les photos posées à la racine de /public (png, jpg, jpeg) ;
 * - sorties : public/images/<nom>-<largeur>.{avif,webp} ;
 * - manifeste : app/utils/images.manifest.json (dimensions + largeurs disponibles),
 *   lu par <AppImage> pour écrire srcset / width / height sans rien coder en dur ;
 * - partage social : public/og.jpg (1200 × 630), tiré de la photo du hero.
 *
 * Ajouter une photo : la déposer dans /public (ex. luma.png), relancer `pnpm images`,
 * puis renseigner `image: 'luma'` dans le fichier de données concerné (app/data).
 *
 * Aucune image n'est agrandie : les largeurs supérieures à la source sont ignorées.
 */
import { mkdir, readdir, writeFile } from 'node:fs/promises'
import { dirname, extname, join, parse } from 'node:path'
import { fileURLToPath } from 'node:url'
import sharp from 'sharp'

const root = join(dirname(fileURLToPath(import.meta.url)), '..')
const sourceDir = join(root, 'public')
const outputDir = join(root, 'public', 'images')
const manifestFile = join(root, 'app', 'utils', 'images.manifest.json')

const WIDTHS = [400, 640, 960, 1280, 1600, 1920]
const EXTENSIONS = new Set(['.png', '.jpg', '.jpeg'])
/** Fichiers de /public qui ne sont pas des photographies à décliner. */
const IGNORED = new Set(['og.jpg'])

/**
 * Recadrages tirés d'une photo source, en attendant des prises de vue dédiées.
 * Coordonnées en pixels dans l'image d'origine.
 */
const CROPS = [
  // Le ventilateur et la baie vitrée : visuel de la section « Manifeste ».
  {
    name: 'hero-ventilateur',
    source: 'hero.png',
    area: { left: 700, top: 0, width: 753, height: 941 },
  },
  // Le salon : visuel de l'espace « Maison ».
  {
    name: 'hero-salon',
    source: 'hero.png',
    area: { left: 180, top: 140, width: 900, height: 801 },
  },
]

/** Écrit toutes les déclinaisons d'une image et renvoie son entrée de manifeste. */
async function generate(name, pipeline) {
  // Le recadrage est figé dans un tampon : sharp applique sinon `resize` avant `extract`.
  const buffer = await pipeline.png().toBuffer()
  const { width, height } = await sharp(buffer).metadata()

  // Largeurs cibles nettement sous la source, plus la largeur native (définition maximale).
  const widths = [...new Set([...WIDTHS.filter((w) => w < width * 0.9), width])]

  for (const w of widths) {
    const resized = sharp(buffer).resize({ width: w })
    await resized
      .clone()
      .avif({ quality: 55, effort: 5 })
      .toFile(join(outputDir, `${name}-${w}.avif`))
    await resized
      .clone()
      .webp({ quality: 78, effort: 5 })
      .toFile(join(outputDir, `${name}-${w}.webp`))
  }

  console.log(`✓ ${name} (${width}×${height}) → ${widths.join(', ')}`)
  return { width, height, widths }
}

await mkdir(outputDir, { recursive: true })

const manifest = {}

const files = (await readdir(sourceDir, { withFileTypes: true }))
  .filter((entry) => entry.isFile())
  .map((entry) => entry.name)
  .filter((file) => EXTENSIONS.has(extname(file).toLowerCase()) && !IGNORED.has(file))
  .sort()

for (const file of files) {
  manifest[parse(file).name] = await generate(parse(file).name, sharp(join(sourceDir, file)))
}

for (const crop of CROPS) {
  if (!files.includes(crop.source)) continue
  manifest[crop.name] = await generate(
    crop.name,
    sharp(join(sourceDir, crop.source)).extract(crop.area),
  )
}

if (files.includes('hero.png')) {
  await sharp(join(sourceDir, 'hero.png'))
    .resize({ width: 1200, height: 630, fit: 'cover', position: 'centre' })
    .jpeg({ quality: 82, mozjpeg: true })
    .toFile(join(sourceDir, 'og.jpg'))
  console.log('✓ og.jpg (1200×630)')
}

await writeFile(manifestFile, JSON.stringify(manifest, null, 2) + '\n')
console.log(`Manifeste écrit : ${manifestFile}`)
