import { Post } from '@/lib/types';
import { localEssays } from '@/content/essays';

const MEDIUM_RSS = 'https://medium.com/feed/@rsqpds';
const CACHE_TTL = 5 * 60 * 1000;

function stripHtml(html: string): string {
  return html
    .replace(/<[^>]*>/g, ' ')
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/&nbsp;/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

function estimateReadingTime(text: string): number {
  const words = text.split(/\s+/).length;
  return Math.max(1, Math.round(words / 200));
}

function extractImage(content: string): string | null {
  const match = content.match(/<img[^>]+src="([^">]+)"/);
  return match ? match[1] : null;
}

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

function formatDate(dateStr: string): string {
  try {
    const d = new Date(dateStr);
    if (Number.isNaN(d.getTime())) return dateStr;
    return d.toLocaleDateString('en-US', {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
    });
  } catch {
    return dateStr;
  }
}

function parseRSS(xml: string): Post[] {
  const items: Post[] = [];
  const itemRegex = /<item>([\s\S]*?)<\/item>/g;
  let matchResult: RegExpExecArray | null;

  while ((matchResult = itemRegex.exec(xml)) !== null) {
    const item = matchResult[1];

    const title = item.match(/<title><!\[CDATA\[([\s\S]*?)\]\]><\/title>/)?.[1]
      || item.match(/<title>([\s\S]*?)<\/title>/)?.[1]
      || '';

    const link = item.match(/<link>([\s\S]*?)<\/link>/)?.[1]
      || item.match(/<guid[^>]*>([\s\S]*?)<\/guid>/)?.[1]
      || '';

    const guid = item.match(/<guid[^>]*>([\s\S]*?)<\/guid>/)?.[1] || link;
    const pubDate = item.match(/<pubDate>([\s\S]*?)<\/pubDate>/)?.[1] || '';

    const contentEncoded = item.match(/<content:encoded><!\[CDATA\[([\s\S]*?)\]\]><\/content:encoded>/)?.[1]
      || item.match(/<description><!\[CDATA\[([\s\S]*?)\]\]><\/description>/)?.[1]
      || '';

    const tagRegex = /<category><!\[CDATA\[([\s\S]*?)\]\]><\/category>/g;
    const tags: string[] = [];
    let tagMatch: RegExpExecArray | null;
    while ((tagMatch = tagRegex.exec(item)) !== null) {
      if (tagMatch[1]) tags.push(tagMatch[1]);
    }

    const image = extractImage(contentEncoded);
    const plainText = stripHtml(contentEncoded);
    const excerpt = plainText.slice(0, 220).trim() + (plainText.length > 220 ? '…' : '');
    const readingTime = estimateReadingTime(plainText);
    const slug = slugify(title);

    if (title && link) {
      items.push({
        slug,
        title: stripHtml(title),
        excerpt,
        content: contentEncoded,
        date: pubDate,
        dateFormatted: formatDate(pubDate),
        readingTime,
        image,
        tags,
        mediumUrl: link,
        guid,
        source: 'medium',
      });
    }
  }

  return items;
}

let cache: { posts: Post[]; ts: number; ok: boolean } | null = null;

/**
 * Pulls posts from the Medium RSS feed. This is a *mirror*, not the source of
 * truth — see content/essays.ts for essays that live on this site first.
 *
 * Tries a JSON proxy first (friendlier to parse, but a third-party
 * dependency), then falls back to fetching the raw RSS feed directly. If both
 * fail, returns an empty list rather than pretending a stale placeholder post
 * is real content — the homepage and archive simply show local essays only
 * until the feed is reachable again.
 */
async function fetchMediumPosts(): Promise<{ posts: Post[]; ok: boolean }> {
  if (cache && Date.now() - cache.ts < CACHE_TTL) {
    return { posts: cache.posts, ok: cache.ok };
  }

  try {
    const res = await fetch(
      `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(MEDIUM_RSS)}&count=20`,
      { next: { revalidate: 300 } }
    );

    if (res.ok) {
      const data = await res.json();
      if (data.status === 'ok' && Array.isArray(data.items) && data.items.length) {
        const posts: Post[] = data.items.map((item: any) => {
          const raw = item.content || item.description || '';
          const plainText = stripHtml(raw);
          const excerpt = plainText.slice(0, 220).trim() + (plainText.length > 220 ? '…' : '');
          return {
            slug: slugify(item.title || ''),
            title: item.title || '',
            excerpt,
            content: raw,
            date: item.pubDate || '',
            dateFormatted: formatDate(item.pubDate || ''),
            readingTime: estimateReadingTime(plainText),
            image: item.thumbnail || extractImage(raw) || null,
            tags: item.categories || [],
            mediumUrl: item.link || item.guid || '',
            guid: item.guid || item.link || '',
            source: 'medium' as const,
          };
        });
        cache = { posts, ts: Date.now(), ok: true };
        return { posts, ok: true };
      }
    }
  } catch {
    // fall through to direct RSS fetch
  }

  try {
    const res = await fetch(MEDIUM_RSS, {
      headers: { 'User-Agent': 'Mozilla/5.0 (compatible; AlphaWordsBot/1.0; +https://alphaswords.com)' },
      next: { revalidate: 300 },
    });
    if (res.ok) {
      const xml = await res.text();
      const posts = parseRSS(xml);
      if (posts.length) {
        cache = { posts, ts: Date.now(), ok: true };
        return { posts, ok: true };
      }
    }
  } catch {
    // both sources failed
  }

  // Don't invent content. Surface that the mirror is temporarily unreachable
  // and let local essays carry the page.
  cache = { posts: [], ts: Date.now(), ok: false };
  return { posts: [], ok: false };
}

function dedupe(posts: Post[]): Post[] {
  const seen = new Map<string, Post>();
  // Local essays are added first and win on slug collisions, since they're
  // the canonical version if a piece has also been mirrored on Medium.
  for (const post of posts) {
    if (!seen.has(post.slug)) seen.set(post.slug, post);
  }
  return Array.from(seen.values());
}

/** All posts — local essays plus the live Medium mirror — newest first. */
export async function fetchAllPosts(): Promise<{ posts: Post[]; mediumReachable: boolean }> {
  const { posts: mediumPosts, ok } = await fetchMediumPosts();
  const merged = dedupe([...localEssays, ...mediumPosts]);
  merged.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
  return { posts: merged, mediumReachable: ok };
}

export async function fetchPostBySlug(slug: string): Promise<Post | null> {
  const local = localEssays.find(p => p.slug === slug);
  if (local) return local;
  const { posts } = await fetchAllPosts();
  return posts.find(p => p.slug === slug) || null;
}

/** Up to `limit` other posts that share a tag with `post`, most recent first. */
export function getRelatedPosts(post: Post, all: Post[], limit = 2): Post[] {
  const shared = all.filter(
    p => p.slug !== post.slug && p.tags.some(t => post.tags.includes(t))
  );
  const rest = all.filter(
    p => p.slug !== post.slug && !p.tags.some(t => post.tags.includes(t))
  );
  return [...shared, ...rest].slice(0, limit);
}
