import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import type { AgentState, LogLine } from "../types";

interface Props {
  lines: LogLine[];
  disabled: boolean;
  onSendMessage(text: string): Promise<void>;
  onOpenVoiceMode?(): Promise<void> | void;
  onExpandChange?(expanded: boolean): void;
  contextLabel?: string | null;
  onClearContext?(): void;
  agentState: AgentState;
  textStreaming: boolean;
  fullScreen?: boolean;
}

interface LogViewProps {
  lines: LogLine[];
  scrollRef: { current: HTMLElement | null };
}

type ChatRole = "user" | "assistant" | "system" | "tool";

interface ActivityStatus {
  tone: "waiting" | "thinking" | "tool" | "replying";
  label: string;
  detail?: string;
}

function SendIcon() {
  return (
    <svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
      <path d="M2 9L16 2L9.5 16L8 10.5L2 9Z" fill="currentColor" />
    </svg>
  );
}

function SpeakerIcon() {
  return (
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
      <path d="M5 9v6h4l5 4V5L9 9H5Z" fill="currentColor" />
      <path
        d="M17 9.5a4 4 0 0 1 0 5"
        fill="none"
        stroke="currentColor"
        stroke-linecap="round"
        stroke-width="1.8"
      />
      <path
        d="M18.8 7a7 7 0 0 1 0 10"
        fill="none"
        stroke="currentColor"
        stroke-linecap="round"
        stroke-width="1.8"
      />
    </svg>
  );
}

function CloseIcon() {
  return (
    <svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
      <path d="M14 4L4 14M4 4L14 14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
    </svg>
  );
}

function formatLine(line: LogLine): string {
  const role = line.role.trim().toLowerCase();
  if (role === "nanobot" || role === "nanobot-progress") {
    return line.text.replace(/^(?:nanobot|napbot)\b\s*[:>-]?\s*/i, "");
  }
  if (role === "user") {
    return line.text;
  }
  if (role === "system" || role === "wisper") {
    return line.text;
  }
  if (role === "tool") {
    return `tool: ${line.text}`;
  }
  return `${line.role}: ${line.text}`;
}

function toChatRole(line: LogLine): ChatRole {
  const role = line.role.trim().toLowerCase();
  if (role === "user") return "user";
  if (role === "nanobot" || role === "nanobot-progress") return "assistant";
  if (role === "tool") return "tool";
  return "system";
}

function isEphemeralLine(line: LogLine): boolean {
  const role = line.role.trim().toLowerCase();
  if (role !== "system") return false;
  return /^(connected|disconnected)\.?$/i.test(line.text.trim());
}

function deriveActivityStatus(
  lines: LogLine[],
  agentState: AgentState,
  textStreaming: boolean,
  sending: boolean,
): ActivityStatus | null {
  if (!textStreaming && !sending && agentState === "idle") return null;

  const latestUserId = [...lines].reverse().find((line) => line.role === "user")?.id ?? -1;
  const latestAssistantProgress = [...lines]
    .reverse()
    .find((line) => line.id > latestUserId && line.role === "nanobot-progress");
  const latestTool = [...lines]
    .reverse()
    .find((line) => line.id > latestUserId && line.role === "tool");

  if (latestAssistantProgress) {
    return { tone: "replying", label: "Nanobot is replying" };
  }
  if (latestTool && (textStreaming || sending)) {
    return {
      tone: "tool",
      label: "Nanobot is using a tool",
      detail: latestTool.text.trim() || undefined,
    };
  }
  if (agentState === "thinking") {
    return { tone: "thinking", label: "Nanobot is thinking" };
  }
  if (agentState === "speaking") {
    return { tone: "replying", label: "Nanobot is replying" };
  }
  if (textStreaming || sending) {
    return { tone: "waiting", label: "Waiting for Nanobot" };
  }
  return null;
}

function LogCompose({
  disabled,
  sending,
  text,
  setText,
  onClose,
  onSend,
  onOpenVoiceMode,
  showClose,
  contextLabel,
  onClearContext,
  activityStatus,
}: {
  disabled: boolean;
  sending: boolean;
  text: string;
  setText(value: string): void;
  onClose(): void;
  onSend(): void;
  onOpenVoiceMode?: () => void;
  showClose: boolean;
  contextLabel?: string | null;
  onClearContext?: () => void;
  activityStatus?: ActivityStatus | null;
}) {
  const onKeyDown = useCallback(
    (e: KeyboardEvent) => {
      if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        onSend();
      }
      if (e.key === "Escape") {
        e.preventDefault();
        onClose();
      }
    },
    [onClose, onSend],
  );

  return (
    <div id="log-compose">
      {showClose ? (
        <div id="log-compose-toolbar">
          <button id="log-close-btn" type="button" aria-label="Close" onClick={onClose}>
            <CloseIcon />
          </button>
        </div>
      ) : null}
      <div id="log-compose-field">
        {activityStatus ? (
          <div id="log-compose-status" data-tone={activityStatus.tone} aria-live="polite">
            <span id="log-compose-status-dots" aria-hidden="true">
              <span class="log-compose-status-dot" />
              <span class="log-compose-status-dot" />
              <span class="log-compose-status-dot" />
            </span>
            <span id="log-compose-status-label">{activityStatus.label}</span>
            {activityStatus.detail ? (
              <span id="log-compose-status-detail">{activityStatus.detail}</span>
            ) : null}
          </div>
        ) : null}
        {contextLabel ? (
          <div id="log-compose-context">
            <button
              id="log-compose-context-chip"
              type="button"
              onClick={() => onClearContext?.()}
              disabled={disabled || sending}
            >
              [{contextLabel}]
            </button>
          </div>
        ) : null}
        <textarea
          id="log-compose-input"
          placeholder="Type a message to nanobot..."
          disabled={disabled || sending}
          value={text}
          onInput={(e) => setText((e.target as HTMLTextAreaElement).value)}
          onKeyDown={onKeyDown}
        />
        <div id="log-compose-actions">
          {onOpenVoiceMode ? (
            <button
              id="log-voice-btn"
              type="button"
              aria-label="Open voice mode"
              disabled={disabled || sending}
              onClick={onOpenVoiceMode}
            >
              <SpeakerIcon />
            </button>
          ) : null}
          <button
            id="log-send-btn"
            type="button"
            aria-label="Send message"
            disabled={disabled || sending || text.trim().length === 0}
            onClick={onSend}
          >
            <SendIcon />
          </button>
        </div>
      </div>
    </div>
  );
}

function ExpandedLogView({ lines, scrollRef }: LogViewProps) {
  return (
    <div
      id="log-scroll"
      ref={(node) => {
        scrollRef.current = node;
      }}
    >
      <div id="log-inner">
        {lines.map((line) => (
          <div key={line.id} class={`line ${line.role}`}>
            {formatLine(line)}
          </div>
        ))}
      </div>
    </div>
  );
}

function ChatLogView({ lines, scrollRef }: LogViewProps) {
  const visibleLines = lines.filter((line) => !isEphemeralLine(line));

  return (
    <div
      id="chat-scroll"
      ref={(node) => {
        scrollRef.current = node;
      }}
    >
      {visibleLines.length === 0 ? (
        <div id="chat-empty">
          <div id="chat-empty-eyebrow">Nanobot</div>
          <h1 id="chat-empty-title">Start a conversation</h1>
          <p id="chat-empty-copy">
            Ask a question, plan something, or switch to voice when you want the live interface.
          </p>
        </div>
      ) : (
        <div id="chat-inner">
          {visibleLines.map((line) => {
            const chatRole = toChatRole(line);
            return (
              <div key={line.id} class={`chat-row ${chatRole}`}>
                <div class={`chat-bubble ${chatRole}`}>
                  {chatRole === "user" && line.contextLabel ? (
                    <span class="chat-context-inline">[{line.contextLabel}] </span>
                  ) : null}
                  {formatLine(line)}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function CollapsedLogView({ lines, scrollRef, onExpand }: LogViewProps & { onExpand(): void }) {
  return (
    <button
      id="log-collapsed"
      ref={(node) => {
        scrollRef.current = node;
      }}
      type="button"
      aria-label="Open message composer"
      onClick={onExpand}
    >
      <div id="log-inner">
        {lines.map((line) => (
          <span key={line.id} class={`line ${line.role}`}>
            {formatLine(line)}
          </span>
        ))}
      </div>
    </button>
  );
}

export function LogPanel({
  lines,
  disabled,
  onSendMessage,
  onOpenVoiceMode,
  onExpandChange,
  contextLabel,
  onClearContext,
  agentState,
  textStreaming,
  fullScreen = false,
}: Props) {
  const [expanded, setExpanded] = useState(false);
  const [text, setText] = useState("");
  const [sending, setSending] = useState(false);
  const scrollRef = useRef<HTMLElement>(null);
  const isExpanded = fullScreen || expanded;
  const activityStatus = useMemo(
    () => (fullScreen ? deriveActivityStatus(lines, agentState, textStreaming, sending) : null),
    [agentState, fullScreen, lines, sending, textStreaming],
  );

  useEffect(
    () => onExpandChange?.(fullScreen ? false : isExpanded),
    [fullScreen, isExpanded, onExpandChange],
  );
  useEffect(() => () => onExpandChange?.(false), [onExpandChange]);
  useEffect(() => {
    if (fullScreen) setExpanded(true);
  }, [fullScreen]);
  useEffect(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [lines, isExpanded]);

  const collapse = useCallback(() => {
    if (fullScreen) return;
    setExpanded(false);
    setText("");
  }, [fullScreen]);

  const expand = useCallback(() => {
    if (!expanded) setExpanded(true);
  }, [expanded]);

  const send = useCallback(async () => {
    const message = text.trim();
    if (!message || sending || disabled) return;
    setSending(true);
    try {
      await onSendMessage(message);
      setText("");
    } catch (err) {
      window.alert(`Could not send message: ${String(err)}`);
    } finally {
      setSending(false);
    }
  }, [disabled, onSendMessage, sending, text]);

  return (
    <div
      id="log"
      class={`${isExpanded ? "expanded" : ""}${fullScreen ? " chat-mode" : ""}`}
      data-no-swipe={fullScreen ? undefined : "1"}
    >
      {fullScreen ? (
        <ChatLogView lines={lines} scrollRef={scrollRef} />
      ) : isExpanded ? (
        <ExpandedLogView lines={lines} scrollRef={scrollRef} />
      ) : (
        <CollapsedLogView lines={lines} scrollRef={scrollRef} onExpand={expand} />
      )}
      {isExpanded && (
        <LogCompose
          disabled={disabled}
          sending={sending}
          text={text}
          setText={setText}
          onClose={collapse}
          onSend={() => {
            void send();
          }}
          onOpenVoiceMode={fullScreen ? () => void onOpenVoiceMode?.() : undefined}
          showClose={!fullScreen}
          activityStatus={activityStatus}
          contextLabel={contextLabel}
          onClearContext={onClearContext}
        />
      )}
    </div>
  );
}
