"use client"

import { useEffect, useRef, useState, useCallback } from "react"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { createSupabaseBrowserClient } from "@/lib/supabase/client"
import { sanitizeEntryContent } from "@/lib/sanitize"
import {
  useToggleEntryReadState,
  useToggleEntrySavedState,
} from "@/lib/queries/use-entry-state-mutations"
import { queryKeys } from "@/lib/queries/query-keys"
import { useUserInterfaceStore } from "@/lib/stores/user-interface-store"
import { useTimeline } from "@/lib/queries/use-timeline"
import { useEntryShare } from "@/lib/queries/use-entry-share"
import { useEntryHighlights } from "@/lib/queries/use-entry-highlights"
import {
  useCreateHighlight,
  useUpdateHighlightNote,
  useDeleteHighlight,
} from "@/lib/queries/use-highlight-mutations"
import {
  serializeSelectionRange,
  deserializeHighlightRange,
  applyHighlightToRange,
  removeHighlightFromDom,
} from "@/lib/highlight-positioning"
import { HighlightSelectionToolbar } from "./highlight-selection-toolbar"
import { HighlightPopover } from "./highlight-popover"
import { formatDistanceToNow, format } from "date-fns"
import { notify } from "@/lib/notify"
import type { Highlight } from "@/lib/types/highlight"

interface EntryDetailRow {
  id: string
  title: string | null
  url: string | null
  author: string | null
  content_html: string | null
  summary: string | null
  published_at: string | null
  enclosure_url: string | null
  feeds: {
    title: string | null
  }
}

function estimateReadingTimeMinutes(html: string): number {
  const text = html.replace(/<[^>]*>/g, "").replace(/&\w+;/g, " ")
  const wordCount = text.split(/\s+/).filter(Boolean).length
  return Math.max(1, Math.round(wordCount / 200))
}

export function EntryDetailPanel({
  entryIdentifier,
}: {
  entryIdentifier: string
}) {
  const supabaseClient = createSupabaseBrowserClient()
  const queryClient = useQueryClient()
  const toggleReadState = useToggleEntryReadState()
  const toggleSavedState = useToggleEntrySavedState()
  const setSelectedEntryIdentifier = useUserInterfaceStore(
    (state) => state.setSelectedEntryIdentifier
  )
  const timeDisplayFormat = useUserInterfaceStore(
    (state) => state.timeDisplayFormat
  )
  const showReadingTime = useUserInterfaceStore(
    (state) => state.showReadingTime
  )
  const proseContainerReference = useRef<HTMLDivElement>(null)
  const [selectionToolbarState, setSelectionToolbarState] = useState<{
    selectionRect: DOMRect
    containerRect: DOMRect
    range: Range
  } | null>(null)
  const [highlightPopoverState, setHighlightPopoverState] = useState<{
    highlightIdentifier: string
    note: string | null
    anchorRect: DOMRect
    containerRect: DOMRect
  } | null>(null)
  const [unpositionedHighlights, setUnpositionedHighlights] = useState<Highlight[]>([])
  const [isShareNoteDialogOpen, setIsShareNoteDialogOpen] = useState(false)
  const [shareNoteText, setShareNoteText] = useState("")
  const shareNoteTextareaReference = useRef<HTMLTextAreaElement>(null)

  const { data: timelineData } = useTimeline()
  const currentEntry = timelineData?.pages
    .flatMap((page) => page)
    .find((entry) => entry.entryIdentifier === entryIdentifier)

  const { data: entryDetail, isLoading } = useQuery({
    queryKey: queryKeys.entryDetail.single(entryIdentifier),
    queryFn: async () => {
      const { data, error } = await supabaseClient
        .from("entries")
        .select(
          "id, title, url, author, content_html, summary, published_at, enclosure_url, feeds!inner(title)"
        )
        .eq("id", entryIdentifier)
        .single()

      if (error) throw error

      return data as unknown as EntryDetailRow
    },
  })

  const { data: shareData } = useEntryShare(entryIdentifier)
  const { data: highlightsData } = useEntryHighlights(entryIdentifier)

  const createHighlight = useCreateHighlight()
  const updateHighlightNote = useUpdateHighlightNote()
  const deleteHighlight = useDeleteHighlight()

  const shareMutation = useMutation({
    mutationFn: async (note?: string | null) => {
      const response = await fetch("/api/share", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ entryIdentifier, note: note ?? null }),
      })
      if (!response.ok) throw new Error("failed to create share")
      return response.json() as Promise<{
        shareToken: string
        shareUrl: string
      }>
    },
    onSuccess: async (data) => {
      await navigator.clipboard.writeText(data.shareUrl)
      notify("link copied")
      queryClient.invalidateQueries({
        queryKey: queryKeys.entryShare.single(entryIdentifier),
      })
      queryClient.invalidateQueries({ queryKey: ["shared-entries"] })
    },
  })

  const unshareMutation = useMutation({
    mutationFn: async (shareToken: string) => {
      const response = await fetch(`/api/share/${shareToken}`, {
        method: "DELETE",
      })
      if (!response.ok) throw new Error("failed to delete share")
    },
    onSuccess: () => {
      notify("share link removed")
      queryClient.invalidateQueries({
        queryKey: queryKeys.entryShare.single(entryIdentifier),
      })
    },
  })

  useEffect(() => {
    if (!currentEntry || currentEntry.isRead) return

    const autoReadTimeout = setTimeout(() => {
      toggleReadState.mutate({
        entryIdentifier,
        isRead: true,
      })
    }, 1500)

    return () => clearTimeout(autoReadTimeout)
  }, [entryIdentifier, currentEntry?.isRead])

  const contentHtml =
    entryDetail?.content_html || entryDetail?.summary || ""
  const sanitisedContent = sanitizeEntryContent(contentHtml)

  useEffect(() => {
    const container = proseContainerReference.current
    if (!container || !sanitisedContent) return

    container.textContent = ""
    const template = document.createElement("template")
    template.innerHTML = sanitisedContent
    container.appendChild(template.content.cloneNode(true))

    const failedHighlights: Highlight[] = []

    if (highlightsData && highlightsData.length > 0) {
      const sortedHighlights = [...highlightsData].sort(
        (a, b) => b.textOffset - a.textOffset
      )
      for (const highlight of sortedHighlights) {
        const range = deserializeHighlightRange(container, highlight)
        if (range) {
          applyHighlightToRange(
            range,
            highlight.identifier,
            highlight.color,
            !!highlight.note
          )
        } else {
          failedHighlights.push(highlight)
        }
      }
    }

    setUnpositionedHighlights(failedHighlights)
  }, [sanitisedContent, highlightsData])

  const handleTextSelection = useCallback(() => {
    const container = proseContainerReference.current
    if (!container) return

    const selection = window.getSelection()
    if (!selection || selection.isCollapsed || !selection.rangeCount) {
      setSelectionToolbarState(null)
      return
    }

    const range = selection.getRangeAt(0)
    if (!container.contains(range.commonAncestorContainer)) {
      setSelectionToolbarState(null)
      return
    }

    const selectionRect = range.getBoundingClientRect()
    const containerRect = container.getBoundingClientRect()

    setSelectionToolbarState({
      selectionRect,
      containerRect,
      range: range.cloneRange(),
    })
    setHighlightPopoverState(null)
  }, [])

  useEffect(() => {
    document.addEventListener("mouseup", handleTextSelection)
    document.addEventListener("touchend", handleTextSelection)
    return () => {
      document.removeEventListener("mouseup", handleTextSelection)
      document.removeEventListener("touchend", handleTextSelection)
    }
  }, [handleTextSelection])

  useEffect(() => {
    const container = proseContainerReference.current
    if (!container) return

    const currentContainer = container

    function handleMarkClick(event: MouseEvent) {
      const target = event.target as HTMLElement
      const markElement = target.closest("mark[data-highlight-identifier]")
      if (!markElement) return

      const highlightIdentifier = markElement.getAttribute("data-highlight-identifier")
      if (!highlightIdentifier) return

      const matchingHighlight = highlightsData?.find(
        (h) => h.identifier === highlightIdentifier
      )

      const anchorRect = markElement.getBoundingClientRect()
      const containerRect = currentContainer.getBoundingClientRect()

      setHighlightPopoverState({
        highlightIdentifier,
        note: matchingHighlight?.note ?? null,
        anchorRect,
        containerRect,
      })
      setSelectionToolbarState(null)
    }

    container.addEventListener("click", handleMarkClick)
    return () => container.removeEventListener("click", handleMarkClick)
  }, [highlightsData])

  useEffect(() => {
    if (isShareNoteDialogOpen) {
      setTimeout(() => shareNoteTextareaReference.current?.focus(), 0)
    }
  }, [isShareNoteDialogOpen])

  useEffect(() => {
    if (!isShareNoteDialogOpen) return

    function handleKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") {
        event.preventDefault()
        event.stopPropagation()
        setIsShareNoteDialogOpen(false)
      }
    }

    document.addEventListener("keydown", handleKeyDown, true)
    return () => document.removeEventListener("keydown", handleKeyDown, true)
  }, [isShareNoteDialogOpen])

  function handleShareConfirm() {
    shareMutation.mutate(shareNoteText.trim() || null)
    setIsShareNoteDialogOpen(false)
  }

  function handleCreateHighlight(note: string | null) {
    const container = proseContainerReference.current
    if (!container || !selectionToolbarState) return

    const serialized = serializeSelectionRange(
      container,
      selectionToolbarState.range
    )
    if (!serialized) return

    createHighlight.mutate({
      entryIdentifier,
      highlightedText: serialized.highlightedText,
      note,
      textOffset: serialized.textOffset,
      textLength: serialized.textLength,
      textPrefix: serialized.textPrefix,
      textSuffix: serialized.textSuffix,
      color: "yellow",
    })

    window.getSelection()?.removeAllRanges()
    setSelectionToolbarState(null)
  }

  function handleUpdateHighlightNote(note: string | null) {
    if (!highlightPopoverState) return
    updateHighlightNote.mutate({
      highlightIdentifier: highlightPopoverState.highlightIdentifier,
      note,
      entryIdentifier,
    })
    setHighlightPopoverState(null)
  }

  function handleDeleteHighlight() {
    if (!highlightPopoverState) return
    const container = proseContainerReference.current
    if (container) {
      removeHighlightFromDom(
        container,
        highlightPopoverState.highlightIdentifier
      )
    }
    deleteHighlight.mutate({
      highlightIdentifier: highlightPopoverState.highlightIdentifier,
      entryIdentifier,
    })
    setHighlightPopoverState(null)
  }

  if (isLoading || !entryDetail) {
    return (
      <div className="flex h-full items-center justify-center text-text-dim">
        loading ...
      </div>
    )
  }

  const readingTimeMinutes = estimateReadingTimeMinutes(contentHtml)
  const isRead = currentEntry?.isRead ?? false
  const isSaved = currentEntry?.isSaved ?? false

  return (
    <div data-detail-panel className="flex h-full flex-col">
      <div className="flex items-center gap-2 overflow-x-auto border-b border-border px-4 py-2">
        <button
          type="button"
          onClick={() =>
            toggleReadState.mutate({
              entryIdentifier,
              isRead: !isRead,
            })
          }
          className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
        >
          {isRead ? "mark unread" : "mark read"}
        </button>
        <button
          type="button"
          onClick={() =>
            toggleSavedState.mutate({
              entryIdentifier,
              isSaved: !isSaved,
            })
          }
          className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
        >
          {isSaved ? "unsave" : "save"}
        </button>
        {entryDetail.url && (
          <a
            href={entryDetail.url}
            target="_blank"
            rel="noopener noreferrer"
            className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
          >
            open original
          </a>
        )}
        {shareData?.isShared ? (
          <button
            type="button"
            onClick={() => unshareMutation.mutate(shareData.shareToken!)}
            className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
          >
            unshare
          </button>
        ) : (
          <button
            type="button"
            onClick={() => {
              setShareNoteText("")
              setIsShareNoteDialogOpen(true)
            }}
            className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
          >
            share
          </button>
        )}
        <div className="flex-1" />
        <button
          type="button"
          onClick={() => setSelectedEntryIdentifier(null)}
          className="hidden px-2 py-1 text-text-dim transition-colors hover:text-text-secondary md:block"
        >
          close
        </button>
      </div>
      <article data-detail-article className="min-h-0 flex-1 overflow-y-scroll px-6 py-4">
        <h2 className="mb-1 text-base text-text-primary">
          {entryDetail.title}
        </h2>
        <div className="mb-4 text-text-dim">
          {entryDetail.feeds?.title && (
            <span>{entryDetail.feeds.title}</span>
          )}
          {entryDetail.author && (
            <span> &middot; {entryDetail.author}</span>
          )}
          {entryDetail.published_at && (
            <span>
              {" "}&middot;{" "}
              {timeDisplayFormat === "absolute"
                ? format(new Date(entryDetail.published_at), "MMM d, h:mm a")
                : formatDistanceToNow(new Date(entryDetail.published_at), {
                    addSuffix: true,
                  })}
            </span>
          )}
          {showReadingTime && (
            <span> &middot; {readingTimeMinutes} min read</span>
          )}
        </div>
        {entryDetail.enclosure_url && (
          <div className="mb-4 border border-border p-3">
            <audio
              controls
              preload="none"
              src={entryDetail.enclosure_url}
              className="w-full"
            />
          </div>
        )}
        {unpositionedHighlights.length > 0 && (
          <div className="mb-4 border border-border px-3 py-2">
            <p className="mb-2 text-text-dim">
              {unpositionedHighlights.length} highlight
              {unpositionedHighlights.length !== 1 && "s"} could not be positioned
              (the article content may have changed)
            </p>
            {unpositionedHighlights.map((highlight) => (
              <div
                key={highlight.identifier}
                className="mb-1 border-l-2 border-text-dim pl-2 text-text-secondary last:mb-0"
              >
                <span className="bg-background-tertiary text-text-primary">
                  {highlight.highlightedText}
                </span>
                {highlight.note && (
                  <span className="ml-2 text-text-dim">
                    — {highlight.note}
                  </span>
                )}
              </div>
            ))}
          </div>
        )}
        <div className="relative">
          <div
            ref={proseContainerReference}
            className="prose-reader text-text-secondary"
          />
          {selectionToolbarState && (
            <HighlightSelectionToolbar
              selectionRect={selectionToolbarState.selectionRect}
              containerRect={selectionToolbarState.containerRect}
              onHighlight={handleCreateHighlight}
              onDismiss={() => setSelectionToolbarState(null)}
            />
          )}
          {highlightPopoverState && (
            <HighlightPopover
              highlightIdentifier={highlightPopoverState.highlightIdentifier}
              note={highlightPopoverState.note}
              anchorRect={highlightPopoverState.anchorRect}
              containerRect={highlightPopoverState.containerRect}
              onUpdateNote={handleUpdateHighlightNote}
              onDelete={handleDeleteHighlight}
              onDismiss={() => setHighlightPopoverState(null)}
            />
          )}
        </div>
      </article>
      {isShareNoteDialogOpen && (
        <div className="fixed inset-0 z-50 flex items-center justify-center">
          <div
            className="fixed inset-0 bg-background-primary/80"
            onClick={() => setIsShareNoteDialogOpen(false)}
          />
          <div className="relative w-full max-w-md border border-border bg-background-secondary p-6">
            <h2 className="mb-4 text-text-primary">share entry</h2>
            <form
              onSubmit={(event) => {
                event.preventDefault()
                handleShareConfirm()
              }}
              className="space-y-4"
            >
              <div className="space-y-2">
                <label htmlFor="share-note-textarea" className="text-text-secondary">
                  add a note (optional)
                </label>
                <textarea
                  ref={shareNoteTextareaReference}
                  id="share-note-textarea"
                  value={shareNoteText}
                  onChange={(event) => setShareNoteText(event.target.value)}
                  rows={4}
                  placeholder="write a note to accompany this shared entry ..."
                  className="w-full resize-y border border-border bg-background-primary px-3 py-2 text-text-primary outline-none placeholder:text-text-dim focus:border-text-dim"
                />
              </div>
              <div className="flex gap-2">
                <button
                  type="button"
                  onClick={() => setIsShareNoteDialogOpen(false)}
                  className="flex-1 border border-border px-4 py-2 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
                >
                  cancel
                </button>
                <button
                  type="submit"
                  disabled={shareMutation.isPending}
                  className="flex-1 border border-border bg-background-tertiary px-4 py-2 text-text-primary transition-colors hover:bg-border disabled:opacity-50"
                >
                  {shareMutation.isPending ? "sharing..." : "share"}
                </button>
              </div>
            </form>
          </div>
        </div>
      )}
    </div>
  )
}
