"use client";

import * as React from "react";
import dynamic from "next/dynamic";
import {
  FileText,
  Layout,
  PanelLeftClose,
  PanelLeftOpen,
  PanelRightClose,
  Share2,
} from "lucide-react";

import { Button } from "@/components/ui/button";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { DraftsBar, type Draft } from "@/components/editor/drafts-bar";
import { EditorToolbar } from "@/components/editor/editor-toolbar";
import { EditorStatusBar } from "@/components/editor/editor-status-bar";
import { FindReplace } from "@/components/editor/find-replace";
import { ToolsSidebar } from "@/components/editor/tools-sidebar";
import {
  TextareaEditor,
  type TextareaEditorHandle,
  afterControlledTextareaChange,
} from "@/components/editor/textarea-editor";

import { computeStats, formatMarkdown, generateTableOfContents } from "@/lib/markdown";
import {
  compressForShare,
  decompressFromShare,
  downloadHtml,
  downloadMarkdown,
  downloadPdfFromPrintDocument,
  printPreview,
} from "@/lib/export";
import { DEFAULT_MARKDOWN, SAMPLE_DOCUMENT, TEMPLATES } from "@/lib/editor-defaults";
import { useDebouncedValue } from "@/hooks/use-debounce";
import { useLocalStorage } from "@/hooks/use-local-storage";
import { cn } from "@/lib/utils";
import { useToast } from "@/hooks/use-toast";

// Markdown preview pulls in react-markdown + rehype/remark — keep it out of the
// initial JS bundle. The editor itself remains a tiny client component.
const MarkdownPreview = dynamic(
  () => import("@/components/editor/markdown-preview").then((m) => m.MarkdownPreview),
  {
    ssr: false,
    loading: () => (
      <div className="grid h-full place-items-center text-sm text-muted-foreground">
        Preparing preview…
      </div>
    ),
  },
);

type ViewMode = "split" | "editor" | "preview";

type Props = {
  initialContent?: string;
};

const STORAGE_KEY = "markdown-viewer:drafts:v1";
const SETTINGS_KEY = "markdown-viewer:settings:v1";

type Settings = {
  showLineNumbers: boolean;
  syncScroll: boolean;
  sidebarOpen: boolean;
};

const DEFAULT_SETTINGS: Settings = {
  showLineNumbers: false,
  syncScroll: true,
  sidebarOpen: true,
};

function newDraft(name: string, content = ""): Draft {
  return {
    id: typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}`,
    name,
    content,
    updatedAt: Date.now(),
  };
}

export function MarkdownEditor({ initialContent }: Props) {
  const { toast } = useToast();

  const [drafts, setDrafts] = useLocalStorage<Draft[]>(STORAGE_KEY, [
    newDraft("Welcome", initialContent ?? DEFAULT_MARKDOWN),
  ]);
  const [activeId, setActiveId] = useLocalStorage<string>(
    "markdown-viewer:active-draft:v1",
    drafts[0]?.id ?? "",
  );
  const [settings, setSettings] = useLocalStorage<Settings>(SETTINGS_KEY, DEFAULT_SETTINGS);

  const [view, setView] = React.useState<ViewMode>("split");
  const [isFullscreen, setIsFullscreen] = React.useState(false);
  const [isFocusMode, setIsFocusMode] = React.useState(false);
  const [showFind, setShowFind] = React.useState(false);
  const [saved, setSaved] = React.useState(true);
  const [confirmClear, setConfirmClear] = React.useState(false);
  const [confirmSample, setConfirmSample] = React.useState(false);

  const editorRef = React.useRef<TextareaEditorHandle>(null);
  const taRef = React.useRef<HTMLTextAreaElement | null>(null);
  const previewRef = React.useRef<HTMLDivElement>(null);
  const fileInputRef = React.useRef<HTMLInputElement>(null);
  const shellRef = React.useRef<HTMLDivElement>(null);

  // Pick the active draft, falling back to the first available one to keep the
  // editor usable if the persisted active id is stale.
  const activeDraft = React.useMemo(
    () => drafts.find((d) => d.id === activeId) ?? drafts[0],
    [drafts, activeId],
  );

  React.useEffect(() => {
    if (!activeDraft) {
      const fresh = newDraft("Untitled", DEFAULT_MARKDOWN);
      setDrafts([fresh]);
      setActiveId(fresh.id);
    }
  }, [activeDraft, setDrafts, setActiveId]);

  // Read shared content from `?d=` query param on first mount.
  React.useEffect(() => {
    if (typeof window === "undefined") return;
    const params = new URLSearchParams(window.location.search);
    const shared = params.get("d");
    if (!shared) return;
    try {
      const content = decompressFromShare(shared);
      const draft = newDraft("Shared draft", content);
      setDrafts((prev) => [draft, ...prev]);
      setActiveId(draft.id);
      const url = new URL(window.location.href);
      url.searchParams.delete("d");
      window.history.replaceState({}, "", url.toString());
      toast({ title: "Shared draft loaded", description: "Imported from the share link." });
    } catch {
      toast({ title: "Couldn't decode shared draft", variant: "destructive" });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const value = activeDraft?.content ?? "";
  const debouncedValue = useDebouncedValue(value, 200);
  const stats = React.useMemo(() => computeStats(debouncedValue), [debouncedValue]);

  function updateContent(next: string) {
    if (!activeDraft) return;
    setSaved(false);
    setDrafts((prev) =>
      prev.map((d) =>
        d.id === activeDraft.id ? { ...d, content: next, updatedAt: Date.now() } : d,
      ),
    );
    // Debounced save indicator.
    window.clearTimeout(saveTimeout.current);
    saveTimeout.current = window.setTimeout(() => setSaved(true), 400);
  }
  const saveTimeout = React.useRef<number>(0);

  function createDraft() {
    const draft = newDraft(`Untitled ${drafts.length + 1}`, "");
    setDrafts((prev) => [...prev, draft]);
    setActiveId(draft.id);
  }
  function closeDraft(id: string) {
    setDrafts((prev) => {
      const next = prev.filter((d) => d.id !== id);
      if (id === activeId && next[0]) setActiveId(next[0].id);
      return next.length > 0 ? next : [newDraft("Untitled", "")];
    });
  }
  function renameDraft(id: string, name: string) {
    setDrafts((prev) => prev.map((d) => (d.id === id ? { ...d, name } : d)));
  }

  function insertSnippet(snippet: string) {
    if (!activeDraft) return;
    const el = editorRef.current?.getElement();
    if (!el) {
      updateContent(value + snippet);
      return;
    }
    const { selectionStart, selectionEnd, value: text } = el;
    const scrollSnapshot = { top: el.scrollTop, left: el.scrollLeft };
    const next = text.slice(0, selectionStart) + snippet + text.slice(selectionEnd);
    updateContent(next);
    afterControlledTextareaChange(el, scrollSnapshot, () => {
      const cursor = selectionStart + snippet.length;
      el.setSelectionRange(cursor, cursor);
    });
  }

  function jumpToLine(line: number) {
    const el = editorRef.current?.getElement();
    if (!el) return;
    const lines = el.value.split("\n");
    const start =
      lines.slice(0, line - 1).reduce((sum, l) => sum + l.length + 1, 0);
    const end = start + (lines[line - 1]?.length ?? 0);
    el.focus();
    el.setSelectionRange(start, end);
    // Approximate scroll-into-view for plain textareas.
    const lineHeight = 24;
    el.scrollTop = Math.max(0, (line - 4) * lineHeight);
  }

  async function handleAction(id: string) {
    switch (id) {
      case "copy":
        await navigator.clipboard.writeText(value);
        toast({ title: "Markdown copied" });
        return;
      case "paste": {
        try {
          const text = await navigator.clipboard.readText();
          insertSnippet(text);
        } catch {
          toast({ title: "Clipboard access denied", variant: "destructive" });
        }
        return;
      }
      case "clear":
        setConfirmClear(true);
        return;
      case "sample":
        // Always confirm before overwriting non-empty content.
        if (value.trim().length > 0 && value.trim() !== SAMPLE_DOCUMENT.trim()) {
          setConfirmSample(true);
        } else {
          updateContent(SAMPLE_DOCUMENT);
          toast({ title: "Sample loaded" });
        }
        return;
      case "download-md":
        downloadMarkdown(value, `${(activeDraft?.name || "document").replace(/\s+/g, "-")}.md`);
        return;
      case "export-html": {
        // Use the rendered preview DOM so we ship one consistent representation.
        const html = previewRef.current?.innerHTML ?? "";
        downloadHtml(
          html,
          activeDraft?.name || "Document",
          `${(activeDraft?.name || "document").replace(/\s+/g, "-")}.html`,
        );
        return;
      }
      case "export-pdf": {
        toast({ title: "Preparing PDF…", description: "" });
        try {
          await downloadPdfFromPrintDocument(
            previewRef.current,
            activeDraft?.name || "Document",
            `${(activeDraft?.name || "document").replace(/\s+/g, "-")}.pdf`,
          );
          toast({ title: "PDF downloaded" });
        } catch (err: unknown) {
          toast({
            title: "PDF export failed",
            description:
              err instanceof Error ? err.message : "Try Print → Save as PDF, or check images (CORS).",
            variant: "destructive",
          });
        }
        return;
      }
      case "print":
        printPreview(previewRef.current, activeDraft?.name || "Document");
        return;
      case "format":
        updateContent(formatMarkdown(value));
        toast({ title: "Markdown beautified" });
        return;
      case "find":
        if (view === "preview") setView("split");
        setShowFind(true);
        return;
      case "toc": {
        const toc = generateTableOfContents(value);
        if (!toc) {
          toast({ title: "No headings yet", description: "Add some headings to generate a TOC." });
          return;
        }
        insertSnippet(`\n${toc}\n`);
        return;
      }
      case "import":
        fileInputRef.current?.click();
        return;
      default:
        editorRef.current?.applyAction(id);
    }
  }

  async function handleShare() {
    try {
      const token = await compressForShare(value);
      const url = `${window.location.origin}/editor?d=${token}`;
      await navigator.clipboard.writeText(url);
      toast({
        title: "Shareable link copied",
        description: "Anyone with the link can open this draft.",
      });
    } catch {
      toast({ title: "Couldn't create share link", variant: "destructive" });
    }
  }

  async function importFile(file: File) {
    const text = await file.text();
    const draft = newDraft(file.name.replace(/\.md$/i, "") || "Imported", text);
    setDrafts((prev) => [...prev, draft]);
    setActiveId(draft.id);
    toast({ title: "Imported", description: file.name });
  }

  function handleDrop(e: React.DragEvent<HTMLDivElement>) {
    e.preventDefault();
    const file = e.dataTransfer.files?.[0];
    if (file && /\.md$|text\/markdown/i.test(file.name + file.type)) importFile(file);
  }

  // Global keyboard shortcuts (fullscreen / preview toggle / save / find / focus).
  React.useEffect(() => {
    function onKey(e: KeyboardEvent) {
      const meta = e.ctrlKey || e.metaKey;
      if (e.key === "F11") {
        e.preventDefault();
        toggleFullscreen();
      } else if (meta && e.shiftKey && e.key.toLowerCase() === "p") {
        e.preventDefault();
        setView((v) => (v === "preview" ? "editor" : "preview"));
      } else if (meta && e.shiftKey && e.key.toLowerCase() === "f") {
        e.preventDefault();
        updateContent(formatMarkdown(value));
      } else if (meta && e.key.toLowerCase() === "f") {
        e.preventDefault();
        if (view === "preview") return;
        setShowFind(true);
      } else if (meta && e.key === ".") {
        e.preventDefault();
        setIsFocusMode((v) => !v);
      } else if (meta && e.key.toLowerCase() === "s") {
        e.preventDefault();
        toast({ title: "Saved", description: "Draft saved locally." });
      } else if (e.key === "Escape" && showFind) {
        setShowFind(false);
      }
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value, showFind, view]);

  React.useEffect(() => {
    if (view === "preview") setShowFind(false);
  }, [view]);

  function toggleFullscreen() {
    const el = shellRef.current;
    if (!el) return;
    if (!document.fullscreenElement) {
      el.requestFullscreen().then(() => setIsFullscreen(true)).catch(() => {});
    } else {
      document.exitFullscreen().then(() => setIsFullscreen(false)).catch(() => {});
    }
  }

  React.useEffect(() => {
    function onChange() {
      setIsFullscreen(Boolean(document.fullscreenElement));
    }
    document.addEventListener("fullscreenchange", onChange);
    return () => document.removeEventListener("fullscreenchange", onChange);
  }, []);

  // Sync scrolling between editor and preview by mapping scroll progress.
  // Attached imperatively because the scrollable element lives inside `TextareaEditor`.
  React.useEffect(() => {
    const ed = editorRef.current?.getElement();
    taRef.current = ed ?? null;
    if (!ed) return;
    function onScroll() {
      if (!settings.syncScroll || !ed) return;
      const pv = previewRef.current?.parentElement;
      if (!pv) return;
      const ratio = ed.scrollTop / Math.max(1, ed.scrollHeight - ed.clientHeight);
      pv.scrollTop = ratio * Math.max(0, pv.scrollHeight - pv.clientHeight);
    }
    ed.addEventListener("scroll", onScroll, { passive: true });
    return () => ed.removeEventListener("scroll", onScroll);
  }, [settings.syncScroll, activeDraft?.id]);

  return (
    <div
      ref={shellRef}
      data-focus={isFocusMode || isFullscreen ? "true" : "false"}
      className={cn(
        "editor-shell relative flex w-full flex-col overflow-hidden bg-background",
      )}
      onDragOver={(e) => e.preventDefault()}
      onDrop={handleDrop}
    >
      {!isFocusMode ? (
        <DraftsBar
          drafts={drafts}
          activeId={activeDraft?.id ?? ""}
          onCreate={createDraft}
          onClose={closeDraft}
          onSelect={setActiveId}
          onRename={renameDraft}
        />
      ) : null}
      {!isFocusMode ? (
        <EditorToolbar
          isFullscreen={isFullscreen}
          isFocusMode={isFocusMode}
          onToggleFullscreen={toggleFullscreen}
          onToggleFocus={() => setIsFocusMode((v) => !v)}
          onAction={handleAction}
        />
      ) : null}

      <div
        className={cn(
          "flex items-center justify-between border-b border-border/60 px-3 py-1.5",
          isFocusMode && "hidden",
        )}
      >
        <ViewSwitcher view={view} onChange={setView} />
        <div className="flex items-center gap-3 text-xs">
          <Tooltip>
            <TooltipTrigger asChild>
              <button
                type="button"
                onClick={handleShare}
                className="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-muted-foreground hover:bg-accent hover:text-foreground"
              >
                <Share2 className="h-4 w-4" /> Share
              </button>
            </TooltipTrigger>
            <TooltipContent>Copy a shareable link to this draft</TooltipContent>
          </Tooltip>
          <Separator orientation="vertical" className="h-4" />
          <label className="flex items-center gap-1.5">
            <Switch
              checked={settings.showLineNumbers}
              onCheckedChange={(checked) => setSettings({ ...settings, showLineNumbers: checked })}
              aria-label="Toggle line numbers"
            />
            <Label className="text-xs">Lines</Label>
          </label>
          <label className="flex items-center gap-1.5">
            <Switch
              checked={settings.syncScroll}
              onCheckedChange={(checked) => setSettings({ ...settings, syncScroll: checked })}
              aria-label="Toggle synced scrolling"
            />
            <Label className="text-xs">Sync</Label>
          </label>
          <Tooltip>
            <TooltipTrigger asChild>
              <Button
                size="icon-sm"
                variant="ghost"
                aria-label={settings.sidebarOpen ? "Hide tools" : "Show tools"}
                aria-pressed={settings.sidebarOpen}
                onClick={() =>
                  setSettings({ ...settings, sidebarOpen: !settings.sidebarOpen })
                }
              >
                {settings.sidebarOpen ? <PanelRightClose /> : <PanelLeftOpen />}
              </Button>
            </TooltipTrigger>
            <TooltipContent>Tools sidebar</TooltipContent>
          </Tooltip>
        </div>
      </div>

      <div className="flex min-h-0 flex-1">
        <div className="flex min-w-0 flex-1 flex-col">
          <div className="flex min-h-0 flex-1">
            {view !== "preview" ? (
              <div
                className={cn(
                  "relative min-h-0 min-w-0 flex-1",
                  view === "split" ? "lg:flex-1" : "flex-1",
                )}
              >
                <FindReplace
                  open={showFind && !isFocusMode}
                  onClose={() => setShowFind(false)}
                  value={value}
                  onChange={(next, selection) => {
                    updateContent(next);
                    if (selection) {
                      const el = editorRef.current?.getElement();
                      if (el) {
                        requestAnimationFrame(() => {
                          el.focus();
                          el.setSelectionRange(selection[0], selection[1]);
                        });
                      }
                    }
                  }}
                  textareaRef={taRef}
                  dock="editor"
                />
                <TextareaEditor
                  ref={editorRef}
                  value={value}
                  onChange={updateContent}
                  showLineNumbers={settings.showLineNumbers}
                />
              </div>
            ) : null}
            {view === "split" ? (
              <Separator orientation="vertical" className="hidden lg:block" />
            ) : null}
            {/* Preview stays mounted so HTML/PDF export always has a DOM to read from. */}
            <div
              className={cn(
                "scrollbar-thin min-w-0 flex-1 overflow-auto bg-background p-6 lg:p-8",
                view === "editor" && "hidden",
              )}
              aria-hidden={view === "editor"}
            >
              <div ref={previewRef}>
                <MarkdownPreview source={debouncedValue} />
              </div>
            </div>
          </div>
          <EditorStatusBar
            stats={stats}
            saved={saved}
            draftName={activeDraft?.name}
          />
        </div>
        {settings.sidebarOpen && !isFocusMode ? (
          <ToolsSidebar
            value={value}
            onChange={updateContent}
            onInsert={insertSnippet}
            onJumpToLine={jumpToLine}
          />
        ) : null}
      </div>

      {isFocusMode ? (
        <>
          <FindReplace
            open={showFind}
            onClose={() => setShowFind(false)}
            value={value}
            onChange={(next, selection) => {
              updateContent(next);
              if (selection) {
                const el = editorRef.current?.getElement();
                if (el) {
                  requestAnimationFrame(() => {
                    el.focus();
                    el.setSelectionRange(selection[0], selection[1]);
                  });
                }
              }
            }}
            textareaRef={taRef}
            dock="focus"
          />
          <button
            type="button"
            onClick={() => setIsFocusMode(false)}
            className="no-print fixed right-4 top-4 z-30 rounded-full bg-foreground/80 px-3 py-1 text-xs text-background opacity-30 backdrop-blur transition-opacity hover:opacity-100"
            aria-label="Exit focus mode"
          >
            Exit focus
          </button>
        </>
      ) : null}

      <ConfirmDialog
        open={confirmClear}
        onOpenChange={setConfirmClear}
        title="Clear this draft?"
        description="The current content will be deleted. Your other drafts are not affected."
        confirmLabel="Clear"
        destructive
        onConfirm={() => {
          updateContent("");
          toast({ title: "Draft cleared" });
        }}
      />

      <ConfirmDialog
        open={confirmSample}
        onOpenChange={setConfirmSample}
        title="Replace current content with sample?"
        description="The sample document is a tour of every editor feature. Your current draft will be overwritten."
        confirmLabel="Load sample"
        onConfirm={() => {
          updateContent(SAMPLE_DOCUMENT);
          toast({ title: "Sample loaded" });
        }}
      />

      <input
        ref={fileInputRef}
        type="file"
        accept=".md,.markdown,text/markdown,text/plain"
        className="sr-only"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) void importFile(file);
          e.currentTarget.value = "";
        }}
      />

      <TemplateLauncher onPick={(t) => insertSnippet(t)} />
    </div>
  );
}

function ViewSwitcher({
  view,
  onChange,
}: {
  view: ViewMode;
  onChange: (next: ViewMode) => void;
}) {
  const options: { id: ViewMode; label: string; icon: React.ReactNode }[] = [
    { id: "editor", label: "Editor", icon: <FileText className="h-4 w-4" /> },
    { id: "split", label: "Split", icon: <Layout className="h-4 w-4" /> },
    { id: "preview", label: "Preview", icon: <PanelLeftClose className="h-4 w-4" /> },
  ];
  return (
    <div role="group" aria-label="View mode" className="inline-flex rounded-md border bg-muted p-0.5">
      {options.map((opt) => (
        <button
          key={opt.id}
          type="button"
          onClick={() => onChange(opt.id)}
          aria-pressed={view === opt.id}
          className={cn(
            "inline-flex items-center gap-1.5 rounded px-2 py-1 text-xs font-medium text-muted-foreground transition-colors",
            view === opt.id && "bg-background text-foreground shadow-sm",
          )}
        >
          {opt.icon}
          <span className="hidden sm:inline">{opt.label}</span>
        </button>
      ))}
    </div>
  );
}

function TemplateLauncher({ onPick }: { onPick: (content: string) => void }) {
  const [open, setOpen] = React.useState(false);

  React.useEffect(() => {
    function onKey(e: KeyboardEvent) {
      if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === "t") {
        e.preventDefault();
        setOpen((o) => !o);
      }
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  if (!open) return null;

  return (
    <div
      className="fixed inset-0 z-50 grid place-items-center bg-black/60 backdrop-blur-sm"
      onClick={() => setOpen(false)}
      role="dialog"
      aria-modal="true"
      aria-label="Insert template"
    >
      <div
        className="w-full max-w-md rounded-xl border bg-popover p-4 shadow-lg"
        onClick={(e) => e.stopPropagation()}
      >
        <h2 className="text-base font-semibold">Insert template</h2>
        <p className="mb-3 text-sm text-muted-foreground">
          Drop a ready-made markdown skeleton at the cursor.
        </p>
        <ul className="space-y-1">
          {TEMPLATES.map((t) => (
            <li key={t.id}>
              <button
                type="button"
                onClick={() => {
                  onPick(t.content);
                  setOpen(false);
                }}
                className="flex w-full flex-col rounded-md px-3 py-2 text-left hover:bg-accent"
              >
                <span className="text-sm font-medium">{t.name}</span>
                <span className="text-xs text-muted-foreground">{t.description}</span>
              </button>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}
