const wikiDb = require('./wiki.db')
const { cleanBody } = require('../../utils/sanitizeHtml')
const { extractTargets } = require('./wiki.links')

function slugifyTag(label) {
  return String(label)
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/(^-|-$)/g, '')
}

// Upsert each label into wiki_tags and set the page's tag set exactly.
async function syncTags(pageId, tags) {
  const ids = []
  const seen = new Set()
  for (const raw of tags) {
    const label = String(raw).trim()
    if (!label) continue
    const slug = slugifyTag(label)
    if (!slug || seen.has(slug)) continue
    seen.add(slug)
    ids.push(await wikiDb.upsertTag(slug, label))
  }
  await wikiDb.setPageTags(pageId, ids)
  await wikiDb.deleteOrphanTags()
}

// Rebuild the page's outgoing internal-link rows from its (sanitized) body.
async function rebuildLinks(pageId, html) {
  await wikiDb.clearLinks(pageId)
  for (const target of extractTargets(html)) {
    await wikiDb.insertLink(pageId, target)
  }
}

// Snapshot the current content of a page into the revision history.
async function writeRevision(page, editorId, changeNote = null) {
  await wikiDb.insertRevision({
    pageId: page.id,
    title: page.title,
    body: page.body,
    excerpt: page.excerpt,
    categoryId: page.category_id,
    editorId,
    changeNote,
  })
}

// ── Pages ──────────────────────────────────────────────────────────────
async function listPublished(filters = {}) {
  return wikiDb.listPublishedSummaries(filters)
}

async function listAll(filters = {}) {
  return wikiDb.listAllSummaries(filters)
}

async function search(q, opts = {}) {
  return wikiDb.searchSummaries(q, opts)
}

// Admin detail: page + its tags.
async function getBySlug(slug) {
  const page = await wikiDb.findBySlug(slug)
  if (!page) return null
  page.tags = await wikiDb.getTagsForPage(page.id)
  return page
}

// Public detail: page + tags + backlinks + missing (red) link targets.
async function getPublishedBySlug(slug) {
  const page = await wikiDb.findPublishedBySlug(slug)
  if (!page) return null
  page.tags = await wikiDb.getTagsForPage(page.id)
  page.backlinks = await wikiDb.getBacklinks(slug, { publishedOnly: true })
  const targets = extractTargets(page.body)
  const existing = await wikiDb.getExistingSlugs(targets)
  page.missing_links = targets.filter((t) => !existing.has(t))
  return page
}

async function create({ slug, title, body, excerpt, categoryId, published, updatedBy, tags }) {
  const clean = cleanBody(body)
  await wikiDb.insert({
    slug,
    title,
    body: clean,
    excerpt: excerpt || null,
    categoryId: categoryId ?? null,
    published: published !== false,
    updatedBy,
  })
  const page = await wikiDb.findBySlug(slug)
  if (Array.isArray(tags)) await syncTags(page.id, tags)
  await rebuildLinks(page.id, clean)
  await writeRevision(page, updatedBy, 'Created')
  return getBySlug(slug)
}

// Map the partial `input` to DB columns (only keys present are written), and
// report the cleaned body for link rebuilding when the body changed. Split out of
// update() so that stays a flat sequence of write steps.
function mapUpdateFields(input, current) {
  const fields = { updated_by: input.updatedBy ?? null }
  let cleanForLinks = null
  if ('title' in input) fields.title = input.title
  if ('body' in input) {
    fields.body = cleanBody(input.body)
    cleanForLinks = fields.body
  }
  if ('excerpt' in input) fields.excerpt = input.excerpt || null
  if ('categoryId' in input) fields.category_id = input.categoryId ?? null
  if ('published' in input) {
    fields.published = input.published ? 1 : 0
    if (input.published && !current.published_at) fields.published_at = new Date()
  }
  return { fields, cleanForLinks }
}

// Partial update — only keys present in `input` are written.
async function update(slug, input) {
  const current = await wikiDb.findBySlug(slug)
  if (!current) return null

  const { fields, cleanForLinks } = mapUpdateFields(input, current)
  await wikiDb.updateBySlug(slug, fields)
  if (Array.isArray(input.tags)) await syncTags(current.id, input.tags)
  if (cleanForLinks != null) await rebuildLinks(current.id, cleanForLinks)
  const page = await wikiDb.findBySlug(slug)
  await writeRevision(page, input.updatedBy ?? null, input.changeNote || null)
  return getBySlug(slug)
}

async function setPublished(slug, published) {
  const current = await wikiDb.findBySlug(slug)
  if (!current) return null
  const fields = { published: published ? 1 : 0 }
  if (published && !current.published_at) fields.published_at = new Date()
  await wikiDb.updateBySlug(slug, fields)
  return getBySlug(slug)
}

async function remove(slug) {
  const res = await wikiDb.deleteBySlug(slug)
  await wikiDb.deleteOrphanTags() // page_tags cascade on delete; drop now-empty tags
  return res
}

// ── Revisions ──────────────────────────────────────────────────────────
async function listRevisions(slug) {
  const page = await wikiDb.findBySlug(slug)
  if (!page) return null
  return wikiDb.listRevisions(page.id)
}

async function getRevision(slug, revId) {
  const page = await wikiDb.findBySlug(slug)
  if (!page) return null
  const rev = await wikiDb.findRevision(revId)
  if (!rev || rev.page_id !== page.id) return null
  return rev
}

// Restore an old revision: overwrite the page with the snapshot, rebuild links,
// then record a new revision (history stays append-only).
async function restoreRevision(slug, revId, editorId) {
  const page = await wikiDb.findBySlug(slug)
  if (!page) return null
  const rev = await wikiDb.findRevision(revId)
  if (!rev || rev.page_id !== page.id) return null

  await wikiDb.updateBySlug(slug, {
    title: rev.title,
    body: rev.body,
    excerpt: rev.excerpt,
    category_id: rev.category_id,
    updated_by: editorId,
  })
  await rebuildLinks(page.id, rev.body)
  const restored = await wikiDb.findBySlug(slug)
  await writeRevision(restored, editorId, `Restored from revision #${revId}`)
  return getBySlug(slug)
}

// ── Tags ───────────────────────────────────────────────────────────────
async function listTags() {
  return wikiDb.listTags()
}

async function getTagBySlug(slug) {
  return wikiDb.findTagBySlug(slug)
}

// ── Categories ─────────────────────────────────────────────────────────
async function listCategories() {
  return wikiDb.listCategories()
}

async function getCategoryBySlug(slug) {
  return wikiDb.findCategoryBySlug(slug)
}

async function getCategoryById(id) {
  return wikiDb.findCategoryById(id)
}

async function createCategory({ slug, title, description, sortOrder }) {
  const id = await wikiDb.insertCategory({ slug, title, description, sortOrder })
  return wikiDb.findCategoryById(id)
}

async function updateCategory(id, input) {
  const fields = {}
  if ('title' in input) fields.title = input.title
  if ('slug' in input) fields.slug = input.slug
  if ('description' in input) fields.description = input.description || null
  if ('sortOrder' in input) fields.sort_order = input.sortOrder
  await wikiDb.updateCategory(id, fields)
  return wikiDb.findCategoryById(id)
}

async function removeCategory(id) {
  return wikiDb.deleteCategory(id)
}

module.exports = {
  listPublished,
  listAll,
  search,
  getBySlug,
  getPublishedBySlug,
  create,
  update,
  setPublished,
  remove,
  listRevisions,
  getRevision,
  restoreRevision,
  listTags,
  getTagBySlug,
  listCategories,
  getCategoryBySlug,
  getCategoryById,
  createCategory,
  updateCategory,
  removeCategory,
}
