import * as React from "react";
import { flushSync } from "react-dom";
import {
  Bold,
  Code,
  Code2,
  ImagePlus,
  Italic,
  type LucideIcon,
  Paperclip,
  Send,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";

/**
 * ChatInputArea — WealthX Design System
 *
 * General-purpose chat input used across any feature that involves
 * a conversation or messaging interface (Policy AI, Support Agent,
 * AI Conversations, Website Chat Widget, etc.).
 *
 * Features:
 * - Textarea with auto-resize up to `maxHeight`
 * - Enter to send / Shift+Enter for new line
 * - Optional markdown formatting toolbar (`showMarkdownToolbar`)
 * - Optional file attachment (Paperclip) — shown only when `onAttachFile` is provided
 * - Optional image upload (ImagePlus) — shown only when `onAttachImage` is provided
 * - Focus ring on outer container via `focus-within`
 * - Fully disabled during streaming / loading states
 *
 * @example
 * <ChatInputArea
 *   value={inputValue}
 *   onChange={setInputValue}
 *   onSend={handleSend}
 *   onAttachFile={handleFiles}
 *   onAttachImage={handleImages}
 *   showMarkdownToolbar
 *   placeholder="Ask anything…"
 * />
 */
/** A taggable entry for the `@mention` autocomplete. */
export interface ChatMention {
  /** Stable id of the mentioned entity. This, not the label, is what is stored. */
  id: string;
  /** Shown in the picker and as the chip text. */
  label: string;
  /** Secondary text shown in the picker (e.g. role / email). */
  sublabel?: string;
}

export interface ChatInputAreaProps {
  /** Controlled text value. */
  value: string;
  /** Called on every keystroke. */
  onChange: (value: string) => void;
  /**
   * Called when the user submits (Enter key or Send button click).
   * Receives the trimmed text. Not called when value is empty or when disabled.
   */
  onSend: (value: string) => void;
  /**
   * When provided, a Paperclip button appears and this callback is fired
   * with the selected FileList. Hidden when omitted.
   */
  onAttachFile?: (files: FileList) => void;
  /**
   * When provided, an ImagePlus button appears and this callback is fired
   * with the selected image FileList. Hidden when omitted.
   */
  onAttachImage?: (files: FileList) => void;
  /** Disables all controls — use while streaming / waiting for a response. */
  disabled?: boolean;
  /** Textarea placeholder text. */
  placeholder?: string;
  /**
   * Hint text rendered below the input box.
   * Pass `false` to hide it entirely.
   * Defaults to "Enter to send · Shift+Enter for new line".
   */
  hint?: string | false;
  /**
   * Maximum textarea height in pixels before scrolling kicks in.
   * @default 160
   */
  maxHeight?: number;
  /** Focus the textarea on mount. */
  autoFocus?: boolean;
  /**
   * Show a markdown formatting toolbar (Bold, Italic, Code, Code block)
   * above the textarea. Wraps selected text or inserts a placeholder.
   * @default false
   */
  showMarkdownToolbar?: boolean;
  /**
   * Show the Send button (and make Enter submit). Set `false` for auto-saving
   * editors (e.g. notes) where there is no explicit "send" — the Send button is
   * hidden and Enter inserts a new line instead of submitting.
   * @default true
   */
  showSend?: boolean;
  /**
   * Fill the available height instead of auto-resizing to content. Use inside a
   * flex container for a full-height editor (e.g. a notes panel).
   * @default false
   */
  fill?: boolean;
  /**
   * Taggable entries for `@mention` autocomplete. When provided, typing `@`
   * followed by a query opens a picker; navigate with ↑/↓, pick with Enter or
   * click, and the selected entry is inserted as the markdown link
   * `[@label](#staff-id)`.
   *
   * The id is what gets stored, so a mention keeps pointing at the same person
   * after a rename and stays unambiguous when two people share a display name.
   * `MarkdownContent` renders these back as mention chips.
   */
  mentions?: ChatMention[];
  className?: string;
}

const DEFAULT_HINT = "Enter to send · Shift+Enter for new line";

/** Trailing `@<partial>` token at the caret that drives mention autocomplete. */
const MENTION_RE = /@([\w'\- ]{0,40})$/;

/**
 * Serialise a picked mention as `[@label](#staff-id)`.
 *
 * A bare `#fragment` href is deliberate: `MarkdownContent` sanitises with the default
 * schema, which strips any href whose protocol is not on its allowlist — so a custom
 * `staff:` scheme would be silently dropped, while a fragment passes untouched. Brackets
 * in the label are escaped so a name cannot break out of the link syntax.
 */
const mentionToken = (id: string, label: string): string =>
  `[@${label.replace(/[[\]]/g, "\\$&")}](#staff-${id})`;

// ---------------------------------------------------------------------------
// Markdown toolbar
// ---------------------------------------------------------------------------

type ToolbarItem =
  | {
      type: "button";
      icon: LucideIcon;
      label: string;
      title: string;
      before: string;
      after: string;
      placeholder: string;
    }
  | { type: "divider" };

/** Static config — defined once, no per-render allocation. */
const TOOLBAR_ITEMS: ToolbarItem[] = [
  {
    type: "button",
    icon: Bold,
    label: "Bold",
    title: "Bold (Ctrl+B)",
    before: "**",
    after: "**",
    placeholder: "bold text",
  },
  {
    type: "button",
    icon: Italic,
    label: "Italic",
    title: "Italic (Ctrl+I)",
    before: "*",
    after: "*",
    placeholder: "italic text",
  },
  {
    type: "button",
    icon: Code,
    label: "Inline code",
    title: "Inline code",
    before: "`",
    after: "`",
    placeholder: "code",
  },
  { type: "divider" },
  {
    type: "button",
    icon: Code2,
    label: "Code block",
    title: "Code block",
    before: "```\n",
    after: "\n```",
    placeholder: "code block",
  },
];

/**
 * Wraps the current selection (or inserts a placeholder) with markdown syntax.
 * Uses flushSync so the selection can be restored synchronously after the
 * controlled value update — no setTimeout timing hack needed.
 */
function applyMarkdown(
  textarea: HTMLTextAreaElement,
  before: string,
  after: string,
  placeholder: string,
  onChange: (value: string) => void,
) {
  const start = textarea.selectionStart;
  const end = textarea.selectionEnd;
  const selected = textarea.value.slice(start, end);
  const insertion = selected || placeholder;
  const next =
    textarea.value.slice(0, start) +
    before +
    insertion +
    after +
    textarea.value.slice(end);

  const newStart = start + before.length;
  const newEnd = newStart + insertion.length;

  // flushSync forces React to flush the state update synchronously so we can
  // restore selection immediately — React-blessed alternative to setTimeout.
  flushSync(() => onChange(next));
  textarea.focus();
  textarea.setSelectionRange(newStart, newEnd);
}

interface MarkdownToolbarProps {
  textareaRef: React.RefObject<HTMLTextAreaElement | null>;
  onChange: (value: string) => void;
  disabled?: boolean;
}

/** Memoised — does not re-render on every parent keystroke. */
const MarkdownToolbar = React.memo(function MarkdownToolbar({
  textareaRef,
  onChange,
  disabled,
}: MarkdownToolbarProps) {
  // Single stable handler — reads format tokens from data attributes.
  const handleFormat = React.useCallback(
    (e: React.MouseEvent<HTMLButtonElement>) => {
      if (!textareaRef.current) return;
      const { before, after, placeholder } = e.currentTarget.dataset as {
        before: string;
        after: string;
        placeholder: string;
      };
      applyMarkdown(textareaRef.current, before, after, placeholder, onChange);
    },
    [textareaRef, onChange],
  );

  return (
    <div className="flex items-center gap-0.5 border-b border-border px-2 py-1">
      {TOOLBAR_ITEMS.map((item, i) =>
        item.type === "divider" ? (
          <span
            key={i}
            className="mx-0.5 h-3.5 w-px bg-border"
            aria-hidden="true"
          />
        ) : (
          <Button
            key={item.label}
            variant="ghost"
            size="icon-sm"
            type="button"
            title={item.title}
            aria-label={item.label}
            disabled={disabled}
            data-before={item.before}
            data-after={item.after}
            data-placeholder={item.placeholder}
            onClick={handleFormat}
          >
            <item.icon className="size-3.5" aria-hidden="true" />
          </Button>
        ),
      )}
    </div>
  );
});

// ---------------------------------------------------------------------------
// ChatInputArea
// ---------------------------------------------------------------------------

export function ChatInputArea({
  value,
  onChange,
  onSend,
  onAttachFile,
  onAttachImage,
  disabled = false,
  placeholder = "Type your message…",
  hint = DEFAULT_HINT,
  maxHeight = 160,
  autoFocus = false,
  showMarkdownToolbar = false,
  showSend = true,
  fill = false,
  mentions = [],
  className,
}: ChatInputAreaProps) {
  const textareaRef = React.useRef<HTMLTextAreaElement>(null);
  const fileInputRef = React.useRef<HTMLInputElement>(null);
  const imageInputRef = React.useRef<HTMLInputElement>(null);

  // @mention autocomplete — `query` is the trailing token after `@`, null = closed.
  const [mentionQuery, setMentionQuery] = React.useState<string | null>(null);
  const [mentionIndex, setMentionIndex] = React.useState(0);

  const mentionMatches = React.useMemo(() => {
    if (mentionQuery === null || mentions.length === 0) return [];
    const q = mentionQuery.toLowerCase();
    return mentions
      .filter((m) => m.label.toLowerCase().includes(q))
      .slice(0, 8);
  }, [mentionQuery, mentions]);

  const detectMention = React.useCallback(
    (text: string) => {
      if (mentions.length === 0) return;
      const m = text.match(MENTION_RE);
      setMentionQuery(m ? m[1] : null);
      setMentionIndex(0);
    },
    [mentions.length],
  );

  const selectMention = React.useCallback(
    (option: ChatMention) => {
      onChange(
        value.replace(MENTION_RE, `${mentionToken(option.id, option.label)} `),
      );
      setMentionQuery(null);
      textareaRef.current?.focus();
    },
    [value, onChange],
  );

  // Focus on mount when autoFocus is requested
  React.useEffect(() => {
    if (autoFocus) {
      setTimeout(() => textareaRef.current?.focus(), 50);
    }
  }, [autoFocus]);

  const handleSend = React.useCallback(() => {
    const text = value.trim();
    if (!text || disabled) return;
    onSend(text);
    // Reset textarea height after clearing (caller is responsible for clearing value)
    if (textareaRef.current) {
      textareaRef.current.style.height = "auto";
    }
  }, [value, disabled, onSend]);

  const handleKeyDown = React.useCallback(
    (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
      // While the mention picker is open it takes over ↑/↓/Enter/Escape.
      if (mentionMatches.length > 0) {
        const last = mentionMatches.length - 1;
        if (e.key === "ArrowDown") {
          e.preventDefault();
          setMentionIndex((i) => (i >= last ? 0 : i + 1));
          return;
        }
        if (e.key === "ArrowUp") {
          e.preventDefault();
          setMentionIndex((i) => (i <= 0 ? last : i - 1));
          return;
        }
        if (e.key === "Enter") {
          e.preventDefault();
          selectMention(mentionMatches[mentionIndex] ?? mentionMatches[0]!);
          return;
        }
        if (e.key === "Escape") {
          e.preventDefault();
          setMentionQuery(null);
          return;
        }
      }
      // Enter submits only when there is a Send action; otherwise it's a newline.
      if (showSend && e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        handleSend();
      }
    },
    [handleSend, showSend, mentionMatches, mentionIndex, selectMention],
  );

  const handleTextareaChange = React.useCallback(
    (e: React.ChangeEvent<HTMLTextAreaElement>) => {
      onChange(e.target.value);
      detectMention(e.target.value);
      // Auto-resize to content (skipped in `fill` mode where the textarea
      // flex-fills its container instead).
      if (fill) return;
      const el = e.target;
      el.style.height = "auto";
      el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
    },
    [onChange, maxHeight, fill, detectMention],
  );

  const handleFileChange = React.useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      if (e.target.files?.length) {
        onAttachFile?.(e.target.files);
        e.target.value = "";
      }
    },
    [onAttachFile],
  );

  const handleImageChange = React.useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      if (e.target.files?.length) {
        onAttachImage?.(e.target.files);
        e.target.value = "";
      }
    },
    [onAttachImage],
  );

  const showFileButton = typeof onAttachFile === "function";
  const showImageButton = typeof onAttachImage === "function";

  return (
    <div
      data-slot="chat-input-area"
      className={cn(
        "flex flex-col gap-1.5",
        fill && "min-h-0 flex-1",
        className,
      )}
    >
      {/* Unified input box — toolbar + textarea + action bar share one border */}
      <div
        className={cn(
          "relative border border-border bg-background flex flex-col focus-within:ring-1 focus-within:ring-ring",
          fill && "min-h-0 flex-1",
        )}
      >
        {/* @mention picker — floats above the input box */}
        {mentionMatches.length > 0 && (
          <ul
            className="absolute bottom-full left-0 z-50 mb-1 max-h-56 w-full overflow-y-auto border border-border bg-popover text-popover-foreground shadow-md"
            role="listbox"
            aria-label="Mention"
          >
            {mentionMatches.map((m, i) => (
              <li key={m.id}>
                <button
                  type="button"
                  role="option"
                  aria-selected={i === mentionIndex}
                  onMouseDown={(e) => {
                    // Keep textarea focus; mousedown fires before blur.
                    e.preventDefault();
                    selectMention(m);
                  }}
                  onMouseEnter={() => setMentionIndex(i)}
                  className={cn(
                    "flex w-full items-center gap-2 px-3 py-2 text-left text-body-small",
                    i === mentionIndex
                      ? "bg-foreground/5"
                      : "hover:bg-foreground/5",
                  )}
                >
                  <span className="min-w-0 flex-1 truncate">{m.label}</span>
                  {m.sublabel && (
                    <span className="shrink-0 truncate text-caption text-muted-foreground">
                      {m.sublabel}
                    </span>
                  )}
                </button>
              </li>
            ))}
          </ul>
        )}

        {/* Markdown toolbar — optional, shown at top of the input box */}
        {showMarkdownToolbar && (
          <MarkdownToolbar
            textareaRef={textareaRef}
            onChange={onChange}
            disabled={disabled}
          />
        )}

        <Textarea
          ref={textareaRef}
          value={value}
          onChange={handleTextareaChange}
          onKeyDown={handleKeyDown}
          placeholder={placeholder}
          disabled={disabled}
          rows={3}
          className={cn(
            "resize-none text-sm border-0 shadow-none focus-visible:ring-0 px-3 pt-3 pb-1",
            fill ? "min-h-0 flex-1" : "min-h-[72px]",
          )}
          aria-label={placeholder}
        />

        {/* Action bar — attachment buttons on the left, Send on the right.
            Hidden entirely when there's nothing to show (no send, no attach). */}
        {(showSend || showFileButton || showImageButton) && (
          <div className="flex items-center justify-between px-2 pb-2">
            {/* Left: attachment buttons */}
            <div className="flex items-center gap-0.5">
              {showFileButton && (
                <>
                  <Button
                    variant="ghost"
                    size="icon-sm"
                    type="button"
                    title="Attach file"
                    aria-label="Attach file"
                    disabled={disabled}
                    onClick={() => fileInputRef.current?.click()}
                  >
                    <Paperclip className="size-3.5" aria-hidden="true" />
                  </Button>
                  <input
                    ref={fileInputRef}
                    type="file"
                    multiple
                    className="sr-only"
                    tabIndex={-1}
                    onChange={handleFileChange}
                  />
                </>
              )}

              {showImageButton && (
                <>
                  <Button
                    variant="ghost"
                    size="icon-sm"
                    type="button"
                    title="Upload image"
                    aria-label="Upload image"
                    disabled={disabled}
                    onClick={() => imageInputRef.current?.click()}
                  >
                    <ImagePlus className="size-3.5" aria-hidden="true" />
                  </Button>
                  <input
                    ref={imageInputRef}
                    type="file"
                    multiple
                    accept="image/*"
                    className="sr-only"
                    tabIndex={-1}
                    onChange={handleImageChange}
                  />
                </>
              )}
            </div>

            {/* Right: Send */}
            {showSend && (
              <Button
                size="icon-sm"
                type="button"
                aria-label="Send message"
                disabled={!value.trim() || disabled}
                onClick={handleSend}
              >
                <Send className="size-3.5" aria-hidden="true" />
              </Button>
            )}
          </div>
        )}
      </div>

      {/* Hint text */}
      {hint !== false && (
        <p className="text-xs text-muted-foreground">{hint}</p>
      )}
    </div>
  );
}
