"use client";

import * as React from "react";
import { CaseSensitive, ChevronDown, ChevronUp, Regex, Replace, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";

type Props = {
  open: boolean;
  onClose: () => void;
  value: string;
  onChange: (next: string, selection?: [number, number]) => void;
  textareaRef: React.RefObject<HTMLTextAreaElement | null>;
  /**
   * `"editor"` — `absolute` top-right of the Markdown source pane (default).
   * `"focus"` — `fixed` top-right while focus mode hides the main toolbar.
   */
  dock?: "editor" | "focus";
};

type Match = { start: number; end: number };

function buildMatches(
  source: string,
  query: string,
  flags: { regex: boolean; matchCase: boolean },
): Match[] {
  if (!query) return [];
  try {
    const pattern = flags.regex
      ? new RegExp(query, flags.matchCase ? "g" : "gi")
      : new RegExp(escapeRegex(query), flags.matchCase ? "g" : "gi");
    const out: Match[] = [];
    let m: RegExpExecArray | null;
    while ((m = pattern.exec(source))) {
      // Avoid zero-length infinite loops.
      const end = m.index + m[0].length;
      out.push({ start: m.index, end });
      if (m[0].length === 0) pattern.lastIndex += 1;
    }
    return out;
  } catch {
    return [];
  }
}

function escapeRegex(value: string): string {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

export function FindReplace({
  open,
  onClose,
  value,
  onChange,
  textareaRef,
  dock = "editor",
}: Props) {
  const [query, setQuery] = React.useState("");
  const [replacement, setReplacement] = React.useState("");
  const [matchCase, setMatchCase] = React.useState(false);
  const [useRegex, setUseRegex] = React.useState(false);
  const [showReplace, setShowReplace] = React.useState(false);
  const [activeIndex, setActiveIndex] = React.useState(0);
  const inputRef = React.useRef<HTMLInputElement>(null);

  const matches = React.useMemo(
    () => buildMatches(value, query, { regex: useRegex, matchCase }),
    [value, query, useRegex, matchCase],
  );

  React.useEffect(() => {
    if (!open) return;
    requestAnimationFrame(() => inputRef.current?.focus());
    setActiveIndex(0);
  }, [open]);

  // Keep activeIndex in range as matches change.
  React.useEffect(() => {
    if (activeIndex >= matches.length) setActiveIndex(0);
  }, [matches.length, activeIndex]);

  function focusMatch(index: number) {
    const m = matches[index];
    const ta = textareaRef.current;
    if (!m || !ta) return;
    ta.focus();
    ta.setSelectionRange(m.start, m.end);
    // Estimate scroll to bring the match into view in a plain textarea.
    const linesBefore = value.slice(0, m.start).split("\n").length - 1;
    ta.scrollTop = Math.max(0, (linesBefore - 4) * 24);
  }

  function next() {
    if (!matches.length) return;
    const i = (activeIndex + 1) % matches.length;
    setActiveIndex(i);
    focusMatch(i);
  }
  function prev() {
    if (!matches.length) return;
    const i = (activeIndex - 1 + matches.length) % matches.length;
    setActiveIndex(i);
    focusMatch(i);
  }

  function replaceCurrent() {
    const m = matches[activeIndex];
    if (!m) return;
    const next = value.slice(0, m.start) + replacement + value.slice(m.end);
    onChange(next, [m.start, m.start + replacement.length]);
  }

  function replaceAll() {
    if (!matches.length) return;
    // Work right-to-left so earlier offsets stay valid as we splice.
    let next = value;
    for (let i = matches.length - 1; i >= 0; i--) {
      const m = matches[i]!;
      next = next.slice(0, m.start) + replacement + next.slice(m.end);
    }
    onChange(next);
  }

  function onKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
    if (e.key === "Escape") {
      e.preventDefault();
      onClose();
    } else if (e.key === "Enter") {
      e.preventDefault();
      if (e.shiftKey) prev();
      else next();
    }
  }

  if (!open) return null;

  return (
    <div
      role="dialog"
      aria-label="Find and replace"
      className={cn(
        "no-print z-40 w-[22rem] max-w-[calc(100%-1rem)] rounded-lg border bg-popover/95 p-2 text-sm shadow-xl backdrop-blur",
        dock === "focus"
          ? "fixed right-3 top-14"
          : "absolute right-2 top-2",
      )}
    >
      <div className="flex items-center gap-1">
        <button
          type="button"
          onClick={() => setShowReplace((v) => !v)}
          className="rounded p-1 text-muted-foreground hover:bg-accent"
          aria-label={showReplace ? "Hide replace" : "Show replace"}
          aria-expanded={showReplace}
        >
          {showReplace ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronUp className="h-3.5 w-3.5" />}
        </button>
        <Input
          ref={inputRef}
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          onKeyDown={onKeyDown}
          placeholder="Find"
          className="h-7 text-xs"
          aria-label="Find"
        />
        <span className="shrink-0 text-[11px] tabular-nums text-muted-foreground">
          {matches.length === 0 ? "0/0" : `${activeIndex + 1}/${matches.length}`}
        </span>
        <ToggleButton
          active={matchCase}
          onClick={() => setMatchCase((v) => !v)}
          label="Match case"
        >
          <CaseSensitive className="h-3.5 w-3.5" />
        </ToggleButton>
        <ToggleButton
          active={useRegex}
          onClick={() => setUseRegex((v) => !v)}
          label="Use regular expression"
        >
          <Regex className="h-3.5 w-3.5" />
        </ToggleButton>
        <Button
          size="icon-sm"
          variant="ghost"
          onClick={prev}
          aria-label="Previous match"
          disabled={!matches.length}
        >
          <ChevronUp className="h-3.5 w-3.5" />
        </Button>
        <Button
          size="icon-sm"
          variant="ghost"
          onClick={next}
          aria-label="Next match"
          disabled={!matches.length}
        >
          <ChevronDown className="h-3.5 w-3.5" />
        </Button>
        <Button size="icon-sm" variant="ghost" onClick={onClose} aria-label="Close">
          <X className="h-3.5 w-3.5" />
        </Button>
      </div>

      {showReplace ? (
        <div className="mt-1.5 flex items-center gap-1">
          <Replace className="ml-1 h-3.5 w-3.5 text-muted-foreground" aria-hidden />
          <Input
            value={replacement}
            onChange={(e) => setReplacement(e.target.value)}
            placeholder="Replace"
            className="h-7 text-xs"
            aria-label="Replace with"
          />
          <Button
            size="sm"
            variant="ghost"
            className="h-7 px-2 text-xs"
            onClick={replaceCurrent}
            disabled={!matches.length}
          >
            Replace
          </Button>
          <Button
            size="sm"
            variant="ghost"
            className="h-7 px-2 text-xs"
            onClick={replaceAll}
            disabled={!matches.length}
          >
            All
          </Button>
        </div>
      ) : null}
    </div>
  );
}

function ToggleButton({
  active,
  onClick,
  label,
  children,
}: {
  active: boolean;
  onClick: () => void;
  label: string;
  children: React.ReactNode;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      aria-pressed={active}
      aria-label={label}
      title={label}
      className={cn(
        "rounded p-1 text-muted-foreground transition-colors hover:bg-accent",
        active && "bg-accent text-foreground",
      )}
    >
      {children}
    </button>
  );
}
