import type { MetadataRoute } from "next";

import { siteUrl } from "@/lib/env";
import { defaultLocale, locales, type Locale } from "@/lib/i18n";
import { blogCategories } from "@/lib/blog/categories";
import {
  getAllPostParams,
  getPost,
  getPostAlternates,
  getPostLocales,
} from "@/lib/blog/posts";
import type { BlogPost } from "@/lib/blog/types";
import { docsPaths } from "@/lib/docs/navigation";
import { STATIC_ROUTES } from "@/lib/seo/locale-availability";

/**
 * Multilingual sitemap. Each indexable URL is emitted per locale and annotated
 * with the full set of `hreflang` alternates (every available language plus
 * `x-default`), as Google and the Next.js docs recommend. Base URL comes from
 * NEXT_PUBLIC_SITE_URL.
 *
 * Marketing + docs pages exist in every locale, so they get both. Blog posts
 * and categories are content-driven: each is listed only for the locales that
 * actually have a translation, so we never advertise a fallback URL as its own
 * indexable page.
 */
type Freq = MetadataRoute.Sitemap[number]["changeFrequency"];

/** hreflang map for a path present in every locale (+ `x-default`). */
function allLocaleLanguages(path: string): Record<string, string> {
  const languages: Record<string, string> = {};
  for (const lang of locales) languages[lang] = `${siteUrl}/${lang}${path}`;
  languages["x-default"] = `${siteUrl}/${defaultLocale}${path}`;
  return languages;
}

/** One entry per locale for a path present in every locale (docs). */
function everyLocale(
  path: string,
  priority: number,
  changeFrequency?: Freq,
): MetadataRoute.Sitemap {
  const languages = allLocaleLanguages(path);
  return locales.map((lang) => ({
    url: `${siteUrl}/${lang}${path}`,
    changeFrequency,
    priority,
    alternates: { languages },
  }));
}

// Per-path priority / changeFrequency for the static (marketing + legal)
// routes. Availability + indexability come from STATIC_ROUTES; this map only
// carries the sitemap tuning (today's MARKETING values). Paths default to
// priority 0.6 with no changeFrequency.
const STATIC_META: Record<
  string,
  { priority: number; changeFrequency?: Freq }
> = {
  "": { priority: 1, changeFrequency: "weekly" },
  "/pricing": { priority: 0.9, changeFrequency: "monthly" },
  "/services": { priority: 0.8, changeFrequency: "monthly" },
  "/services/cms-optimization": { priority: 0.7 },
  "/services/seo-code-optimization": { priority: 0.7 },
  "/services/wordpress-management": { priority: 0.7 },
  "/services/wordpress-seo": { priority: 0.7 },
  "/about": { priority: 0.6 },
  "/contact": { priority: 0.6 },
  "/blog": { priority: 0.7, changeFrequency: "weekly" },
  "/privacy": { priority: 0.3, changeFrequency: "yearly" },
  "/terms": { priority: 0.3, changeFrequency: "yearly" },
};

export default function sitemap(): MetadataRoute.Sitemap {
  const entries: MetadataRoute.Sitemap = [];

  // Static marketing + legal pages — driven by the route registry. Emit one
  // entry per AVAILABLE locale, but only for indexable routes (so `/about`,
  // flagged non-indexable, is dropped). Alternates list the same available
  // locales plus `x-default` (the default locale if available, else the first
  // available one).
  for (const [path, { locales: routeLocales, indexable }] of Object.entries(
    STATIC_ROUTES,
  )) {
    if (!indexable) continue;
    const meta = STATIC_META[path] ?? { priority: 0.6 };
    const languages: Record<string, string> = {};
    for (const lang of routeLocales) {
      languages[lang] = `${siteUrl}/${lang}${path}`;
    }
    const xDefaultLang = routeLocales.includes(defaultLocale)
      ? defaultLocale
      : routeLocales[0];
    languages["x-default"] = `${siteUrl}/${xDefaultLang}${path}`;
    for (const lang of routeLocales) {
      entries.push({
        url: `${siteUrl}/${lang}${path}`,
        changeFrequency: meta.changeFrequency,
        priority: meta.priority,
        alternates: { languages },
      });
    }
  }

  // Docs — derived from the docs STRUCTURE; all docs exist in both locales.
  for (const path of docsPaths) {
    entries.push(...everyLocale(path, path === "/docs" ? 0.6 : 0.5));
  }

  // Indexable post instances (one per real translation), excluding any post
  // flagged noindex so the sitemap never advertises a non-indexable URL.
  const postInstances: {
    lang: Locale;
    category: string;
    slug: string;
    post: BlogPost;
  }[] = [];
  for (const { category, slug } of getAllPostParams()) {
    for (const lang of getPostLocales(category, slug)) {
      const post = getPost(lang, category, slug);
      if (!post || post.seo?.noindex) continue;
      postInstances.push({ lang, category, slug, post });
    }
  }

  // Blog posts — real lastmod from the post date, hreflang from the resolver.
  for (const { lang, category, slug, post } of postInstances) {
    const { languages } = getPostAlternates(lang, category, slug);
    entries.push({
      url: `${siteUrl}/${lang}/blog/${category}/${slug}`,
      lastModified: post.updated ?? post.date,
      changeFrequency: "monthly",
      priority: 0.6,
      alternates: { languages },
    });
  }

  // Blog categories — available in a locale when an indexable post in it exists
  // for that locale (avoids advertising empty fallback category pages).
  const categoryLocales = new Map<string, Set<string>>();
  // Most recent post lastmod per category, so a category page reflects the
  // freshness of its newest indexable post (falling back to the same
  // `updated ?? date` convention used by the blog post entries above).
  const categoryLastModified = new Map<string, string>();
  for (const { lang, category, post } of postInstances) {
    let set = categoryLocales.get(category);
    if (!set) {
      set = new Set<string>();
      categoryLocales.set(category, set);
    }
    set.add(lang);

    const postModified = post.updated ?? post.date;
    const current = categoryLastModified.get(category);
    if (!current || postModified > current) {
      categoryLastModified.set(category, postModified);
    }
  }
  for (const cat of blogCategories) {
    const langs = [...(categoryLocales.get(cat.slug) ?? [])];
    if (langs.length === 0) continue;
    const languages: Record<string, string> = {};
    for (const lang of langs) {
      languages[lang] = `${siteUrl}/${lang}/blog/${cat.slug}`;
    }
    const xDefaultLang = langs.includes(defaultLocale)
      ? defaultLocale
      : langs[0];
    languages["x-default"] = `${siteUrl}/${xDefaultLang}/blog/${cat.slug}`;
    const lastModified = categoryLastModified.get(cat.slug);
    for (const lang of langs) {
      entries.push({
        url: `${siteUrl}/${lang}/blog/${cat.slug}`,
        lastModified,
        changeFrequency: "weekly",
        priority: 0.5,
        alternates: { languages },
      });
    }
  }

  return entries;
}
