"use client";

import { useRef, useState, type ChangeEvent } from "react";
import { clsx } from "clsx";
import {
  Heading1,
  Heading2,
  Heading3,
  Bold,
  Italic,
  List,
  ListOrdered,
  Link as LinkIcon,
  Image as ImageIcon,
  Quote,
  Code,
  Minus,
  Eye,
  EyeOff,
  Pilcrow,
  Video,
} from "lucide-react";
import { uploadAdminImage } from "@/app/admin/uploads/actions";
import { parseVideoUrl, videoShortcode } from "@/lib/blog/video-embed";

type Props = {
  name: string;
  label?: string;
  hint?: string;
  defaultValue?: string;
  rows?: number;
  maxLength?: number;
  required?: boolean;
  /** Image upload bucket for the image button. Defaults to "blog". */
  folder?: string;
};

type Action =
  | { kind: "wrap"; before: string; after: string; placeholder?: string }
  | { kind: "line-prefix"; prefix: string }
  | { kind: "numbered-list" }
  | { kind: "block"; block: string }
  | { kind: "rule" }
  | { kind: "link" }
  | { kind: "image" }
  | { kind: "video" };

type ButtonDef = {
  label: string;
  shortcut?: string;
  Icon: typeof Bold;
  action: Action;
};

const BUTTONS: ButtonDef[] = [
  {
    label: "Heading 1",
    Icon: Heading1,
    action: { kind: "line-prefix", prefix: "# " },
  },
  {
    label: "Heading 2",
    Icon: Heading2,
    action: { kind: "line-prefix", prefix: "## " },
  },
  {
    label: "Heading 3",
    Icon: Heading3,
    action: { kind: "line-prefix", prefix: "### " },
  },
  {
    label: "Paragraph",
    Icon: Pilcrow,
    action: { kind: "line-prefix", prefix: "" },
  },
  {
    label: "Bold",
    shortcut: "Ctrl+B",
    Icon: Bold,
    action: { kind: "wrap", before: "**", after: "**", placeholder: "bold text" },
  },
  {
    label: "Italic",
    shortcut: "Ctrl+I",
    Icon: Italic,
    action: { kind: "wrap", before: "_", after: "_", placeholder: "italic text" },
  },
  {
    label: "Inline code",
    Icon: Code,
    action: { kind: "wrap", before: "`", after: "`", placeholder: "code" },
  },
  {
    label: "Bulleted list",
    Icon: List,
    action: { kind: "line-prefix", prefix: "- " },
  },
  {
    label: "Numbered list",
    Icon: ListOrdered,
    action: { kind: "numbered-list" },
  },
  {
    label: "Blockquote",
    Icon: Quote,
    action: { kind: "line-prefix", prefix: "> " },
  },
  { label: "Link", Icon: LinkIcon, action: { kind: "link" } },
  { label: "Image", Icon: ImageIcon, action: { kind: "image" } },
  { label: "Video", Icon: Video, action: { kind: "video" } },
  { label: "Divider", Icon: Minus, action: { kind: "rule" } },
];

/**
 * Apply an Action to a textarea's value at the current selection, return the
 * new value and the caret range. This is pure: no DOM reads outside of the
 * provided inputs.
 */
function applyAction(
  value: string,
  selStart: number,
  selEnd: number,
  action: Action,
  linkHref?: string,
  imageUrl?: string,
  imageAlt?: string,
  videoShort?: string,
): { value: string; start: number; end: number } {
  const before = value.slice(0, selStart);
  const selected = value.slice(selStart, selEnd);
  const after = value.slice(selEnd);

  switch (action.kind) {
    case "wrap": {
      const body = selected || action.placeholder || "";
      const inserted = action.before + body + action.after;
      const start = selStart + action.before.length;
      return {
        value: before + inserted + after,
        start,
        end: start + body.length,
      };
    }

    case "line-prefix": {
      // Expand selection to the start of the first affected line.
      const lineStart = before.lastIndexOf("\n") + 1;
      const linesBefore = value.slice(0, lineStart);
      const block = value.slice(lineStart, selEnd);
      const lines = block.split("\n");
      // Remove any existing heading / list prefix before applying the new one.
      const stripped = lines.map((l) =>
        l.replace(/^(#{1,6}\s+|[-*+]\s+|\d+\.\s+|>\s+)/, ""),
      );
      const updated = stripped
        .map((l) => (l.length === 0 && action.prefix === "" ? l : action.prefix + l))
        .join("\n");
      const newValue = linesBefore + updated + after;
      const caret = linesBefore.length + updated.length;
      return { value: newValue, start: caret, end: caret };
    }

    case "numbered-list": {
      const lineStart = before.lastIndexOf("\n") + 1;
      const linesBefore = value.slice(0, lineStart);
      const block = value.slice(lineStart, selEnd);
      const lines = block.split("\n");
      const stripped = lines.map((l) =>
        l.replace(/^(#{1,6}\s+|[-*+]\s+|\d+\.\s+|>\s+)/, ""),
      );
      const updated = stripped
        .map((l, i) => (l ? `${i + 1}. ${l}` : l))
        .join("\n");
      const newValue = linesBefore + updated + after;
      const caret = linesBefore.length + updated.length;
      return { value: newValue, start: caret, end: caret };
    }

    case "block": {
      const block = `\n\n${action.block}\n\n`;
      const newValue = before + block + after;
      const caret = selStart + block.length;
      return { value: newValue, start: caret, end: caret };
    }

    case "rule": {
      const block = "\n\n---\n\n";
      const newValue = before + block + after;
      const caret = selStart + block.length;
      return { value: newValue, start: caret, end: caret };
    }

    case "link": {
      const label = selected || "link text";
      const href = linkHref || "https://";
      const inserted = `[${label}](${href})`;
      return {
        value: before + inserted + after,
        start: selStart + 1,
        end: selStart + 1 + label.length,
      };
    }

    case "image": {
      const url = imageUrl ?? "";
      if (!url) return { value, start: selStart, end: selEnd };
      const alt = imageAlt || selected || "image";
      const inserted = `\n\n![${alt}](${url})\n\n`;
      const caret = selStart + inserted.length;
      return { value: before + inserted + after, start: caret, end: caret };
    }

    case "video": {
      const inserted = videoShort ?? "";
      if (!inserted) return { value, start: selStart, end: selEnd };
      const caret = selStart + inserted.length;
      return { value: before + inserted + after, start: caret, end: caret };
    }
  }
}

export function MarkdownEditor({
  name,
  label,
  hint,
  defaultValue = "",
  rows = 18,
  maxLength = 100_000,
  required,
  folder = "blog",
}: Props) {
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [value, setValue] = useState<string>(defaultValue);
  const [preview, setPreview] = useState<boolean>(false);
  const [uploadingImage, setUploadingImage] = useState<boolean>(false);

  function focusAndSelect(start: number, end: number) {
    const ta = textareaRef.current;
    if (!ta) return;
    ta.focus();
    requestAnimationFrame(() => {
      ta.setSelectionRange(start, end);
    });
  }

  function run(
    action: Action,
    extras?: {
      linkHref?: string;
      imageUrl?: string;
      imageAlt?: string;
      videoShort?: string;
    },
  ) {
    const ta = textareaRef.current;
    if (!ta) return;
    const result = applyAction(
      ta.value,
      ta.selectionStart,
      ta.selectionEnd,
      action,
      extras?.linkHref,
      extras?.imageUrl,
      extras?.imageAlt,
      extras?.videoShort,
    );
    setValue(result.value);
    focusAndSelect(result.start, result.end);
  }

  function handleClick(action: Action) {
    if (action.kind === "link") {
      const href = window.prompt("Link URL", "https://");
      if (!href) return;
      run(action, { linkHref: href });
      return;
    }
    if (action.kind === "image") {
      fileInputRef.current?.click();
      return;
    }
    if (action.kind === "video") {
      const url = window.prompt(
        "Video URL — paste a YouTube, Vimeo, or direct MP4/WEBM link",
        "https://",
      );
      if (!url) return;
      const parsed = parseVideoUrl(url);
      if (!parsed) {
        window.alert(
          "Couldn't parse that URL. Supported: YouTube, Vimeo, or direct MP4/WEBM.",
        );
        return;
      }
      const caption = window.prompt("Caption (optional)", "") ?? "";
      run(action, { videoShort: videoShortcode(url, caption) });
      return;
    }
    run(action);
  }

  async function handleImageFile(e: ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;
    setUploadingImage(true);
    try {
      const fd = new FormData();
      fd.set("file", file);
      fd.set("folder", folder);
      const result = await uploadAdminImage(fd);
      if (!result.ok) {
        window.alert(`Upload failed: ${result.error}`);
        return;
      }
      const alt = window.prompt("Alt text (optional)", file.name) ?? "";
      run(
        { kind: "image" },
        { imageUrl: result.url, imageAlt: alt },
      );
    } finally {
      setUploadingImage(false);
      if (fileInputRef.current) fileInputRef.current.value = "";
    }
  }

  function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
    if (!(e.ctrlKey || e.metaKey)) return;
    if (e.key === "b") {
      e.preventDefault();
      run({ kind: "wrap", before: "**", after: "**" });
    } else if (e.key === "i") {
      e.preventDefault();
      run({ kind: "wrap", before: "_", after: "_" });
    }
  }

  return (
    <div className="flex flex-col gap-2">
      {label && (
        <div className="flex items-center justify-between">
          <label className="text-[11px] font-bold uppercase tracking-wider text-[var(--color-slate)]">
            {label}
            {required && <span className="ml-1 text-[var(--color-blue)]">*</span>}
          </label>
          <button
            type="button"
            onClick={() => setPreview((p) => !p)}
            className="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[11px] font-semibold text-[var(--color-slate)] transition-colors hover:text-[var(--color-blue)]"
          >
            {preview ? (
              <>
                <EyeOff className="h-3 w-3" aria-hidden /> Edit
              </>
            ) : (
              <>
                <Eye className="h-3 w-3" aria-hidden /> Preview
              </>
            )}
          </button>
        </div>
      )}

      <div className="overflow-hidden rounded-lg border border-[var(--color-line)] bg-white focus-within:border-[var(--color-blue)] focus-within:ring-2 focus-within:ring-[var(--color-mist)]">
        {!preview && (
          <div
            role="toolbar"
            aria-label="Formatting"
            className="flex flex-wrap items-center gap-0.5 border-b border-[var(--color-line)] bg-[var(--color-line)]/20 px-2 py-1.5"
          >
            {BUTTONS.map((btn, idx) => (
              <button
                key={btn.label}
                type="button"
                onClick={() => handleClick(btn.action)}
                title={btn.shortcut ? `${btn.label} (${btn.shortcut})` : btn.label}
                disabled={btn.action.kind === "image" && uploadingImage}
                className={clsx(
                  "inline-flex h-8 w-8 items-center justify-center rounded-md text-[var(--color-ink)]/75 transition-colors hover:bg-white hover:text-[var(--color-blue)] disabled:cursor-wait disabled:opacity-60",
                  // Visual group separators
                  (idx === 4 || idx === 7 || idx === 10) && "ml-1 border-l border-[var(--color-line)] pl-2 rounded-none",
                )}
              >
                <btn.Icon className="h-[14px] w-[14px]" aria-hidden />
                <span className="sr-only">{btn.label}</span>
              </button>
            ))}
          </div>
        )}

        {preview ? (
          <div
            className="min-h-[200px] bg-white p-5"
            aria-label="Markdown preview"
          >
            <MarkdownPreview source={value} />
          </div>
        ) : (
          <textarea
            ref={textareaRef}
            name={name}
            value={value}
            onChange={(e) => setValue(e.target.value)}
            onKeyDown={handleKeyDown}
            rows={rows}
            maxLength={maxLength}
            required={required}
            spellCheck
            className="block w-full resize-y border-0 bg-white px-4 py-3 font-mono text-[13.5px] leading-relaxed text-[var(--color-ink)] focus:outline-none"
            placeholder="Start writing in Markdown… Use the toolbar or Ctrl+B / Ctrl+I for formatting."
          />
        )}
      </div>

      {hint && !preview && (
        <p className="text-[12px] text-[var(--color-slate)]">
          {hint} · Markdown: # heading · **bold** · _italic_ · [link](url) · ![alt](image) · &gt; quote · - list · videos via the{" "}
          <Video className="inline h-3 w-3 align-[-2px]" aria-hidden /> button (YouTube, Vimeo, MP4).
        </p>
      )}

      <input
        ref={fileInputRef}
        type="file"
        accept="image/jpeg,image/png,image/webp,image/gif,image/svg+xml"
        onChange={handleImageFile}
        className="hidden"
      />
    </div>
  );
}

/**
 * Minimal in-browser markdown preview. Renders headings, bold, italic, code,
 * lists, blockquotes, links, images, and horizontal rules. Intentionally
 * conservative — full markdown rendering happens at display time on the
 * public blog page.
 */
function MarkdownPreview({ source }: { source: string }) {
  if (!source.trim()) {
    return (
      <p className="italic text-[var(--color-slate)]">
        Nothing to preview yet.
      </p>
    );
  }
  return (
    <div
      className="legacy-html"
      dangerouslySetInnerHTML={{ __html: simpleMarkdownToHtml(source) }}
    />
  );
}

function escapeHtml(s: string): string {
  return s
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function simpleMarkdownToHtml(src: string): string {
  const lines = src.split("\n");
  const out: string[] = [];
  let listType: "ul" | "ol" | null = null;
  let paragraph: string[] = [];

  const closeList = () => {
    if (listType) {
      out.push(`</${listType}>`);
      listType = null;
    }
  };
  const flushParagraph = () => {
    if (paragraph.length) {
      out.push(`<p>${inlineMd(paragraph.join(" "))}</p>`);
      paragraph = [];
    }
  };

  for (const raw of lines) {
    const line = raw.replace(/\s+$/g, "");
    if (!line.trim()) {
      flushParagraph();
      closeList();
      continue;
    }
    if (/^---+$/.test(line)) {
      flushParagraph();
      closeList();
      out.push("<hr />");
      continue;
    }
    const heading = /^(#{1,6})\s+(.*)$/.exec(line);
    if (heading) {
      flushParagraph();
      closeList();
      const level = heading[1].length;
      out.push(`<h${level}>${inlineMd(heading[2])}</h${level}>`);
      continue;
    }
    const quote = /^>\s+(.*)$/.exec(line);
    if (quote) {
      flushParagraph();
      closeList();
      out.push(`<blockquote>${inlineMd(quote[1])}</blockquote>`);
      continue;
    }
    const ul = /^[-*+]\s+(.*)$/.exec(line);
    const ol = /^\d+\.\s+(.*)$/.exec(line);
    if (ul) {
      flushParagraph();
      if (listType !== "ul") {
        closeList();
        out.push("<ul>");
        listType = "ul";
      }
      out.push(`<li>${inlineMd(ul[1])}</li>`);
      continue;
    }
    if (ol) {
      flushParagraph();
      if (listType !== "ol") {
        closeList();
        out.push("<ol>");
        listType = "ol";
      }
      out.push(`<li>${inlineMd(ol[1])}</li>`);
      continue;
    }
    closeList();
    paragraph.push(line);
  }
  flushParagraph();
  closeList();
  return out.join("\n");
}

function inlineMd(s: string): string {
  let out = escapeHtml(s);
  // Images
  out = out.replace(
    /!\[([^\]]*)\]\(([^)\s]+)\)/g,
    '<img src="$2" alt="$1" />',
  );
  // Links
  out = out.replace(
    /\[([^\]]+)\]\(([^)\s]+)\)/g,
    '<a href="$2">$1</a>',
  );
  // Inline code
  out = out.replace(/`([^`]+)`/g, "<code>$1</code>");
  // Bold / italic
  out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
  out = out.replace(/(^|[\s(])_([^_]+)_(?=[\s).,!?;:]|$)/g, "$1<em>$2</em>");
  return out;
}
