import React, { useMemo, useRef, useState } from "react";
import { Link } from "react-router";
import { Controller } from "react-hook-form";
import { t } from "@lingui/core/macro";
import { Plural, Trans } from "@lingui/react/macro";
import { API_URL, VALIDATION } from "../config/api.ts";
import type {
  Comment,
  CreateCommentRequest,
  RawComment,
  UpdateCommentRequest,
  User,
} from "../model.ts";
import { deserializeComment } from "../model.ts";
import { Avatar } from "./Avatar.tsx";
import { Markdown } from "./Markdown.tsx";
import { TextEditor, type TextEditorHandle } from "./TextEditor.tsx";
import { LikeButton } from "./LikeButton.tsx";
import { relativeTime } from "../utils/relativeTime.ts";
import { Tooltip } from "./Tooltip.tsx";
import { ConfirmModal } from "./ConfirmModal.tsx";
import { useWS } from "../hooks/useWS.ts";
import {
  expectOk,
  FormError,
  FormProvider,
  SubmitButton,
  useApiForm,
} from "./form/index.ts";

function authHeaders(token: string | null, json = true): HeadersInit {
  const headers: Record<string, string> = {};
  if (json) headers["Content-Type"] = "application/json";
  if (token) headers["Authorization"] = `Bearer ${token}`;
  return headers;
}

interface CommentFormProps {
  /** Async submit; throw to surface a root error, resolve to clear/close. */
  onSubmit: (body: string) => Promise<void>;
  submitLabel: React.ReactNode;
  pendingLabel: React.ReactNode;
  errorTitle: string;
  placeholder?: string;
  defaultValue?: string;
  onCancel?: () => void;
  /** Top-level form: only show Cancel once there's text to discard. */
  cancelWhenEmpty?: boolean;
  editorRef?: React.Ref<TextEditorHandle>;
  /** `top` renders the avatar-flanked layout; `inline` is for reply/edit. */
  layout?: "inline" | "top";
  leading?: React.ReactNode;
}

/**
 * Single-field comment editor (post / reply / edit). Wraps the shared
 * react-hook-form lifecycle so the three call sites stay identical.
 */
function CommentForm({
  onSubmit,
  submitLabel,
  pendingLabel,
  errorTitle,
  placeholder,
  defaultValue = "",
  onCancel,
  cancelWhenEmpty,
  editorRef,
  layout = "inline",
  leading,
}: CommentFormProps) {
  const form = useApiForm<{ body: string }>({
    defaultValues: { body: defaultValue },
  });
  const body = form.watch("body");
  const disabled = !body.trim() || body.length > VALIDATION.COMMENT_BODY_MAX;

  const submit = form.submit(async ({ body }) => {
    if (!body.trim() || body.length > VALIDATION.COMMENT_BODY_MAX) return;
    await onSubmit(body);
    form.reset({ body: "" });
  });

  const showCancel = onCancel && (!cancelWhenEmpty || body.trim().length > 0);

  const inner = (
    <>
      <Controller
        name="body"
        control={form.control}
        render={({ field }) => (
          <TextEditor
            ref={editorRef}
            className="comment-reply-textarea"
            value={field.value}
            onChange={field.onChange}
            onSubmit={() => void submit()}
            placeholder={placeholder}
            autoResize
            rows={1}
            maxLength={VALIDATION.COMMENT_BODY_MAX}
          />
        )}
      />
      <FormError title={errorTitle} />
      <div className="comment-form-actions">
        <SubmitButton
          className="comment-submit-btn"
          pendingLabel={pendingLabel}
          disabled={disabled}
        >
          {submitLabel}
        </SubmitButton>
        {showCancel && (
          <button
            type="button"
            className="comment-cancel-btn"
            onClick={() => {
              form.reset({ body: "" });
              onCancel?.();
            }}
          >
            <Trans>Cancel</Trans>
          </button>
        )}
      </div>
    </>
  );

  return (
    <FormProvider {...form}>
      {layout === "top"
        ? (
          <form className="comment-top-form" onSubmit={submit}>
            <div className="comment-top-form-inner">
              {leading}
              <div className="comment-top-form-body">{inner}</div>
            </div>
          </form>
        )
        : (
          <form className="comment-form" onSubmit={submit}>
            {inner}
          </form>
        )}
    </FormProvider>
  );
}

interface CommentThreadProps {
  dumpId: string;
  comments: Comment[];
  currentUser: User | null;
  token: string | null;
  onCommentCreated: (comment: Comment) => void;
  onCommentDeleted: (commentId: string) => void;
  onCommentUpdated: (comment: Comment) => void;
}

function buildTree(comments: Comment[]): Map<string, Comment[]> {
  const map = new Map<string, Comment[]>();
  for (const c of comments) {
    const key = c.parentId ?? "root";
    if (!map.has(key)) map.set(key, []);
    map.get(key)!.push(c);
  }
  return map;
}

interface CommentNodeProps {
  comment: Comment;
  tree: Map<string, Comment[]>;
  depth: number;
  dumpId: string;
  currentUser: User | null;
  token: string | null;
  onCommentCreated: (comment: Comment) => void;
  onCommentDeleted: (commentId: string) => void;
  onCommentUpdated: (comment: Comment) => void;
}

function CommentNode({
  comment,
  tree,
  depth,
  dumpId,
  currentUser,
  token,
  onCommentCreated,
  onCommentDeleted,
  onCommentUpdated,
}: CommentNodeProps) {
  const [replyOpen, setReplyOpen] = useState(false);
  const [editOpen, setEditOpen] = useState(false);
  const [confirmDelete, setConfirmDelete] = useState(false);

  const replyEditorRef = useRef<TextEditorHandle>(null);
  const editEditorRef = useRef<TextEditorHandle>(null);

  const { likeCounts, myLikes, castCommentLike, removeCommentLike } = useWS();

  const children = tree.get(comment.id) ?? [];

  async function handleReply(body: string) {
    const res = await fetch(`${API_URL}/api/dumps/${dumpId}/comments`, {
      method: "POST",
      headers: authHeaders(token),
      body: JSON.stringify(
        { body, parentId: comment.id } satisfies CreateCommentRequest,
      ),
    });
    onCommentCreated(deserializeComment(await expectOk<RawComment>(res)));
    setReplyOpen(false);
  }

  async function handleDelete() {
    if (!token) return;
    const res = await fetch(`${API_URL}/api/comments/${comment.id}`, {
      method: "DELETE",
      headers: { Authorization: `Bearer ${token}` },
    }).catch(() => null);
    if (res?.ok) {
      onCommentDeleted(comment.id);
    }
  }

  async function handleEditSave(body: string) {
    const res = await fetch(`${API_URL}/api/comments/${comment.id}`, {
      method: "PATCH",
      headers: authHeaders(token),
      body: JSON.stringify({ body } satisfies UpdateCommentRequest),
    });
    onCommentUpdated(deserializeComment(await expectOk<RawComment>(res)));
    setEditOpen(false);
  }

  const canDelete = !comment.deleted && !!currentUser &&
    (currentUser.id === comment.userId || currentUser.isAdmin);
  const canEdit = !comment.deleted && !!currentUser &&
    (currentUser.id === comment.userId || currentUser.isAdmin);

  if (comment.deleted) {
    return (
      <li className="comment-node">
        <div className="comment-node-inner comment-node-inner--deleted">
          <div className="comment-avatar comment-avatar--deleted">
            <div
              className="comment-avatar-placeholder"
              style={{ width: 28, height: 28 }}
            />
          </div>
          <div className="comment-content">
            <p className="comment-deleted-placeholder">
              <Trans>[deleted]</Trans>
            </p>
          </div>
        </div>
        {children.length > 0 && (
          <ul
            className="comment-replies"
            style={{ "--depth": depth } as React.CSSProperties}
          >
            {children.map((child) => (
              <CommentNode
                key={child.id}
                comment={child}
                tree={tree}
                depth={depth + 1}
                dumpId={dumpId}
                currentUser={currentUser}
                token={token}
                onCommentCreated={onCommentCreated}
                onCommentDeleted={onCommentDeleted}
                onCommentUpdated={onCommentUpdated}
              />
            ))}
          </ul>
        )}
      </li>
    );
  }

  return (
    <li className="comment-node">
      <div className="comment-node-inner" id={`comment-${comment.id}`}>
        <div className="comment-avatar">
          <Avatar
            userId={comment.userId}
            username={comment.authorUsername}
            hasAvatar={!!comment.authorAvatarMime}
            size={28}
          />
        </div>
        <div className="comment-content">
          <div className="comment-meta">
            <Link
              to={`/users/${comment.authorUsername}`}
              className="comment-author"
            >
              {comment.authorUsername}
            </Link>
            <Link
              to={`/dumps/${dumpId}#comment-${comment.id}`}
              className="comment-time"
            >
              <Tooltip text={comment.createdAt.toLocaleString()}>
                <time dateTime={comment.createdAt.toISOString()}>
                  {relativeTime(comment.createdAt)}
                </time>
              </Tooltip>
            </Link>
            {comment.updatedAt && (
              <Tooltip text={t`Edited ${comment.updatedAt.toLocaleString()}`}>
                <span className="comment-edited">
                  <Trans>edited {relativeTime(comment.updatedAt)}</Trans>
                </span>
              </Tooltip>
            )}
          </div>
          {editOpen
            ? (
              <CommentForm
                editorRef={editEditorRef}
                defaultValue={comment.body}
                onSubmit={handleEditSave}
                submitLabel={<Trans>Save</Trans>}
                pendingLabel={<Trans>Saving…</Trans>}
                errorTitle={t`Failed to save edit`}
                onCancel={() => setEditOpen(false)}
              />
            )
            : <Markdown className="comment-body">{comment.body}</Markdown>}
          <div className="comment-actions">
            {!editOpen && (
              <LikeButton
                commentId={comment.id}
                count={likeCounts[comment.id] ?? comment.likeCount}
                liked={myLikes.has(comment.id)}
                disabled={!currentUser}
                onLike={castCommentLike}
                onUnlike={removeCommentLike}
              />
            )}
            {currentUser && !editOpen && (
              <button
                type="button"
                className="comment-action-btn"
                onClick={() => {
                  setReplyOpen((v) => !v);
                  setTimeout(() => replyEditorRef.current?.focus(), 0);
                }}
              >
                <Trans>Reply</Trans>
              </button>
            )}
            {canEdit && !editOpen && (
              <button
                type="button"
                className="comment-action-btn"
                onClick={() => {
                  setEditOpen(true);
                  setTimeout(() => editEditorRef.current?.focus(), 0);
                }}
              >
                <Trans>Edit</Trans>
              </button>
            )}
            {canDelete && !editOpen && (
              <button
                type="button"
                className="comment-action-btn comment-delete-btn"
                onClick={() => setConfirmDelete(true)}
              >
                <Trans>Delete</Trans>
              </button>
            )}
            {confirmDelete && (
              <ConfirmModal
                message={t`Delete this comment?`}
                confirmLabel={t`Delete`}
                onConfirm={() => {
                  setConfirmDelete(false);
                  handleDelete();
                }}
                onCancel={() => setConfirmDelete(false)}
              />
            )}
          </div>
          {replyOpen && (
            <CommentForm
              editorRef={replyEditorRef}
              onSubmit={handleReply}
              submitLabel={<Trans>Post reply</Trans>}
              pendingLabel={<Trans>Posting…</Trans>}
              errorTitle={t`Failed to post reply`}
              placeholder={t`Write a reply…`}
              onCancel={() => setReplyOpen(false)}
            />
          )}
        </div>
      </div>
      {children.length > 0 && (
        <ul
          className="comment-replies"
          style={{ "--depth": depth } as React.CSSProperties}
        >
          {children.map((child) => (
            <CommentNode
              key={child.id}
              comment={child}
              tree={tree}
              depth={depth + 1}
              dumpId={dumpId}
              currentUser={currentUser}
              token={token}
              onCommentCreated={onCommentCreated}
              onCommentDeleted={onCommentDeleted}
              onCommentUpdated={onCommentUpdated}
            />
          ))}
        </ul>
      )}
    </li>
  );
}

export function CommentThread({
  dumpId,
  comments,
  currentUser,
  token,
  onCommentCreated,
  onCommentDeleted,
  onCommentUpdated,
}: CommentThreadProps) {
  const tree = useMemo(() => buildTree(comments), [comments]);
  const roots = tree.get("root") ?? [];

  async function handleTopLevelSubmit(body: string) {
    const res = await fetch(`${API_URL}/api/dumps/${dumpId}/comments`, {
      method: "POST",
      headers: authHeaders(token),
      body: JSON.stringify({ body } satisfies CreateCommentRequest),
    });
    onCommentCreated(deserializeComment(await expectOk<RawComment>(res)));
  }

  const visibleCount = comments.filter((c) => !c.deleted).length;

  return (
    <section className="comment-section">
      <h2 className="comment-section-title">
        <Plural value={visibleCount} one="# comment" other="# comments" />
      </h2>

      {currentUser && (
        <CommentForm
          layout="top"
          leading={
            <div className="comment-avatar">
              <Avatar
                userId={currentUser.id}
                username={currentUser.username}
                hasAvatar={!!currentUser.avatarMime}
                size={28}
              />
            </div>
          }
          onSubmit={handleTopLevelSubmit}
          submitLabel={<Trans>Post comment</Trans>}
          pendingLabel={<Trans>Posting…</Trans>}
          errorTitle={t`Failed to post comment`}
          placeholder={t`Add a comment…`}
          onCancel={() => {}}
          cancelWhenEmpty
        />
      )}

      {roots.length > 0 && (
        <ul className="comment-list">
          {roots.map((comment) => (
            <CommentNode
              key={comment.id}
              comment={comment}
              tree={tree}
              depth={0}
              dumpId={dumpId}
              currentUser={currentUser}
              token={token}
              onCommentCreated={onCommentCreated}
              onCommentDeleted={onCommentDeleted}
              onCommentUpdated={onCommentUpdated}
            />
          ))}
        </ul>
      )}
    </section>
  );
}
