const postsDb = require('./posts.db')
const { cleanBody, deriveExcerpt } = require('../../utils/sanitizeHtml')

// Sanitize body HTML and treat an empty editor (TipTap emits `<p></p>`) as null
// so we never store a meaningless empty paragraph.
function normalizeBody(body) {
  const clean = cleanBody(body)
  if (clean == null) return null
  return String(clean).trim() === '<p></p>' ? null : clean
}

// URL category (kebab) <-> DB enum value.
const CATEGORY_MAP = {
  news: 'news',
  'five-on-friday': 'five_on_friday',
  newsletter: 'newsletter',
  screenshots: 'screenshot',
}
const URL_CATEGORIES = Object.keys(CATEGORY_MAP)
const DB_CATEGORIES = Object.values(CATEGORY_MAP)

function toDbCategory(urlCategory) {
  return CATEGORY_MAP[urlCategory] || null
}

function isValidUrlCategory(urlCategory) {
  return Boolean(CATEGORY_MAP[urlCategory])
}

function isValidDbCategory(dbCategory) {
  return DB_CATEGORIES.includes(dbCategory)
}

async function listPublished(urlCategory) {
  return postsDb.listPublished(toDbCategory(urlCategory))
}

async function getPublished(urlCategory, idOrSlug) {
  const id = Number.isInteger(Number(idOrSlug)) ? Number(idOrSlug) : -1
  return postsDb.findPublished(toDbCategory(urlCategory), id, String(idOrSlug))
}

async function listAll(dbCategory) {
  return postsDb.listAll(dbCategory || null)
}

async function getById(id) {
  return postsDb.findById(id)
}

async function create(post) {
  const body = normalizeBody(post.body)
  // Auto-fill the excerpt from the first of the body when left blank.
  const excerpt = post.excerpt && String(post.excerpt).trim() ? post.excerpt : deriveExcerpt(body)
  const id = await postsDb.insert({ ...post, body, excerpt: excerpt || null })
  return postsDb.findById(id)
}

async function update(id, fields) {
  const next = { ...fields }
  if ('body' in next) next.body = normalizeBody(next.body)
  // If the body is being updated and no non-empty excerpt was supplied,
  // derive one from the new body.
  if ('body' in next && !(next.excerpt && String(next.excerpt).trim())) {
    next.excerpt = deriveExcerpt(next.body) || null
  }
  await postsDb.update(id, next)
  return postsDb.findById(id)
}

async function setPublished(id, published) {
  const current = await postsDb.findById(id)
  if (!current) return null
  const fields = { published: published ? 1 : 0 }
  // Stamp published_at the first time a post goes live.
  if (published && !current.published_at) fields.published_at = new Date()
  await postsDb.update(id, fields)
  return postsDb.findById(id)
}

// Announcement pipeline back-pointers (see model/announceJobs).
async function linkAnnounceJob(id, jobId) {
  await postsDb.update(id, { announce_job_id: jobId })
}

async function markAnnounced(id, at = new Date()) {
  await postsDb.update(id, { announced_at: at })
}

async function remove(id) {
  return postsDb.remove(id)
}

async function counts() {
  const rows = await postsDb.countByCategory()
  return rows.reduce((acc, row) => {
    acc[row.category] = Number(row.c)
    return acc
  }, {})
}

module.exports = {
  URL_CATEGORIES,
  DB_CATEGORIES,
  toDbCategory,
  isValidUrlCategory,
  isValidDbCategory,
  listPublished,
  getPublished,
  listAll,
  getById,
  create,
  update,
  setPublished,
  linkAnnounceJob,
  markAnnounced,
  remove,
  counts,
}
