// Author: Simon-Pierre Boucher
// Contact: contact@spboucher.ai
// Project: chat.spboucher.ai

"use client";

import { useEffect, useRef, useState } from "react";
import { Markdown } from "./Markdown";
import { siblingInfo, type ApiMessage, type ApiModel } from "./types";

interface StreamingView {
  assistantMessageId: string;
  generationId: string;
  content: string;
  reasoning: string;
  error: string | null;
}

interface MessageListProps {
  thread: ApiMessage[];
  allMessages: ApiMessage[];
  pendingUser: ApiMessage | null;
  streaming: StreamingView | null;
  models: ApiModel[];
  currentModelName: string | null;
  onSelectBranch: (parentKey: string, childId: string) => void;
  onRegenerate: (assistantMessageId: string, withModelId?: string) => void;
}

export function MessageList({
  thread,
  allMessages,
  pendingUser,
  streaming,
  models,
  currentModelName,
  onSelectBranch,
  onRegenerate,
}: MessageListProps) {
  const scrollRef = useRef<HTMLDivElement>(null);
  const [autoFollow, setAutoFollow] = useState(true);

  // Streaming text auto-follows the bottom; scrolling up pauses it.
  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    const onScroll = () => {
      const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
      setAutoFollow(nearBottom);
    };
    el.addEventListener("scroll", onScroll, { passive: true });
    return () => el.removeEventListener("scroll", onScroll);
  }, []);

  useEffect(() => {
    const el = scrollRef.current;
    if (el && autoFollow) el.scrollTop = el.scrollHeight;
  }, [thread, pendingUser, streaming?.content, streaming?.reasoning, autoFollow]);

  const jumpToLatest = () => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
    setAutoFollow(true);
  };

  const isEmpty = thread.length === 0 && !pendingUser && !streaming;

  return (
    <div className="message-scroll" ref={scrollRef}>
      {isEmpty ? (
        <div className="empty-state" style={{ display: "flex", flexDirection: "column", justifyContent: "center", minHeight: "60dvh" }}>
          <div>
            <div className="glyph">▮▯▮</div>
            <h2>Pick a model and ask something</h2>
            <p>{currentModelName ? `${currentModelName} is loaded and ready.` : "Loading the model catalog…"}</p>
          </div>
        </div>
      ) : (
        <div className="message-column">
          {thread.map((m) => (
            <MessageItem
              key={m.id}
              message={m}
              allMessages={allMessages}
              streaming={streaming}
              models={models}
              onSelectBranch={onSelectBranch}
              onRegenerate={onRegenerate}
            />
          ))}
          {pendingUser && <div className="msg-user pending">{pendingUser.content}</div>}
          {streaming && !thread.some((m) => m.id === streaming.assistantMessageId) && (
            <StreamingMessage streaming={streaming} modelName={currentModelName} />
          )}
        </div>
      )}
      {!autoFollow && (streaming || thread.length > 0) && (
        <button className="jump-pill" onClick={jumpToLatest}>
          ↓ jump to latest
        </button>
      )}
    </div>
  );
}

function StreamingMessage({ streaming, modelName }: { streaming: StreamingView; modelName: string | null }) {
  return (
    <div className="msg-assistant">
      <div className="msg-attribution">
        <span className="live-dot" aria-hidden />
        <span className="model-name">{modelName ?? "model"}</span>
        <span>streaming</span>
      </div>
      {streaming.reasoning && (
        <details className="reasoning-block" open>
          <summary>reasoning</summary>
          <div>{streaming.reasoning}</div>
        </details>
      )}
      <div className={`msg-body${streaming.error ? "" : " streaming-caret"}`}>
        <Markdown content={streaming.content} />
      </div>
      {streaming.error && <div className="msg-error">{streaming.error}</div>}
    </div>
  );
}

function MessageItem({
  message,
  allMessages,
  streaming,
  models,
  onSelectBranch,
  onRegenerate,
}: {
  message: ApiMessage;
  allMessages: ApiMessage[];
  streaming: StreamingView | null;
  models: ApiModel[];
  onSelectBranch: (parentKey: string, childId: string) => void;
  onRegenerate: (assistantMessageId: string, withModelId?: string) => void;
}) {
  const [copied, setCopied] = useState(false);
  const isStreamingThis = streaming?.assistantMessageId === message.id;
  const content = isStreamingThis ? streaming.content : message.content;
  const reasoning = isStreamingThis ? streaming.reasoning : message.reasoning;
  const { index, count, siblings } = siblingInfo(allMessages, message);
  const parentKey = message.parent_id ?? "root";
  const modelGone = message.model_id !== null && !models.some((m) => m.id === message.model_id && m.available);

  const copy = async () => {
    try {
      await navigator.clipboard.writeText(content);
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    } catch {
      /* clipboard unavailable */
    }
  };

  if (message.role === "user") {
    return (
      <>
        <div className="msg-user">{content}</div>
        {count > 1 && (
          <BranchNav
            index={index}
            count={count}
            onPrev={() => onSelectBranch(parentKey, siblings[index - 1].id)}
            onNext={() => onSelectBranch(parentKey, siblings[index + 1].id)}
          />
        )}
      </>
    );
  }

  return (
    <div className="msg-assistant">
      <div className="msg-attribution">
        {isStreamingThis && <span className="live-dot" aria-hidden />}
        <span className="model-name">{message.model_name ?? message.model_id ?? "model"}</span>
        {modelGone && !isStreamingThis && <span title="This model is no longer in the catalog">· Unavailable</span>}
        <span>
          ·{" "}
          {new Date(message.created_at).toLocaleTimeString([], {
            hour: "2-digit",
            minute: "2-digit",
          })}
        </span>
        {message.status === "cancelled" && <span>· stopped</span>}
      </div>

      {reasoning && (
        <details className="reasoning-block" open={isStreamingThis && !content}>
          <summary>reasoning</summary>
          <div>{reasoning}</div>
        </details>
      )}

      <div className={`msg-body${isStreamingThis && !streaming?.error ? " streaming-caret" : ""}`}>
        <Markdown content={content} />
      </div>

      {(message.status === "failed" || (isStreamingThis && streaming?.error)) && (
        <div className="msg-error">
          {isStreamingThis ? streaming?.error : message.error_message ?? "This generation failed."}
        </div>
      )}

      {!isStreamingThis && (
        <div className="msg-actions">
          <button className="msg-action-btn" onClick={copy}>
            {copied ? "Copied" : "Copy"}
          </button>
          <button className="msg-action-btn" onClick={() => onRegenerate(message.id)} title="Regenerate with the current model — creates a branch">
            ⟳ Regenerate
          </button>
          {count > 1 && (
            <BranchNav
              index={index}
              count={count}
              onPrev={() => onSelectBranch(parentKey, siblings[index - 1].id)}
              onNext={() => onSelectBranch(parentKey, siblings[index + 1].id)}
            />
          )}
        </div>
      )}
    </div>
  );
}

function BranchNav({
  index,
  count,
  onPrev,
  onNext,
}: {
  index: number;
  count: number;
  onPrev: () => void;
  onNext: () => void;
}) {
  return (
    <div className="branch-nav" style={{ alignSelf: "flex-end" }}>
      <button aria-label="Previous branch" disabled={index <= 0} onClick={onPrev}>
        ‹
      </button>
      <span>
        {index + 1}/{count}
      </span>
      <button aria-label="Next branch" disabled={index >= count - 1} onClick={onNext}>
        ›
      </button>
    </div>
  );
}
