/**
 * blog_renderer — pipeline markdown → HTML pour les articles du blog.
 *
 * Appelé :
 *  - au seeder (3 articles fondateurs en DB)
 *  - depuis l'admin React au save (PR3) via un endpoint dédié
 *  - depuis le cron weekly_blog_generator (PR4) pour les drafts IA
 *
 * Le HTML rendu est cache dans `posts.content_html` pour éviter de re-parser
 * le markdown à chaque hit page. Si tu changes ce module, prévois une
 * migration de re-render des posts existants.
 */

import { Marked, type Tokens } from 'marked'

/** Mots/min retenus pour le calcul reading_time (moyenne lecteur fr web). */
const WORDS_PER_MINUTE = 220

/**
 * Renderer marked configuré pour le blog Rubis :
 *  - GFM (tables, autolinks, ~strikethrough~)
 *  - heading IDs auto pour ancres / future TOC
 *  - liens externes en target=_blank rel=noopener
 *  - br: false (un saut de ligne ne devient pas <br>, seul un \n\n crée un <p>)
 */
const marked = new Marked({
  gfm: true,
  breaks: false,
  pedantic: false,
})

marked.use({
  renderer: {
    heading({ tokens, depth }: Tokens.Heading): string {
      const text = this.parser.parseInline(tokens)
      const id = slugify(stripTags(text))
      return `<h${depth} id="${id}">${text}</h${depth}>\n`
    },
    link({ href, title, tokens }: Tokens.Link): string {
      const text = this.parser.parseInline(tokens)
      const isExternal = /^https?:\/\//.test(href) && !href.startsWith('https://rubis.pro')
      const titleAttr = title ? ` title="${escapeHtmlAttr(title)}"` : ''
      const relAttr = isExternal ? ' rel="noopener noreferrer"' : ''
      const targetAttr = isExternal ? ' target="_blank"' : ''
      return `<a href="${href}"${titleAttr}${relAttr}${targetAttr}>${text}</a>`
    },
    image({ href, title, text }: Tokens.Image): string {
      // Lazy par défaut, dimensions à enrichir via image processing futur.
      const altAttr = `alt="${escapeHtmlAttr(text || '')}"`
      const titleAttr = title ? ` title="${escapeHtmlAttr(title)}"` : ''
      return `<img src="${href}" ${altAttr}${titleAttr} loading="lazy" decoding="async" />`
    },
  },
})

export type RenderedPost = {
  contentHtml: string
  wordCount: number
  readingTimeMinutes: number
}

/** Markdown → HTML + métriques de lecture. */
export function renderPost(contentMd: string): RenderedPost {
  const contentHtml = marked.parse(contentMd, { async: false }) as string
  const wordCount = countWords(contentMd)
  const readingTimeMinutes = Math.max(1, Math.round(wordCount / WORDS_PER_MINUTE))
  return { contentHtml, wordCount, readingTimeMinutes }
}

/**
 * Slug ASCII kebab-case déterministe.
 * "Comment relancer un client — sans rien casser" → "comment-relancer-un-client-sans-rien-casser"
 */
export function slugify(input: string): string {
  return input
    .normalize('NFD')
    .replace(/[̀-ͯ]/g, '') // diacritiques
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 200)
}

function countWords(md: string): number {
  // On strippe code blocks + balisage MD avant de compter — sinon les ``` et **
  // gonflent artificiellement.
  const text = md
    .replace(/```[\s\S]*?```/g, ' ')
    .replace(/`[^`]*`/g, ' ')
    .replace(/!\[[^\]]*\]\([^)]*\)/g, ' ')
    .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
    .replace(/[*_~#>]/g, ' ')
  const matches = text.match(/\S+/g)
  return matches ? matches.length : 0
}

function escapeHtmlAttr(s: string): string {
  return s.replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}

function stripTags(s: string): string {
  return s.replace(/<[^>]+>/g, '')
}
