import fs from 'fs/promises'
import path from 'path'
import { cache } from 'react'
import matter from 'gray-matter'
import { compileMDX } from 'next-mdx-remote/rsc'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypeSlug from 'rehype-slug'
import remarkGfm from 'remark-gfm'
import { mdxComponents } from '@/lib/blog/mdx'
import type { BlogMeta, BlogPost, TagWithCount } from '@/lib/blog/schema'
import { AuthorSchema, BlogFrontmatterSchema } from '@/lib/blog/schema'
import { AUTHORS_DIR, BLOG_DIR, byDateDesc, ensureContentDirs, toIsoDate } from '@/lib/blog/utils'

const postComponentsRegistry: Record<string, Record<string, React.ComponentType>> = {}

let cachedMeta: BlogMeta[] | null = null
let cachedAuthors: Record<string, any> | null = null

async function loadAuthors(): Promise<Record<string, any>> {
  if (cachedAuthors) return cachedAuthors
  await ensureContentDirs()
  const files = await fs.readdir(AUTHORS_DIR).catch(() => [])
  const authors: Record<string, any> = {}
  for (const file of files) {
    if (!file.endsWith('.json')) continue
    const raw = await fs.readFile(path.join(AUTHORS_DIR, file), 'utf-8')
    const json = JSON.parse(raw)
    const author = AuthorSchema.parse(json)
    authors[author.id] = author
  }
  cachedAuthors = authors
  return authors
}

function slugify(text: string): string {
  return text
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9\s-]/g, '')
    .replace(/\s+/g, '-')
}

async function scanFrontmatters(): Promise<BlogMeta[]> {
  if (cachedMeta) {
    return cachedMeta
  }
  await ensureContentDirs()
  const entries = await fs.readdir(BLOG_DIR).catch(() => [])
  const authorsMap = await loadAuthors()
  const results = await Promise.all(
    entries.map(async (slug): Promise<BlogMeta | null> => {
      const postDir = path.join(BLOG_DIR, slug)
      const stat = await fs.stat(postDir).catch(() => null)
      if (!stat || !stat.isDirectory()) return null
      const mdxPath = path.join(postDir, 'index.mdx')
      const hasMdx = await fs
        .stat(mdxPath)
        .then((s) => s.isFile())
        .catch(() => false)
      if (!hasMdx) return null
      const raw = await fs.readFile(mdxPath, 'utf-8')
      const { data, content: mdxContent } = matter(raw)
      const fm = BlogFrontmatterSchema.parse(data)
      const wordCount = mdxContent
        .replace(/```[\s\S]*?```/g, '')
        .replace(/import\s+.*?from\s+['"].*?['"]/g, '')
        .replace(/<[^>]+>/g, '')
        .replace(/[#*_~`[\]()!|>-]/g, '')
        .split(/\s+/)
        .filter((w) => w.length > 0).length
      const authors = fm.authors.map((id) => authorsMap[id]).filter(Boolean)
      if (authors.length === 0) throw new Error(`Authors not found for "${slug}"`)
      return {
        slug: fm.slug,
        title: fm.title,
        description: fm.description,
        date: toIsoDate(fm.date),
        updated: fm.updated ? toIsoDate(fm.updated) : undefined,
        author: authors[0],
        authors,
        readingTime: fm.readingTime,
        tags: fm.tags,
        ogImage: fm.ogImage,
        canonical: fm.canonical,
        ogAlt: fm.ogAlt,
        about: fm.about,
        timeRequired: fm.timeRequired,
        faq: fm.faq,
        wordCount,
        draft: fm.draft,
        featured: fm.featured ?? false,
      }
    })
  )
  cachedMeta = results.filter((result): result is BlogMeta => result !== null).sort(byDateDesc)
  return cachedMeta
}

export async function getAllPostMeta(): Promise<BlogMeta[]> {
  return (await scanFrontmatters()).filter((p) => !p.draft)
}

export const getNavBlogPosts = cache(
  async (): Promise<Pick<BlogMeta, 'slug' | 'title' | 'ogImage'>[]> => {
    const allPosts = await getAllPostMeta()
    const featuredPost = allPosts.find((p) => p.featured) ?? allPosts[0]
    if (!featuredPost) return []
    const recentPosts = allPosts.filter((p) => p.slug !== featuredPost.slug).slice(0, 5)
    return [featuredPost, ...recentPosts].map((p) => ({
      slug: p.slug,
      title: p.title,
      ogImage: p.ogImage,
    }))
  }
)

export async function getAllTags(): Promise<TagWithCount[]> {
  const posts = await getAllPostMeta()
  const counts: Record<string, number> = {}
  for (const p of posts) {
    for (const t of p.tags) counts[t] = (counts[t] || 0) + 1
  }
  return Object.entries(counts)
    .map(([tag, count]) => ({ tag, count }))
    .sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag))
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const BLOG_COMPONENT_LOADERS: Record<
  string,
  () => Promise<Record<string, React.ComponentType<any>>>
> = {
  enterprise: () => import('@/content/blog/enterprise/components'),
  'v0-5': () => import('@/content/blog/v0-5/components'),
}

async function loadPostComponents(slug: string): Promise<Record<string, React.ComponentType>> {
  if (postComponentsRegistry[slug]) {
    return postComponentsRegistry[slug]
  }

  const loader = BLOG_COMPONENT_LOADERS[slug]
  if (!loader) {
    postComponentsRegistry[slug] = {}
    return {}
  }

  try {
    const postComponents = await loader()
    postComponentsRegistry[slug] = postComponents
    return postComponents
  } catch {
    postComponentsRegistry[slug] = {}
    return {}
  }
}

export async function getPostBySlug(slug: string): Promise<BlogPost> {
  const meta = await scanFrontmatters()
  const found = meta.find((m) => m.slug === slug)
  if (!found) throw new Error(`Post not found: ${slug}`)
  const mdxPath = path.join(BLOG_DIR, slug, 'index.mdx')
  const raw = await fs.readFile(mdxPath, 'utf-8')
  const { content, data } = matter(raw)
  const fm = BlogFrontmatterSchema.parse(data)

  const postComponents = await loadPostComponents(slug)
  const mergedComponents = { ...mdxComponents, ...postComponents }

  const compiled = await compileMDX({
    source: content,
    components: mergedComponents as any,
    options: {
      parseFrontmatter: false,
      mdxOptions: {
        remarkPlugins: [remarkGfm],
        rehypePlugins: [
          rehypeSlug,
          [rehypeAutolinkHeadings, { behavior: 'wrap', properties: { className: 'anchor' } }],
        ],
      },
    },
  })
  const headings: { text: string; id: string }[] = []
  const lines = content.split('\n')
  for (const line of lines) {
    const match = /^##\s+(.+)$/.exec(line.trim())
    if (match) {
      const text = match[1].trim()
      headings.push({ text, id: slugify(text) })
    }
  }
  return {
    ...found,
    Content: () => (compiled as any).content,
    updated: fm.updated ? toIsoDate(fm.updated) : found.updated,
    headings,
  }
}

export function invalidateBlogCaches() {
  cachedMeta = null
  cachedAuthors = null
  Object.keys(postComponentsRegistry).forEach((key) => delete postComponentsRegistry[key])
}

export async function getRelatedPosts(slug: string, limit = 3): Promise<BlogMeta[]> {
  const posts = await getAllPostMeta()
  const current = posts.find((p) => p.slug === slug)
  if (!current) return []
  const others = posts.filter((p) => p.slug !== slug)
  const scored = others
    .map((p) => ({
      post: p,
      score: p.tags.filter((t) => current.tags.includes(t)).length,
    }))
    .sort((a, b) => b.score - a.score || byDateDesc(a.post, b.post))
    .slice(0, limit)
    .map((x) => x.post)
  return scored
}
