<script setup lang="ts">
import type {BotInfo} from '@easyflow/types';
import type {ServerSentEventMessage} from 'fetch-event-stream';

import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue';
import {useRoute} from 'vue-router';
import ElXMarkdown from 'vue-element-plus-x/es/XMarkdown/index.js';

import {IconifyIcon} from '@easyflow/icons';
import {$t} from '@easyflow/locales';
import {useAccessStore} from '@easyflow/stores';
import {uuid} from '@easyflow/utils';
import {useTitle} from '@vueuse/core';

import {ElAvatar, ElButton, ElInput, ElSkeleton} from 'element-plus';

import {api, sseClient} from '#/api/request';
import defaultBotAvatar from '#/assets/ai/bot/defaultBotAvatar.png';

type MessageRole = 'assistant' | 'user';
type ToolStatus = 'TOOL_CALL' | 'TOOL_RESULT';

interface ToolTraceItem {
  id: string;
  name: string;
  status: ToolStatus;
  arguments?: string;
  result?: string;
}

interface AssistantTextSegment {
  id: string;
  type: 'text';
  content: string;
}

interface AssistantToolSegment {
  id: string;
  type: 'tool';
  toolId: string;
}

type AssistantSegment = AssistantTextSegment | AssistantToolSegment;

interface BubbleMessage {
  id: string;
  role: MessageRole;
  content: string;
  createdAt: number;
  loading?: boolean;
  toolCalls?: ToolTraceItem[];
  segments?: AssistantSegment[];
}

interface HistoryMessage {
  content: string;
  role: string;
}

const route = useRoute();
const accessStore = useAccessStore();
const pageTitle = useTitle();

const botInfo = ref<BotInfo | null>(null);
const conversationId = ref<string>('');
const initializing = ref(true);
const blocked = ref(false);
const initError = ref('');
const sending = ref(false);
const userStoppedStreaming = ref(false);
const isComposing = ref(false);
const senderValue = ref('');
const messages = ref<BubbleMessage[]>([]);
const historyMessages = ref<HistoryMessage[]>([]);
const messageContainerRef = ref<HTMLElement | null>(null);
const dockRef = ref<HTMLElement | null>(null);
const shouldAutoScroll = ref(true);
const dockHeight = ref(170);
const collapsedToolIds = ref<Set<string>>(new Set());
const botId = computed(() => String(route.params.botId || ''));
const isEmbedMode = computed(() => String(route.query.embed || '') === '1');
const presetQuestions = computed(() =>
  (botInfo.value?.options?.presetQuestions || []).filter(
    (item: any) => typeof item?.description === 'string' && item.description.trim() !== '',
  ),
);
const messageContainerStyle = computed(() => ({
  '--public-chat-dock-space': `${Math.max(dockHeight.value + 14, 132)}px`,
}));
let dockResizeObserver: null | ResizeObserver = null;

const SCROLL_FOLLOW_THRESHOLD = 48;
const setPageTitle = () => {
  const botTitle = String(botInfo.value?.title || '').trim();
  pageTitle.value = botTitle || $t('bot.publicChatTitle');
};

const isAtBottom = () => {
  if (!messageContainerRef.value) return true;
  const { scrollTop, scrollHeight, clientHeight } = messageContainerRef.value;
  return scrollHeight - (scrollTop + clientHeight) <= SCROLL_FOLLOW_THRESHOLD;
};

const onMessageContainerScroll = () => {
  shouldAutoScroll.value = isAtBottom();
};

const updateDockHeight = () => {
  dockHeight.value = dockRef.value?.offsetHeight || 0;
};

const scrollToBottom = (force = false) => {
  nextTick(() => {
    if (!messageContainerRef.value) return;
    if (!force && !shouldAutoScroll.value) return;
    messageContainerRef.value.scrollTop = messageContainerRef.value.scrollHeight;
  });
};

const resetConversationState = () => {
  botInfo.value = null;
  conversationId.value = '';
  sending.value = false;
  senderValue.value = '';
  messages.value = [];
  historyMessages.value = [];
  blocked.value = false;
  initError.value = '';
  shouldAutoScroll.value = true;
  collapsedToolIds.value = new Set();
};

const appendMessage = (role: MessageRole, content = '', loading = false) => {
  const item: BubbleMessage = {
    id: uuid(),
    role,
    content,
    createdAt: Date.now(),
    loading,
  };
  if (role === 'assistant' && content.trim()) {
    item.segments = [
      {
        id: uuid(),
        type: 'text',
        content,
      },
    ];
  }
  messages.value.push(item);
  scrollToBottom(true);
};

const updateLastAssistantMessage = (patch: Partial<BubbleMessage>) => {
  const lastIndex = messages.value.length - 1;
  if (lastIndex < 0) return;
  const last = messages.value[lastIndex];
  if (!last || last.role !== 'assistant') return;
  messages.value[lastIndex] = { ...last, ...patch };
  scrollToBottom();
};

const normalizeEventKey = (value: any) => String(value || '').toUpperCase();
const safeJsonParse = (value: string) => {
  try {
    return JSON.parse(value);
  } catch {
    return null;
  }
};
const safeJsonParseLoose = (value: string) => {
  const parsed = safeJsonParse(value);
  if (parsed) {
    return parsed;
  }
  const firstBrace = value.indexOf('{');
  const lastBrace = value.lastIndexOf('}');
  if (firstBrace < 0 || lastBrace <= firstBrace) {
    return null;
  }
  return safeJsonParse(value.slice(firstBrace, lastBrace + 1));
};

const normalizeToolPayloadValue = (value: any) => {
  if (value === null || value === undefined) return '';
  if (typeof value === 'string') return value;
  try {
    return JSON.stringify(value, null, 2);
  } catch {
    return String(value);
  }
};

const getLastAssistantMessage = () => {
  const last = messages.value[messages.value.length - 1];
  if (!last || last.role !== 'assistant') {
    return null;
  }
  return last;
};

const appendAssistantTextDelta = (delta: string) => {
  if (!delta) return;
  const last = getLastAssistantMessage();
  if (!last) return;
  const segments = [...(last.segments || [])];
  const tail = segments[segments.length - 1];
  if (tail && tail.type === 'text') {
    segments[segments.length - 1] = {
      ...tail,
      content: `${tail.content}${delta}`,
    };
  } else {
    segments.push({
      id: uuid(),
      type: 'text',
      content: delta,
    });
  }
  updateLastAssistantMessage({
    content: `${last.content}${delta}`,
    segments,
    loading: true,
  });
};

const getToolById = (message: BubbleMessage, toolId: string) =>
  (message.toolCalls || []).find((item) => item.id === toolId);

const getRenderableSegments = (message: BubbleMessage) => {
  if (message.role !== 'assistant') {
    return [];
  }
  const segments = [...(message.segments || [])];
  const hasTextSegment = segments.some(
    (segment) => segment.type === 'text' && segment.content.trim() !== '',
  );
  if (!hasTextSegment && message.content.trim()) {
    segments.push({
      id: `${message.id}-fallback-text`,
      type: 'text',
      content: message.content,
    });
  }
  return segments;
};

const hasAssistantRenderableContent = (message: BubbleMessage) => {
  const segments = getRenderableSegments(message);
  return segments.some((segment) => {
    if (segment.type === 'text') {
      return segment.content.trim() !== '';
    }
    return Boolean(getToolById(message, segment.toolId));
  });
};

const upsertToolTrace = (patch: Partial<ToolTraceItem> & { id?: string }) => {
  const last = getLastAssistantMessage();
  if (!last) {
    return;
  }
  const nextToolCalls = [...(last.toolCalls || [])];
  let id = String(patch.id || '');
  if (!id && patch.status === 'TOOL_RESULT') {
    const pending = [...nextToolCalls].reverse().find((item) => item.status !== 'TOOL_RESULT');
    if (pending) {
      id = pending.id;
    }
  }
  if (!id) {
    id = uuid();
  }
  const idx = nextToolCalls.findIndex((item) => item.id === id);
  const current = idx >= 0 ? nextToolCalls[idx] : null;
  const next: ToolTraceItem = {
    id,
    name: String(patch.name || current?.name || $t('bot.publicChatToolUnknown')),
    status: (patch.status || current?.status || 'TOOL_CALL') as ToolStatus,
    arguments: patch.arguments ?? current?.arguments,
    result: patch.result ?? current?.result,
  };
  if (idx >= 0) {
    nextToolCalls[idx] = next;
  } else {
    nextToolCalls.push(next);
  }
  const segments = [...(last.segments || [])];
  const hasToolSegment = segments.some(
    (segment) => segment.type === 'tool' && segment.toolId === id,
  );
  if (!hasToolSegment) {
    segments.push({
      id: uuid(),
      type: 'tool',
      toolId: id,
    });
    const nextCollapsed = new Set(collapsedToolIds.value);
    nextCollapsed.add(id);
    collapsedToolIds.value = nextCollapsed;
  }
  updateLastAssistantMessage({
    toolCalls: nextToolCalls,
    segments,
    loading: true,
  });
};

const isToolCollapsed = (toolId: string) => collapsedToolIds.value.has(toolId);
const toggleToolCollapsed = (toolId: string) => {
  const next = new Set(collapsedToolIds.value);
  if (next.has(toolId)) {
    next.delete(toolId);
  } else {
    next.add(toolId);
  }
  collapsedToolIds.value = next;
};

const extractToolMetaFromPayload = (payload: any) => {
  if (!payload || typeof payload !== 'object') {
    return null;
  }
  const id = payload?.tool_call_id || payload?.toolCallId || payload?.id || '';
  const name = payload?.name || payload?.toolName || '';
  const hasArgs = payload?.arguments !== undefined;
  const hasResult = payload?.result !== undefined || payload?.content !== undefined;
  if (!id && !name && !hasArgs && !hasResult) {
    return null;
  }
  return {
    id: String(id || ''),
    name: String(name || ''),
    arguments: hasArgs ? normalizeToolPayloadValue(payload?.arguments) : undefined,
    result: hasResult
      ? normalizeToolPayloadValue(payload?.result !== undefined ? payload?.result : payload?.content)
      : undefined,
  };
};

const handleToolSseEvent = (sseData: any, normalizedType?: string) => {
  const payload = sseData?.payload || {};
  const inferred = extractToolMetaFromPayload(payload);
  const normalized = normalizedType || normalizeEventKey(sseData?.type);
  let status = normalized as ToolStatus;
  if (status !== 'TOOL_CALL' && status !== 'TOOL_RESULT') {
    if (inferred?.result && !inferred?.arguments) {
      status = 'TOOL_RESULT';
    } else if (inferred?.arguments || inferred?.name || inferred?.id) {
      status = 'TOOL_CALL';
    } else {
      return;
    }
  }
  if (!inferred) {
    return;
  }
  upsertToolTrace({
    id: inferred.id,
    name: inferred.name,
    status,
    arguments:
      status === 'TOOL_CALL'
        ? inferred.arguments
        : undefined,
    result:
      status === 'TOOL_RESULT'
        ? inferred.result
        : undefined,
  });
};

const handleNeedSaveToolMessage = (role: string, content: string) => {
  if (role === 'assistant') {
    const parsed = safeJsonParseLoose(content);
    const toolCalls = Array.isArray(parsed?.toolCalls) ? parsed.toolCalls : [];
    for (const call of toolCalls) {
      upsertToolTrace({
        id: String(call?.id || uuid()),
        name: String(call?.name || ''),
        status: 'TOOL_CALL',
        arguments: normalizeToolPayloadValue(call?.arguments),
      });
    }
    return;
  }

  if (role === 'tool') {
    const parsed = safeJsonParseLoose(content);
    const toolCallId = String(parsed?.toolCallId || parsed?.tool_call_id || '');
    const resultText =
      typeof parsed?.content === 'string'
        ? parsed.content
        : normalizeToolPayloadValue(parsed || content);
    upsertToolTrace({
      id: toolCallId || uuid(),
      status: 'TOOL_RESULT',
      result: resultText,
    });
  }
};

const onSseMessage = (event: ServerSentEventMessage) => {
  if (event.event === 'done') {
    sending.value = false;
    updateLastAssistantMessage({ loading: false });
    return;
  }
  if (!event.data) {
    return;
  }

  let sseData: any = null;
  try {
    sseData = JSON.parse(event.data);
  } catch {
    return;
  }
  const eventDomain = normalizeEventKey(sseData?.domain);
  const eventType = normalizeEventKey(sseData?.type);
  if (
    eventDomain === 'SYSTEM' &&
    sseData?.payload?.code === 'SYSTEM_ERROR'
  ) {
    sending.value = false;
    updateLastAssistantMessage({
      content: sseData.payload?.message || $t('bot.publicChatInitError'),
      loading: false,
    });
    return;
  }

  if (
    eventDomain === 'TOOL'
    || eventType === 'TOOL_CALL'
    || eventType === 'TOOL_RESULT'
  ) {
    handleToolSseEvent(sseData, eventType);
    return;
  }

  if (event.event === 'needSaveMessage') {
    const role = String(sseData?.payload?.role || '').trim().toLowerCase();
    const content = sseData?.payload?.content;
    if (
      (role === 'assistant' || role === 'user' || role === 'system' || role === 'tool')
      && typeof content === 'string'
    ) {
      historyMessages.value.push({ role, content });
      handleNeedSaveToolMessage(role, content);
    }
    return;
  }

  if (eventType === 'MESSAGE' && eventDomain === 'LLM') {
    const deltaRaw = sseData?.payload?.delta ?? sseData?.payload?.content ?? '';
    const delta = typeof deltaRaw === 'string' ? deltaRaw : normalizeToolPayloadValue(deltaRaw);
    appendAssistantTextDelta(delta);
  }

  handleToolSseEvent(sseData, eventType);
};

const doSendMessage = () => {
  const prompt = senderValue.value.trim();
  if (!prompt || sending.value || blocked.value || !conversationId.value) {
    return;
  }

  appendMessage('user', prompt);
  appendMessage('assistant', '', true);
  shouldAutoScroll.value = true;
  userStoppedStreaming.value = false;
  senderValue.value = '';
  sending.value = true;

  const requestMessages = [...historyMessages.value, { role: 'user', content: prompt }];

  sseClient.post(
    '/api/v1/bot/chat',
    {
      botId: botId.value,
      prompt,
      conversationId: conversationId.value,
      messages: requestMessages,
    },
    {
      onMessage: onSseMessage,
      onFinished: () => {
        sending.value = false;
        updateLastAssistantMessage({ loading: false });
      },
      onError: (error: any) => {
        sending.value = false;
        const abortLike =
          userStoppedStreaming.value
          || error?.name === 'AbortError'
          || String(error?.message || '').includes('aborted');
        if (abortLike) {
          const last = messages.value[messages.value.length - 1];
          if (last?.role === 'assistant' && !last.content.trim()) {
            updateLastAssistantMessage({
              loading: false,
              content: $t('bot.publicChatStopped'),
            });
          } else {
            updateLastAssistantMessage({ loading: false });
          }
          return;
        }
        updateLastAssistantMessage({
          loading: false,
          content: $t('bot.publicChatInitError'),
        });
      },
    },
  );
};

const stopStreaming = () => {
  userStoppedStreaming.value = true;
  sseClient.abort();
  sending.value = false;
  const last = messages.value[messages.value.length - 1];
  if (last?.role === 'assistant' && !last.content.trim()) {
    updateLastAssistantMessage({
      loading: false,
      content: $t('bot.publicChatStopped'),
    });
    return;
  }
  updateLastAssistantMessage({ loading: false });
};

const onEnter = (evt: Event | KeyboardEvent) => {
  if (!(evt instanceof KeyboardEvent)) {
    return;
  }
  // 输入法组合输入期间，Enter 仅用于上屏，不触发发送
  if (isComposing.value || evt.isComposing || evt.keyCode === 229) {
    return;
  }
  if (evt.shiftKey) return;
  evt.preventDefault();
  doSendMessage();
};

const onCompositionStart = () => {
  isComposing.value = true;
};

const onCompositionEnd = () => {
  isComposing.value = false;
};

const usePresetQuestion = (question: string) => {
  senderValue.value = question;
  doSendMessage();
};

const initPublicChat = async () => {
  resetConversationState();
  initializing.value = true;
  setPageTitle();

  try {
    if (!accessStore.accessToken) {
      const tokenResp = await api.get('/api/temp-token/create');
      if (tokenResp.errorCode !== 0 || !tokenResp.data) {
        throw new Error($t('bot.publicChatInitError'));
      }
      accessStore.setAccessToken(tokenResp.data);
    }

    const botResp = await api.get('/api/v1/bot/getDetail', {
      params: { id: botId.value },
    });
    if (botResp.errorCode !== 0 || !botResp.data) {
      throw new Error($t('bot.publicChatInitError'));
    }
    botInfo.value = botResp.data;
    setPageTitle();
    blocked.value = !Boolean(botResp.data?.options?.anonymousEnabled);

    const conversationResp = await api.get('/api/v1/bot/generateConversationId');
    if (conversationResp.errorCode !== 0 || !conversationResp.data) {
      throw new Error($t('bot.publicChatInitError'));
    }
    conversationId.value = String(conversationResp.data);

    const welcomeMessage = botResp.data?.options?.welcomeMessage;
    if (typeof welcomeMessage === 'string' && welcomeMessage.trim() !== '') {
      appendMessage('assistant', welcomeMessage.trim());
    }
  } catch (error) {
    console.error(error);
    initError.value = $t('bot.publicChatInitError');
    setPageTitle();
  } finally {
    initializing.value = false;
  }
};

onMounted(() => {
  initPublicChat();
  nextTick(() => {
    messageContainerRef.value?.addEventListener('scroll', onMessageContainerScroll, {
      passive: true,
    });
    updateDockHeight();
    if (dockRef.value && typeof ResizeObserver !== 'undefined') {
      dockResizeObserver = new ResizeObserver(() => {
        updateDockHeight();
      });
      dockResizeObserver.observe(dockRef.value);
    }
  });
});

watch(
  () => route.params.botId,
  () => {
    initPublicChat();
  },
);

watch(
  () => messages.value.length,
  () => {
    scrollToBottom();
  },
);

watch(
  () => [
    initializing.value,
    initError.value,
    blocked.value,
    presetQuestions.value.length,
    sending.value,
  ],
  () => {
    nextTick(updateDockHeight);
  },
  { flush: 'post' },
);

onBeforeUnmount(() => {
  sseClient.abort();
  messageContainerRef.value?.removeEventListener('scroll', onMessageContainerScroll);
  dockResizeObserver?.disconnect();
  dockResizeObserver = null;
});
</script>

<template>
  <div
    :class="[
      'public-chat-page',
      isEmbedMode ? 'public-chat-page-embed' : '',
    ]"
  >
    <div class="public-chat-shell">
      <header class="public-chat-header">
        <div class="public-chat-title-group">
          <ElAvatar :size="40" :src="botInfo?.icon || defaultBotAvatar" />
          <div class="public-chat-title-wrap">
            <h1 class="public-chat-title">
              {{ botInfo?.title || $t('bot.publicChatTitle') }}
            </h1>
            <p class="public-chat-subtitle">
              {{ $t('bot.publicChatSubtitle') }}
            </p>
          </div>
        </div>
      </header>

      <main ref="messageContainerRef" class="public-chat-messages" :style="messageContainerStyle">
        <ElSkeleton :loading="initializing" animated :rows="5">
          <template #default>
            <div v-if="initError" class="public-chat-banner public-chat-banner-error">
              {{ initError }}
            </div>
            <div v-else-if="blocked" class="public-chat-banner public-chat-banner-warning">
              <p class="public-chat-banner-title">{{ $t('bot.publicPageBlocked') }}</p>
              <p>{{ $t('bot.publicPageBlockedTip') }}</p>
            </div>
            <template v-else>
              <div
                v-for="item in messages"
                :key="item.id"
                :class="[
                  'public-chat-message-row',
                  item.role === 'user' ? 'is-user' : 'is-assistant',
                ]"
              >
                <ElAvatar
                  v-if="item.role === 'assistant'"
                  :size="34"
                  :src="botInfo?.icon || defaultBotAvatar"
                  class="public-chat-message-avatar"
                />
                <div class="public-chat-message">
                  <div
                    :class="[
                      'public-chat-bubble',
                      item.role === 'user' ? 'public-chat-bubble-user' : 'public-chat-bubble-assistant',
                    ]"
                  >
                    <template v-if="item.role === 'user'">
                      <span>{{ item.content }}</span>
                    </template>
                    <template v-else>
                      <template v-if="item.loading && !hasAssistantRenderableContent(item)">
                        <span class="public-chat-waiting">
                          <span class="public-chat-waiting-spinner"></span>
                          <span>{{ $t('bot.publicChatThinking') }}</span>
                        </span>
                      </template>
                      <template v-else>
                        <div
                          v-for="segment in getRenderableSegments(item)"
                          :key="segment.id"
                          :class="[
                            'public-chat-segment',
                            segment.type === 'tool' ? 'is-tool' : 'is-text',
                          ]"
                        >
                          <template v-if="segment.type === 'text'">
                            <ElXMarkdown
                              class="public-chat-markdown"
                              :markdown="segment.content"
                            />
                          </template>
                          <template v-else>
                            <template v-if="getToolById(item, segment.toolId)">
                              <div class="public-chat-tool-segment">
                                <div class="public-chat-tool-item">
                                  <div class="public-chat-tool-header">
                                    <span class="public-chat-tool-name">
                                      <IconifyIcon icon="solar:widget-5-outline" />
                                      {{ getToolById(item, segment.toolId)?.name }}
                                    </span>
                                    <div class="public-chat-tool-actions">
                                      <span
                                        :class="[
                                          'public-chat-tool-status',
                                          getToolById(item, segment.toolId)?.status === 'TOOL_RESULT' ? 'is-done' : 'is-calling',
                                        ]"
                                      >
                                        {{
                                          getToolById(item, segment.toolId)?.status === 'TOOL_RESULT'
                                            ? $t('bot.publicChatToolDone')
                                            : $t('bot.publicChatToolCalling')
                                        }}
                                      </span>
                                      <button
                                        class="public-chat-tool-toggle"
                                        type="button"
                                        @click="toggleToolCollapsed(segment.toolId)"
                                      >
                                        {{
                                          isToolCollapsed(segment.toolId)
                                            ? $t('bot.publicChatToolExpand')
                                            : $t('bot.publicChatToolCollapse')
                                        }}
                                      </button>
                                    </div>
                                  </div>
                                  <pre
                                    v-if="!isToolCollapsed(segment.toolId) && getToolById(item, segment.toolId)?.arguments"
                                    class="public-chat-tool-content"
                                  >{{ getToolById(item, segment.toolId)?.arguments }}</pre>
                                  <pre
                                    v-if="!isToolCollapsed(segment.toolId) && getToolById(item, segment.toolId)?.result && getToolById(item, segment.toolId)?.status === 'TOOL_RESULT'"
                                    class="public-chat-tool-content"
                                  >{{ getToolById(item, segment.toolId)?.result }}</pre>
                                </div>
                              </div>
                            </template>
                          </template>
                        </div>
                        <span v-if="item.loading" class="public-chat-dot-typing">
                          <i></i><i></i><i></i>
                        </span>
                      </template>
                    </template>
                  </div>
                </div>
              </div>
            </template>
          </template>
        </ElSkeleton>
      </main>

      <div ref="dockRef" class="public-chat-dock">
        <section v-if="!initializing && !initError && !blocked" class="public-chat-preset-list">
          <button
            v-for="item in presetQuestions"
            :key="item.key"
            class="public-chat-preset-item"
            type="button"
            @click="usePresetQuestion(item.description)"
          >
            {{ item.description }}
          </button>
        </section>

        <footer class="public-chat-footer">
          <div class="public-chat-composer">
            <ElInput
              class="public-chat-input"
              v-model="senderValue"
              type="textarea"
              :rows="2"
              resize="none"
              :placeholder="$t('bot.publicChatPlaceholder')"
              :disabled="blocked || initializing || !!initError || sending"
              @keydown.enter="onEnter"
              @compositionstart="onCompositionStart"
              @compositionend="onCompositionEnd"
            />
            <span class="public-chat-footer-hint">
              {{ $t('bot.publicChatInputHint') }}
            </span>
            <ElButton
              v-if="sending"
              plain
              class="public-chat-input-send-btn is-stop"
              @click="stopStreaming"
            >
              <template #icon>
                <IconifyIcon icon="solar:stop-circle-outline" />
              </template>
              {{ $t('bot.publicChatStop') }}
            </ElButton>
            <ElButton
              v-else
              type="primary"
              class="public-chat-input-send-btn"
              :disabled="blocked || initializing || !!initError || !senderValue.trim()"
              @click="doSendMessage"
            >
              <template #icon>
                <IconifyIcon icon="solar:plain-linear" />
              </template>
              {{ $t('bot.publicChatSend') }}
            </ElButton>
          </div>
        </footer>
      </div>
    </div>
  </div>
</template>

<style scoped>
.public-chat-page {
  --pc-primary: #2563eb;
  --pc-primary-soft: #dbeafe;
  --pc-ink-strong: #0f172a;
  --pc-ink: #1e293b;
  --pc-ink-soft: #475569;
  --pc-line: #e2e8f0;
  --pc-line-soft: #edf2f7;
  --pc-surface: #ffffff;
  --pc-surface-soft: #f8fbff;
  --pc-surface-muted: #f8fafc;
  box-sizing: border-box;
  position: relative;
  isolation: isolate;
  min-height: 100vh;
  padding: 24px;
  overflow: hidden;
  background:
    radial-gradient(1200px 360px at 100% -20%, rgb(37 99 235 / 14%) 0%, transparent 60%),
    radial-gradient(900px 300px at -10% 120%, rgb(14 165 233 / 12%) 0%, transparent 62%),
    linear-gradient(180deg, #f8fbff 0%, #f3f7fc 100%);
}

.public-chat-page::before {
  position: absolute;
  inset: -30% auto auto -20%;
  z-index: -1;
  width: 440px;
  height: 440px;
  content: '';
  pointer-events: none;
  background: radial-gradient(circle, rgb(59 130 246 / 16%) 0%, transparent 68%);
  filter: blur(8px);
}

.public-chat-page-embed {
  min-height: 100%;
  padding: 0;
  background: var(--pc-surface-soft);
}

.public-chat-page-embed::before {
  content: none;
}

.public-chat-shell {
  position: relative;
  display: flex;
  flex-direction: column;
  max-width: 980px;
  height: calc(100vh - 48px);
  margin: 0 auto;
  overflow: hidden;
  background: var(--pc-surface);
  border: 1px solid var(--pc-line);
  border-radius: 20px;
  box-shadow:
    0 2px 6px rgb(15 23 42 / 5%),
    0 24px 48px rgb(15 23 42 / 8%);
}

.public-chat-page-embed .public-chat-shell {
  height: 100vh;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.public-chat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 24px;
  background: rgb(255 255 255 / 92%);
  border-bottom: 1px solid var(--pc-line-soft);
  backdrop-filter: saturate(130%) blur(6px);
}

.public-chat-title-group {
  display: flex;
  gap: 12px;
  align-items: center;
}

.public-chat-title-wrap {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.public-chat-title {
  margin: 0;
  font-family: Manrope, 'Noto Sans SC', sans-serif;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: 0.01em;
  color: var(--pc-ink-strong);
}

.public-chat-subtitle {
  margin: 0;
  font-size: 12px;
  color: #64748b;
  letter-spacing: 0.02em;
}

.public-chat-messages {
  flex: 1;
  padding: 20px 24px var(--public-chat-dock-space, 180px);
  overflow-y: auto;
  background: rgb(255 255 255);
}

.public-chat-banner {
  padding: 14px 16px;
  font-size: 14px;
  line-height: 1.5;
  border-radius: 10px;
}

.public-chat-banner-title {
  margin: 0 0 4px;
  font-weight: 600;
}

.public-chat-banner-error {
  color: #b42318;
  background: #fff1f2;
  border: 1px solid #fecdd3;
}

.public-chat-banner-warning {
  color: #9a3412;
  background: #fffbeb;
  border: 1px solid #fed7aa;
}

.public-chat-message-row {
  display: flex;
  margin-bottom: 18px;
  animation: message-fade-in 0.22s ease;
}

.public-chat-message-row.is-user {
  justify-content: flex-end;
}

.public-chat-message-row.is-assistant {
  align-items: flex-start;
  gap: 10px;
  justify-content: flex-start;
}

.public-chat-message-avatar {
  flex: 0 0 auto;
  margin-top: 1px;
}

.public-chat-message {
  min-width: 0;
  max-width: min(88%, 760px);
}

.public-chat-bubble {
  display: inline-block;
  min-width: 72px;
  min-height: 44px;
  padding: 11px 14px;
  font-size: 14px;
  line-height: 1.7;
  white-space: pre-wrap;
  border-radius: 12px;
}

.public-chat-bubble-user {
  color: #ffffff;
  background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
  border-top-right-radius: 4px;
  box-shadow: 0 6px 16px rgb(37 99 235 / 24%);
}

.public-chat-bubble-assistant {
  display: block;
  width: 100%;
  min-width: 0;
  min-height: auto;
  padding: 0;
  color: #0f172a;
  background: transparent;
  border: 0;
  border-radius: 0;
}

.public-chat-dot-typing {
  display: inline-flex;
  gap: 4px;
  margin-left: 6px;
}

.public-chat-dot-typing i {
  width: 4px;
  height: 4px;
  background: #64748b;
  border-radius: 50%;
  animation: dot-pulse 1.2s infinite ease-in-out;
}

.public-chat-dot-typing i:nth-child(2) {
  animation-delay: 0.2s;
}

.public-chat-dot-typing i:nth-child(3) {
  animation-delay: 0.4s;
}

.public-chat-segment {
  margin-bottom: 10px;
}

.public-chat-segment:last-child {
  margin-bottom: 0;
}

.public-chat-segment.is-text {
  white-space: normal;
}

.public-chat-segment.is-tool {
  display: block;
  margin: 12px 0;
}

.public-chat-waiting {
  display: inline-flex;
  gap: 10px;
  align-items: center;
  color: var(--pc-ink-soft);
}

.public-chat-waiting-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid #bfdbfe;
  border-top-color: #2563eb;
  border-radius: 50%;
  animation: waiting-spin 0.8s linear infinite;
}

.public-chat-tool-segment {
  width: 560px;
  max-width: 560px;
}

.public-chat-message-row.is-assistant .public-chat-tool-segment {
  max-width: min(560px, 100%);
}

.public-chat-tool-item {
  width: 100%;
  font-size: 12px;
  line-height: 1.45;
  padding: 10px 12px;
  background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
  border: 1px solid #d7e2f2;
  border-left: 3px solid var(--pc-primary);
  border-radius: 12px;
  box-shadow: 0 2px 6px rgb(15 23 42 / 5%);
}

.public-chat-markdown {
  color: var(--pc-ink);
}

.public-chat-markdown :deep(*) {
  overflow-wrap: anywhere;
}

.public-chat-markdown :deep(p) {
  margin: 0 0 10px;
}

.public-chat-markdown :deep(p:last-child) {
  margin-bottom: 0;
}

.public-chat-markdown :deep(a) {
  color: var(--pc-primary);
  text-decoration: underline;
  word-break: break-all;
}

.public-chat-markdown :deep(img) {
  display: block;
  max-width: min(100%, 420px);
  height: auto;
  margin: 10px 0;
  border: 1px solid #dbe5f1;
  border-radius: 10px;
  box-shadow: 0 6px 18px rgb(15 23 42 / 8%);
}

.public-chat-markdown :deep(pre) {
  padding: 10px 12px;
  overflow: auto;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
}

.public-chat-tool-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.public-chat-tool-actions {
  display: inline-flex;
  gap: 8px;
  align-items: center;
}

.public-chat-tool-name {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  overflow: hidden;
  font-size: 12px;
  font-weight: 600;
  color: var(--pc-ink-strong);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.public-chat-tool-status {
  flex-shrink: 0;
  padding: 2px 8px;
  font-size: 11px;
  line-height: 1.25;
  border: 1px solid transparent;
  border-radius: 999px;
}

.public-chat-tool-status.is-calling {
  color: #1d4ed8;
  background: #eff6ff;
  border-color: #bfdbfe;
}

.public-chat-tool-status.is-done {
  color: #15803d;
  background: #ecfdf3;
  border-color: #bbf7d0;
}

.public-chat-tool-toggle {
  padding: 3px 10px;
  font-size: 11px;
  line-height: 1.4;
  color: #334155;
  cursor: pointer;
  background: #ffffff;
  border: 1px solid #d7e2f2;
  border-radius: 999px;
  transition: all 0.15s ease;
}

.public-chat-tool-toggle:hover {
  color: #1d4ed8;
  border-color: #bfdbfe;
}

.public-chat-tool-content {
  max-height: 160px;
  padding: 9px 10px;
  margin: 8px 0 0;
  overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
    'Courier New', monospace;
  font-size: 12px;
  line-height: 1.5;
  color: #334155;
  white-space: pre-wrap;
  word-break: break-word;
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
}

.public-chat-dock {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 12;
  padding: 0 24px 18px;
  pointer-events: none;
}

.public-chat-preset-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 0 4px 0;
  margin-bottom: 10px;
  pointer-events: auto;
  background: transparent;
  border-top: 0;
}

.public-chat-preset-item {
  max-width: 100%;
  padding: 8px 14px;
  overflow: hidden;
  font-size: 12px;
  color: #334155;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
  background: rgb(255 255 255 / 96%);
  border: 1px solid #dbe3ef;
  border-radius: 999px;
  box-shadow: 0 4px 12px rgb(15 23 42 / 8%);
  transition: all 0.18s ease;
}

.public-chat-preset-item:hover {
  color: #1d4ed8;
  background: #eff6ff;
  border-color: #bfdbfe;
}

.public-chat-footer {
  padding: 0 10px;
  pointer-events: auto;
  background: transparent;
  border-top: 0;
}

.public-chat-composer {
  position: relative;
  padding: 0 0 4px;
  overflow: hidden;
  background: rgb(255 255 255);
  border: 1px solid #dce5f1;
  border-radius: 22px;
  box-shadow:
    0 8px 26px rgb(15 23 42 / 10%),
    0 1px 0 rgb(255 255 255 / 95%) inset;
}

.public-chat-footer-hint {
  position: absolute;
  bottom: 16px;
  left: 18px;
  z-index: 1;
  font-size: 12px;
  color: #94a3b8;
  opacity: 0.9;
  pointer-events: none;
}

.public-chat-input-send-btn {
  position: absolute;
  right: 14px;
  bottom: 14px;
  z-index: 2;
  height: 40px;
  padding: 0 15px;
  border-radius: 999px;
}

.public-chat-input-send-btn.is-stop {
  padding-right: 12px;
  padding-left: 12px;
}

.public-chat-input :deep(.el-textarea__inner) {
  min-height: 126px !important;
  font-size: 14px;
  line-height: 1.6;
  background: rgb(255 255 255);
  border-color: transparent;
  border-radius: 20px;
  padding: 16px 124px 48px 18px;
  box-shadow: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.public-chat-input :deep(.el-textarea) {
  background: rgb(255 255 255);
}

.public-chat-input :deep(.el-textarea__inner:focus) {
  border-color: transparent;
  box-shadow: 0 0 0 3px rgb(37 99 235 / 10%);
}

@keyframes message-fade-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes dot-pulse {
  0%,
  80%,
  100% {
    transform: scale(0.75);
    opacity: 0.5;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}

@keyframes waiting-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (width <= 768px) {
  .public-chat-page {
    padding: 0;
  }

  .public-chat-shell {
    height: 100vh;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }

  .public-chat-header,
  .public-chat-messages {
    padding-right: 14px;
    padding-left: 14px;
  }

  .public-chat-dock {
    padding-right: 14px;
    padding-bottom: 12px;
    padding-left: 14px;
  }

  .public-chat-preset-list {
    gap: 6px;
    margin-bottom: 8px;
    padding-right: 2px;
    padding-left: 2px;
  }

  .public-chat-footer {
    padding-right: 6px;
    padding-left: 6px;
  }

  .public-chat-message {
    max-width: 94%;
  }

  .public-chat-input :deep(.el-textarea__inner) {
    min-height: 118px !important;
    padding-right: 118px;
    padding-bottom: 46px;
  }
}
</style>
