import { randomUUID } from 'node:crypto'

export const DEFAULT_REDIRECT_RULES = [
  {
    id: 'spotify',
    path: '/spotify',
    target: 'https://open.spotify.com/show/0Gq1TzoJOdReSZ1gYQi8Xl',
    statusCode: 301,
  },
  {
    id: 'apple',
    path: '/apple',
    target: 'https://podcasts.apple.com/search?term=Verse+by+Verse+with+Nate',
    statusCode: 301,
  },
  {
    id: 'amazon',
    path: '/amazon',
    target: 'https://music.amazon.com/podcasts/202322bf-db86-4e7d-9a6b-4db7cbccbccf/verse-by-verse-with-nate',
    statusCode: 301,
  },
]

export const DEFAULT_SEO = {
  title: 'Verse by Verse with Nate',
  description: 'Verse by Verse with Nate explores Scripture one verse at a time with practical Bible teaching.',
  ogTitle: 'Verse by Verse with Nate',
  ogDescription: 'A Journey Through Scripture - verse by verse, nugget by nugget.',
  ogImage: '/images/podcast-art.jpeg',
  canonicalUrl: 'https://versebyversewithnate.us/',
  robotsPolicy: 'index,follow',
  sitemapPaths: ['/', '/start-here', '/questions', '/privacy', '/terms', '/about', '/contact', '/episodes', '/resources', '/study', '/study/titus', '/study/colossians'],
}

export const DEFAULT_LEGAL = {
  privacyTitle: 'Privacy Policy',
  privacyBody: [
    'We respect your privacy and collect limited data to operate and improve this site.',
    'If you consent to analytics cookies, we may store masked IP-based location signals and returning visitor activity.',
    'Contact form details are used only to respond to your message and ministry communication requests.',
  ],
  termsTitle: 'Terms',
  termsBody: [
    'Content on this site is for informational and ministry purposes.',
    'External links are provided for convenience and are subject to third-party policies.',
    'By using this site, you agree to lawful use and respectful communication.',
  ],
}

export const DEFAULT_PODCAST_FEATURED_LINKS = []
export const DEFAULT_PUBLISH_STATE = {
  draftUpdatedAt: null,
  publishedAt: null,
}

export function escapeHtml(value) {
  return String(value)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;')
}

export function splitName(fullName) {
  const parts = String(fullName).trim().split(/\s+/).filter(Boolean)
  return {
    firstName: parts[0] ?? '',
    lastName: parts.slice(1).join(' '),
  }
}

export function sanitizeUrl(value) {
  if (typeof value !== 'string') return ''
  const trimmed = value.trim()
  if (!trimmed) return ''
  if (trimmed.startsWith('/')) return trimmed
  if (/^https?:\/\//i.test(trimmed)) return trimmed
  return ''
}

export function normalizeRedirectPath(value) {
  if (typeof value !== 'string') return ''
  const trimmed = value.trim()
  if (!trimmed) return ''
  const withSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`
  const normalized = withSlash.replace(/\/+/g, '/')
  if (normalized === '/') return ''
  if (normalized.startsWith('/api/') || normalized.startsWith('/admin')) return ''
  return normalized
}

export function normalizeSitemapPath(value) {
  if (typeof value !== 'string') return ''
  const trimmed = value.trim()
  if (!trimmed) return ''
  if (trimmed === '/') return '/'
  return normalizeRedirectPath(trimmed)
}

export function sanitizeRedirectRules(value) {
  const source = Array.isArray(value) ? value : []
  const seen = new Set()
  const out = []

  for (const item of source) {
    const pathValue = normalizeRedirectPath(item?.path)
    const target = sanitizeUrl(item?.target)
    const statusCode = Number(item?.statusCode) === 302 ? 302 : 301
    if (!pathValue || !target) continue
    if (seen.has(pathValue)) continue
    seen.add(pathValue)
    out.push({
      id: typeof item?.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
      path: pathValue,
      target,
      statusCode,
    })
  }

  return out.length > 0 ? out : DEFAULT_REDIRECT_RULES
}

export function sanitizeFeaturedLinks(value) {
  const source = Array.isArray(value) ? value : []
  return source
    .filter(item => item && typeof item === 'object')
    .map(item => {
      const discussionQuestions = Array.isArray(item.discussionQuestions)
        ? item.discussionQuestions
          .filter(question => typeof question === 'string')
          .map(question => question.trim())
          .filter(Boolean)
          .slice(0, 30)
        : []

      return {
        id: typeof item.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
        title: typeof item.title === 'string' ? item.title.trim().slice(0, 140) : '',
        episodeNumber: typeof item.episodeNumber === 'string' ? item.episodeNumber.trim().slice(0, 20) : '',
        summary: typeof item.summary === 'string' ? item.summary.trim().slice(0, 600) : '',
        url: sanitizeUrl(item.url),
        embedUrl: sanitizeUrl(item.embedUrl),
        showNotes: typeof item.showNotes === 'string' ? item.showNotes.trim().slice(0, 10000) : '',
        discussionQuestions,
      }
    })
    .filter(
      item =>
        item.title ||
        item.summary ||
        item.url ||
        item.embedUrl ||
        item.showNotes ||
        item.discussionQuestions.length > 0,
    )
}

function sanitizeCustomLinks(value) {
  const source = Array.isArray(value) ? value : []
  return source
    .filter(item => item && typeof item === 'object')
    .map(item => {
      const placement =
        item?.placement === 'platforms'
        || item?.placement === 'footer'
        || item?.placement === 'resources'
        || item?.placement === 'otherSites'
        || item?.placement === 'externalSites'
        || item?.placement === 'books'
        || item?.placement === 'recommended'
          ? item.placement
          : 'footer'

      return {
        id: typeof item.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
        label: typeof item.label === 'string' ? item.label.trim().slice(0, 140) : '',
        url: sanitizeUrl(item.url),
        imageUrl: sanitizeUrl(item.imageUrl),
        description: typeof item.description === 'string' ? item.description.trim().slice(0, 4000) : '',
        amazonUrl: sanitizeUrl(item.amazonUrl),
        amazonLabel: typeof item.amazonLabel === 'string' ? item.amazonLabel.trim().slice(0, 120) : '',
        tags: Array.isArray(item.tags)
          ? item.tags.filter(tag => typeof tag === 'string').map(tag => tag.trim()).filter(Boolean).slice(0, 20)
          : [],
        placement,
      }
    })
    .filter(item => item.label && (item.url || item.amazonUrl || item.placement === 'books' || item.placement === 'recommended' || item.placement === 'resources'))
}

function sanitizeCustomBlocks(value) {
  const source = Array.isArray(value) ? value : []
  return source
    .filter(item => item && typeof item === 'object')
    .map(item => ({
      id: typeof item.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
      heading: typeof item.heading === 'string' ? item.heading.trim().slice(0, 140) : '',
      body: typeof item.body === 'string' ? item.body.trim().slice(0, 4000) : '',
      page: item?.page === 'homepage' || item?.page === 'start-here' || item?.page === 'episodes' || item?.page === 'downloads' || item?.page === 'about' || item?.page === 'contact' || item?.page === 'questions'
        ? item.page
        : 'downloads',
      linkUrl: sanitizeUrl(item.linkUrl),
      linkLabel: typeof item.linkLabel === 'string' ? item.linkLabel.trim().slice(0, 120) : '',
    }))
    .filter(item => item.heading || item.body)
}

function sanitizeArchivedSeriesResourceLinks(value) {
  const source = Array.isArray(value) ? value : []
  return source
    .filter(item => item && typeof item === 'object')
    .map(item => ({
      id: typeof item.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
      label: typeof item.label === 'string' ? item.label.trim().slice(0, 120) : '',
      description: typeof item.description === 'string' ? item.description.trim().slice(0, 4000) : '',
      url: sanitizeUrl(item.url),
      amazonUrl: sanitizeUrl(item.amazonUrl),
      amazonLabel: typeof item.amazonLabel === 'string' ? item.amazonLabel.trim().slice(0, 120) : '',
    }))
    .filter(item => item.label && item.url)
}

function sanitizeArchivedSeriesNotes(value) {
  const source = Array.isArray(value) ? value : []
  return source
    .filter(item => item && typeof item === 'object')
    .map(item => ({
      id: typeof item.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
      heading: typeof item.heading === 'string' ? item.heading.trim().slice(0, 140) : '',
      body: typeof item.body === 'string' ? item.body.trim().slice(0, 4000) : '',
    }))
    .filter(item => item.heading || item.body)
}

function sanitizeColossiansStudySections(value) {
  function normalizeReleasedAt(value) {
    if (typeof value !== 'string' || !value.trim()) return ''
    const parsed = Date.parse(value.trim())
    if (!Number.isFinite(parsed)) return ''
    return new Date(parsed).toISOString().replace('.000Z', 'Z')
  }

  const source = Array.isArray(value) ? value : []
  return source
    .filter(item => item && typeof item === 'object')
    .map(item => {
      const releasedAt = normalizeReleasedAt(item.releasedAt)
      return {
        id: typeof item.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
        chapter: Number.isInteger(item.chapter) && item.chapter >= 1 && item.chapter <= 999 ? item.chapter : 1,
        reference: typeof item.reference === 'string' ? item.reference.trim().slice(0, 40) : '',
        title: typeof item.title === 'string' ? item.title.trim().slice(0, 160) : '',
        audioEmbedUrl: sanitizeUrl(item.audioEmbedUrl),
        passageText: typeof item.passageText === 'string' ? item.passageText.trim().slice(0, 12000) : '',
        summary: typeof item.summary === 'string' ? item.summary.trim().slice(0, 600) : '',
        commentary: typeof item.commentary === 'string' ? item.commentary.trim().slice(0, 12000) : '',
        greekNotes: Array.isArray(item.greekNotes)
          ? item.greekNotes.filter(note => typeof note === 'string').map(note => note.trim()).filter(Boolean).slice(0, 20)
          : [],
        studyQuestions: Array.isArray(item.studyQuestions)
          ? item.studyQuestions.filter(question => typeof question === 'string').map(question => question.trim()).filter(Boolean).slice(0, 20)
          : [],
        announcement: typeof item.announcement === 'string' ? item.announcement.trim().slice(0, 600) : '',
        checkpointPrompt: typeof item.checkpointPrompt === 'string' ? item.checkpointPrompt.trim().slice(0, 600) : '',
        checkpointQuestions: Array.isArray(item.checkpointQuestions)
          ? item.checkpointQuestions.filter(question => typeof question === 'string').map(question => question.trim()).filter(Boolean).slice(0, 20)
          : [],
        ...(releasedAt ? { releasedAt } : {}),
      }
    })
    .filter(item => item.title || item.summary || item.commentary || item.greekNotes.length > 0 || item.studyQuestions.length > 0 || item.passageText || item.checkpointPrompt || item.checkpointQuestions.length > 0)
}

function sanitizeStudies(value) {
  const source = Array.isArray(value) ? value : []
  return source
    .filter(item => item && typeof item === 'object')
    .map(item => ({
      id: typeof item.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
      slug: typeof item.slug === 'string' ? item.slug.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') : '',
      title: typeof item.title === 'string' ? item.title.trim().slice(0, 160) : '',
      description: typeof item.description === 'string' ? item.description.trim().slice(0, 800) : '',
      homepageEyebrow: typeof item.homepageEyebrow === 'string' ? item.homepageEyebrow.trim().slice(0, 80) : '',
      showOnHomepage: item.showOnHomepage === true,
      showNewTag: item.showNewTag === true,
      newTagLabel: typeof item.newTagLabel === 'string' ? item.newTagLabel.trim().slice(0, 24) : '',
      status: item.status === 'planned' ? 'planned' : 'active',
      difficulty: item.difficulty === 'advanced' ? 'advanced' : (item.difficulty === 'intermediate' ? 'intermediate' : 'beginner'),
      estimatedHours: Number.isFinite(Number(item.estimatedHours)) ? Math.max(1, Math.min(500, Number(item.estimatedHours))) : 8,
      completionBadge: typeof item.completionBadge === 'string' ? item.completionBadge.trim().slice(0, 120) : '',
      numberOfChapters: Number.isInteger(item.numberOfChapters) && item.numberOfChapters >= 1 && item.numberOfChapters <= 999 ? item.numberOfChapters : 1,
      sections: sanitizeColossiansStudySections(item.sections),
    }))
    .filter(item => item.slug && item.title)
}

function sanitizeArchivedSeries(value) {
  const source = Array.isArray(value) ? value : []
  return source
    .filter(item => item && typeof item === 'object')
    .map(item => ({
      id: typeof item.id === 'string' && item.id.trim() ? item.id.trim() : randomUUID(),
      label: typeof item.label === 'string' ? item.label.trim().slice(0, 140) : '',
      title: typeof item.title === 'string' ? item.title.trim().slice(0, 200) : '',
      description: typeof item.description === 'string' ? item.description.trim().slice(0, 1000) : '',
      imageUrl: sanitizeUrl(item.imageUrl),
      listenUrl: sanitizeUrl(item.listenUrl),
      studyGuideTitle: typeof item.studyGuideTitle === 'string' ? item.studyGuideTitle.trim().slice(0, 140) : '',
      studyGuideDescription: typeof item.studyGuideDescription === 'string' ? item.studyGuideDescription.trim().slice(0, 4000) : '',
      studyGuideUrl: sanitizeUrl(item.studyGuideUrl),
      resourceLinks: sanitizeArchivedSeriesResourceLinks(item.resourceLinks),
      notes: sanitizeArchivedSeriesNotes(item.notes),
      episodeRange:
        Number.isInteger(item?.episodeRange?.from)
        && Number.isInteger(item?.episodeRange?.to)
        && item.episodeRange.from > 0
        && item.episodeRange.to >= item.episodeRange.from
          ? { from: item.episodeRange.from, to: item.episodeRange.to }
          : undefined,
    }))
    .filter(
      item =>
        item.title ||
        item.description ||
        item.resourceLinks.length > 0 ||
        item.notes.length > 0,
    )
}

export function sanitizeSiteContent(siteContent) {
  if (!siteContent || typeof siteContent !== 'object' || Array.isArray(siteContent)) return {}

  const seo = siteContent.seo && typeof siteContent.seo === 'object' ? siteContent.seo : {}
  const legal = siteContent.legal && typeof siteContent.legal === 'object' ? siteContent.legal : {}

  return {
    ...siteContent,
    customLinks: sanitizeCustomLinks(siteContent.customLinks),
    customBlocks: sanitizeCustomBlocks(siteContent.customBlocks),
    archivedSeries: sanitizeArchivedSeries(siteContent.archivedSeries),
    studies: sanitizeStudies(siteContent.studies),
    colossiansStudySections: sanitizeColossiansStudySections(siteContent.colossiansStudySections),
    redirects: sanitizeRedirectRules(siteContent.redirects),
    podcastFeaturedLinks: sanitizeFeaturedLinks(siteContent.podcastFeaturedLinks ?? DEFAULT_PODCAST_FEATURED_LINKS),
    episodesSeoIntro: typeof siteContent.episodesSeoIntro === 'string' && siteContent.episodesSeoIntro.trim() ? siteContent.episodesSeoIntro.trim().slice(0, 600) : '',
    welcomeEmailSpotifyBtnLabel: typeof siteContent.welcomeEmailSpotifyBtnLabel === 'string' ? siteContent.welcomeEmailSpotifyBtnLabel.trim().slice(0, 80) : '',
    welcomeEmailAppleBtnLabel: typeof siteContent.welcomeEmailAppleBtnLabel === 'string' ? siteContent.welcomeEmailAppleBtnLabel.trim().slice(0, 80) : '',
    welcomeEmailStartHereLinkLabel: typeof siteContent.welcomeEmailStartHereLinkLabel === 'string' ? siteContent.welcomeEmailStartHereLinkLabel.trim().slice(0, 80) : '',
    studyWelcomeEmailSubject: typeof siteContent.studyWelcomeEmailSubject === 'string' ? siteContent.studyWelcomeEmailSubject.trim().slice(0, 200) : '',
    studyWelcomeEmailBody: typeof siteContent.studyWelcomeEmailBody === 'string' ? siteContent.studyWelcomeEmailBody.trim().slice(0, 1000) : '',
    studyWelcomeEmailCtaLabel: typeof siteContent.studyWelcomeEmailCtaLabel === 'string' ? siteContent.studyWelcomeEmailCtaLabel.trim().slice(0, 80) : '',
    studyWelcomeEmailCtaPath: typeof siteContent.studyWelcomeEmailCtaPath === 'string' ? siteContent.studyWelcomeEmailCtaPath.trim().slice(0, 200) : '',
    studyWelcomeEmailSignoff: typeof siteContent.studyWelcomeEmailSignoff === 'string' ? siteContent.studyWelcomeEmailSignoff.trim().slice(0, 200) : '',
    studyDeletedEmailSubject: typeof siteContent.studyDeletedEmailSubject === 'string' ? siteContent.studyDeletedEmailSubject.trim().slice(0, 200) : '',
    studyDeletedEmailBody: typeof siteContent.studyDeletedEmailBody === 'string' ? siteContent.studyDeletedEmailBody.trim().slice(0, 1000) : '',
    studyDeletedEmailCtaLabel: typeof siteContent.studyDeletedEmailCtaLabel === 'string' ? siteContent.studyDeletedEmailCtaLabel.trim().slice(0, 80) : '',
    studyDeletedEmailCtaPath: typeof siteContent.studyDeletedEmailCtaPath === 'string' ? siteContent.studyDeletedEmailCtaPath.trim().slice(0, 200) : '',
    studyDeletedEmailSignoff: typeof siteContent.studyDeletedEmailSignoff === 'string' ? siteContent.studyDeletedEmailSignoff.trim().slice(0, 200) : '',
    studyReminderEmailSubjectPrefix: typeof siteContent.studyReminderEmailSubjectPrefix === 'string' ? siteContent.studyReminderEmailSubjectPrefix.trim().slice(0, 200) : '',
    studyReminderEmailBody: typeof siteContent.studyReminderEmailBody === 'string' ? siteContent.studyReminderEmailBody.trim().slice(0, 1000) : '',
    studyReminderEmailCtaLabel: typeof siteContent.studyReminderEmailCtaLabel === 'string' ? siteContent.studyReminderEmailCtaLabel.trim().slice(0, 80) : '',
    studyReminderEmailSignoff: typeof siteContent.studyReminderEmailSignoff === 'string' ? siteContent.studyReminderEmailSignoff.trim().slice(0, 200) : '',
    emailChangeSubject: typeof siteContent.emailChangeSubject === 'string' ? siteContent.emailChangeSubject.trim().slice(0, 200) : '',
    emailChangeBody: typeof siteContent.emailChangeBody === 'string' ? siteContent.emailChangeBody.trim().slice(0, 500) : '',
    emailChangeCtaLabel: typeof siteContent.emailChangeCtaLabel === 'string' ? siteContent.emailChangeCtaLabel.trim().slice(0, 80) : '',
    twoFaOtpEmailSubject: typeof siteContent.twoFaOtpEmailSubject === 'string' ? siteContent.twoFaOtpEmailSubject.trim().slice(0, 200) : '',
    twoFaOtpEmailBody: typeof siteContent.twoFaOtpEmailBody === 'string' ? siteContent.twoFaOtpEmailBody.trim().slice(0, 500) : '',
    twoFaOtpEmailExpiry: typeof siteContent.twoFaOtpEmailExpiry === 'string' ? siteContent.twoFaOtpEmailExpiry.trim().slice(0, 300) : '',
    seo: {
      title: typeof seo.title === 'string' && seo.title.trim() ? seo.title.trim().slice(0, 120) : DEFAULT_SEO.title,
      description: typeof seo.description === 'string' && seo.description.trim() ? seo.description.trim().slice(0, 240) : DEFAULT_SEO.description,
      ogTitle: typeof seo.ogTitle === 'string' && seo.ogTitle.trim() ? seo.ogTitle.trim().slice(0, 120) : DEFAULT_SEO.ogTitle,
      ogDescription: typeof seo.ogDescription === 'string' && seo.ogDescription.trim() ? seo.ogDescription.trim().slice(0, 240) : DEFAULT_SEO.ogDescription,
      ogImage: sanitizeUrl(seo.ogImage) || DEFAULT_SEO.ogImage,
      canonicalUrl: sanitizeUrl(seo.canonicalUrl) || DEFAULT_SEO.canonicalUrl,
      robotsPolicy: typeof seo.robotsPolicy === 'string' && seo.robotsPolicy.trim() ? seo.robotsPolicy.trim() : DEFAULT_SEO.robotsPolicy,
      sitemapPaths: Array.isArray(seo.sitemapPaths)
        ? seo.sitemapPaths.map(pathItem => normalizeSitemapPath(pathItem)).filter(Boolean)
        : [...DEFAULT_SEO.sitemapPaths],
    },
    legal: {
      privacyTitle: typeof legal.privacyTitle === 'string' && legal.privacyTitle.trim() ? legal.privacyTitle.trim().slice(0, 120) : DEFAULT_LEGAL.privacyTitle,
      privacyBody: Array.isArray(legal.privacyBody) && legal.privacyBody.length > 0
        ? legal.privacyBody.filter(line => typeof line === 'string').map(line => line.trim()).filter(Boolean).slice(0, 20)
        : [...DEFAULT_LEGAL.privacyBody],
      termsTitle: typeof legal.termsTitle === 'string' && legal.termsTitle.trim() ? legal.termsTitle.trim().slice(0, 120) : DEFAULT_LEGAL.termsTitle,
      termsBody: Array.isArray(legal.termsBody) && legal.termsBody.length > 0
        ? legal.termsBody.filter(line => typeof line === 'string').map(line => line.trim()).filter(Boolean).slice(0, 20)
        : [...DEFAULT_LEGAL.termsBody],
    },
  }
}

export function escapeXml(value) {
  return String(value)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;')
}

export function buildAbsoluteUrl(baseUrl, maybeRelativePath) {
  const safeBase = typeof baseUrl === 'string' && baseUrl.trim() ? baseUrl.trim() : DEFAULT_SEO.canonicalUrl
  const root = safeBase.endsWith('/') ? safeBase.slice(0, -1) : safeBase
  if (typeof maybeRelativePath !== 'string' || !maybeRelativePath.trim()) return root
  const value = maybeRelativePath.trim()
  if (/^https?:\/\//i.test(value)) return value
  if (value.startsWith('/')) return `${root}${value}`
  return `${root}/${value}`
}

export function injectSeoIntoHtml(html, siteContent) {
  const seo = siteContent?.seo ?? DEFAULT_SEO
  const title = seo.title || DEFAULT_SEO.title
  const description = seo.description || DEFAULT_SEO.description
  const ogTitle = seo.ogTitle || title
  const ogDescription = seo.ogDescription || description
  const canonical = buildAbsoluteUrl(seo.canonicalUrl || DEFAULT_SEO.canonicalUrl, '/')
  const ogImage = buildAbsoluteUrl(canonical, seo.ogImage || DEFAULT_SEO.ogImage)
  const robots = seo.robotsPolicy || DEFAULT_SEO.robotsPolicy

  return html
    .replace(/<title>[\s\S]*?<\/title>/i, `<title>${escapeHtml(title)}</title>`)
    .replace(/<meta name="description" content="[^"]*"\s*\/?>/i, `<meta name="description" content="${escapeHtml(description)}" />`)
    .replace(/<meta name="robots" content="[^"]*"\s*\/?>/i, `<meta name="robots" content="${escapeHtml(robots)}" />`)
    .replace(/<meta property="og:title" content="[^"]*"\s*\/?>/i, `<meta property="og:title" content="${escapeHtml(ogTitle)}" />`)
    .replace(/<meta property="og:description" content="[^"]*"\s*\/?>/i, `<meta property="og:description" content="${escapeHtml(ogDescription)}" />`)
    .replace(/<meta property="og:image" content="[^"]*"\s*\/?>/i, `<meta property="og:image" content="${escapeHtml(ogImage)}" />`)
    .replace(/<meta property="og:image:secure_url" content="[^"]*"\s*\/?>/i, `<meta property="og:image:secure_url" content="${escapeHtml(ogImage)}" />`)
    .replace(/<meta property="og:url" content="[^"]*"\s*\/?>/i, `<meta property="og:url" content="${escapeHtml(canonical)}" />`)
    .replace(/<link rel="canonical" href="[^"]*"\s*\/?>/i, `<link rel="canonical" href="${escapeHtml(canonical)}" />`)
}

export function normalizeAssetBaseName(name) {
  if (typeof name !== 'string') return `upload-${Date.now()}`
  const cleaned = name
    .toLowerCase()
    .replace(/[^a-z0-9._-]+/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '')
  return cleaned || `upload-${Date.now()}`
}

export function inferImageExtensionFromDataUrl(dataUrl) {
  if (typeof dataUrl !== 'string') return null
  if (dataUrl.startsWith('data:image/png;base64,')) return '.png'
  if (dataUrl.startsWith('data:image/jpeg;base64,')) return '.jpg'
  if (dataUrl.startsWith('data:image/webp;base64,')) return '.webp'
  if (dataUrl.startsWith('data:image/gif;base64,')) return '.gif'
  if (dataUrl.startsWith('data:application/pdf;base64,')) return '.pdf'
  if (dataUrl.startsWith('data:application/msword;base64,')) return '.doc'
  if (dataUrl.startsWith('data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,')) return '.docx'
  return null
}

export function normalizeIp(rawIp) {
  if (!rawIp) return 'unknown'

  let ip = String(rawIp).trim()

  if (ip.includes(',')) {
    ip = ip.split(',')[0].trim()
  }

  if (ip.startsWith('::ffff:')) {
    ip = ip.slice(7)
  }

  if (ip === '::1') {
    ip = '127.0.0.1'
  }

  return ip || 'unknown'
}

export function getClientIp(req) {
  // Use req.ip: Express derives this from x-forwarded-for according to the
  // configured trust proxy hop count, preventing header spoofing by clients.
  return normalizeIp(req.ip)
}

export function parseCookies(cookieHeader) {
  if (!cookieHeader) return {}

  return cookieHeader
    .split(';')
    .map(v => v.trim())
    .filter(Boolean)
    .reduce((acc, part) => {
      const idx = part.indexOf('=')
      if (idx === -1) return acc
      const key = part.slice(0, idx).trim()
      const value = part.slice(idx + 1).trim()
      try {
        acc[key] = decodeURIComponent(value)
      } catch {
        acc[key] = value
      }
      return acc
    }, {})
}

export function hasVisitorConsent(req) {
  const cookies = parseCookies(req.headers.cookie)
  return cookies['vbn_analytics_consent'] === 'yes'
}

// Secure cookies are required in production unless explicitly disabled with
// ALLOW_INSECURE_COOKIES=true — needed when the app is reached over plain
// HTTP (e.g. by LAN/VPN IP during a server migration, before TLS is set up),
// because browsers silently drop Secure cookies on http:// origins.
export function cookieSecureFlag() {
  if (process.env.ALLOW_INSECURE_COOKIES === 'true') return ''
  return process.env.NODE_ENV === 'production' ? '; Secure' : ''
}

export function setConsentCookie(res, consent) {
  const value = consent ? 'yes' : 'no'
  res.append('Set-Cookie', `vbn_analytics_consent=${value}; Max-Age=31536000; Path=/; SameSite=Lax${cookieSecureFlag()}`)
}

export function isPrivateOrLocalIp(ip) {
  return (
    ip === '127.0.0.1' ||
    ip === 'localhost' ||
    ip.startsWith('10.') ||
    ip.startsWith('192.168.') ||
    /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(ip) ||
    ip.startsWith('fc') ||
    ip.startsWith('fd') ||
    ip.startsWith('fe80:') ||
    ip === 'unknown'
  )
}
