"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import {
  Check,
  ChevronDown,
  ChevronLeft,
  CircleCheck,
  ExternalLink,
  MessageSquare,
  Paperclip,
  Pencil,
  Trash2,
  Undo2,
  X,
} from "lucide-react";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
} from "@/components/ui/sheet";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Markdown } from "@/components/markdown";
import { useImagePaste } from "@/hooks/use-image-paste";
import {
  formatTimestamp,
  fromDatetimeLocalUTC,
  toDatetimeLocalUTC,
} from "@/lib/format";
import { cn } from "@/lib/utils";
import { CATEGORY_DOT, CATEGORY_STYLES } from "@/lib/status-styles";
import {
  DEFAULT_NOTE_CATEGORY,
  NOTE_CATEGORIES,
  type Comment,
  type Note,
  type NoteCategory,
} from "@/lib/types";

/** The thing a notes list hangs off of — a page or a feature. */
export interface NotesParent {
  id: string;
  title: string;
  notes: Note[];
  // For page parents: the composed staging/live URLs, so the thread offers a
  // one-click way through to the actual page being reviewed. Null/absent for
  // features and issues (which have no page URL).
  stagingUrl?: string | null;
  liveUrl?: string | null;
}

export interface NewNoteInput {
  content: string;
  category: NoteCategory;
  imageDataUrl: string | null;
  imageFile: File | null;
}

export interface NewCommentInput {
  content: string;
  imageDataUrl: string | null;
  imageFile: File | null;
}

export interface NotePatch {
  category?: NoteCategory;
  resolved?: boolean;
  created_at?: string;
  content?: string;
}

export function CategorySelect({
  value,
  onSelect,
}: {
  value: NoteCategory;
  onSelect: (next: NoteCategory) => void;
}) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        render={
          <button
            type="button"
            aria-label={`Category: ${value}`}
            className={cn(
              "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
              CATEGORY_STYLES[value],
            )}
          >
            <span className={cn("size-1.5 rounded-full", CATEGORY_DOT[value])} />
            {value}
            <ChevronDown className="size-3 opacity-60" />
          </button>
        }
      />
      <DropdownMenuContent align="start" className="min-w-44">
        {NOTE_CATEGORIES.map((option) => (
          <DropdownMenuItem
            key={option}
            onClick={() => onSelect(option)}
            className="gap-2"
          >
            <span className={cn("size-2 rounded-full", CATEGORY_DOT[option])} />
            <span className="flex-1">{option}</span>
            {option === value && <Check className="size-3.5 opacity-70" />}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

/** Click the timestamp to edit it (UTC wall-clock, consistent with display). */
function EditableDate({
  iso,
  onChange,
}: {
  iso: string;
  onChange: (iso: string) => void;
}) {
  const [editing, setEditing] = useState(false);
  const [value, setValue] = useState(() => toDatetimeLocalUTC(iso));

  useEffect(() => {
    setValue(toDatetimeLocalUTC(iso));
  }, [iso]);

  if (!editing) {
    return (
      <button
        type="button"
        onClick={() => setEditing(true)}
        title="Edit date"
        className="font-mono text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
      >
        {formatTimestamp(iso)}
      </button>
    );
  }

  return (
    <span className="inline-flex items-center gap-1">
      <input
        type="datetime-local"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        className="h-7 rounded-md border bg-background px-1.5 text-xs focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
      />
      <button
        type="button"
        aria-label="Save date"
        onClick={() => {
          if (value) onChange(fromDatetimeLocalUTC(value));
          setEditing(false);
        }}
        className="rounded p-0.5 text-green-600 hover:bg-accent"
      >
        <Check className="size-4" />
      </button>
      <button
        type="button"
        aria-label="Cancel"
        onClick={() => {
          setValue(toDatetimeLocalUTC(iso));
          setEditing(false);
        }}
        className="rounded p-0.5 text-muted-foreground hover:bg-accent"
      >
        <X className="size-4" />
      </button>
    </span>
  );
}

export function AuthorLine({ email }: { email: string | null }) {
  if (!email) return null;
  return (
    <span className="text-xs font-medium text-muted-foreground">{email}</span>
  );
}

/** Trash button with an inline "Delete? Yes / No" confirm step. */
export function DeleteButton({
  onDelete,
  label = "Delete",
}: {
  onDelete: () => void;
  label?: string;
}) {
  const [confirming, setConfirming] = useState(false);

  if (confirming) {
    return (
      <span className="inline-flex items-center gap-1.5 text-xs">
        <span className="text-muted-foreground">Delete?</span>
        <button
          type="button"
          onClick={(e) => {
            e.stopPropagation();
            onDelete();
          }}
          className="font-medium text-destructive hover:underline"
        >
          Yes
        </button>
        <button
          type="button"
          onClick={(e) => {
            e.stopPropagation();
            setConfirming(false);
          }}
          className="text-muted-foreground hover:underline"
        >
          No
        </button>
      </span>
    );
  }

  return (
    <button
      type="button"
      aria-label={label}
      title={label}
      onClick={(e) => {
        e.stopPropagation();
        setConfirming(true);
      }}
      className="rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-destructive"
    >
      <Trash2 className="size-3.5" />
    </button>
  );
}

/** Inline content editor (textarea + save/cancel). */
export function ContentEditor({
  initial,
  onSave,
  onCancel,
}: {
  initial: string;
  onSave: (content: string) => void;
  onCancel: () => void;
}) {
  const [value, setValue] = useState(initial);
  return (
    <div className="mt-1 space-y-1.5">
      <Textarea
        value={value}
        onChange={(e) => setValue(e.target.value)}
        className="min-h-16 resize-none"
        autoFocus
      />
      <div className="flex gap-1.5">
        <Button size="sm" onClick={() => onSave(value.trim())}>
          Save
        </Button>
        <Button size="sm" variant="ghost" onClick={onCancel}>
          Cancel
        </Button>
      </div>
    </div>
  );
}

/** Detects an in-progress "@mention" token immediately before the caret.
 * Emails contain a second "@", so we grab the whole trailing non-space run and
 * only treat it as a mention if it starts with "@". */
function detectMention(value: string, caret: number) {
  const token = value.slice(0, caret).match(/(\S*)$/)?.[1] ?? "";
  if (!token.startsWith("@")) return null;
  return { start: caret - token.length, end: caret, query: token.slice(1) };
}

/** Shared input for adding a note or a comment (text + paste-to-attach image).
 * Typing "@" surfaces an autocomplete of `mentionUsers` (full profile emails). */
export function Composer({
  label,
  placeholder,
  submitLabel,
  showCategory,
  defaultCategory,
  onSubmit,
  mentionUsers = [],
}: {
  label: string;
  placeholder: string;
  submitLabel: string;
  showCategory: boolean;
  defaultCategory: NoteCategory;
  onSubmit: (data: {
    content: string;
    category: NoteCategory;
    imageDataUrl: string | null;
    imageFile: File | null;
  }) => void;
  mentionUsers?: string[];
}) {
  const [content, setContent] = useState("");
  const [category, setCategory] = useState<NoteCategory>(defaultCategory);
  const { image, error, handlePaste, clear } = useImagePaste();
  const canSubmit = Boolean(content.trim() || image);

  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const [mention, setMention] = useState<{
    start: number;
    end: number;
    query: string;
  } | null>(null);
  const [activeIdx, setActiveIdx] = useState(0);

  const matches = useMemo(() => {
    if (!mention) return [];
    const q = mention.query.toLowerCase();
    return mentionUsers.filter((u) => u.toLowerCase().includes(q)).slice(0, 6);
  }, [mention, mentionUsers]);
  const showMentions = mention !== null && matches.length > 0;

  function syncMention(value: string, caret: number) {
    setMention(mentionUsers.length > 0 ? detectMention(value, caret) : null);
    setActiveIdx(0);
  }

  function pickMention(email: string) {
    if (!mention) return;
    const next =
      content.slice(0, mention.start) + `@${email} ` + content.slice(mention.end);
    const caret = mention.start + email.length + 2; // "@" + email + trailing space
    setContent(next);
    setMention(null);
    requestAnimationFrame(() => {
      const ta = textareaRef.current;
      if (ta) {
        ta.focus();
        ta.setSelectionRange(caret, caret);
      }
    });
  }

  function submit() {
    if (!canSubmit) return;
    onSubmit({
      content: content.trim(),
      category,
      imageDataUrl: image?.dataUrl ?? null,
      imageFile: image?.file ?? null,
    });
    setContent("");
    setCategory(defaultCategory);
    setMention(null);
    clear();
  }

  return (
    <>
      <label htmlFor="composer-input" className="text-xs font-medium">
        {label}
      </label>
      <div className="relative">
        <Textarea
          ref={textareaRef}
          id="composer-input"
          value={content}
          onChange={(e) => {
            setContent(e.target.value);
            syncMention(e.target.value, e.target.selectionStart ?? e.target.value.length);
          }}
          onKeyDown={(e) => {
            if (!showMentions) return;
            if (e.key === "ArrowDown") {
              e.preventDefault();
              setActiveIdx((i) => (i + 1) % matches.length);
            } else if (e.key === "ArrowUp") {
              e.preventDefault();
              setActiveIdx((i) => (i - 1 + matches.length) % matches.length);
            } else if (e.key === "Enter" || e.key === "Tab") {
              e.preventDefault();
              pickMention(matches[activeIdx]);
            } else if (e.key === "Escape") {
              e.preventDefault();
              setMention(null);
            }
          }}
          onBlur={() => setTimeout(() => setMention(null), 120)}
          onPaste={handlePaste}
          placeholder={placeholder}
          className="min-h-20 resize-none"
        />
        {showMentions && (
          <ul className="absolute left-0 bottom-full z-50 mb-1 max-h-48 w-full overflow-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md">
            {matches.map((email, i) => (
              <li key={email}>
                <button
                  type="button"
                  // mousedown (not click) so the textarea doesn't blur first
                  onMouseDown={(e) => {
                    e.preventDefault();
                    pickMention(email);
                  }}
                  className={cn(
                    "flex w-full items-center rounded px-2 py-1.5 text-left text-sm",
                    i === activeIdx ? "bg-accent" : "hover:bg-accent",
                  )}
                >
                  {email}
                </button>
              </li>
            ))}
          </ul>
        )}
      </div>

      {error && <p className="text-xs text-destructive">{error}</p>}

      {image && (
        <div className="relative w-fit">
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img
            src={image.dataUrl}
            alt="Pasted screenshot preview"
            className="max-h-40 w-auto rounded-md border"
          />
          <button
            type="button"
            onClick={clear}
            aria-label="Remove pasted image"
            className="absolute -top-2 -right-2 rounded-full border bg-background p-0.5 shadow-sm hover:bg-accent"
          >
            <X className="size-3.5" />
          </button>
        </div>
      )}

      <div className="flex items-center justify-end gap-2">
        {showCategory && (
          <CategorySelect value={category} onSelect={setCategory} />
        )}
        <Button size="sm" onClick={submit} disabled={!canSubmit}>
          {submitLabel}
        </Button>
      </div>
    </>
  );
}

export function NoteImage({ url }: { url: string }) {
  return (
    // eslint-disable-next-line @next/next/no-img-element
    <img
      src={url}
      alt="Attachment"
      className="mt-2 max-h-64 w-auto rounded-md border"
    />
  );
}

function IconButton({
  label,
  onClick,
  children,
}: {
  label: string;
  onClick: () => void;
  children: React.ReactNode;
}) {
  return (
    <button
      type="button"
      aria-label={label}
      title={label}
      onClick={onClick}
      className="rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
    >
      {children}
    </button>
  );
}

function NoteCard({
  note,
  canEdit,
  onUpdate,
  onDelete,
  onOpenThread,
}: {
  note: Note;
  canEdit: boolean;
  onUpdate: (patch: NotePatch) => void;
  onDelete?: () => void;
  onOpenThread?: () => void;
}) {
  const [editing, setEditing] = useState(false);

  return (
    <div
      className={cn(
        "rounded-lg border p-3 text-sm",
        note.resolved ? "bg-muted/20 opacity-65" : "bg-muted/30",
      )}
    >
      <div className="mb-1.5 flex flex-wrap items-center gap-x-2 gap-y-1">
        <CategorySelect
          value={note.category}
          onSelect={(next) => onUpdate({ category: next })}
        />
        <AuthorLine email={note.author_email} />
        <EditableDate
          iso={note.created_at}
          onChange={(iso) => onUpdate({ created_at: iso })}
        />
        <div className="ml-auto flex items-center gap-0.5">
          {canEdit && !editing && (
            <IconButton label="Edit note" onClick={() => setEditing(true)}>
              <Pencil className="size-3.5" />
            </IconButton>
          )}
          {canEdit && onDelete && (
            <DeleteButton onDelete={onDelete} label="Delete note" />
          )}
          <button
            type="button"
            onClick={() => onUpdate({ resolved: !note.resolved })}
            className={cn(
              "inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs font-medium transition-colors",
              note.resolved
                ? "text-muted-foreground hover:bg-accent"
                : "text-green-700 hover:bg-green-50 dark:text-green-300 dark:hover:bg-green-950",
            )}
          >
            {note.resolved ? (
              <>
                <Undo2 className="size-3.5" /> Reopen
              </>
            ) : (
              <>
                <CircleCheck className="size-3.5" /> Resolve
              </>
            )}
          </button>
        </div>
      </div>

      {editing ? (
        <ContentEditor
          initial={note.content}
          onSave={(content) => {
            onUpdate({ content });
            setEditing(false);
          }}
          onCancel={() => setEditing(false)}
        />
      ) : onOpenThread ? (
        <div
          role="button"
          tabIndex={0}
          onClick={onOpenThread}
          onKeyDown={(e) => {
            if (e.key === "Enter" || e.key === " ") {
              e.preventDefault();
              onOpenThread();
            }
          }}
          className="w-full cursor-pointer text-left"
        >
          {note.content && (
            <Markdown
              content={note.content}
              className={cn(note.resolved && "line-through")}
            />
          )}
          {note.image_url && <NoteImage url={note.image_url} />}
          <span className="mt-2 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground">
            <MessageSquare className="size-3" />
            {note.comments.length > 0
              ? `${note.comments.length} ${
                  note.comments.length === 1 ? "comment" : "comments"
                }`
              : "Reply"}
          </span>
        </div>
      ) : (
        <>
          {note.content && (
            <Markdown
              content={note.content}
              onChange={(content) => onUpdate({ content })}
              className={cn(note.resolved && "line-through")}
            />
          )}
          {note.image_url && <NoteImage url={note.image_url} />}
        </>
      )}
    </div>
  );
}

export interface CommentLike {
  id: string;
  content: string;
  image_url: string | null;
  author_email: string | null;
  created_at: string;
}

export function CommentBlock({
  comment,
  canEdit,
  onUpdate,
}: {
  comment: CommentLike;
  canEdit: boolean;
  onUpdate: (content: string) => void;
}) {
  const [editing, setEditing] = useState(false);

  return (
    <div
      id={`comment-${comment.id}`}
      className="scroll-mt-24 rounded-lg border bg-muted/30 p-3 text-sm target:ring-2 target:ring-primary"
    >
      <div className="mb-1 flex items-center gap-2">
        <AuthorLine email={comment.author_email} />
        <span className="font-mono text-xs text-muted-foreground">
          {formatTimestamp(comment.created_at)}
        </span>
        {canEdit && !editing && (
          <span className="ml-auto">
            <IconButton label="Edit comment" onClick={() => setEditing(true)}>
              <Pencil className="size-3.5" />
            </IconButton>
          </span>
        )}
      </div>
      {editing ? (
        <ContentEditor
          initial={comment.content}
          onSave={(content) => {
            onUpdate(content);
            setEditing(false);
          }}
          onCancel={() => setEditing(false)}
        />
      ) : (
        <>
          {comment.content && (
            <Markdown
              content={comment.content}
              onChange={(content) => onUpdate(content)}
            />
          )}
          {comment.image_url && <NoteImage url={comment.image_url} />}
        </>
      )}
    </div>
  );
}

// Staging / live page links shown in the notes sheet header, so a reviewer in
// a comment thread can jump straight to the actual page. Renders nothing for
// parents without URLs (features, issues).
function ParentLinks({
  staging,
  live,
}: {
  staging?: string | null;
  live?: string | null;
}) {
  if (!staging && !live) return null;
  const base =
    "inline-flex items-center gap-1 rounded px-2 py-0.5 text-[11px] font-medium transition-opacity hover:opacity-80";
  return (
    <div className="mt-1 flex flex-wrap items-center gap-1.5">
      {staging && (
        <a
          href={staging}
          target="_blank"
          rel="noopener noreferrer"
          className={cn(
            base,
            "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200",
          )}
        >
          <ExternalLink className="size-3" />
          Staging
        </a>
      )}
      {live && (
        <a
          href={live}
          target="_blank"
          rel="noopener noreferrer"
          className={cn(
            base,
            "bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-200",
          )}
        >
          <ExternalLink className="size-3" />
          Live
        </a>
      )}
    </div>
  );
}

export function NotesSheet({
  parent,
  activeNote,
  open,
  onOpenChange,
  onAddNote,
  onUpdateNote,
  onDeleteNote,
  onAddComment,
  onUpdateComment,
  onOpenThread,
  onBack,
  currentUserEmail,
  defaultCategory = DEFAULT_NOTE_CATEGORY,
  mentionUsers = [],
}: {
  parent: NotesParent | null;
  activeNote: Note | null;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onAddNote: (pageId: string, note: NewNoteInput) => void;
  onUpdateNote: (noteId: string, patch: NotePatch) => void;
  onDeleteNote: (noteId: string) => void;
  onAddComment: (noteId: string, comment: NewCommentInput) => void;
  onUpdateComment: (commentId: string, content: string) => void;
  onOpenThread: (noteId: string) => void;
  onBack: () => void;
  currentUserEmail: string | null;
  defaultCategory?: NoteCategory;
  mentionUsers?: string[];
}) {
  const canEdit = (authorEmail: string | null) =>
    authorEmail !== null && authorEmail === currentUserEmail;

  // Notes list: open first, newest within each group.
  const notes: Note[] = parent
    ? [...parent.notes].sort((a, b) => {
        if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;
        return b.created_at.localeCompare(a.created_at);
      })
    : [];
  const openCount = notes.filter((n) => !n.resolved).length;

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent className="flex w-full flex-col sm:max-w-md">
        {activeNote ? (
          <>
            <SheetHeader className="border-b">
              <button
                type="button"
                onClick={onBack}
                className="flex w-fit items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
              >
                <ChevronLeft className="size-3.5" />
                Back
              </button>
              <SheetTitle>{parent ? parent.title : "Comment thread"}</SheetTitle>
              <ParentLinks
                staging={parent?.stagingUrl}
                live={parent?.liveUrl}
              />
              <SheetDescription>
                Comment thread · {activeNote.comments.length}{" "}
                {activeNote.comments.length === 1 ? "comment" : "comments"}
              </SheetDescription>
            </SheetHeader>

            <div className="flex-1 space-y-3 overflow-y-auto px-4">
              <NoteCard
                note={activeNote}
                canEdit={canEdit(activeNote.author_email)}
                onUpdate={(patch) => onUpdateNote(activeNote.id, patch)}
                onDelete={() => {
                  onDeleteNote(activeNote.id);
                  onBack();
                }}
              />

              {activeNote.comments.length > 0 && (
                <div className="space-y-2 border-l-2 pl-3">
                  {activeNote.comments.map((c) => (
                    <CommentBlock
                      key={c.id}
                      comment={c}
                      canEdit={canEdit(c.author_email)}
                      onUpdate={(content) => onUpdateComment(c.id, content)}
                    />
                  ))}
                </div>
              )}
            </div>

            <SheetFooter className="border-t">
              <Composer
                key={`comment-${activeNote.id}`}
                label="Add a comment"
                placeholder="Add a comment… or paste a screenshot with ⌘V / Ctrl+V"
                submitLabel="Add Comment"
                showCategory={false}
                defaultCategory={defaultCategory}
                mentionUsers={mentionUsers}
                onSubmit={({ content, imageDataUrl, imageFile }) =>
                  onAddComment(activeNote.id, {
                    content,
                    imageDataUrl,
                    imageFile,
                  })
                }
              />
            </SheetFooter>
          </>
        ) : (
          <>
            <SheetHeader className="border-b">
              <SheetTitle>{parent ? parent.title : "Notes"}</SheetTitle>
              <SheetDescription>
                {openCount} open · {notes.length} total
              </SheetDescription>
              <ParentLinks
                staging={parent?.stagingUrl}
                live={parent?.liveUrl}
              />
            </SheetHeader>

            <div className="flex-1 space-y-3 overflow-y-auto px-4">
              {notes.length === 0 ? (
                <p className="py-8 text-center text-sm text-muted-foreground">
                  No notes yet. Add the first one below.
                </p>
              ) : (
                notes.map((note) => (
                  <NoteCard
                    key={note.id}
                    note={note}
                    canEdit={canEdit(note.author_email)}
                    onUpdate={(patch) => onUpdateNote(note.id, patch)}
                    onDelete={() => onDeleteNote(note.id)}
                    onOpenThread={() => onOpenThread(note.id)}
                  />
                ))
              )}
            </div>

            <SheetFooter className="border-t">
              <Composer
                key={`note-${parent?.id ?? "none"}`}
                label="Add a note"
                placeholder="Type a note… or paste a screenshot with ⌘V / Ctrl+V"
                submitLabel="Add Note"
                showCategory
                defaultCategory={defaultCategory}
                mentionUsers={mentionUsers}
                onSubmit={(data) => {
                  if (parent) onAddNote(parent.id, data);
                }}
              />
            </SheetFooter>
          </>
        )}
      </SheetContent>
    </Sheet>
  );
}
