// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {css, html, LitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';

type Provider = 'openai'|'claude'|'mock';
type MessageRole = 'user'|'assistant';
type MessageStatus = 'done'|'thinking'|'error';

interface ChatMessage {
  role: MessageRole;
  content: string;
  time: number;
  status: MessageStatus;
}

interface ProviderSetting {
  model: string;
  endpoint: string;
  key: string;
}

interface PersistedConfig {
  provider?: Provider;
  openai?: Partial<ProviderSetting>;
  claude?: Partial<ProviderSetting>;
}

const CONFIG_KEY = 'sgclaw-chat-ui-v1';
const STORAGE_KEY = 'sgclaw-chat-messages-v1';

const PROVIDER_DEFAULTS: Record<Provider, Omit<ProviderSetting, 'key'>> = {
  openai: {
    endpoint: 'https://api.openai.com/v1/chat/completions',
    model: 'gpt-4o-mini',
  },
  claude: {
    endpoint: 'https://api.anthropic.com/v1/messages',
    model: 'claude-3-5-sonnet-20240620',
  },
  mock: {
    endpoint: '',
    model: 'mock-local',
  },
};

const MAX_SAVED_MESSAGES = 200;

declare global {
  interface Window {
    __SGCLAW_TEST_OPENAI_KEY__?: string;
    __SGCLAW_TEST_CLAUDE_KEY__?: string;
  }

  interface HTMLElementTagNameMap {
    'sgclaw-chat-function-app': SgClawChatFunctionApp;
  }
}

function normalizeText(text: string): string {
  return (text || '').replace(/\r\n/g, '\n').trim();
}

function clampHeight(rawHeight: number): string {
  return `${Math.min(150, Math.max(52, rawHeight))}px`;
}

function formatTime(ts: number): string {
  const d = new Date(ts);
  const hh = String(d.getHours()).padStart(2, '0');
  const mm = String(d.getMinutes()).padStart(2, '0');
  return `${hh}:${mm}`;
}

function fallbackMockReply(provider: Provider, prompt: string): string {
  const hints: Record<Exclude<Provider, undefined>, string> = {
    openai: '已收到你的问题，当前在 mock 环境中使用的是演示回复。',
    claude: '我已理解你的请求，当前 mock 模式返回的是占位说明。',
    mock: '这是本地模拟回复。可在左侧切换到 OpenAI 或 Claude 获取真实模型结果。',
  };

  return `${hints[provider] || '收到。'}\n\n原文：${prompt}`;
}

export class SgClawChatFunctionApp extends LitElement {
  static get is() {
    return 'sgclaw-chat-function-app';
  }

  static override get styles() {
    return css`
      :host {
        display: block;
        --bg: #0f172a;
        --bg-soft: rgba(15, 23, 42, 0.8);
        --card: rgba(15, 23, 42, 0.64);
        --panel: rgba(15, 23, 42, 0.92);
        --line: rgba(148, 163, 184, 0.2);
        --text: #e2e8f0;
        --text-subtle: #94a3b8;
        --accent: #38bdf8;
        --accent-soft: rgba(56, 189, 248, 0.15);
        --ok: #22c55e;
        --warn: #f59e0b;
        --bad: #ef4444;
        --chip: rgba(56, 189, 248, 0.14);
        --shadow: 0 20px 60px rgba(3, 7, 18, 0.35);
        color: var(--text);
        background:
          radial-gradient(circle at 20% 0%, rgba(56, 189, 248, 0.24), transparent 35%),
          radial-gradient(circle at 80% 15%, rgba(168, 85, 247, 0.16), transparent 30%),
          linear-gradient(140deg, #020617, #0f172a 55%, #111827 100%);
        min-height: 100%;
        padding: 20px;
        font-family: 'Inter', 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
      }

      :host * { box-sizing: border-box; }

      .page {
        width: min(1280px, 100%);
        margin: 0 auto;
        display: flex;
        flex-direction: column;
        gap: 16px;
        min-height: calc(100vh - 40px);
      }

      .top-bar {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 16px;
        padding: 14px 18px;
        border: 1px solid var(--line);
        border-radius: 14px;
        background: var(--panel);
        backdrop-filter: blur(18px);
        box-shadow: var(--shadow);
        position: sticky;
        top: 0;
        z-index: 10;
      }

      .title {
        margin: 0;
        font-size: 20px;
        font-weight: 700;
        letter-spacing: 0.02em;
        display: flex;
        align-items: center;
        gap: 10px;
      }

      .status-chip {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        padding: 4px 10px;
        border-radius: 999px;
        border: 1px solid var(--line);
        background: var(--accent-soft);
        font-size: 12px;
        color: #bfdbfe;
        white-space: nowrap;
      }

      .status-dot {
        width: 8px;
        height: 8px;
        border-radius: 999px;
        background: var(--ok);
        display: inline-block;
      }

      .layout {
        display: grid;
        grid-template-columns: 280px 1fr;
        gap: 16px;
        min-height: 0;
        flex: 1;
      }

      .side {
        background: var(--panel);
        border: 1px solid var(--line);
        border-radius: 14px;
        padding: 16px;
        display: flex;
        flex-direction: column;
        gap: 16px;
        box-shadow: var(--shadow);
      }

      .side h2 {
        margin: 0;
        font-size: 16px;
        letter-spacing: 0.02em;
      }

      .note {
        color: var(--text-subtle);
        font-size: 13px;
        margin: 0;
        line-height: 1.55;
      }

      .control {
        display: flex;
        flex-direction: column;
        gap: 6px;
      }

      .control label {
        font-size: 12px;
        color: #cbd5e1;
      }

      .control input,
      .control select,
      .control textarea {
        width: 100%;
        border-radius: 10px;
        border: 1px solid var(--line);
        background: rgba(15, 23, 42, 0.78);
        color: var(--text);
        padding: 10px 12px;
        font-size: 13px;
        outline: none;
        transition: 0.2s border-color, 0.2s box-shadow;
        font-family: inherit;
      }

      .control input::placeholder,
      .control textarea::placeholder {
        color: #64748b;
      }

      .control input:focus,
      .control select:focus,
      .control textarea:focus {
        border-color: #60a5fa;
        box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.18);
      }

      .quick-grid {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
      }

      .chip {
        border: 1px solid var(--line);
        border-radius: 999px;
        background: var(--chip);
        color: #e2e8f0;
        padding: 6px 10px;
        font-size: 12px;
        cursor: pointer;
        transition: 0.18s background;
        white-space: nowrap;
      }

      .chip:hover {
        background: rgba(56, 189, 248, 0.26);
      }

      .chat-card {
        background: var(--panel);
        border: 1px solid var(--line);
        border-radius: 14px;
        display: flex;
        flex-direction: column;
        min-height: 0;
        min-width: 0;
        box-shadow: var(--shadow);
        overflow: hidden;
      }

      .chat-toolbar {
        border-bottom: 1px solid var(--line);
        padding: 14px 16px;
        display: flex;
        gap: 10px;
        align-items: center;
        flex-wrap: wrap;
      }

      .toolbar-btn {
        border: 0;
        background: #0ea5e9;
        color: #f8fafc;
        font-weight: 600;
        border-radius: 10px;
        padding: 8px 12px;
        font-size: 12px;
        cursor: pointer;
        transition: 0.2s transform, 0.2s opacity;
      }

      .toolbar-btn:hover {
        opacity: 0.92;
        transform: translateY(-1px);
      }

      .toolbar-btn.secondary {
        background: transparent;
        color: #dbeafe;
        border: 1px solid var(--line);
      }

      .toolbar-btn:disabled {
        opacity: 0.4;
        cursor: not-allowed;
      }

      .status-line {
        margin-left: auto;
        font-size: 12px;
        color: var(--text-subtle);
        display: inline-flex;
        align-items: center;
        gap: 6px;
      }

      .messages {
        flex: 1;
        min-height: 420px;
        overflow-y: auto;
        padding: 16px;
        display: flex;
        flex-direction: column;
        gap: 12px;
        scroll-behavior: smooth;
      }

      .empty-state {
        border: 1px dashed var(--line);
        border-radius: 12px;
        color: #cbd5e1;
        padding: 24px;
        text-align: center;
        background: rgba(15, 23, 42, 0.56);
      }

      .bubble {
        max-width: min(75%, 760px);
        border-radius: 14px;
        padding: 12px 14px;
        box-shadow: 0 8px 20px rgba(2, 6, 23, 0.25);
        border: 1px solid transparent;
        white-space: pre-wrap;
        word-break: break-word;
        position: relative;
        animation: fadeIn 0.2s ease;
      }

      .bubble.user {
        align-self: flex-end;
        background: linear-gradient(145deg, rgba(14, 165, 233, 0.2), rgba(14, 165, 233, 0.08));
        border-color: rgba(56, 189, 248, 0.35);
      }

      .bubble.assistant {
        align-self: flex-start;
        background: rgba(30, 41, 59, 0.82);
        border-color: rgba(148, 163, 184, 0.28);
      }

      .bubble.error {
        background: rgba(127, 29, 29, 0.35);
        border-color: rgba(239, 68, 68, 0.4);
        color: #fecaca;
      }

      .bubble .meta {
        display: flex;
        justify-content: space-between;
        gap: 10px;
        align-items: center;
        margin-bottom: 6px;
        font-size: 11px;
        color: #94a3b8;
      }

      .role-tag {
        font-weight: 700;
        font-size: 11px;
        padding: 2px 8px;
        border-radius: 999px;
        border: 1px solid var(--line);
        color: #cbd5e1;
        background: rgba(15, 23, 42, 0.75);
      }

      .composer {
        padding: 12px;
        border-top: 1px solid var(--line);
        background: rgba(15, 23, 42, 0.9);
      }

      .input-line {
        display: grid;
        grid-template-columns: 1fr auto;
        gap: 10px;
        align-items: end;
      }

      textarea {
        width: 100%;
        resize: none;
        min-height: 52px;
        max-height: 150px;
        border: 1px solid var(--line);
        border-radius: 12px;
        padding: 10px 12px;
        background: rgba(15, 23, 42, 0.84);
        color: var(--text);
        outline: none;
        font-family: inherit;
        font-size: 14px;
        line-height: 1.5;
      }

      .input-line .toolbar-btn {
        height: 52px;
        width: 88px;
      }

      .error-tip {
        margin-top: 8px;
        color: #fca5a5;
        font-size: 12px;
        min-height: 18px;
      }

      .tip {
        margin: 0;
        color: #94a3b8;
        font-size: 12px;
      }

      .copy-btn {
        position: absolute;
        right: 8px;
        top: 8px;
        border: 0;
        background: transparent;
        color: #9ca3af;
        font-size: 12px;
        cursor: pointer;
      }

      .copy-btn:hover {
        color: #dbeafe;
      }

      .typing {
        display: inline-flex;
        gap: 6px;
      }

      .typing-dot {
        width: 8px;
        aspect-ratio: 1;
        border-radius: 50%;
        background: #f8fafc;
        display: inline-block;
        animation: bounce 1s infinite;
      }

      .typing-dot:nth-child(2) { animation-delay: 0.15s; }
      .typing-dot:nth-child(3) { animation-delay: 0.3s; }

      @keyframes fadeIn {
        from { transform: translateY(6px); opacity: 0.7; }
        to { transform: translateY(0); opacity: 1; }
      }

      @keyframes bounce {
        0%, 80%, 100% {
          transform: translateY(0);
        }
        40% {
          transform: translateY(-4px);
        }
      }

      @media (max-width: 1100px) {
        .layout {
          grid-template-columns: 1fr;
        }

        .side {
          order: 2;
        }

        .chat-card {
          min-height: 72vh;
        }

        .bubble {
          max-width: 100%;
        }
      }

      @media (max-width: 680px) {
        :host {
          padding: 10px;
        }

        .top-bar {
          flex-direction: column;
          align-items: flex-start;
        }

        .chat-toolbar {
          flex-wrap: wrap;
        }

        .input-line {
          grid-template-columns: 1fr;
        }
      }
    `;
  }

  static override get properties() {
    return {
      provider: {type: String},
      endpoint: {type: String},
      model: {type: String},
      apiKey: {type: String},
      messages: {type: Array},
      userInput: {type: String},
      busy: {type: Boolean},
      errorTip: {type: String},
      statusText: {type: String},
    };
  }

  protected accessor provider: Provider = 'openai';
  protected accessor endpoint: string = PROVIDER_DEFAULTS.openai.endpoint;
  protected accessor model: string = PROVIDER_DEFAULTS.openai.model;
  protected accessor apiKey: string = '';
  protected accessor messages: ChatMessage[] = [
    {
      role: 'assistant',
      content: '你好，我是 sgClaw 聊天界面。你可以直接切换到 OpenAI/Claude 或使用 mock 模式进行体验。',
      time: Date.now(),
      status: 'done',
    },
  ];

  protected accessor userInput = '';
  protected accessor busy = false;
  protected accessor errorTip = '';
  protected accessor statusText = '就绪';

  private openai: ProviderSetting = {
    model: PROVIDER_DEFAULTS.openai.model,
    endpoint: PROVIDER_DEFAULTS.openai.endpoint,
    key: '',
  };

  private claude: ProviderSetting = {
    model: PROVIDER_DEFAULTS.claude.model,
    endpoint: PROVIDER_DEFAULTS.claude.endpoint,
    key: '',
  };

  override connectedCallback(): void {
    super.connectedCallback();
    this.loadPersistedState_();
    this.statusText = this.getStatusText_();
  }

  override render() {
    return html`
      <div class="page" id="app">
        <header class="top-bar">
          <h1 class="title">SG <span style="color:#38bdf8">Claw</span> Chat</h1>
          <div class="status-chip">
            <span class="status-dot"></span>
            <span>${this.statusText}</span>
          </div>
        </header>

        <div class="layout">
          <aside class="side">
            <section>
              <h2>连接配置</h2>
              <p class="note">支持 OpenAI、Claude 与本地 mock 模式；未配置 key 时自动回退到本地模拟回复。</p>
            </section>

            <div class="control">
              <label for="provider">服务方</label>
              <select id="provider" .value="${this.provider}" @change="${this.onProviderChange_}">
                <option value="openai">OpenAI</option>
                <option value="claude">Claude</option>
                <option value="mock">本地 Mock</option>
              </select>
            </div>

            <div class="control">
              <label for="endpoint">接口地址</label>
              <input
                id="endpoint"
                type="text"
                placeholder="https://api.openai.com/v1/chat/completions"
                .value="${this.endpoint}"
                @change="${this.onEndpointChange_}"
              >
            </div>

            <div class="control">
              <label for="model">模型</label>
              <input
                id="model"
                type="text"
                placeholder="gpt-4o-mini"
                .value="${this.model}"
                @change="${this.onModelChange_}"
              >
            </div>

            <div class="control" id="apiKeyWrap">
              <label for="apiKey">API Key</label>
              <input
                id="apiKey"
                type="password"
                placeholder="仅本页使用，不会上传"
                .value="${this.apiKey}"
                @change="${this.onApiKeyChange_}"
              >
            </div>

            <section>
              <h2>快捷问题</h2>
              <div class="quick-grid">
                <button class="chip" data-prompt="请帮我总结这段对话的关键结论" @click="${this.onQuickPrompt_}">总结结论</button>
                <button class="chip" data-prompt="给我一个适合 sgClaw 的自动化验证思路" @click="${this.onQuickPrompt_}">验证思路</button>
                <button class="chip" data-prompt="把这件事情分解成 5 步执行计划" @click="${this.onQuickPrompt_}">分解任务</button>
              </div>
            </section>

            <section>
              <h2>帮助</h2>
              <p class="note">输入消息后回车直接发送，Shift+Enter 换行。历史记录会保存在当前浏览器本地。</p>
              <button class="toolbar-btn secondary" @click="${this.onExportChat_}" type="button">导出对话</button>
              <button class="toolbar-btn secondary" @click="${this.onClearChat_}" type="button">清空会话</button>
              <p class="tip">已保存 ${this.messages.length} 条消息，用户提问 ${this.messages.filter((m) => m.role === 'user').length} 条</p>
            </section>
          </aside>

          <section class="chat-card">
            <div class="chat-toolbar">
              <button class="toolbar-btn" @click="${this.onFocusInput_}" type="button">开始对话</button>
              <div class="status-line">当前：${this.provider === 'openai' ? 'OpenAI' : this.provider === 'claude' ? 'Claude' : '本地 Mock'} · ${this.model}</div>
            </div>

            <div class="messages" id="messages" @keydown="${this.onContainerKeyDown_}">
              ${this.messages.length === 0 ? html`
                <div class="empty-state">尚无历史消息，发一条开始吧。</div>
              ` : html`
                ${this.messages.map((msg, idx) => this.renderMessage_(msg, idx))}
              `}
            </div>

            <div class="composer">
              <div class="input-line">
                <textarea
                  id="userInput"
                  rows="2"
                  placeholder="输入你的问题，如：请帮我分析 sgClaw 的对话流程"
                  .value="${this.userInput}"
                  ?disabled="${this.busy}"
                  @input="${this.onUserInputInput_}"
                  @keydown="${this.onUserInputKeyDown_}"
                ></textarea>
                <button class="toolbar-btn" type="button" @click="${this.onSendMessage_}" ?disabled="${this.busy}">发送</button>
              </div>
              <p class="error-tip">${this.errorTip}</p>
              <p class="tip">注意：聊天数据仅保存在本地浏览器，不会上传到服务器。</p>
            </div>
          </section>
        </div>
      </div>
    `;
  }

  private renderMessage_(msg: ChatMessage, idx: number) {
    return html`
      <article class="bubble ${msg.role} ${msg.status === 'error' ? 'error' : ''}">
        <div class="meta">
          <span class="role-tag">${msg.role === 'user' ? '我' : 'sgClaw'}</span>
          <span>${formatTime(msg.time || Date.now())}</span>
        </div>
        ${msg.status === 'thinking' && msg.role === 'assistant'
          ? html`
              <div class="typing" aria-label="助手正在输入">
                <span class="typing-dot"></span>
                <span class="typing-dot"></span>
                <span class="typing-dot"></span>
              </div>
            `
          : html`<div>${msg.content}</div>`
        }
        ${msg.role === 'assistant' && msg.content ? html`
          <button class="copy-btn" type="button" @click="${() => this.onCopyMessage_(idx)}">复制</button>
        ` : ''}
      </article>
    `;
  }

  private saveConfig_(): void {
    const cfg: PersistedConfig = {
      provider: this.provider,
      openai: this.openai,
      claude: this.claude,
    };
    localStorage.setItem(CONFIG_KEY, JSON.stringify(cfg));
  }

  private saveMessages_(): void {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(this.messages));
  }

  private loadPersistedState_(): void {
    try {
      const rawConfig = localStorage.getItem(CONFIG_KEY);
      if (rawConfig) {
        const cfg = JSON.parse(rawConfig) as PersistedConfig;
        if (cfg.provider) {
          this.provider = cfg.provider;
        }
        if (cfg.openai) {
          this.openai = {...this.openai, ...cfg.openai};
        }
        if (cfg.claude) {
          this.claude = {...this.claude, ...cfg.claude};
        }
      }

      const rawMessages = localStorage.getItem(STORAGE_KEY);
      if (rawMessages) {
        const list = JSON.parse(rawMessages);
        if (Array.isArray(list) && list.length) {
          const normalized = list
            .filter((item) => item && typeof item.content === 'string' && (item.role === 'user' || item.role === 'assistant'))
            .map((item) => {
              const role = item.role as MessageRole;
              const content = String(item.content);
              const time = Number.isFinite(item.time) ? item.time : Date.now();
              return {
                role,
                content,
                time,
                status: item.status === 'thinking' || item.status === 'error' ? item.status : 'done',
              } as ChatMessage;
            });

          if (normalized.length) {
            this.messages = normalized.slice(-80);
          }
        }
      }
    } catch (_e) {
      // Ignore legacy state.
    }

    this.openai.key = window.__SGCLAW_TEST_OPENAI_KEY__ || this.openai.key || '';
    this.claude.key = window.__SGCLAW_TEST_CLAUDE_KEY__ || this.claude.key || '';

    this.syncProviderState_();
  }

  private syncProviderState_(): void {
    const defaults = PROVIDER_DEFAULTS[this.provider];
    if (this.provider === 'openai') {
      this.apiKey = this.openai.key || '';
      this.model = this.openai.model || defaults.model;
      this.endpoint = this.openai.endpoint || defaults.endpoint;
    } else if (this.provider === 'claude') {
      this.apiKey = this.claude.key || '';
      this.model = this.claude.model || defaults.model;
      this.endpoint = this.claude.endpoint || defaults.endpoint;
    } else {
      this.apiKey = '';
      this.model = defaults.model;
      this.endpoint = defaults.endpoint;
    }

    this.statusText = this.getStatusText_();
    this.saveConfig_();
  }

  private getStatusText_(): string {
    if (this.busy) {
      return '正在响应...';
    }

    if (this.provider === 'mock') {
      return '离线 mock 模式';
    }

    if (this.apiKey) {
      return `已配置 ${this.provider === 'openai' ? 'OpenAI' : 'Claude'}`;
    }

    return `未配置 ${this.provider === 'openai' ? 'OpenAI' : 'Claude'} Key，发送时将自动回退到 Mock`;
  }

  private validateProvider_(): boolean {
    if (this.provider === 'mock') {
      return true;
    }

    if (!this.apiKey) {
      this.errorTip = `未配置 ${this.provider === 'openai' ? 'OpenAI' : 'Claude'} API Key，使用 mock 回复`;
      this.statusText = this.getStatusText_();
      return false;
    }

    if (!this.endpoint) {
      this.errorTip = '请先填写接口地址';
      this.statusText = this.getStatusText_();
      return false;
    }

    if (!this.model) {
      this.errorTip = '请先填写模型名称';
      this.statusText = this.getStatusText_();
      return false;
    }

    return true;
  }

  private buildHistory_(): Array<{role: MessageRole; content: string}> {
    return this.messages
      .filter((msg) => msg.role === 'user' || msg.role === 'assistant')
      .slice(-20)
      .map((msg) => ({role: msg.role, content: msg.content}));
  }

  private callOpenAI_(prompt: string): Promise<string> {
    const body = {
      model: this.model,
      temperature: 0.35,
      messages: this.buildHistory_().concat([{role: 'user' as const, content: prompt}]),
    };

    return fetch(this.endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${this.apiKey}`,
      },
      body: JSON.stringify(body),
    }).then((resp) => {
      if (!resp.ok) {
        return resp.text().then((text) => {
          throw new Error(`OpenAI 响应异常: ${resp.status} ${text.slice(0, 120)}`);
        });
      }
      return resp.json();
    }).then((data: {choices?: Array<{message?: {content?: string}}>}): string => {
      if (!data.choices || !data.choices[0] || !data.choices[0].message) {
        throw new Error('OpenAI 返回结构异常');
      }
      return data.choices[0].message.content || '（无文本返回）';
    });
  }

  private callClaude_(prompt: string): Promise<string> {
    const messages = this.buildHistory_().map((msg) => ({
      role: msg.role === 'assistant' ? 'assistant' : 'user',
      content: msg.content,
    }));

    return fetch(this.endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': this.apiKey,
        'anthropic-version': '2023-06-01',
      },
      body: JSON.stringify({
        model: this.model,
        max_tokens: 1024,
        messages: messages.concat([{role: 'user', content: prompt}]),
        system: '你是 SGClaw 侧边界面内置助手，提供简洁、准确、可执行的技术建议。',
      }),
    }).then((resp) => {
      if (!resp.ok) {
        return resp.text().then((text) => {
          throw new Error(`Claude 响应异常: ${resp.status} ${text.slice(0, 120)}`);
        });
      }
      return resp.json();
    }).then((data: {content?: Array<{type?: string, text?: string}>}) => {
      if (!data.content || !Array.isArray(data.content) || data.content.length === 0) {
        throw new Error('Claude 返回结构异常');
      }
      return data.content
        .map((block) => (block.type === 'text' ? (block.text || '') : `[${block.type}]`))
        .join('\n');
    });
  }

  private postMessage_(prompt: string): Promise<string> {
    if (!this.validateProvider_()) {
      return Promise.resolve(fallbackMockReply(this.provider, prompt));
    }

    if (this.provider === 'openai') {
      return this.callOpenAI_(prompt);
    }

    if (this.provider === 'claude') {
      return this.callClaude_(prompt);
    }

    return Promise.resolve(fallbackMockReply(this.provider, prompt));
  }

  private adjustInputHeight_(): void {
    const input = this.shadowRoot?.querySelector<HTMLTextAreaElement>('#userInput');
    if (!input) {
      return;
    }
    input.style.height = 'auto';
    input.style.height = clampHeight(input.scrollHeight);
  }

  private onFocusInput_(): void {
    const input = this.shadowRoot?.querySelector<HTMLTextAreaElement>('#userInput');
    input?.focus();
  }

  private onProviderChange_(event: Event): void {
    this.provider = (event.target as HTMLSelectElement).value as Provider;
    this.syncProviderState_();
  }

  private onEndpointChange_(event: Event): void {
    const value = (event.target as HTMLInputElement).value;
    if (this.provider === 'openai') {
      this.openai.endpoint = value;
    } else if (this.provider === 'claude') {
      this.claude.endpoint = value;
    }
    this.endpoint = value;
    this.saveConfig_();
  }

  private onModelChange_(event: Event): void {
    const value = (event.target as HTMLInputElement).value;
    if (this.provider === 'openai') {
      this.openai.model = value;
    } else if (this.provider === 'claude') {
      this.claude.model = value;
    }
    this.model = value;
    this.saveConfig_();
    this.statusText = this.getStatusText_();
  }

  private onApiKeyChange_(event: Event): void {
    const value = (event.target as HTMLInputElement).value.trim();
    if (this.provider === 'openai') {
      this.openai.key = value;
    } else if (this.provider === 'claude') {
      this.claude.key = value;
    }
    this.apiKey = value;
    this.errorTip = '';
    this.saveConfig_();
    this.statusText = this.getStatusText_();
  }

  private onUserInputInput_(event: Event): void {
    this.errorTip = '';
    this.userInput = (event.target as HTMLTextAreaElement).value;
    this.adjustInputHeight_();
  }

  private onUserInputKeyDown_(event: KeyboardEvent): void {
    if (event.key === 'Enter' && !event.shiftKey) {
      event.preventDefault();
      this.onSendMessage_();
    }
  }

  private onContainerKeyDown_(event: KeyboardEvent): void {
    if (event.key === 'Enter' && !event.shiftKey && event.target && (event.target as HTMLElement).tagName === 'TEXTAREA') {
      event.preventDefault();
      this.onSendMessage_();
    }
  }

  private onQuickPrompt_(event: Event): void {
    if (this.busy) {
      return;
    }
    const prompt = (event.currentTarget as HTMLButtonElement).dataset.prompt || '';
    this.userInput = prompt;
    this.adjustInputHeight_();
    this.requestUpdate();
    this.onSendMessage_();
  }

  private onSendMessage_(): void {
    const prompt = normalizeText(this.userInput);
    if (!prompt || this.busy) {
      return;
    }

    this.errorTip = '';

    const userMsg: ChatMessage = {
      role: 'user',
      content: prompt,
      time: Date.now(),
      status: 'done',
    };
    const assistantMsg: ChatMessage = {
      role: 'assistant',
      content: '',
      time: Date.now(),
      status: 'thinking',
    };

    this.messages = [...this.messages, userMsg, assistantMsg];
    const assistantIndex = this.messages.length - 1;

    this.userInput = '';
    this.adjustInputHeight_();
    this.busy = true;
    this.statusText = this.getStatusText_();

    this.updateComplete.then(() => {
      const container = this.shadowRoot?.querySelector<HTMLElement>('.messages');
      if (container) {
        container.scrollTop = container.scrollHeight;
      }
    });

    this.postMessage_(prompt)
      .then((reply) => {
        this.messages = this.messages.map((item, index) => {
          if (index === assistantIndex) {
            return {
              ...item,
              content: reply,
              status: 'done',
              time: Date.now(),
            };
          }
          return item;
        });
      })
      .catch((err: unknown) => {
        this.messages = this.messages.map((item, index) => {
          if (index === assistantIndex) {
            return {
              ...item,
              content: `对话失败：${err instanceof Error ? err.message : '未知错误'}`,
              status: 'error',
              time: Date.now(),
            };
          }
          return item;
        });
      })
      .finally(() => {
        this.busy = false;
        if (this.messages.length > MAX_SAVED_MESSAGES) {
          this.messages = this.messages.slice(-MAX_SAVED_MESSAGES);
        }
        this.saveMessages_();
        this.statusText = this.getStatusText_();
        this.requestUpdate();
        this.updateComplete.then(() => {
          const container = this.shadowRoot?.querySelector<HTMLElement>('.messages');
          if (container) {
            container.scrollTop = container.scrollHeight;
          }
          const input = this.shadowRoot?.querySelector<HTMLTextAreaElement>('#userInput');
          input?.focus();
        });
      });
  }

  private onCopyMessage_(index: number): void {
    const target = this.messages[index]?.content || '';
    if (!target) {
      return;
    }
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(target);
      return;
    }
  }

  private onExportChat_(): void {
    const payload = {
      provider: this.provider,
      model: this.model,
      endpoint: this.endpoint,
      exportedAt: new Date().toISOString(),
      messages: this.messages,
    };
    const blob = new Blob([JSON.stringify(payload, null, 2)], {type: 'application/json'});
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = `sgclaw-chat-${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
    link.click();
    URL.revokeObjectURL(link.href);
  }

  private onClearChat_(): void {
    if (!window.confirm('确定清空当前聊天记录吗？')) {
      return;
    }
    this.messages = [{
      role: 'assistant',
      content: '记录已清空。请继续输入你的新问题。',
      time: Date.now(),
      status: 'done',
    }];
    this.saveMessages_();
    this.statusText = this.getStatusText_();
    this.requestUpdate();
  }
}

customElements.define(SgClawChatFunctionApp.is, SgClawChatFunctionApp);
