"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import "react-quill-new/dist/quill.snow.css";

import { ApiError } from "@/uhm/api/http";
import { fetchWikiBySlug, type Wiki } from "@/uhm/api/wikis";

type TocItem = {
  id: string;
  level: number;
  text: string;
};

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function tiptapJsonToPlainText(node: unknown): string {
  if (node == null) return "";
  if (typeof node === "string") return node;
  if (Array.isArray(node)) return node.map(tiptapJsonToPlainText).join("");

  if (isRecord(node)) {
    if (node.type === "text" && typeof node.text === "string") return node.text;
    if (node.type === "hardBreak") return "\n";
    if ("content" in node) return tiptapJsonToPlainText(node.content);
  }

  return "";
}

function escapeHtml(input: string): string {
  return input
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll("\"", "&quot;")
    .replaceAll("'", "&#39;");
}

function normalizeWikiContentToHtml(raw: string | null | undefined): string {
  const value = String(raw || "").trim();
  if (!value.length) return "";

  // New format: HTML string.
  if (value[0] === "<") return value;

  // Legacy format: Tiptap JSON string.
  if (value[0] === "{") {
    try {
      const json: unknown = JSON.parse(value);
      const text = tiptapJsonToPlainText(json).trim();
      if (!text.length) return "";
      return `<p>${escapeHtml(text).replace(/\n/g, "<br/>")}</p>`;
    } catch {
      // fall through
    }
  }

  // Unknown plaintext: treat as plain text.
  return `<p>${escapeHtml(value).replace(/\n/g, "<br/>")}</p>`;
}

function slugifyHeading(raw: string): string {
  const input = String(raw || "").trim();
  if (!input.length) return "";
  return input
    .toLowerCase()
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+/, "")
    .replace(/-+$/, "")
    .slice(0, 80);
}

function isExternalHref(href: string): boolean {
  const h = href.trim().toLowerCase();
  return (
    h.startsWith("http://") ||
    h.startsWith("https://") ||
    h.startsWith("mailto:") ||
    h.startsWith("tel:") ||
    h.startsWith("sms:")
  );
}

function rewriteHtmlAndBuildToc(inputHtml: string, wikiBaseUrl: string): { html: string; toc: TocItem[] } {
  const parser = new DOMParser();
  const doc = parser.parseFromString(inputHtml, "text/html");

  // Basic hardening: do not render scripts in user content.
  for (const el of Array.from(doc.querySelectorAll("script"))) el.remove();

  // Rewrite internal wiki links: Quill stores slug as <a href="other-wiki-slug">...</a>
  for (const a of Array.from(doc.querySelectorAll("a[href]"))) {
    const href = String(a.getAttribute("href") || "").trim();
    if (!href.length) continue;
    if (href === "__missing__") continue;
    if (href.startsWith("#")) continue;
    if (href.startsWith("/")) continue;
    if (isExternalHref(href)) continue;

    const match = href.match(/^([^?#]+)([?#].*)?$/);
    const slugPart = String(match?.[1] || "").replace(/^\/+/, "").trim();
    const suffix = String(match?.[2] || "");

    const normalizedSlug = slugPart;
    if (!normalizedSlug.length) continue;

    a.setAttribute("href", `${wikiBaseUrl}${encodeURIComponent(normalizedSlug)}${suffix}`);
    a.setAttribute("target", "_self");
  }

  // Build TOC from headings and ensure they have stable IDs.
  const toc: TocItem[] = [];
  const seen = new Map<string, number>();
  const headings = Array.from(doc.body.querySelectorAll("h1,h2,h3,h4,h5,h6"));
  for (const h of headings) {
    const text = String(h.textContent || "").trim();
    if (!text.length) continue;

    const level = Number(String(h.tagName || "").replace(/^H/i, "")) || 1;
    const existingId = String(h.getAttribute("id") || "").trim();
    if (existingId) {
      toc.push({ id: existingId, level, text });
      continue;
    }

    const base = slugifyHeading(text) || "heading";
    const n = (seen.get(base) || 0) + 1;
    seen.set(base, n);
    const id = n === 1 ? base : `${base}-${n}`;

    h.setAttribute("id", id);
    toc.push({ id, level, text });
  }

  return { html: doc.body.innerHTML, toc };
}

function formatDate(value?: string | null): string {
  const raw = String(value || "").trim();
  if (!raw) return "-";
  const d = new Date(raw);
  if (Number.isNaN(d.getTime())) return raw;
  return d.toLocaleString();
}

export default function WikiBySlugClient({ slug }: { slug: string }) {
  const [wiki, setWiki] = useState<Wiki | null>(null);
  const [status, setStatus] = useState<"idle" | "loading" | "error" | "ready">("idle");
  const [error, setError] = useState<string | null>(null);
  const [renderHtml, setRenderHtml] = useState<string>("");
  const [toc, setToc] = useState<TocItem[]>([]);
  const [activeHeadingId, setActiveHeadingId] = useState<string | null>(null);
  const [linkPreview, setLinkPreview] = useState<{
    slug: string;
    top: number;
    left: number;
    width: number;
    height: number;
    visible: boolean;
  } | null>(null);
  const [linkPreviewData, setLinkPreviewData] = useState<{
    slug: string;
    title: string;
    quote: string | null;
    status: "idle" | "loading" | "ready" | "error";
  } | null>(null);

  const normalizedSlug = useMemo(() => String(slug || "").trim(), [slug]);
  const contentRootRef = useRef<HTMLDivElement | null>(null);
  const hidePreviewTimerRef = useRef<number | null>(null);
  const previewCacheRef = useRef<Map<string, { title: string; quote: string | null }>>(new Map());

  // Load wiki data by slug.
  useEffect(() => {
    const value = String(normalizedSlug || "").trim();
    if (!value.length) {
      setWiki(null);
      setStatus("error");
      setError("Missing wiki slug.");
      return;
    }

    let disposed = false;
    (async () => {
      setStatus("loading");
      setError(null);
      try {
        const res = await fetchWikiBySlug(value);
        if (disposed) return;
        if (!res) {
          setWiki(null);
          setStatus("ready");
          setRenderHtml("");
          setToc([]);
          return;
        }
        setWiki(res);
        setStatus("ready");
      } catch (err) {
        if (disposed) return;
        const msg =
          err instanceof ApiError
            ? err.message
            : err instanceof Error
              ? err.message
              : "Failed to load wiki.";
        setStatus("error");
        setError(msg);
      }
    })();

    return () => {
      disposed = true;
    };
  }, [normalizedSlug]);

  // Transform content: normalize -> rewrite internal links -> inject heading ids + toc.
  useEffect(() => {
    if (!wiki) {
      setRenderHtml("");
      setToc([]);
      return;
    }

    const raw =
      (wiki.content ?? (wiki as unknown as { doc?: string | null }).doc ?? "") || "";
    const html = normalizeWikiContentToHtml(raw);

    try {
      const base = `${window.location.origin}/wiki/`;
      const processed = rewriteHtmlAndBuildToc(html, base);
      setRenderHtml(processed.html);
      setToc(processed.toc);
      setActiveHeadingId(processed.toc[0]?.id ?? null);
    } catch (err) {
      console.error("Failed to process wiki HTML", err);
      setRenderHtml(html);
      setToc([]);
    }
  }, [wiki]);

  // Track active heading for TOC highlight.
  useEffect(() => {
    if (!toc.length) return;
    const root = contentRootRef.current;
    if (!root) return;

    const headings = toc
      .map((t) => root.querySelector<HTMLElement>(`#${CSS.escape(t.id)}`))
      .filter((el): el is HTMLElement => Boolean(el));
    if (!headings.length) return;

    const obs = new IntersectionObserver(
      (entries) => {
        const visible = entries
          .filter((e) => e.isIntersecting)
          .sort((a, b) => (a.boundingClientRect.top ?? 0) - (b.boundingClientRect.top ?? 0));
        const top = visible[0]?.target as HTMLElement | undefined;
        const id = top?.id || null;
        if (id) setActiveHeadingId(id);
      },
      { root: null, rootMargin: "-20% 0px -70% 0px", threshold: [0, 1] }
    );

    for (const h of headings) obs.observe(h);
    return () => obs.disconnect();
  }, [toc]);

  // Hover preview for internal wiki links (title + first blockquote).
  useEffect(() => {
    const root = contentRootRef.current;
    if (!root) return;
    if (typeof window === "undefined") return;

    const clearHideTimer = () => {
      if (hidePreviewTimerRef.current != null) {
        window.clearTimeout(hidePreviewTimerRef.current);
        hidePreviewTimerRef.current = null;
      }
    };

    const hideSoon = () => {
      clearHideTimer();
      hidePreviewTimerRef.current = window.setTimeout(() => {
        setLinkPreview((prev) => (prev ? { ...prev, visible: false } : prev));
      }, 140);
    };

    const resolveInternalWikiSlug = (href: string): string | null => {
      const h = href.trim();
      if (!h.length) return null;
      if (h === "__missing__") return null;
      if (h.startsWith("#")) return null;

      const stripQueryHash = (s: string) => {
        const m = s.match(/^([^?#]+)([?#].*)?$/);
        return String(m?.[1] || "");
      };

      if (h.startsWith("/wiki/")) {
        const path = stripQueryHash(h);
        const slugPart = path.slice("/wiki/".length).trim();
        return slugPart ? decodeURIComponent(slugPart) : null;
      }

      const originPrefix = window.location.origin + "/wiki/";
      if (h.startsWith(originPrefix)) {
        const rest = stripQueryHash(h.slice(originPrefix.length));
        const slugPart = rest.trim();
        return slugPart ? decodeURIComponent(slugPart) : null;
      }

      return null;
    };

    const fetchPreview = async (targetSlug: string) => {
      const key = targetSlug.trim();
      if (!key.length) return;

      const cached = previewCacheRef.current.get(key);
      if (cached) {
        setLinkPreviewData({ slug: key, title: cached.title, quote: cached.quote, status: "ready" });
        return;
      }

      setLinkPreviewData((prev) => ({ slug: key, title: prev?.title || key, quote: null, status: "loading" }));
      try {
        const row = await fetchWikiBySlug(key);
        if (!row) {
          setLinkPreviewData({ slug: key, title: key, quote: null, status: "error" });
          return;
        }

        const html = normalizeWikiContentToHtml(row.content ?? "");
        let quote: string | null = null;
        try {
          const parser = new DOMParser();
          const doc = parser.parseFromString(html, "text/html");
          const bq = doc.body.querySelector("blockquote");
          const txt = String(bq?.textContent || "").trim();
          quote = txt.length ? txt : null;
        } catch {
          quote = null;
        }

        const title = String(row.title || "").trim() || key;
        previewCacheRef.current.set(key, { title, quote });
        setLinkPreviewData({ slug: key, title, quote, status: "ready" });
      } catch {
        setLinkPreviewData({ slug: key, title: key, quote: null, status: "error" });
      }
    };

    const showForAnchor = (a: HTMLAnchorElement) => {
      const href = String(a.getAttribute("href") || "").trim();
      const targetSlug = resolveInternalWikiSlug(href);
      if (!targetSlug) return;

      // Avoid previews on touch devices.
      if (window.matchMedia && window.matchMedia("(hover: none)").matches) return;

      const rect = a.getBoundingClientRect();
      const width = 420;
      const height = 320;
      const margin = 12;

      const preferredLeft = rect.right + margin;
      const maxLeft = Math.max(margin, window.innerWidth - width - margin);
      const left = Math.min(preferredLeft, maxLeft);

      const preferredTop = rect.top;
      const maxTop = Math.max(margin, window.innerHeight - height - margin);
      const top = Math.max(margin, Math.min(preferredTop, maxTop));

      clearHideTimer();
      setLinkPreview({ slug: targetSlug, top, left, width, height, visible: true });
      void fetchPreview(targetSlug);
    };

    const onMouseOver = (evt: MouseEvent) => {
      const target = evt.target as HTMLElement | null;
      const a = target?.closest?.("a") as HTMLAnchorElement | null;
      if (!a) return;
      showForAnchor(a);
    };

    const onMouseOut = (evt: MouseEvent) => {
      const target = evt.target as HTMLElement | null;
      const related = evt.relatedTarget as HTMLElement | null;
      const fromA = target?.closest?.("a");
      if (!fromA) return;
      if (related && related.closest?.(".uhm-wiki-link-preview")) return;
      hideSoon();
    };

    const onKeyDown = (evt: KeyboardEvent) => {
      if (evt.key === "Escape") {
        clearHideTimer();
        setLinkPreview((prev) => (prev ? { ...prev, visible: false } : prev));
      }
    };

    const onScroll = () => {
      setLinkPreview((prev) => (prev ? { ...prev, visible: false } : prev));
    };

    root.addEventListener("mouseover", onMouseOver);
    root.addEventListener("mouseout", onMouseOut);
    window.addEventListener("keydown", onKeyDown);
    window.addEventListener("scroll", onScroll, { passive: true });

    return () => {
      root.removeEventListener("mouseover", onMouseOver);
      root.removeEventListener("mouseout", onMouseOut);
      window.removeEventListener("keydown", onKeyDown);
      window.removeEventListener("scroll", onScroll);
      clearHideTimer();
    };
  }, [renderHtml]);

  return (
    <div className="min-h-screen bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100">
      <div className="mx-auto max-w-6xl px-4 py-6">
        <div className="mb-5 flex items-start justify-between gap-4">
          <div className="min-w-0">
            <div className="text-xs text-gray-500 dark:text-gray-400">Wiki</div>
            <h1 className="mt-1 text-2xl font-bold leading-tight break-words">
              {wiki?.title?.trim() || normalizedSlug || "Wiki"}
            </h1>
            <div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-gray-600 dark:text-gray-300">
              <span className="break-all">
                <span className="font-semibold">Slug:</span> {normalizedSlug || "-"}
              </span>
              <span className="break-all">
                <span className="font-semibold">ID:</span> {wiki?.id || "-"}
              </span>
              <span className="break-all">
                <span className="font-semibold">Project:</span>{" "}
                {wiki?.project_id || "-"}
              </span>
              <span>
                <span className="font-semibold">Created:</span> {formatDate(wiki?.created_at)}
              </span>
              <span>
                <span className="font-semibold">Updated:</span> {formatDate(wiki?.updated_at)}
              </span>
            </div>
          </div>

          <div className="flex items-center gap-2">
            <Link
              href="/"
              className="h-9 inline-flex items-center rounded-lg border border-gray-200 dark:border-gray-800 px-3 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-white/[0.04] transition"
            >
              Home
            </Link>
          </div>
        </div>

        {status === "loading" ? (
          <div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6">
            <div className="h-5 w-40 rounded bg-gray-100 dark:bg-white/[0.06] animate-pulse" />
            <div className="mt-3 h-4 w-2/3 rounded bg-gray-100 dark:bg-white/[0.06] animate-pulse" />
          </div>
        ) : status === "error" ? (
          <div className="rounded-xl border border-red-200 dark:border-red-900/50 bg-red-50 dark:bg-red-950/30 p-6 text-sm text-red-700 dark:text-red-200">
            {error || "Failed to load wiki."}
          </div>
        ) : wiki == null ? (
          <div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6 text-sm text-gray-700 dark:text-gray-200">
            Không tìm thấy wiki với slug: <span className="font-semibold break-all">{normalizedSlug}</span>
          </div>
        ) : (
          <div className="grid grid-cols-1 gap-6 lg:grid-cols-[280px_minmax(0,1fr)]">
            <aside className="lg:sticky lg:top-6 self-start rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-4">
              <div className="text-sm font-semibold text-gray-900 dark:text-gray-100">Mục lục</div>
              {!toc.length ? (
                <div className="mt-2 text-xs text-gray-500 dark:text-gray-400">Không có tiêu đề (H1/H2/...).</div>
              ) : (
                <nav className="mt-3 max-h-[70vh] overflow-auto pr-1">
                  <div className="grid gap-1">
                    {toc.map((t) => {
                      const pad = Math.max(0, Math.min(5, t.level - 1)) * 10;
                      const isActive = activeHeadingId === t.id;
                      return (
                        <a
                          key={t.id}
                          href={`#${t.id}`}
                          className={`rounded-md px-2 py-1 text-xs leading-5 transition ${
                            isActive
                              ? "bg-brand-50 text-brand-700 dark:bg-brand-500/10 dark:text-brand-300"
                              : "text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-white/[0.04]"
                          }`}
                          style={{ paddingLeft: 8 + pad }}
                          title={t.text}
                        >
                          {t.text}
                        </a>
                      );
                    })}
                  </div>
                </nav>
              )}

              <div className="mt-4 border-t border-gray-200 dark:border-gray-800 pt-3">
                <div className="text-[11px] text-gray-500 dark:text-gray-400 break-all">
                  Link: {`${typeof window !== "undefined" ? window.location.origin : ""}/wiki/${normalizedSlug}`}
                </div>
              </div>
            </aside>

            <main className="min-w-0">
              <div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 overflow-hidden">
                <div
                  ref={contentRootRef}
                  className="uhm-wiki-view ql-editor text-sm text-gray-900 dark:text-gray-100"
                  dangerouslySetInnerHTML={{ __html: renderHtml }}
                />
              </div>
            </main>
          </div>
        )}
      </div>

      {linkPreview && linkPreview.visible ? (
        <div
          className="uhm-wiki-link-preview fixed z-[9999]"
          style={{
            top: linkPreview.top,
            left: linkPreview.left,
            width: linkPreview.width,
            height: linkPreview.height,
          }}
          onMouseEnter={() => {
            if (hidePreviewTimerRef.current != null) {
              window.clearTimeout(hidePreviewTimerRef.current);
              hidePreviewTimerRef.current = null;
            }
          }}
          onMouseLeave={() => {
            setLinkPreview((prev) => (prev ? { ...prev, visible: false } : prev));
          }}
        >
          <div className="h-full w-full overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 shadow-lg">
            <div className="h-full w-full p-3 grid grid-rows-[auto_1fr] gap-2">
              <div className="min-w-0">
                <div className="text-[11px] text-gray-500 dark:text-gray-400 break-all">
                  /wiki/{linkPreview.slug}
                </div>
                <div className="mt-0.5 text-sm font-semibold text-gray-900 dark:text-gray-100 truncate">
                  {linkPreviewData?.slug === linkPreview.slug
                    ? linkPreviewData.status === "loading"
                      ? "Loading..."
                      : linkPreviewData.status === "error"
                        ? "Not found"
                        : linkPreviewData.title
                    : "Loading..."}
                </div>
              </div>

              <div className="min-h-0 overflow-auto">
                {linkPreviewData?.slug === linkPreview.slug && linkPreviewData.status === "ready" ? (
                  linkPreviewData.quote ? (
                      <div className="text-xs text-gray-500 dark:text-gray-400 whitespace-pre-wrap break-words">
                        {linkPreviewData.quote}
                      </div>
                  ) : (
                    <div className="text-xs text-gray-500 dark:text-gray-400">No resume.</div>
                  )
                ) : (
                  <div className="text-xs text-gray-500 dark:text-gray-400">Loading preview...</div>
                )}
              </div>
            </div>
          </div>
        </div>
      ) : null}

      <style jsx global>{`
        /* Quill view container tweaks: allow page-level scrolling instead of inner scroll. */
        .uhm-wiki-view.ql-editor {
          height: auto;
          overflow-y: visible;
          padding: 18px 20px;
        }
        /* Improve readability for view mode (Quill resets block margins to 0). */
        .uhm-wiki-view.ql-editor p {
          margin: 0 0 0.75em;
        }
        .uhm-wiki-view.ql-editor h1 {
          margin: 1.25em 0 0.6em;
          font-size: 1.9em;
          font-weight: 800;
          line-height: 1.2;
        }
        .uhm-wiki-view.ql-editor h2 {
          margin: 1.15em 0 0.55em;
          font-size: 1.55em;
          font-weight: 800;
          line-height: 1.25;
        }
        .uhm-wiki-view.ql-editor h3 {
          margin: 1.05em 0 0.5em;
          font-size: 1.25em;
          font-weight: 700;
          line-height: 1.3;
        }
        .uhm-wiki-view.ql-editor h4,
        .uhm-wiki-view.ql-editor h5,
        .uhm-wiki-view.ql-editor h6 {
          margin: 0.95em 0 0.45em;
          font-size: 1.05em;
          font-weight: 700;
          line-height: 1.35;
        }
        .uhm-wiki-view.ql-editor ul,
        .uhm-wiki-view.ql-editor ol {
          margin: 0 0 0.75em;
          padding-left: 1.5em;
        }
        .uhm-wiki-view.ql-editor blockquote {
          margin: 0 0 0.75em;
          padding-left: 12px;
          border-left: 3px solid rgba(148, 163, 184, 0.6);
          color: rgba(71, 85, 105, 1);
        }
        :is(.dark *) .uhm-wiki-view.ql-editor blockquote {
          border-left-color: rgba(100, 116, 139, 0.6);
          color: rgba(203, 213, 225, 0.95);
        }
        .uhm-wiki-view.ql-editor pre {
          margin: 0 0 0.75em;
          padding: 12px 14px;
          border: 1px solid rgba(226, 232, 240, 1);
          border-radius: 10px;
          background: rgba(248, 250, 252, 1);
          overflow: auto;
        }
        :is(.dark *) .uhm-wiki-view.ql-editor pre {
          border-color: rgba(51, 65, 85, 1);
          background: rgba(2, 6, 23, 0.4);
        }
        .uhm-wiki-view.ql-editor img {
          max-width: 100%;
          height: auto;
          border-radius: 8px;
        }
        .uhm-wiki-view.ql-editor h1,
        .uhm-wiki-view.ql-editor h2,
        .uhm-wiki-view.ql-editor h3,
        .uhm-wiki-view.ql-editor h4,
        .uhm-wiki-view.ql-editor h5,
        .uhm-wiki-view.ql-editor h6 {
          scroll-margin-top: 16px;
        }
        .uhm-wiki-view.ql-editor a {
          text-decoration: underline;
          text-decoration-thickness: from-font;
          text-underline-offset: 2px;
        }
        .uhm-wiki-view.ql-editor a[href]:not([href=""]):not([href="__missing__"]) {
          color: #2563eb;
        }
        :is(.dark *) .uhm-wiki-view.ql-editor a[href]:not([href=""]):not([href="__missing__"]) {
          color: #60a5fa;
        }
        .uhm-wiki-view.ql-editor a[href="__missing__"] {
          cursor: default;
          pointer-events: none;
        }
        .uhm-wiki-view.ql-editor a:not([href]),
        .uhm-wiki-view.ql-editor a[href=""],
        .uhm-wiki-view.ql-editor a[href="__missing__"] {
          color: #dc2626;
        }
        :is(.dark *) .uhm-wiki-view.ql-editor a:not([href]),
        :is(.dark *) .uhm-wiki-view.ql-editor a[href=""],
        :is(.dark *) .uhm-wiki-view.ql-editor a[href="__missing__"] {
          color: #f87171;
        }
      `}</style>
    </div>
  );
}
