import type { CollectionEntry } from 'astro:content';
import { getCollection } from 'astro:content';
import { getImage } from 'astro:assets';
import type { ImageMetadata } from 'astro';

export const site = {
  name: 'Andras Schmelczer',
  title: 'Andras Schmelczer — Software engineer',
  description:
    'Andras Schmelczer writes about software systems, AI deployment, graphics, simulations, and tools.',
  url: 'https://schmelczer.dev',
  email: 'andras@schmelczer.dev',
  github: 'https://github.com/schmelczer',
  linkedin: 'https://www.linkedin.com/in/andras-schmelczer',
  cv: '/media/downloads/cv-andras-schmelczer.pdf',
};

// Single source of truth for primary navigation. The Header consumes every
// entry where `footerOnly` is falsy AND `href !== '/'` (Home is implicit via
// the site title). The Footer renders every entry regardless. Items marked
// `footerOnly: true` appear only in the Footer.
export interface NavItem {
  href: string;
  label: string;
  footerOnly?: boolean;
}

export const navItems: readonly NavItem[] = [
  { href: '/', label: 'Home' },
  { href: '/articles/', label: 'Articles' },
  { href: '/projects/', label: 'Projects' },
  { href: '/about/', label: 'About' },
  { href: '/tags/', label: 'Tags' },
  { href: '/rss.xml', label: 'RSS', footerOnly: true },
];

export function formatDate(date: Date) {
  return new Intl.DateTimeFormat('en', {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
  }).format(date);
}

export function formatDateShort(date: Date) {
  return new Intl.DateTimeFormat('en', {
    month: 'short',
    day: 'numeric',
  }).format(date);
}

export function yearOf(date: Date) {
  return new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date);
}

export function entrySlug(entry: { id: string }) {
  return entry.id.replace(/\.mdx?$/, '').replace(/\/index$/, '');
}

export function articlePath(entry: { id: string } | string) {
  const slug = typeof entry === 'string' ? entry : entrySlug(entry);
  return `/articles/${slug}/`;
}

export function tagSlug(tag: string) {
  return tag
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '');
}

export function tagPath(tag: string) {
  return `/tags/${tagSlug(tag)}/`;
}

// Anchor used for `id="..."` on project cards and `#fragment` deep links.
// Always derived from the canonical `sourceProjectId` slug now that the
// legacy anchor mapping has been dropped.
export function projectAnchor(slugOrEntry: string | CollectionEntry<'projects'>) {
  return typeof slugOrEntry === 'string' ? slugOrEntry : slugOrEntry.data.sourceProjectId;
}

export function getAllTags(posts: { data: { tags: readonly string[] } }[]) {
  return [...new Set(posts.flatMap((post) => post.data.tags))].sort((a, b) =>
    a.localeCompare(b)
  );
}

// Memoized published-posts loader. Build steps call `getPublishedPosts()`
// from many pages (index, articles, RSS, sitemap, tag pages, post layouts).
// Caching the promise means `getCollection('posts')` runs once per build.
let publishedPostsPromise: Promise<CollectionEntry<'posts'>[]> | undefined;

export function getPublishedPosts(): Promise<CollectionEntry<'posts'>[]> {
  if (!publishedPostsPromise) {
    publishedPostsPromise = getCollection('posts').then((posts) =>
      posts
        .filter((post) => !post.data.draft)
        .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
    );
  }
  return publishedPostsPromise;
}

export async function getProjects(): Promise<CollectionEntry<'projects'>[]> {
  return (await getCollection('projects')).sort(
    (a, b) => b.data.sortDate.valueOf() - a.data.sortDate.valueOf()
  );
}

export function adjacentPosts(
  posts: CollectionEntry<'posts'>[],
  current: CollectionEntry<'posts'>
) {
  const index = posts.findIndex((post) => post.id === current.id);
  if (index === -1) return { previous: undefined, next: undefined };
  return {
    previous: index < posts.length - 1 ? posts[index + 1] : undefined,
    next: index > 0 ? posts[index - 1] : undefined,
  };
}

export function getRelatedPosts(
  posts: CollectionEntry<'posts'>[],
  current: CollectionEntry<'posts'>,
  limit = 3
) {
  const currentTags = new Set(current.data.tags);
  return posts
    .filter((post) => post.id !== current.id)
    .map((post) => ({
      post,
      overlap: post.data.tags.filter((tag) => currentTags.has(tag)).length,
    }))
    .filter(({ overlap }) => overlap > 0)
    .sort((a, b) => b.overlap - a.overlap)
    .slice(0, limit)
    .map(({ post }) => post);
}

export function absoluteUrl(path: string) {
  return new URL(path, site.url).toString();
}

// Canonical Person JSON-LD. Used by the home page and About page; both share
// `@id` so search engines treat them as the same entity. Pass `extra` to
// add or override fields (e.g. `jobTitle`, richer `description`).
export function buildPersonJsonLd(extra?: Record<string, unknown>) {
  return {
    '@context': 'https://schema.org',
    '@type': 'Person',
    '@id': absoluteUrl('/about/#person'),
    name: site.name,
    url: site.url,
    email: `mailto:${site.email}`,
    sameAs: [site.github, site.linkedin],
    description: site.description,
    ...extra,
  };
}

// Wraps `getImage` with the standard OG dimensions (1200x630 JPEG). Used by
// Base.astro for the default OG image and by Post.astro for per-post
// thumbnails. Keeps OG output consistent across the site.
export function optimizeOgImage(src: ImageMetadata) {
  return getImage({
    src,
    width: 1200,
    height: 630,
    format: 'jpg',
  });
}

interface BreadcrumbCrumb {
  name: string;
  href: string;
}

interface BreadcrumbInput {
  articles?: boolean;
  projects?: boolean;
  tagsIndex?: boolean;
  tag?: string;
  post?: CollectionEntry<'posts'>;
}

// Builds the breadcrumb trail shared by JSON-LD (BreadcrumbList) and the
// visible Breadcrumbs component. Home is always first. Flags append crumbs
// in a fixed order: Articles → Tags → tag → Post (or Projects). A `tag`
// implies both Articles and Tags so callers don't have to set every flag.
export function buildBreadcrumbTrail({
  articles,
  projects,
  tagsIndex,
  tag,
  post,
}: BreadcrumbInput): BreadcrumbCrumb[] {
  const trail: BreadcrumbCrumb[] = [{ name: 'Home', href: '/' }];
  if (articles || post || tagsIndex || tag) {
    trail.push({ name: 'Articles', href: '/articles/' });
  }
  if (tagsIndex || tag) {
    trail.push({ name: 'Tags', href: '/tags/' });
  }
  if (tag) {
    trail.push({ name: `#${tag}`, href: tagPath(tag) });
  }
  if (post) {
    trail.push({ name: post.data.title, href: articlePath(post) });
  }
  if (projects) {
    trail.push({ name: 'Projects', href: '/projects/' });
  }
  return trail;
}

// Builds the schema.org BreadcrumbList JSON-LD object for a given trail.
// Shared by every page that emits breadcrumb structured data.
export function buildBreadcrumbJsonLd(trail: BreadcrumbCrumb[]) {
  return {
    '@context': 'https://schema.org',
    '@type': 'BreadcrumbList',
    itemListElement: trail.map((crumb, index) => ({
      '@type': 'ListItem',
      position: index + 1,
      name: crumb.name,
      item: absoluteUrl(crumb.href),
    })),
  };
}
