/* Threads, review-modelled (the notes sheet), translated simpler.
 *
 *   StatusChip  — the category/status pill with its dot
 *   ThreadCard  — one thread in a list: chip · author · time · resolve/reopen,
 *                 the body, "N replies / Reply" to open it
 *   ThreadView  — the drill-in: the card, then replies in a left-ruled column
 *   ThreadStarter — a subject nobody has spoken about, with an inline composer
 *   Composer    — the pinned-footer writing surface (⌘/Ctrl+Enter sends);
 *                 paste, drop or attach a file and it rides along as markdown
 *
 * All presentational. The app maps its own rows into `ThreadRow` and supplies
 * the callbacks; nothing here knows what a subject is. Bodies render through
 * `Body` (markdown-lite, images only from the app's own URLs). */

import { useEffect, useRef, useState, type ReactNode } from "react";
import { Body } from "./Body";
import { fullTime, relTime } from "./time";

export type ThreadReply = {
  id: number | string;
  /** A DOM id for the row ("c-88"), so a notification's deep link can land
   *  on the exact reply (04 §3) and the app can let it settle out of a wash. */
  domId?: string;
  author: string;
  authorIsTeam?: boolean;
  body: string;
  at: string;
  editedAt?: string | null;
  /** Rendered after the body (e.g. an "edited" label with history). */
  after?: ReactNode;
  canEdit?: boolean;
  canDelete?: boolean;
};

export type ThreadRow = {
  id: number | string;
  /** A DOM id for the card, as on `ThreadReply.domId`. */
  domId?: string;
  /** A short anchor label ("#3") when the thread is pinned to a spot. */
  anchor?: string | null;
  status?: { label: string; tone?: string } | null;
  author: string;
  authorIsTeam?: boolean;
  at: string;
  body: string;
  resolved: boolean;
  replyCount: number;
  replies?: ThreadReply[];
  /** The opening comment's own edit/delete affordances. */
  canEdit?: boolean;
  canDelete?: boolean;
  after?: ReactNode;
};

// ---------------------------------------------------------------- chip

export function StatusChip({ label, tone }: { label: string; tone?: string }) {
  // The tone is a token EXPRESSION supplied by the caller ("var(--x)") — the
  // one place computed colour is data rather than styling (frontend.md).
  return (
    <span className="cu-status"
          style={tone ? ({ ["--chip-tone" as string]: tone } as React.CSSProperties) : undefined}>
      {label}
    </span>
  );
}

// ---------------------------------------------------------------- card

function Meta({ t, children }: { t: ThreadRow; children?: ReactNode }) {
  return (
    <div className="cu-thread-meta">
      {t.anchor && <span className="cu-thread-anchor">{t.anchor}</span>}
      {t.status && <StatusChip label={t.status.label} tone={t.status.tone} />}
      <span className={`cu-thread-author ${t.authorIsTeam ? "is-team" : ""}`}>
        {t.author}
      </span>
      <time dateTime={t.at} title={fullTime(t.at)}>{relTime(t.at)}</time>
      <span className="cu-thread-actions">{children}</span>
    </div>
  );
}

export function ThreadCard({
  thread, active, onOpen, onSelect, onResolve, onLocate, onEdit, onDelete, locateLabel,
  imagePrefix, expanded, children,
}: {
  /** The card holds its conversation INLINE: when expanded, `children` (the
   *  replies and a composer) render under the foot. The list stays a list —
   *  nobody is taken anywhere to reply. */
  expanded?: boolean;
  children?: ReactNode;
  thread: ThreadRow;
  active?: boolean;
  /** The app's own attachment URL prefix — the only images that render. */
  imagePrefix?: string;
  /** The Reply button: open the thread. */
  onOpen?: () => void;
  /** The CARD: go to the subject (the spot on the canvas, the point in the
   *  walkthrough) — or, for a subject with nowhere to go, open the thread.
   *  Reading is the common act; a card that opens a drill-in on every click
   *  made the list a corridor. */
  onSelect?: () => void;
  onResolve?: (resolved: boolean) => void;
  /** e.g. "View on canvas" — jump to where the thread is anchored. */
  onLocate?: () => void;
  locateLabel?: string;
  onEdit?: (body: string) => void;
  onDelete?: () => void;
}) {
  const [editing, setEditing] = useState<string | null>(null);
  return (
    <div className={`cu-thread ${thread.resolved ? "is-resolved" : ""} ${active ? "is-active" : ""} ${onSelect ? "is-clickable" : ""}`}
         id={thread.domId}
         onClick={onSelect && editing === null ? onSelect : undefined}
         role={onSelect ? "button" : undefined}
         tabIndex={onSelect ? 0 : undefined}
         onKeyDown={onSelect ? (e) => { if (e.key === "Enter") onSelect(); } : undefined}>
      <Meta t={thread}>
        {onResolve && (
          <button type="button"
                  className={`cu-thread-act ${thread.resolved ? "" : "is-resolve"}`}
                  onClick={(e) => { e.stopPropagation(); onResolve(!thread.resolved); }}>
            {thread.resolved ? "↶ Reopen" : "✓ Resolve"}
          </button>
        )}
      </Meta>
      {editing !== null ? (
        <form className="cu-edit" onClick={(e) => e.stopPropagation()} onSubmit={(e) => {
          e.preventDefault();
          const b = editing.trim();
          if (b && onEdit) onEdit(b);
          setEditing(null);
        }}>
          <textarea value={editing} rows={3} autoFocus aria-label="Edit"
                    onChange={(e) => setEditing(e.target.value)} />
          <span className="cu-edit-btns">
            <button type="submit" className="cu-btn cu-btn-primary">Save</button>
            <button type="button" className="cu-btn" onClick={() => setEditing(null)}>
              Cancel
            </button>
          </span>
        </form>
      ) : (
        <div className="cu-thread-body">
          <Body text={thread.body} imagePrefix={imagePrefix} />{thread.after}
        </div>
      )}
      <div className="cu-thread-foot" onClick={(e) => e.stopPropagation()}>
        {onOpen && (
          <button type="button" className={`cu-thread-open ${expanded ? "is-on" : ""}`}
                  aria-expanded={expanded} onClick={onOpen}>
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none"
                 stroke="currentColor" strokeWidth="2" strokeLinecap="round"
                 strokeLinejoin="round" aria-hidden="true">
              <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
            </svg>
            {thread.replyCount > 0
              ? `${thread.replyCount} ${thread.replyCount === 1 ? "reply" : "replies"}`
              : "Reply"}
          </button>
        )}
        {onLocate && (
          <button type="button" className="cu-thread-open" onClick={onLocate}>
            {locateLabel ?? "Show me"} →
          </button>
        )}
        <span className="cu-thread-tools">
          {thread.canEdit && onEdit && editing === null && (
            <button type="button" onClick={() => setEditing(thread.body)}>Edit</button>
          )}
          {thread.canDelete && onDelete && (
            <button type="button" onClick={onDelete}>Remove</button>
          )}
        </span>
      </div>
      {expanded && children && (
        <div className="cu-thread-inline" onClick={(e) => e.stopPropagation()}>{children}</div>
      )}
    </div>
  );
}

// ------------------------------------------------------------- starter

/** A subject nobody has spoken about yet, listed beside the threads that
 *  exist — "Page › Homepage — nothing yet, write the first comment →". Opening
 *  it is an inline composer (pass one as `children`), so the first word on a
 *  subject is as easy as the tenth and "where do I say this?" has one answer.
 *  Presentational: the app decides which subjects exist and posts the words. */
export function ThreadStarter({
  anchor, heading, hint, open, onToggle, children,
}: {
  /** The breadcrumb label, in the app's own nouns ("Page › Homepage"). */
  anchor: string;
  heading: string;
  /** Shown while closed; while open the row says "Write it below." */
  hint?: string;
  open: boolean;
  onToggle: () => void;
  /** The composer, rendered under the row while open. */
  children?: ReactNode;
}) {
  return (
    <div className={`cu-starter ${open ? "is-open" : ""}`}>
      <button type="button" className="cu-starter-row" aria-expanded={open}
              onClick={onToggle}>
        <span className="cu-starter-anchor">{anchor}</span>
        <span className="cu-starter-text">
          <b>{heading}</b>
          <span>{open ? "Write it below." : (hint ?? "Nothing yet — write the first comment →")}</span>
        </span>
      </button>
      {open && children && <div className="cu-thread-inline">{children}</div>}
    </div>
  );
}

// ------------------------------------------------------------- replies

/** The replies column — under an expanded card, or in a full thread view. */
export function ThreadReplies({ replies, onEdit, onDelete, imagePrefix }: {
  replies: ThreadReply[];
  onEdit?: (id: ThreadReply["id"], body: string) => void;
  onDelete?: (id: ThreadReply["id"]) => void;
  imagePrefix?: string;
}) {
  const [editing, setEditing] = useState<{ id: ThreadReply["id"]; body: string } | null>(null);
  if (replies.length === 0) return null;
  return (
    <div className="cu-replies">
      {replies.map((r) => (
        <div key={r.id} className="cu-reply" id={r.domId}>
          <span className="cu-reply-meta">
            <span className={`cu-thread-author ${r.authorIsTeam ? "is-team" : ""}`}>
              {r.author}
            </span>
            <time dateTime={r.at} title={fullTime(r.at)}>{relTime(r.at)}</time>
          </span>
          {editing?.id === r.id ? (
            <form className="cu-edit" onSubmit={(e) => {
              e.preventDefault();
              const b = editing.body.trim();
              if (b) onEdit?.(r.id, b);
              setEditing(null);
            }}>
              <textarea value={editing.body} rows={2} autoFocus aria-label="Edit reply"
                        onChange={(e) => setEditing({ id: r.id, body: e.target.value })} />
              <span className="cu-edit-btns">
                <button type="submit" className="cu-btn cu-btn-primary">Save</button>
                <button type="button" className="cu-btn"
                        onClick={() => setEditing(null)}>Cancel</button>
              </span>
            </form>
          ) : (
            <>
              <span className="cu-reply-body">
                <Body text={r.body} imagePrefix={imagePrefix} />{r.after}
              </span>
              {(r.canEdit || r.canDelete) && (
                <span className="cu-reply-tools">
                  {r.canEdit && onEdit && (
                    <button type="button"
                            onClick={() => setEditing({ id: r.id, body: r.body })}>
                      Edit
                    </button>
                  )}
                  {r.canDelete && onDelete && (
                    <button type="button" onClick={() => onDelete(r.id)}>
                      Remove
                    </button>
                  )}
                </span>
              )}
            </>
          )}
        </div>
      ))}
    </div>
  );
}

// ---------------------------------------------------------------- view

export function ThreadView({
  thread, onResolve, onLocate, locateLabel, onEditRoot, onDeleteRoot,
  onEditReply, onDeleteReply, imagePrefix,
}: {
  thread: ThreadRow;
  imagePrefix?: string;
  onResolve?: (resolved: boolean) => void;
  onLocate?: () => void;
  locateLabel?: string;
  onEditRoot?: (body: string) => void;
  onDeleteRoot?: () => void;
  onEditReply?: (id: ThreadReply["id"], body: string) => void;
  onDeleteReply?: (id: ThreadReply["id"]) => void;
}) {
  const [editing, setEditing] = useState<{ id: ThreadReply["id"]; body: string } | null>(null);
  return (
    <div className="cu-thread-view">
      <ThreadCard thread={thread} onResolve={onResolve} onLocate={onLocate}
                  locateLabel={locateLabel} onEdit={onEditRoot} onDelete={onDeleteRoot}
                  imagePrefix={imagePrefix} />
      {(thread.replies?.length ?? 0) > 0 && (
        <div className="cu-replies">
          {thread.replies!.map((r) => (
            <div key={r.id} className="cu-reply" id={r.domId}>
              <span className="cu-reply-meta">
                <span className={`cu-thread-author ${r.authorIsTeam ? "is-team" : ""}`}>
                  {r.author}
                </span>
                <time dateTime={r.at} title={fullTime(r.at)}>{relTime(r.at)}</time>
              </span>
              {editing?.id === r.id ? (
                <form className="cu-edit" onSubmit={(e) => {
                  e.preventDefault();
                  const b = editing.body.trim();
                  if (b) onEditReply?.(r.id, b);
                  setEditing(null);
                }}>
                  <textarea value={editing.body} rows={2} autoFocus aria-label="Edit reply"
                            onChange={(e) => setEditing({ id: r.id, body: e.target.value })} />
                  <span className="cu-edit-btns">
                    <button type="submit" className="cu-btn cu-btn-primary">Save</button>
                    <button type="button" className="cu-btn"
                            onClick={() => setEditing(null)}>Cancel</button>
                  </span>
                </form>
              ) : (
                <>
                  <span className="cu-reply-body">
                    <Body text={r.body} imagePrefix={imagePrefix} />{r.after}
                  </span>
                  {(r.canEdit || r.canDelete) && (
                    <span className="cu-reply-tools">
                      {r.canEdit && onEditReply && (
                        <button type="button"
                                onClick={() => setEditing({ id: r.id, body: r.body })}>
                          Edit
                        </button>
                      )}
                      {r.canDelete && onDeleteReply && (
                        <button type="button" onClick={() => onDeleteReply(r.id)}>
                          Remove
                        </button>
                      )}
                    </span>
                  )}
                </>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ------------------------------------------------------------ composer

const ATTACH_ACCEPT = "image/png,image/jpeg,image/gif,image/webp,application/pdf";

/** One uploaded file waiting to ride along with the next comment. */
type Pending = { md: string; alt: string; url: string; image: boolean };

function pendingOf(md: string): Pending {
  const m = md.trim().match(/^(!?)\[([^\]]*)\]\(([^)\s]+)\)$/);
  return m
    ? { md: md.trim(), alt: m[2], url: m[3], image: m[1] === "!" }
    : { md: md.trim(), alt: md.trim(), url: "", image: false };
}

export function Composer({
  label, about, placeholder, submitLabel, busy, error, onSubmit, autoFocus,
  onCancel, hint, onAttach,
}: {
  label: string;
  /** What the note is about, when that isn't obvious ("a question about step 3"). */
  about?: ReactNode;
  placeholder?: string;
  submitLabel?: string;
  busy?: boolean;
  error?: string;
  onSubmit: (body: string) => void;
  autoFocus?: boolean;
  onCancel?: () => void;
  hint?: string;
  /** Upload one file now and resolve to the markdown that embeds it
   *  (Interaction Standard addendum: files ride along in the body). Present
   *  = paste, drop and the paperclip all work. */
  onAttach?: (file: File) => Promise<string>;
}) {
  const [body, setBody] = useState("");
  // Files never appear in the text box — the markdown is the wire format, not
  // something a person should read while writing. They wait in the strip
  // below and are appended to the words on Send.
  const [pending, setPending] = useState<Pending[]>([]);
  const [uploading, setUploading] = useState(0);
  const [attachErr, setAttachErr] = useState<string | null>(null);
  const [dragging, setDragging] = useState(false);
  const ref = useRef<HTMLTextAreaElement | null>(null);
  const fileInput = useRef<HTMLInputElement | null>(null);
  useEffect(() => { if (autoFocus) ref.current?.focus(); }, [autoFocus]);

  // Every file — pasted, dropped or picked — uploads at once, so Send stays
  // ONE action and never waits on a transfer.
  const addFiles = (files: File[]) => {
    if (!onAttach || files.length === 0) return;
    setAttachErr(null);
    setUploading((n) => n + files.length);
    for (const f of files) {
      onAttach(f)
        .then((md) => setPending((p) => [...p, pendingOf(md)]))
        .catch((e: Error) => setAttachErr(e.message || "Upload failed."))
        .finally(() => setUploading((n) => n - 1));
    }
  };
  const removeFile = (md: string) => setPending((p) => p.filter((x) => x.md !== md));

  const ready = (body.trim().length > 0 || pending.length > 0) && !busy && uploading === 0;
  const send = () => {
    if (!ready) return;
    // Words first, then the files — the way a note with a screenshot reads.
    onSubmit([body.trim(), ...pending.map((p) => p.md)].filter(Boolean).join("\n"));
    setBody("");
    setPending([]);
    setAttachErr(null);
  };
  const err = error ?? attachErr ?? undefined;

  return (
    <form className={`cu-composer ${dragging ? "is-dragover" : ""}`}
          onSubmit={(e) => { e.preventDefault(); send(); }}
          onDragOver={onAttach ? (e) => { e.preventDefault(); setDragging(true); } : undefined}
          onDragLeave={onAttach ? () => setDragging(false) : undefined}
          onDrop={onAttach ? (e) => {
            e.preventDefault(); setDragging(false);
            addFiles(Array.from(e.dataTransfer?.files ?? []));
          } : undefined}>
      <span className="cu-composer-label">{label}</span>
      {about && <span className="cu-composer-about">{about}</span>}
      <textarea ref={ref} value={body} placeholder={placeholder}
                aria-label={label}
                onChange={(e) => setBody(e.target.value)}
                onPaste={onAttach ? (e) => {
                  const files = Array.from(e.clipboardData?.files ?? []);
                  if (files.length > 0) { e.preventDefault(); addFiles(files); }
                } : undefined}
                onKeyDown={(e) => {
                  if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) send();
                }} />
      {pending.length > 0 && (
        <div className="cu-composer-files" aria-label="Attached">
          {pending.map((p) => (
            <span key={p.md} className="cu-file-chip">
              {p.image
                ? <img src={p.url} alt="" />
                : <span className="cu-file-chip-doc" aria-hidden="true">PDF</span>}
              <span title={p.alt}>{p.alt || "file"}</span>
              <button type="button" aria-label={`Remove ${p.alt || "file"}`}
                      onClick={() => removeFile(p.md)}>×</button>
            </span>
          ))}
        </div>
      )}
      {err && <p className="cu-composer-err">{err}</p>}
      <div className="cu-composer-row">
        <span className="cu-composer-hint">
          {hint ?? (onAttach ? "⌘/Ctrl + Enter to send · paste or drop an image" : "⌘/Ctrl + Enter to send")}
        </span>
        {onAttach && (
          <>
            <button type="button" className="cu-attach" aria-label="Attach a file"
                    title="Attach an image or PDF" onClick={() => fileInput.current?.click()}>
              <svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true" fill="none"
                   stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" />
              </svg>
            </button>
            <input ref={fileInput} type="file" hidden multiple accept={ATTACH_ACCEPT}
                   onChange={(e) => {
                     addFiles(Array.from(e.target.files ?? []));
                     e.target.value = "";
                   }} />
          </>
        )}
        {onCancel && (
          <button type="button" className="cu-btn" onClick={onCancel}>Cancel</button>
        )}
        <button type="submit" className="cu-btn cu-btn-primary" disabled={!ready}>
          {uploading > 0 ? "Uploading…" : busy ? "Sending…" : (submitLabel ?? "Send")}
        </button>
      </div>
    </form>
  );
}
