/**
 * Whitespace in FAST templates
 * ────────────────────────────
 * The formatter adds newlines/indentation to static template string parts,
 * which FAST renders as real text nodes. With `white-space: pre-wrap` these
 * are visible as blank lines. To avoid this:
 *
 * - Apply `white-space: pre-wrap` only on leaf elements whose *content* needs
 *   it, never on containers.
 * - Inside pre-wrap elements keep content on the same line as the opening tag:
 *   GOOD `<div>${expr}</div>` / BAD `<div>\n  ${expr}\n</div>`
 * - Extract shared or deeply-nested templates as named module-level consts to
 *   keep indentation shallow.
 * - Chain adjacent `when()` calls as `${when(a,tA)}${when(b,tB)}` — separate
 *   lines let the formatter inject whitespace between them.
 */

import type {
  ChatAttachment,
  ChatMessage,
  ChatToolCall,
  InteractionPresentation,
} from '@genesislcap/foundation-ai';
import { isChatToolCallUnknown } from '@genesislcap/foundation-ai';
import { classNames, html, ref, repeat, when, ViewTemplate } from '@genesislcap/web-core';
import { SettingsModalTemplate } from '../components/settings-modal';
import { compactionDividerLabel } from '../utils/history-transform';
import type { FoundationAiAssistant } from './main';
import { LOADING_STYLE_ANIMATIONS } from './main.types';

function unknownToolPayload(tc: ChatToolCall): string {
  if (!isChatToolCallUnknown(tc)) return '';
  // A stale tool was real earlier in this agent's lifetime but isn't available
  // in the current state — distinct from a hallucinated name that never existed.
  const lines = [
    tc.stale ? `${tc.name} — no longer available at this step` : `${tc.name} — tool does not exist`,
  ];
  if (tc.availableTools.length > 0) {
    lines.push('Available tools:');
    tc.availableTools.forEach((t) => lines.push(`  • ${t}`));
  }
  return lines.join('\n');
}

const HALO_SPEED_DEFAULT = 1.5;
const HALO_SPEED_ORCHESTRATING = 0.4;
const HALO_BORDER_SIZE_DEFAULT = 3;

// Avatar markup is owned by the component (`assistantIconSafe` / `userIconSafe`),
// which holds the sanitized SVG string for the default or any consumer override
// and is injected via `:innerHTML`. A raster `image-src` / `user-image-src`
// takes precedence over the inline icon. The exported `*IconSvg` string
// constants in `./main` let apps opt back into a specific built-in mark.

const assistantAvatarTemplate = html<ChatMessage, FoundationAiAssistant>`
  ${(m, c) => {
    const parent = c.parent as FoundationAiAssistant;
    return parent.imageSrc
      ? html<ChatMessage, FoundationAiAssistant>`
          <img src="${() => parent.imageSrc}" alt="Assistant" class="avatar-img" />
        `
      : html<ChatMessage, FoundationAiAssistant>`
          <span class="avatar-icon" :innerHTML="${() => parent.assistantIconSafe}"></span>
        `;
  }}
`;

const userAvatarTemplate = html<ChatMessage, FoundationAiAssistant>`
  ${(m, c) => {
    const parent = c.parent as FoundationAiAssistant;
    return parent.userImageSrc
      ? html<ChatMessage, FoundationAiAssistant>`
          <img src="${() => parent.userImageSrc}" alt="You" class="avatar-img" />
        `
      : html<ChatMessage, FoundationAiAssistant>`
          <span class="avatar-icon" :innerHTML="${() => parent.userIconSafe}"></span>
        `;
  }}
`;

/**
 * @beta
 */
const messageType = (m: ChatMessage): string => {
  switch (true) {
    case m.role === 'system-event':
      return 'agent-switch';
    case m.role === 'user':
    // A synthetic-user message is a display-only echo of an interaction
    // outcome; render it identically to a real user message.
    case m.role === 'synthetic-user':
      return 'user';
    // Reasoning (chain-of-thought summary) and narration (interstitial prose the model emits
    // alongside a tool call) both render as the "Thinking" bubble — visually set apart from a real
    // final agent reply, even though they stay separate categories (own log entry + own toggle:
    // `showThinkingSteps` vs `showNarration`). `thinking` is the legacy flag for pre-split sessions.
    case m.category === 'reasoning':
    case m.category === 'narration':
    case !!m.thinking:
      return 'ai-thinking';
    case !!m.toolCalls:
      return 'ai-function';
    case !!m.interaction:
      return 'interaction';
    default:
      return 'ai';
  }
};

const senderLabel: Record<string, string> = {
  user: 'You',
  'ai-thinking': 'Thinking',
  'ai-function': 'Tool Call',
  interaction: 'Assistant',
  ai: 'Assistant',
};

/**
 * Sender label for a message. Reasoning and narration share the "Thinking" bubble, so the label is
 * the only thing that tells them apart: narration reads "Working" (the action it narrates alongside
 * its tool call), reasoning stays "Thinking". Everything else uses the {@link messageType} map.
 */
const senderLabelFor = (m: ChatMessage): string =>
  m.category === 'narration' ? 'Working' : senderLabel[messageType(m)];

/**
 * Resolves how the host frames an interaction widget, defaulting to `'label'`
 * (the historical "Assistant" label, no bubble). Callers guard on
 * `m.interaction` first; non-interaction messages never consult this.
 */
const interactionPresentation = (m: ChatMessage): InteractionPresentation =>
  m.interaction?.presentation ?? 'label';

/**
 * Space-separated `part` names for a message bubble, so a host can style bubbles
 * from its own stylesheet via `::part()`. Every bubble gets `message` plus its
 * type (`message-user`, `message-ai`, `message-ai-thinking`, `message-ai-function`,
 * `message-interaction`). Interaction bubbles additionally carry a
 * presentation-specific part (`message-interaction-label` / `-bare` / `-bubble`)
 * so a host can target, say, only the bubble-framed widgets.
 */
const messageParts = (m: ChatMessage): string => {
  const type = messageType(m);
  return type === 'interaction'
    ? `message message-interaction message-interaction-${interactionPresentation(m)}`
    : `message message-${type}`;
};

// ─── Sub-agent trace fragments ────────────────────────────────────────────────

const subAgentAssistantTemplate = html<ChatMessage>`
  <div class="sub-agent-message sub-agent-assistant">${(m) => m.content}</div>
`;

const subAgentToolCallTemplate = html<ChatMessage>`
  <div class="sub-agent-message sub-agent-tool-call">
    ${repeat(
      (m) => m.toolCalls!,
      html<ChatToolCall>`
        <span class="sub-agent-tool-name">${(tc) => tc.name}</span>
      `,
    )}
  </div>
`;

const subAgentToolResultTemplate = html<ChatMessage>`
  <div class="sub-agent-message sub-agent-tool-result">${(m) => m.toolResult!.content}</div>
`;

/** Renders one row of a sub-agent trace (assistant text, tool call, or tool result). */
const subAgentMessageRowTemplate = html<ChatMessage>`
  ${when(
    (m) => m.role === 'assistant' && !m.toolCalls?.length && !!m.content,
    subAgentAssistantTemplate,
  )}${when((m) => !!m.toolCalls?.length, subAgentToolCallTemplate)}${when(
    (m) => m.role === 'tool' && !!m.toolResult?.content,
    subAgentToolResultTemplate,
  )}
`;

/** Collapsed <details> trace shown inside a tool-call card once the sub-agent finishes. */
const subAgentTraceTemplate = html<ChatToolCall>`
  <details class="sub-agent-trace">
    <summary class="sub-agent-trace-summary">
      ${(tc) => tc.subAgentTrace![0]?.agentName ?? 'Sub-agent'} trace
    </summary>
    ${repeat(
      (tc) => tc.subAgentTrace!.filter((m) => m.role !== 'user'),
      subAgentMessageRowTemplate,
    )}
  </details>
`;

/** Live streaming trace panel shown while a sub-agent is actively running. */
const liveSubAgentTraceTemplate = html<FoundationAiAssistant>`
  <div class="live-sub-agent-trace" part="live-sub-agent-trace">
    <span class="live-sub-agent-name">${(x) => x.liveSubAgentName ?? 'Sub-agent'}</span>
    ${repeat(
      (x) => x.liveSubAgentTrace.filter((m) => m.role !== 'user'),
      subAgentMessageRowTemplate,
    )}
  </div>
`;

// The interchangeable loading indicators. These MUST be stable module-level
// instances: the binding that selects between them reads `enabledAnimations`,
// which is backed by the redux store proxy and re-evaluates on every change to
// the aiAssistant slice (i.e. every new message, including hidden tool-call and
// thinking-step messages). Returning a fresh `html` instance from that binding
// would make FAST tear down and rebuild the indicator DOM each time, restarting
// the CSS animations and rAF loops. Stable references let FAST reuse the
// existing view so the animation runs uninterrupted.
const thinkingDotsTemplate = html<FoundationAiAssistant>`
  <div class="thinking-dots">
    <div class="dot dot-1"></div>
    <div class="dot dot-2"></div>
    <div class="dot dot-3"></div>
    <div class="dot dot-4"></div>
  </div>
`;

const thinkingWavesTemplate = html<FoundationAiAssistant>`
  <div class="thinking-waves" part="thinking-waves">
    <ai-waves-indicator></ai-waves-indicator>
    <span class="thinking-caption">Thinking...</span>
  </div>
`;

const thinkingFlowingWavesTemplate = html<FoundationAiAssistant>`
  <div class="thinking-flowing-waves" part="thinking-flowing-waves">
    <ai-flowing-waves-indicator></ai-flowing-waves-indicator>
    <span class="thinking-caption">Thinking...</span>
  </div>
`;

const thinkingPlasmaTemplate = html<FoundationAiAssistant>`
  <div class="thinking-plasma" part="thinking-plasma">
    <ai-plasma-orb-indicator></ai-plasma-orb-indicator>
    <span class="thinking-caption">Thinking...</span>
  </div>
`;

/**
 * Picks the loading indicator for the currently enabled style, falling back to
 * the dots (also the default when no animations config is supplied). Returns a
 * stable template instance per style — see the note above.
 */
const selectThinkingTemplate = (x: FoundationAiAssistant): ViewTemplate<FoundationAiAssistant> => {
  const enabled = x.enabledAnimations;
  if (enabled.includes('waves')) return thinkingWavesTemplate;
  if (enabled.includes('flowingWaves')) return thinkingFlowingWavesTemplate;
  if (enabled.includes('plasma')) return thinkingPlasmaTemplate;
  return thinkingDotsTemplate;
};

/** Shown in place of the transcript while a manual compaction runs (GENC-1351 §5.7). */
const compactingTemplate = html<FoundationAiAssistant>`
  <div class="compacting-indicator" part="compacting-indicator">
    ${thinkingDotsTemplate}
    <span class="compacting-caption">Compacting…</span>
  </div>
`;

/**
 * Shown in place of the transcript while a page-reload restore is in flight
 * (GENC-1351 §6) — a slow, server-backed `provider.load()`. Reuses the compaction
 * indicator's visual (same centered dotted loader) with its own `part` for host
 * styling. Invisible for synchronous providers, which resolve before paint.
 */
const restoringTemplate = html<FoundationAiAssistant>`
  <div class="compacting-indicator" part="restoring-indicator">
    ${thinkingDotsTemplate}
    <span class="compacting-caption">Restoring…</span>
  </div>
`;

// ─── Public factory ───────────────────────────────────────────────────────────

/** @internal */
export const FoundationAiAssistantTemplate = (
  designSystemPrefix: string,
): ViewTemplate<FoundationAiAssistant> => {
  const buttonTag = `${designSystemPrefix}-button`;
  const textareaTag = `${designSystemPrefix}-text-area`;
  const iconTag = `${designSystemPrefix}-icon`;

  // ── Tool call item ──────────────────────────────────────────────────────────

  const toolCallItemTemplate = html<ChatToolCall>`
    ${when(
      (tc) => tc.foldEvent === 'open',
      html<ChatToolCall>`
        <pre class="payload fold-event fold-open">&#9658; ${(tc) => tc.name}</pre>
      `,
    )}
    ${when(
      (tc) => tc.foldEvent === 'close',
      html<ChatToolCall>`
        <pre class="payload fold-event fold-close">&#9668; ${(tc) => tc.name}</pre>
      `,
    )}
    ${when(
      (tc) => !tc.foldEvent && !isChatToolCallUnknown(tc),
      html<ChatToolCall>`
        <pre class="payload">
${(tc) => (tc.foldPath?.length ? `${tc.foldPath.join(' › ')} › ` : '')}<strong>${(tc) =>
            tc.name}</strong>(${(tc) => JSON.stringify(tc.args, null, 2)})</pre
        >
      `,
    )}
    ${when(
      isChatToolCallUnknown,
      html<ChatToolCall>`
        <pre class="payload unknown-tool">${(tc) => unknownToolPayload(tc)}</pre>
      `,
    )}
    ${when((tc) => !!tc.subAgentTrace?.length, subAgentTraceTemplate)}
  `;

  // ── Message row ─────────────────────────────────────────────────────────────

  const messageRowTemplate = html<ChatMessage, FoundationAiAssistant>`
    ${when(
      (m) => m.role === 'system-event',
      html<ChatMessage>`
        <div class="agent-switch-indicator" part="agent-switch-indicator">
          <span class="agent-switch-label">${(m) => m.content}</span>
        </div>
      `,
    )}
    ${when(
      (m) => m.role === 'compacted-summary',
      html<ChatMessage>`
        <div class="agent-switch-indicator compaction-divider" part="compaction-divider">
          <span class="agent-switch-label">${(m) => compactionDividerLabel(m)}</span>
        </div>
      `,
    )}
    ${when(
      (m) => m.role !== 'system-event' && m.role !== 'compacted-summary',
      html<ChatMessage, FoundationAiAssistant>`
        <div
          class="message-row ${(m) => messageType(m)} ${(m) =>
            m.interaction ? `present-${interactionPresentation(m)}` : ''}"
        >
          <div class="avatar ${(m) => messageType(m)}">
            ${when(
              // Keyed on messageType (not raw role) so a synthetic-user message,
              // which renders as 'user', gets the user avatar rather than the
              // assistant one.
              (m) => messageType(m) === 'user',
              userAvatarTemplate,
            )}${when((m) => messageType(m) !== 'user', assistantAvatarTemplate)}
          </div>
          <div
            class="message ${(m) => messageType(m)} ${(m) =>
              classNames(['narration', m.category === 'narration'])}"
            part="${(m) => messageParts(m)}"
          >
            ${when(
              // A 'bare' interaction owns its full presentation — suppress the
              // host sender label. Every other message keeps it.
              (m) => !(m.interaction && interactionPresentation(m) === 'bare'),
              html<ChatMessage, FoundationAiAssistant>`
                <div class="sender">
                  ${(m, c) =>
                    messageType(m) === 'ai-function' &&
                    m.agentName &&
                    (c.parent as FoundationAiAssistant).showAgentSwitchIndicator
                      ? `Tool Call · ${m.agentLabel ?? m.agentName}`
                      : senderLabelFor(m)}
                </div>
              `,
            )}
            <div class="content">
              ${when(
                (m) => m.content,
                html<ChatMessage>`
                  <ai-chat-markdown :content="${(m) => m.content}"></ai-chat-markdown>
                `,
              )}
              ${when(
                (m) => m.toolCalls,
                html<ChatMessage>`
                  ${repeat((m) => m.toolCalls ?? [], toolCallItemTemplate)}
                `,
              )}
              ${when(
                (m) => m.interaction,
                html<ChatMessage, FoundationAiAssistant>`
                  <ai-chat-interaction-wrapper
                    :componentName=${(m) => m.interaction!.componentName}
                    :data=${(m) => m.interaction!.data}
                    :interactionId=${(m) => m.interaction!.interactionId}
                    :resolved=${(m) => m.interaction!.resolved}
                    @interaction-completed=${(m, c) => c.parent.handleInteractionCompleted(c.event)}
                  ></ai-chat-interaction-wrapper>
                `,
              )}
            </div>
          </div>
        </div>
      `,
    )}
  `;

  // ── Agent picker ────────────────────────────────────────────────────────────

  const agentToggleButtonTemplate = html<FoundationAiAssistant>`
    <${buttonTag}
      class="agent-toggle-button"
      part="agent-toggle-button"
      appearance="stealth"
      title=${(x) => x.agentToggleTitle}
      ?disabled=${(x) => x.busy || x.pinLocked}
      @click=${(x) => x.toggleAgentPicker()}
    >
      ${when(
        (x) => x.agentPickerOpen,
        html<FoundationAiAssistant>`<${iconTag} name="chevron-down"></${iconTag}>`,
      )}
      ${when(
        (x) => !x.agentPickerOpen && x.pinnedAgentName !== null,
        html<FoundationAiAssistant>`
          <${iconTag}
            name="thumbtack"
            style="${(x) => (x.pinnedAgentColour ? `color: ${x.pinnedAgentColour}` : '')}"
          ></${iconTag}>
        `,
      )}
      ${when(
        (x) => !x.agentPickerOpen && x.pinnedAgentName === null,
        html<FoundationAiAssistant>`
          <span class="agent-toggle-label">Auto</span>
        `,
      )}
    </${buttonTag}>
  `;

  const attachButtonTemplate = html<FoundationAiAssistant>`
    <${buttonTag}
      class="attach-button"
      part="attach-button"
      appearance="stealth"
      title=${(x) => `Attach file (${x.chatConfig.ui?.acceptedFiles})`}
      ?disabled=${(x) => x.busy}
      @click=${(x) => x.triggerFileInput()}
    ><${iconTag} name="paperclip"></${iconTag}></${buttonTag}>
  `;

  const inputLeftControlsTemplate = html<FoundationAiAssistant>`
    <div class="input-left-controls" part="input-left-controls">
      ${when((x) => x.agentPickerEnabled, agentToggleButtonTemplate)}
      ${when((x) => !!x.chatConfig.ui?.acceptedFiles, attachButtonTemplate)}
    </div>
  `;

  const agentPickerPanelTemplate = html<FoundationAiAssistant>`
    <div class="agent-picker-panel" part="agent-picker-panel">
      <foundation-ai-agent-picker
        part="agent-picker"
        :designSystemPrefix="${(x) => x.designSystemPrefix}"
        :mode="${(x) => x.agentPicker}"
        :agents="${(x) => x.agents ?? []}"
        :pinnedAgentName="${(x) => x.pinnedAgentName}"
        @agent-pinned="${(x, c) => {
          x.pinnedAgentName = (c.event as CustomEvent<string | null>).detail;
          x.toggleAgentPicker();
        }}"
      ></foundation-ai-agent-picker>
    </div>
  `;

  // ── Session lifecycle menu (GENC-1351 §5.5) ──────────────────────────────────

  const sessionMenuButtonTemplate = html<FoundationAiAssistant>`
    <${buttonTag}
      class="session-menu-button"
      part="session-menu-button"
      appearance="stealth"
      title="Session options"
      aria-label="Session options"
      @click=${(x) => x.toggleSessionMenu()}
    ><span class="session-menu-icon" aria-hidden="true">⋮</span></${buttonTag}>
  `;

  // Two fixed, equal-width buttons (Compact + Clear), centered. Clicking either
  // swaps that pair *in place* for [Confirm] [Back] of identical size and shape —
  // only the labels and colour change, so nothing moves. Confirm runs the
  // action; Back returns to the pair. The two-step confirm is the safety; there
  // is no shifting warning text.
  const sessionMenuPanelTemplate = html<FoundationAiAssistant>`
    <div class="session-menu-panel" part="session-menu-panel">
      ${when(
        (x) => !x.confirmingClear && !x.confirmingCompact,
        html<FoundationAiAssistant>`
          <${buttonTag}
            class="session-menu-item"
            appearance="stealth"
            ?disabled=${(x) => !x.compactable}
            title=${(x) =>
              x.compactable
                ? 'Summarize older messages to free up context'
                : 'Not enough conversation yet to be worth compacting'}
            @click=${(x) => (x.confirmingCompact = true)}
          >Compact</${buttonTag}><${buttonTag}
            class="session-menu-item"
            appearance="stealth"
            @click=${(x) => (x.confirmingClear = true)}
          >Clear</${buttonTag}>
        `,
      )}${when(
        (x) => x.confirmingCompact,
        html<FoundationAiAssistant>`
          <${buttonTag}
            class="session-menu-item"
            appearance="accent"
            title="Summarize older messages now"
            @click=${(x) => {
              void x.compactSession();
            }}
          >Confirm</${buttonTag}><${buttonTag}
            class="session-menu-item"
            appearance="outline"
            @click=${(x) => (x.confirmingCompact = false)}
          >Back</${buttonTag}>
        `,
      )}${when(
        (x) => x.confirmingClear,
        html<FoundationAiAssistant>`
          <${buttonTag}
            class="session-menu-item danger"
            appearance="accent"
            title="Permanently clear this conversation"
            @click=${(x) => {
              void x.clearSession();
            }}
          >Confirm</${buttonTag}><${buttonTag}
            class="session-menu-item"
            appearance="outline"
            @click=${(x) => (x.confirmingClear = false)}
          >Back</${buttonTag}>
        `,
      )}
    </div>
  `;

  // ── Root template ───────────────────────────────────────────────────────────

  return html<FoundationAiAssistant>`
    <div class="chat-wrapper" part="chat-wrapper">
      ${when(
        (x) => !!x.chatConfig.ui?.acceptedFiles,
        html<FoundationAiAssistant>`
          <input
            class="file-input"
            type="file"
            multiple
            accept=${(x) => x.chatConfig.ui?.acceptedFiles}
            @change=${(x, c) => x.handleFileSelect(c.event)}
          />
        `,
      )}
      ${when(
        (x) => x.settingsCogVisible,
        html<FoundationAiAssistant>`
          <div
            class="chat-header ${(x) => (x.popoutMode !== 'collapse' ? 'draggable' : '')}"
            part="chat-header"
            @mousedown="${(x, c) => x.onChatHeaderMouseDown(c.event as MouseEvent)}"
          >
            ${when(
              (x) => !!x.headerTitle,
              html<FoundationAiAssistant>`
                <span class="chat-title" part="chat-title">${(x) => x.headerTitle}</span>
              `,
            )}
            <div class="header-actions" part="header-actions">
              <slot name="header-actions-extra"></slot>
              ${when(
                (x) => !!x.popoutMode,
                html<FoundationAiAssistant>`
                  <${buttonTag}
                    class="popout-button"
                    part="popout-button"
                    appearance="stealth"
                    title="${(x) => (x.popoutMode === 'expand' ? 'Expand to panel' : 'Pop back to bubble')}"
                    ?disabled=${(x) => x.hasActivePendingInteraction}
                    @click=${(x) => x.handlePopout()}
                  ><${iconTag} name="${(x) => (x.popoutMode === 'expand' ? 'external-link-alt' : 'compress-alt')}"></${iconTag}></${buttonTag}>
                `,
              )}
              <${buttonTag}
                class="settings-button"
                part="settings-button"
                appearance="stealth"
                title="Settings"
                aria-haspopup="dialog"
                @click=${(x) => x.openSettingsModal()}
              ><${iconTag} name="cog"></${iconTag}></${buttonTag}>
            </div>
          </div>
        `,
      )}

      <slot name="below-header"></slot>

      <div class="splash-wrapper" part="splash">
        <slot name="splash"></slot>
      </div>

      <div class="messages" part="messages" ${ref('messagesEl')}>
        <div class="messages-content" part="messages-content">
          ${when((x) => x.compacting, compactingTemplate)}
          ${when((x) => x.restoring, restoringTemplate)}
          ${repeat(
            (x) => (x.compacting || x.restoring ? [] : x.visibleMessages),
            messageRowTemplate,
          )}
          ${
            /* GENC-1429: recycle:false so each pending interaction gets a FRESH wrapper. With
                recycling, the trailing slot's wrapper is reused across interactions (e.g. a tool that
                shows the code-diff approval, then the preview-status card): the reused wrapper still
                carries the prior interaction's truthy `resolved` (bound last, after componentName/
                interactionId trigger the re-render), so the completion listener's `if (this.resolved)
                return` guard swallows the next auto-resolve widget's completion and the driver hangs
                awaiting it. A fresh wrapper starts with resolved=undefined, so the guard is correct. */ ''
          }
          ${repeat((x) => x.activeInteractionRow, messageRowTemplate, { recycle: false })}
          ${when(
            (x) => x.liveSubAgentTrace.length > 0 && x.showToolCalls,
            liveSubAgentTraceTemplate,
          )}
          ${when(
            (x) =>
              x.showLoadingIndicator &&
              (x.chatConfig.ui?.animations == null ||
                LOADING_STYLE_ANIMATIONS.some((s) => x.enabledAnimations.includes(s))),
            html<FoundationAiAssistant>`
              <div class="message-row ai" part="thinking">
                <div class="avatar">
                  ${(x) =>
                    x.imageSrc
                      ? html<FoundationAiAssistant>`
                          <img src="${() => x.imageSrc}" alt="Assistant" class="avatar-img" />
                        `
                      : html<FoundationAiAssistant>`
                          <span
                            class="avatar-icon"
                            :innerHTML="${() => x.assistantIconSafe}"
                          ></span>
                        `}
                </div>
                ${(x) => selectThinkingTemplate(x)}
              </div>
            `,
          )}
        </div>
      </div>

      ${when(
        (x) =>
          !x.composerHiddenByConfig && (x.attachments.length > 0 || x.attachmentErrors.length > 0),
        html<FoundationAiAssistant>`
          <div class="attachment-chips" part="attachment-chips">
            ${repeat(
              (x) => x.attachments,
              html<ChatAttachment, FoundationAiAssistant>`
                <span class="attachment-chip" part="attachment-chip">
                  ${(a) => a.name}
                  <button
                    class="chip-remove"
                    type="button"
                    @click=${(a, c) => c.parent.removeAttachment(a)}
                  >
                    &times;
                  </button>
                </span>
              `,
            )}
            ${repeat(
              (x) => x.attachmentErrors,
              html<string, FoundationAiAssistant>`
                <span class="attachment-chip attachment-chip-error" part="attachment-chip-error">
                  ${(err) => err}
                  <button
                    class="chip-remove"
                    type="button"
                    @click=${(err, c) => c.parent.removeAttachmentError(err)}
                  >
                    &times;
                  </button>
                </span>
              `,
            )}
          </div>
        `,
      )}
      ${when(
        (x) => x.chatConfig.suggestions?.behavior !== 'never',
        html<FoundationAiAssistant>`
          <chat-suggestions
            :state="${(x) => x.suggestionsState}"
            @suggestion-clicked="${(x, c) =>
              x.handleSuggestionClick((c.event as CustomEvent).detail)}"
          ></chat-suggestions>
        `,
      )}
      ${when((x) => x.agentPickerEnabled && x.agentPickerOpen, agentPickerPanelTemplate)}${when(
        (x) => x.sessionMenuOpen && !x.busy,
        sessionMenuPanelTemplate,
      )}
      ${when(
        (x) =>
          !x.composerHiddenByConfig &&
          !(x.busy && x.effectiveChatInputDuringExecution === 'hidden'),
        html<FoundationAiAssistant>`
          ${when(
            (x) => x.isComposerResizable,
            html<FoundationAiAssistant>`
              <div
                class="composer-resize-handle"
                part="composer-resize-handle"
                role="separator"
                aria-orientation="horizontal"
                title="Drag to resize the message box"
                @pointerdown=${(x, c) => x.onComposerResizeStart(c.event as PointerEvent)}
                @pointermove=${(x, c) => x.onComposerResizeMove(c.event as PointerEvent)}
                @pointerup=${(x, c) => x.onComposerResizeEnd(c.event as PointerEvent)}
                @pointercancel=${(x, c) => x.onComposerResizeEnd(c.event as PointerEvent)}
              ></div>
            `,
          )}
          <div class="input-row" part="input-row">
            ${when(
              (x) => x.agentPickerEnabled || !!x.chatConfig.ui?.acceptedFiles,
              inputLeftControlsTemplate,
            )}
            <${textareaTag}
              ${ref('chatInputEl')}
              class="chat-input"
              part="input"
              style=${(x) => x.composerHeightStyle}
              placeholder=${(x) => x.effectivePlaceholder}
              :value=${(x) => x.inputValue}
              ?disabled=${(x) => x.busy || x.compacting || x.restoring}
              @input=${(x, c) => (x.inputValue = (c.event.target as any).value)}
              @keydown=${(x, c) => {
                if (
                  (c.event as KeyboardEvent).key === 'Enter' &&
                  !(c.event as KeyboardEvent).shiftKey
                ) {
                  c.event.preventDefault();
                  x.handleSendClick();
                  return false;
                }
                return true;
              }}
            ></${textareaTag}>
            <div class="input-right-controls">
              ${when(
                (x) => x.busy && !x.chatConfig.ui?.hideStopButton,
                html<FoundationAiAssistant>`
                  <${buttonTag}
                    class="stop-button"
                    part="stop-button"
                    appearance="stealth"
                    title="${(x) => (x.state === 'cancelling' ? 'Stopping…' : 'Stop')}"
                    aria-label="${(x) => (x.state === 'cancelling' ? 'Stopping' : 'Stop')}"
                    ?disabled=${(x) => x.state === 'cancelling'}
                    @click=${(x) => x.handleStopClick()}
                  ><${iconTag}
                      class="${(x) => classNames('stop-icon', ['stop-icon-spinning', x.state === 'cancelling'])}"
                      name="${(x) => (x.state === 'cancelling' ? 'spinner' : 'stop')}"
                    ></${iconTag}></${buttonTag}>
                `,
              )}
              ${when(
                (x) => !x.busy && !x.compacting && !x.restoring && x.sessionMenuEnabled,
                sessionMenuButtonTemplate,
              )}
              <${buttonTag}
                class="send-button"
                part="send-button"
                ?disabled=${(x) => x.busy || x.compacting || x.restoring || (!x.inputValue.trim() && !x.attachments.length)}
                @click=${(x) => x.handleSendClick()}
              >Send</${buttonTag}>
            </div>
          </div>
        `,
      )}
      <ai-halo-overlay
        part="halo-overlay"
        ?active=${(x) => x.showHalo !== 'no' && x.enabledAnimations.includes('halo')}
        :speed="${(x) =>
          x.showHalo === 'orchestrating' ? HALO_SPEED_ORCHESTRATING : HALO_SPEED_DEFAULT}"
        direction="${(x) => (x.showHalo === 'orchestrating' ? 'ccw' : 'cw')}"
        :borderSize="${(x) => (x.showHalo === 'orchestrating' ? 2 : HALO_BORDER_SIZE_DEFAULT)}"
      ></ai-halo-overlay>
    </div>
    ${SettingsModalTemplate(designSystemPrefix)}
  `;
};
