import { useEffect, useRef, useState } from 'react'
import { XMarkIcon } from '@heroicons/react/24/outline'
import { useAuth } from '~/lib/auth-context'
import { useData } from '~/lib/data-context'
import {
  addSuggestions,
  dismissSuggestion,
  getBskyFollows,
  listPublicPublications,
  listSuggestions,
  subscribe,
  unsubscribe,
  SessionExpiredError,
  type SuggestionItem,
} from '~/lib/api-client'
import CenteredMessage from '~/components/CenteredMessage'
import EmptyState from '~/components/EmptyState'
import PageLayout from '~/components/PageLayout'
import PublicationCard from '~/components/PublicationCard'
import Spinner from '~/components/Spinner'
import { SITE_NAME } from '~/lib/site-config'

export function meta() {
  return [{ title: `Suggestions — ${SITE_NAME}` }]
}

const DISPLAY_PAGE_SIZE = 20
const FOLLOWS_BATCH = 5
const SUGGESTIONS_BULK_SIZE = 100

async function fetchAllFollowDids(did: string): Promise<string[]> {
  const dids: string[] = []
  let cursor: string | undefined
  do {
    const page = await getBskyFollows(did, cursor)
    dids.push(...page.follows.map((f) => f.did))
    cursor = page.cursor
  } while (cursor)
  return dids
}


export default function Suggestions() {
  const { session, loading: authLoading, signOut } = useAuth()
  const { suggestions, setSuggestions, removeSuggestion, subscriptions, removeSubscription, removeResolvedPub } = useData()
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [findLoading, setFindLoading] = useState(false)
  const [findMessage, setFindMessage] = useState<string | null>(null)
  const [findError, setFindError] = useState<string | null>(null)
  const [displayCount, setDisplayCount] = useState(DISPLAY_PAGE_SIZE)
  const [dismissing, setDismissing] = useState<Record<string, boolean>>({})
  const [subscribeLoading, setSubscribeLoading] = useState<Record<string, boolean>>({})
  // Local overrides for subscribe state: pubAtUri → rkey (subscribed) | null (unsubscribed)
  const [subscribeOverrides, setSubscribeOverrides] = useState<Record<string, string | null>>({})
  const sentinelRef = useRef<HTMLDivElement>(null)

  // Load suggestions on first visit
  useEffect(() => {
    if (authLoading || !session || suggestions !== undefined) return
    let cancelled = false
    setLoading(true)
    setError(null)
    listSuggestions(session)
      .then((items) => { if (!cancelled) setSuggestions(items) })
      .catch((err) => {
        if (cancelled) return
        if (err instanceof SessionExpiredError) { signOut(); return }
        setError(err instanceof Error ? err.message : 'Failed to load suggestions')
      })
      .finally(() => { if (!cancelled) setLoading(false) })
    return () => { cancelled = true }
  }, [session, authLoading, suggestions, setSuggestions, signOut])

  // Infinite scroll — reveal more items from the already-loaded list
  useEffect(() => {
    const total = suggestions?.length ?? 0
    if (displayCount >= total || !sentinelRef.current) return
    const sentinel = sentinelRef.current
    const observer = new IntersectionObserver((entries) => {
      if (!entries[0].isIntersecting) return
      setDisplayCount((prev) => prev + DISPLAY_PAGE_SIZE)
    })
    observer.observe(sentinel)
    return () => observer.disconnect()
  }, [suggestions, displayCount])

  async function handleFindSuggestions() {
    if (!session) return
    setFindLoading(true)
    setFindMessage(null)
    setFindError(null)
    try {
      const followedDids = await fetchAllFollowDids(session.sub)

      const candidates: Array<Omit<SuggestionItem, 'show'>> = []
      for (let i = 0; i < followedDids.length; i += FOLLOWS_BATCH) {
        const batch = followedDids.slice(i, i + FOLLOWS_BATCH)
        const results = await Promise.allSettled(batch.map((did) => listPublicPublications(did)))
        for (const result of results) {
          if (result.status !== 'fulfilled') continue
          for (const pub of result.value) {
            const rkey = pub.atUri.split('/').pop()
            const authorDid = pub.atUri.slice('at://'.length).split('/')[0]
            if (!rkey || authorDid === session.sub) continue
            if (subscriptions?.some((s) => s.publicationAtUri === pub.atUri)) continue
            candidates.push({ pubAtUri: pub.atUri, rkey, authorDid, pubName: pub.name, pubUrl: pub.url, pubDescription: pub.description, authorHandle: pub.authorHandle })
          }
        }
      }

      let totalAdded = 0
      for (let i = 0; i < candidates.length; i += SUGGESTIONS_BULK_SIZE) {
        const { added } = await addSuggestions(session, candidates.slice(i, i + SUGGESTIONS_BULK_SIZE))
        totalAdded += added
      }

      const updated = await listSuggestions(session)
      setSuggestions(updated)
      setDisplayCount(DISPLAY_PAGE_SIZE)
      setFindMessage(totalAdded === 0 ? 'No new suggestions found.' : `Added ${totalAdded} new suggestion${totalAdded === 1 ? '' : 's'}.`)
    } catch (err) {
      if (err instanceof SessionExpiredError) { signOut(); return }
      setFindError(err instanceof Error ? err.message : 'Something went wrong')
    } finally {
      setFindLoading(false)
    }
  }

  async function handleSubscribe(item: SuggestionItem) {
    if (!session) return
    setSubscribeLoading((prev) => ({ ...prev, [item.pubAtUri]: true }))
    try {
      await subscribe(session, item.pubAtUri)
      await dismissSuggestion(session, item.rkey)
      removeSuggestion(item.rkey)
    } catch {
      // non-fatal
    } finally {
      setSubscribeLoading((prev) => ({ ...prev, [item.pubAtUri]: false }))
    }
  }

  async function handleUnsubscribe(item: SuggestionItem, rkey: string) {
    if (!session) return
    setSubscribeLoading((prev) => ({ ...prev, [item.pubAtUri]: true }))
    try {
      await unsubscribe(session, rkey)
      setSubscribeOverrides((prev) => ({ ...prev, [item.pubAtUri]: null }))
      removeSubscription(rkey)
      removeResolvedPub(item.pubAtUri)
    } catch {
      // non-fatal
    } finally {
      setSubscribeLoading((prev) => ({ ...prev, [item.pubAtUri]: false }))
    }
  }

  async function handleDismiss(item: SuggestionItem) {
    if (!session) return
    setDismissing((prev) => ({ ...prev, [item.rkey]: true }))
    try {
      await dismissSuggestion(session, item.rkey)
      removeSuggestion(item.rkey)
    } catch {
      // non-fatal
    } finally {
      setDismissing((prev) => ({ ...prev, [item.rkey]: false }))
    }
  }

  if (authLoading) {
    return (
      <div className="flex-1 flex items-center justify-center">
        <Spinner />
      </div>
    )
  }

  if (!session) {
    return <CenteredMessage message="Sign in to view suggestions." action={{ label: 'Sign in', to: '/' }} />
  }

  const subscribedUris = new Set(subscriptions?.map((s) => s.publicationAtUri) ?? [])
  const visibleSuggestions = (suggestions ?? []).filter((item) => !subscribedUris.has(item.pubAtUri))
  const displayed = visibleSuggestions.slice(0, displayCount)
  const hasMore = visibleSuggestions.length > displayCount

  return (
    <PageLayout spacing="8">
      <div>
        <h1 className="font-display text-2xl font-semibold text-stone-900 dark:text-white">Suggestions</h1>
        <p className="text-sm text-stone-500 dark:text-stone-400 mt-1">Publications from people you follow on Bluesky</p>
      </div>

      <div className="flex items-center gap-3">
        <button
          onClick={handleFindSuggestions}
          disabled={findLoading}
          className="flex items-center gap-2 px-4 py-2 rounded-lg bg-stone-900 dark:bg-stone-100 text-stone-50 dark:text-stone-900 text-sm font-medium hover:bg-stone-700 dark:hover:bg-stone-200 transition-colors disabled:opacity-50"
        >
          {findLoading && <Spinner size="sm" />}
          {findLoading ? 'Finding…' : 'Find suggestions'}
        </button>
        {findMessage && <p className="text-sm text-stone-500 dark:text-stone-400">{findMessage}</p>}
        {findError && <p className="text-sm text-red-500">{findError}</p>}
      </div>

      {error && <p className="text-sm text-red-500">{error}</p>}

      {loading && (
        <div className="flex justify-center py-12">
          <Spinner />
        </div>
      )}

      {!loading && suggestions !== undefined && visibleSuggestions.length === 0 && (
        <EmptyState
          message="No suggestions yet."
          detail={'Click "Find suggestions" to discover publications from people you follow on Bluesky.'}
        />
      )}

      {displayed.length > 0 && (
        <ul className="space-y-2">
          {displayed.map((item) => {
            const overrideRkey = subscribeOverrides[item.pubAtUri]
            const subscriptionRkey = overrideRkey !== undefined
              ? overrideRkey
              : (subscriptions?.find((s) => s.publicationAtUri === item.pubAtUri)?.rkey ?? null)
            const isSubscribed = !!subscriptionRkey
            const subLoading = !!subscribeLoading[item.pubAtUri]
            return (
              <PublicationCard
                key={item.pubAtUri}
                pub={{ atUri: item.pubAtUri, name: item.pubName, url: item.pubUrl, description: item.pubDescription, authorHandle: item.authorHandle }}
                action={
                  <>
                    <button
                      onClick={() => isSubscribed ? handleUnsubscribe(item, subscriptionRkey!) : handleSubscribe(item)}
                      disabled={subLoading || subscriptions === undefined}
                      className={`text-sm px-3 py-1 rounded-full transition-colors disabled:opacity-50 ${
                        isSubscribed
                          ? 'border border-stone-300 dark:border-stone-600 text-stone-600 dark:text-stone-300 hover:border-stone-400 dark:hover:border-stone-500 hover:bg-stone-50 dark:hover:bg-stone-800/50'
                          : 'bg-stone-900 dark:bg-stone-100 text-stone-50 dark:text-stone-900 hover:bg-stone-700 dark:hover:bg-stone-200'
                      }`}
                    >
                      {subLoading ? (isSubscribed ? 'Unsubscribing…' : 'Subscribing…') : (isSubscribed ? 'Subscribed' : 'Subscribe')}
                    </button>
                    <button
                      onClick={() => handleDismiss(item)}
                      disabled={!!dismissing[item.rkey]}
                      title="Dismiss suggestion"
                      className="p-1.5 rounded-md text-stone-400 dark:text-stone-500 hover:text-stone-700 dark:hover:text-stone-200 hover:bg-stone-100 dark:hover:bg-stone-800 transition-colors disabled:opacity-50"
                    >
                      <XMarkIcon className="h-4 w-4" />
                    </button>
                  </>
                }
              />
            )
          })}
        </ul>
      )}

      <div ref={sentinelRef} />

      {!hasMore && !loading && visibleSuggestions.length > 0 && (
        <p className="text-center text-sm text-stone-400 dark:text-stone-600 py-4">
          You've reached the end.
        </p>
      )}
    </PageLayout>
  )
}
