﻿import { useEffect, useRef, useState } from "react";
import type { AssistantConversationItem, AssistantSelectionChip } from "../state/types";
import { buildConversationExportForCopy } from "../utils/conversationExport";
import { JsonView } from "./JsonView";
import { PanelFrame } from "./PanelFrame";

interface AssistantPanelProps {
  sessionId: string;
  conversation: AssistantConversationItem[];
  inputValue: string;
  onInputChange: (value: string) => void;
  selectedContextChip: AssistantSelectionChip | null;
  onSelectContextChip: (value: AssistantSelectionChip) => void;
  onClearContextChip: () => void;
  useMock: boolean;
  onUseMockChange: (value: boolean) => void;
  onSend: () => Promise<void> | void;
  onClear: () => void;
  busy: boolean;
  statusText: string;
  errorMessage: string;
  showCommentAction?: boolean;
  onCommentAssistantMessage?: (item: AssistantConversationItem, index: number) => void;
  isAssistantMessageCommented?: (item: AssistantConversationItem, index: number) => boolean;
  canCommentAssistantMessage?: (item: AssistantConversationItem, index: number) => boolean;
}

function roleLabel(role: AssistantConversationItem["role"]): string {
  return role === "assistant" ? "Ассистент" : "Вы";
}

function shortTime(iso: string): string {
  const date = new Date(iso);
  if (Number.isNaN(date.getTime())) {
    return iso;
  }
  return date.toLocaleTimeString("ru-RU");
}

async function copyTextToClipboard(text: string): Promise<boolean> {
  if (navigator.clipboard && window.isSecureContext) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch {
      // Fall back to legacy path below.
    }
  }

  const textarea = document.createElement("textarea");
  textarea.value = text;
  textarea.setAttribute("readonly", "true");
  textarea.style.position = "fixed";
  textarea.style.opacity = "0";
  textarea.style.pointerEvents = "none";
  document.body.appendChild(textarea);
  textarea.select();

  let copied = false;
  try {
    copied = document.execCommand("copy");
  } catch {
    copied = false;
  } finally {
    document.body.removeChild(textarea);
  }

  return copied;
}

function CommentBubbleIcon({ commented }: { commented: boolean }) {
  const className = commented ? "comment-icon-svg commented" : "comment-icon-svg";
  return (
    <svg className={className} viewBox="0 0 24 24" aria-hidden="true" focusable="false">
      <path d="M5 6.5h14v9H11.5l-4.5 3v-3H5z" />
    </svg>
  );
}

function normalizeAssistantMessageText(raw: string): string {
  return raw
    .replace(/\r\n?/g, "\n")
    .replace(/([^\n])\s+(Блок\s+\d+\.)/gi, "$1\n\n$2")
    .replace(/([^\n])\s+(\d+\.\s)/g, "$1\n$2");
}

function splitAssistantMessageBlocks(text: string): string[] {
  const normalized = normalizeAssistantMessageText(text);
  const lines = normalized.split("\n");
  const blocks: string[] = [];
  let current: string[] = [];

  const flush = () => {
    if (current.length === 0) return;
    blocks.push(current.join("\n"));
    current = [];
  };

  for (const rawLine of lines) {
    const line = rawLine.trimEnd();
    const trimmed = line.trim();
    if (!trimmed) {
      flush();
      continue;
    }

    const isBlockHeading = /^Блок\s+\d+\./i.test(trimmed);
    const isNumberedItem = /^\d+\.\s/.test(trimmed);
    if ((isBlockHeading || isNumberedItem) && current.length > 0) {
      flush();
    }

    current.push(line);
  }

  flush();
  return blocks.length > 0 ? blocks : [text];
}

function renderInlineBold(text: string, keyPrefix: string): JSX.Element[] {
  const result: JSX.Element[] = [];
  const regex = /\*\*(.+?)\*\*/g;
  let lastIndex = 0;
  let partIndex = 0;
  let match: RegExpExecArray | null;

  while ((match = regex.exec(text)) !== null) {
    if (match.index > lastIndex) {
      result.push(<span key={`${keyPrefix}-t-${partIndex}`}>{text.slice(lastIndex, match.index)}</span>);
      partIndex += 1;
    }

    result.push(<strong key={`${keyPrefix}-b-${partIndex}`}>{match[1]}</strong>);
    partIndex += 1;
    lastIndex = regex.lastIndex;
  }

  if (lastIndex < text.length) {
    result.push(<span key={`${keyPrefix}-t-${partIndex}`}>{text.slice(lastIndex)}</span>);
  }

  return result.length > 0 ? result : [<span key={`${keyPrefix}-raw`}>{text}</span>];
}

function lineClassName(line: string): string {
  const trimmed = line.trimStart();
  if (/^Блок\s+\d+\./i.test(trimmed)) return "assistant-msg-line heading";
  if (/^\d+\.\s/.test(trimmed)) return "assistant-msg-line numbered";
  if (/^-\s/.test(trimmed)) return "assistant-msg-line bullet";
  return "assistant-msg-line";
}

function buildSelectionPreview(label: string, maxChars = 40): string {
  const normalized = label.replace(/\s+/g, " ").trim();
  if (normalized.length <= maxChars) {
    return normalized;
  }

  const firstWords = normalized.split(" ").slice(0, 3).join(" ").trim();
  if (firstWords.length >= 10 && firstWords.length <= maxChars) {
    return `${firstWords}…`;
  }

  return `${normalized.slice(0, maxChars - 1).trimEnd()}…`;
}

function stripBlockSelectionPrefix(line: string): string {
  return line.replace(/\*\*(.+?)\*\*/g, "$1").replace(/^\d+\.\s*/, "").trim();
}

function extractSelectionAnchorText(block: string): string {
  const firstLine = block
    .replace(/\r\n?/g, "\n")
    .split("\n")
    .map((line) => line.trim())
    .find(Boolean);
  const normalizedFirstLine = stripBlockSelectionPrefix(firstLine ?? "");
  const primarySegment = normalizedFirstLine.split("|")[0]?.trim() ?? normalizedFirstLine;
  return primarySegment.replace(/\s+/g, " ").trim();
}

function isSelectableEntityBlock(block: string): boolean {
  const firstLine = block
    .replace(/\r\n?/g, "\n")
    .split("\n")
    .map((line) => line.trim())
    .find(Boolean);
  if (!firstLine || !/^\d+\.\s/.test(firstLine)) {
    return false;
  }
  const normalizedFirstLine = stripBlockSelectionPrefix(firstLine);
  return normalizedFirstLine.includes("|");
}

function buildSelectionChipForBlock(item: AssistantConversationItem, block: string): AssistantSelectionChip | null {
  const normalizedBlock = block
    .replace(/\r\n?/g, "\n")
    .replace(/\*\*(.+?)\*\*/g, "$1")
    .split("\n")
    .map((line, index) => {
      const trimmed = line.trim();
      if (index === 0) {
        return trimmed.replace(/^\d+\.\s*/, "");
      }
      return trimmed;
    })
    .filter(Boolean)
    .join(" ")
    .replace(/\s+/g, " ")
    .trim();

  if (!normalizedBlock) {
    return null;
  }

  const anchorText = extractSelectionAnchorText(block) || normalizedBlock;
  return {
    message_id: item.message_id,
    source_text: normalizedBlock,
    anchor_text: anchorText,
    preview_text: buildSelectionPreview(anchorText)
  };
}

function renderAssistantMessageBody(
  item: AssistantConversationItem,
  selectedContextChip: AssistantSelectionChip | null,
  onSelectContextChip: (value: AssistantSelectionChip) => void,
  onClearContextChip: () => void
): JSX.Element[] {
  const blocks = splitAssistantMessageBlocks(item.text);
  return blocks.map((block, blockIndex) => {
    const lines = block.split("\n");
    const isSelectable = item.role === "assistant" && isSelectableEntityBlock(block);
    const selectionChip = isSelectable ? buildSelectionChipForBlock(item, block) : null;
    const selected =
      Boolean(selectionChip) &&
      selectedContextChip?.message_id === selectionChip?.message_id &&
      selectedContextChip?.source_text === selectionChip?.source_text;

    const body = lines.map((line, lineIndex) => (
      <p key={`line-${blockIndex}-${lineIndex}`} className={lineClassName(line)}>
        {renderInlineBold(line, `line-${blockIndex}-${lineIndex}`)}
      </p>
    ));

    if (!isSelectable || !selectionChip) {
      return (
        <div key={`block-${blockIndex}`} className="assistant-msg-block">
          {body}
        </div>
      );
    }

    return (
      <div
        key={`block-${blockIndex}`}
        className={selected ? "assistant-msg-block selectable active" : "assistant-msg-block selectable"}
        role="button"
        tabIndex={0}
        onClick={() => {
          if (selected) {
            onClearContextChip();
            return;
          }
          onSelectContextChip(selectionChip);
        }}
        onKeyDown={(event) => {
          if (event.key !== "Enter" && event.key !== " ") {
            return;
          }
          event.preventDefault();
          if (selected) {
            onClearContextChip();
            return;
          }
          onSelectContextChip(selectionChip);
        }}
      >
        {body}
      </div>
    );
  });
}

export function AssistantPanel({
  sessionId,
  conversation,
  inputValue,
  onInputChange,
  selectedContextChip,
  onSelectContextChip,
  onClearContextChip,
  useMock,
  onUseMockChange,
  onSend,
  onClear,
  busy,
  statusText,
  errorMessage,
  showCommentAction = false,
  onCommentAssistantMessage,
  isAssistantMessageCommented,
  canCommentAssistantMessage
}: AssistantPanelProps) {
  const listRef = useRef<HTMLDivElement | null>(null);
  const stickToBottomRef = useRef(true);
  const copyResetTimerRef = useRef<number | null>(null);
  const [copyState, setCopyState] = useState<"idle" | "success" | "error">("idle");
  const [copyModeLabel, setCopyModeLabel] = useState<"чат" | "тех">("чат");

  function scrollChatToBottom(forceStick = false): void {
    if (!listRef.current) return;
    if (forceStick) {
      stickToBottomRef.current = true;
    }
    listRef.current.scrollTop = listRef.current.scrollHeight;
  }

  useEffect(() => {
    if (stickToBottomRef.current) {
      scrollChatToBottom();
    }
  }, [conversation]);

  useEffect(() => {
    return () => {
      if (copyResetTimerRef.current !== null) {
        window.clearTimeout(copyResetTimerRef.current);
      }
    };
  }, []);

  async function handleCopyConversation(mode: "default" | "technical"): Promise<void> {
    if (conversation.length === 0) {
      return;
    }

    const exportText = buildConversationExportForCopy(sessionId, conversation, mode);
    const copied = await copyTextToClipboard(exportText);
    setCopyModeLabel(mode === "technical" ? "тех" : "чат");
    setCopyState(copied ? "success" : "error");

    if (copyResetTimerRef.current !== null) {
      window.clearTimeout(copyResetTimerRef.current);
    }
    copyResetTimerRef.current = window.setTimeout(() => {
      setCopyState("idle");
    }, 2200);
  }

  function handleChatScroll(): void {
    if (!listRef.current) return;
    const node = listRef.current;
    const distanceToBottom = node.scrollHeight - node.scrollTop - node.clientHeight;
    stickToBottomRef.current = distanceToBottom < 16;
  }

  return (
    <PanelFrame className="assistant-panel-frame" title="Режим ассистента">
      <div className="assistant-live-shell">
        <div className="assistant-toolbar">
          <div className="assistant-toolbar-actions">
            <button
              type="button"
              className="assistant-copy-btn"
              onClick={() => {
                void handleCopyConversation("default");
              }}
              disabled={conversation.length === 0}
              title="Экспорт только user-facing чата"
            >
              Скопировать чат
            </button>
            <button
              type="button"
              className="assistant-copy-btn"
              onClick={() => {
                void handleCopyConversation("technical");
              }}
              disabled={conversation.length === 0}
              title="Технический экспорт с debug payload"
            >
              Скопировать техчат
            </button>
            <button type="button" className="assistant-copy-btn" onClick={() => onClear()} disabled={busy && conversation.length === 0}>
              Сбросить сессию
            </button>
          </div>
          <div className="assistant-toolbar-meta">
            {sessionId ? <span className="status-chip">{`session: ${sessionId}`}</span> : null}
            <div className="assistant-toolbar-meta-right">
              {statusText ? <span className="assistant-live-status">{statusText}</span> : null}
              {copyState === "success" ? <span className="assistant-copy-feedback success">Скопировано ({copyModeLabel})</span> : null}
              {copyState === "error" ? <span className="assistant-copy-feedback error">Ошибка копирования</span> : null}
            </div>
          </div>
          {errorMessage ? <p className="error-text assistant-toolbar-error">{errorMessage}</p> : null}
        </div>

        <div ref={listRef} className="assistant-chat-list" onScroll={handleChatScroll}>
          {conversation.map((item, index) => {
            const commentEnabled =
              item.role === "assistant" &&
              showCommentAction &&
              typeof onCommentAssistantMessage === "function" &&
              (typeof canCommentAssistantMessage === "function" ? canCommentAssistantMessage(item, index) : true);
            const commented =
              item.role === "assistant" && typeof isAssistantMessageCommented === "function"
                ? isAssistantMessageCommented(item, index)
                : false;
            return (
            <article key={item.message_id} className={`assistant-msg ${item.role}`}>
              <header className="assistant-msg-head">
                <div className="assistant-msg-head-main">
                  <strong>{roleLabel(item.role)}</strong>
                  <span>{shortTime(item.created_at)}</span>
                </div>
                {item.role === "assistant" && showCommentAction ? (
                  <div className="assistant-msg-head-actions">
                    <button
                      type="button"
                      className={commented ? "autoruns-comment-icon assistant-comment-btn commented" : "autoruns-comment-icon assistant-comment-btn"}
                      onClick={() => onCommentAssistantMessage?.(item, index)}
                      disabled={!commentEnabled}
                      title={
                        commentEnabled
                          ? "Комментировать ответ ассистента"
                          : "Комментарий недоступен для этого сообщения"
                      }
                      aria-label={
                        commentEnabled
                          ? "Комментировать ответ ассистента"
                          : "Комментарий недоступен для этого сообщения"
                      }
                    >
                      <CommentBubbleIcon commented={commented} />
                    </button>
                  </div>
                ) : null}
              </header>
              <div className="assistant-msg-body">
                {renderAssistantMessageBody(item, selectedContextChip, onSelectContextChip, onClearContextChip)}
              </div>
              {item.role === "assistant" && item.debug ? (
                <details className="assistant-debug">
                  <summary>Показать технический разбор</summary>
                  <JsonView value={item.debug} />
                </details>
              ) : null}
            </article>
            );
          })}
        </div>

        <div className="assistant-compose">
          {selectedContextChip ? (
            <div className="assistant-compose-context">
              <span className="assistant-compose-context-label">Выбранный объект</span>
              <div className="assistant-compose-context-pill" title={selectedContextChip.source_text}>
                <span className="assistant-compose-context-pill-text">{selectedContextChip.preview_text}</span>
                <button
                  type="button"
                  className="assistant-compose-context-clear"
                  onClick={onClearContextChip}
                  aria-label="Убрать выбранный объект"
                  title="Убрать выбранный объект"
                >
                  ×
                </button>
              </div>
            </div>
          ) : null}
          <label className="full-width">
            Сообщение
            <textarea
              className="assistant-input-textarea"
              value={inputValue}
              onChange={(event) => onInputChange(event.target.value)}
              rows={4}
              placeholder={
                selectedContextChip
                  ? "Продолжите вопрос по выбранному объекту..."
                  : "Введите вопрос к данным компании..."
              }
            />
          </label>
          <div className="button-row assistant-send-row">
            <label className="checkbox-row">
              <input type="checkbox" checked={useMock} onChange={(event) => onUseMockChange(event.target.checked)} />
              Mock-режим
            </label>
            <button
              type="button"
              className="assistant-send-btn"
              onClick={() => {
                scrollChatToBottom(true);
                void onSend();
              }}
              disabled={busy || !inputValue.trim()}
            >
              {busy ? "Выполняю..." : "Отправить"}
            </button>
          </div>
        </div>
      </div>
    </PanelFrame>
  );
}
