// FILE: browser-panel.tsx
// Purpose: Renders Cradle's BrowserPanel chrome and anchors the native Electron WebContentsView.
// Layer: Browser feature UI
// Depends on: BrowserPanel Zustand metadata cache, Electron browser preload bridge

import { GlobeLine as GlobeIcon } from '@mingcute/react'
import type { FileUIPart } from 'ai'
import type { FormEvent, KeyboardEvent as ReactKeyboardEvent } from 'react'
import {
  lazy,
  Suspense,
  useCallback,
  useEffect,
  useEffectEvent,
  useLayoutEffect,
  useRef,
  useState,
} from 'react'

import {
  submitChatComposerContextIngress,
  submitChatComposerFileIngress,
  submitChatPromptIngress,
} from '~/features/chat/prompt-ingress'
import { PullRequestDetailPanel } from '~/features/pull-requests/pull-request-detail-panel'
import type { TerminalMetadata } from '~/features/tui/terminal-metadata'
import { WorkspaceFileEditor } from '~/features/workspace/workspace-file-editor'
import { WorkspaceFilePreview } from '~/features/workspace/workspace-file-preview'
import type {
  BrowserAnnotationAnchor,
  BrowserAnnotationDesignChange,
  BrowserAnnotationElement,
  BrowserAnnotationLayoutHint,
  BrowserAnnotationRecord,
  BrowserPanelTab,
  BrowserTabState,
  BrowserWebTab,
  ThreadBrowserState,
} from '~/store/browser-panel'
import {
  BROWSER_ANNOTATION_DESIGN_CSS_PROPERTIES,
  DEFAULT_BROWSER_PANEL_OWNER_ID,
  selectOwnerBrowserAnnotations,
  selectOwnerBrowserHistory,
  selectOwnerBrowserState,
  useBrowserPanelStore,
} from '~/store/browser-panel'
import { useLayoutStore } from '~/store/layout'

import { releaseSideConversation } from '../chat/commands/chat-response-command'
import type { BrowserAnnotationAdjustmentApplyDetail } from './browser-annotation-adjustment-panel'
import { BROWSER_ANNOTATION_ADJUSTMENT_APPLY_EVENT } from './browser-annotation-adjustment-panel'
import {
  countBrowserAnnotationDesignChanges,
  formatBrowserAnnotationAnchor,
} from './browser-annotation-presenter'
import { BrowserAnnotationRailView } from './browser-annotation-rail-view'
import { BrowserChromeStatusView } from './browser-chrome-status-view'
import { BrowserNavigationToolbarView } from './browser-navigation-toolbar-view'
import { BrowserNewTabSurfaceView } from './browser-new-tab-surface-view'
import type { BrowserAddressSuggestion } from './browser-panel.logic'
import {
  browserAddressDisplayValue,
  buildBrowserAddressSuggestions,
  normalizeBrowserAddressInput,
  resolveBrowserAddressSync,
  resolveBrowserChromeStatus,
} from './browser-panel.logic'
import { BrowserPanelCreateSurfaceView } from './browser-panel-create-surface-view'
import { BrowserPanelTabStripView } from './browser-panel-tab-strip-view'
import { ContextUsageReport } from './context-usage-report'
import {
  BROWSER_NATIVE_SURFACE_OCCLUSION_ATTRIBUTE,
  BROWSER_NATIVE_SURFACE_OCCLUSION_SELECTOR,
} from './native-surface-occlusion'
import { useNativeBrowserSurfaceSuppressionStore } from './native-surface-suppression'
import { PlanDocumentViewer } from './plan-document-viewer'
import type { PlanRefineEditorDirtyDetail, PlanRefineEditorSaveDetail } from './plan-refine-editor'
import {
  PLAN_REFINE_EDITOR_DIRTY_EVENT,
  PLAN_REFINE_EDITOR_SAVE_EVENT,
  PlanRefineEditor,
} from './plan-refine-editor'
import { SideConversationPanel } from './side-conversation-panel'
import { SubagentOutputPanel } from './subagent-output-panel'
import { useLocalServers } from './use-local-servers'
import { WorkflowOutputPanel } from './workflow-output-panel'
import { WorkspaceDiffViewer } from './workspace-diff-viewer'

interface BrowserPanelProps {
  ownerId?: string | null
  activeSessionId?: string | null
  activeSessionTitle?: string | null
  terminalCwd?: string | null
  nativeBoundsPaused?: boolean
  nativeSurfaceVisible?: boolean
  onCloseLastTab?: (ownerId: string) => void
}

interface BrowserPromptAttachment {
  filename?: string
  mediaType?: string
  url: string
}

interface BrowserPromptRequest {
  threadId: string
  tabId: string
  text: string
  attachments: BrowserPromptAttachment[]
  sourceUrl: string | null
  sourceTitle: string | null
}

interface BrowserAnnotationRuntimeEvent {
  threadId: string
  tabId: string
  type:
    | 'ready'
    | 'selected-element'
    | 'save'
    | 'submit'
    | 'cancel'
    | 'closed'
    | 'toggle'
    | 'copy'
    | 'clear'
    | 'delete'
    | 'edit'
    | 'layout-sync'
  anchor?: BrowserAnnotationAnchor
  annotationId?: string
  runtimeAnnotationId?: string
  selectedElement?: BrowserAnnotationElement | null
  body?: string
  output?: string
  annotations?: Array<{
    id: string
    anchor: BrowserAnnotationAnchor
    body: string
    designChange?: BrowserAnnotationDesignChange | null
    status?: 'saved' | 'sent'
  }>
  layoutHints?: BrowserAnnotationLayoutHint[]
  attachedImages?: BrowserPromptAttachment[]
  designChange?: BrowserAnnotationDesignChange | null
  elements?: BrowserAnnotationElement[]
  surfaceSize?: {
    width: number
    height: number
  }
  sourceUrl: string | null
  sourceTitle: string | null
}

const BROWSER_BOUNDS_SYNC_STABLE_FRAME_TARGET = 1
const BROWSER_SCREENSHOT_CHUNK_SIZE = 0x8000
const BROWSER_NATIVE_OCCLUSION_MARGIN = 6
const BROWSER_NATIVE_OCCLUSION_MIN_SIZE = 32
const EMPTY_BROWSER_PANEL_TABS: BrowserPanelTab[] = []
const EMPTY_BROWSER_ANNOTATION_LAYOUT_HINTS: BrowserAnnotationLayoutHint[] = []
const EMPTY_BROWSER_ANNOTATION_LAYOUT_HINTS_BY_TAB_ID: Record<
  string,
  BrowserAnnotationLayoutHint[] | undefined
> = {}

function loadBrowserTuiShellView() {
  return import('~/features/tui/shell-view').then(module => ({ default: module.ShellView }))
}

const BrowserTuiShellView = lazy(loadBrowserTuiShellView)

interface BrowserAnnotationRuntimeSession {
  tabId: string
  editingAnnotationId: string | null
}

interface BrowserAnnotationCropRect {
  x: number
  y: number
  width: number
  height: number
}

interface BrowserNativeBounds {
  x: number
  y: number
  width: number
  height: number
}

interface BrowserWebviewElement extends HTMLElement {
  getWebContentsId?: () => number
}

const BROWSER_RENDERER_OBSCURING_OVERLAY_SELECTOR = [
  '[data-slot="popover-content"]',
  '[data-slot="dropdown-menu-content"]',
  '[data-slot="context-menu-content"]',
  '[data-slot="hover-card-content"]',
  '[data-slot="select-content"]',
  '[data-slot="command-dialog-content"]',
  '[data-slot="dialog-content"]',
  '[data-slot="alert-dialog-content"]',
  '[role="dialog"][aria-modal="true"]',
].join(', ')

function readBrowserBridge() {
  return window.cradle?.browser ?? null
}

function normalizeBrowserNativeBounds(rect: DOMRect): BrowserNativeBounds | null {
  const width = Math.max(0, Math.floor(rect.width))
  const height = Math.max(0, Math.floor(rect.height))
  if (width === 0 || height === 0) {
    return null
  }

  return {
    x: Math.max(0, Math.floor(rect.x)),
    y: Math.max(0, Math.floor(rect.y)),
    width,
    height,
  }
}

function browserNativeBoundsFromEdges(input: {
  left: number
  top: number
  right: number
  bottom: number
}): BrowserNativeBounds | null {
  const x = Math.max(0, Math.floor(input.left))
  const y = Math.max(0, Math.floor(input.top))
  const right = Math.max(x, Math.ceil(input.right))
  const bottom = Math.max(y, Math.ceil(input.bottom))
  const width = right - x
  const height = bottom - y

  if (width < BROWSER_NATIVE_OCCLUSION_MIN_SIZE || height < BROWSER_NATIVE_OCCLUSION_MIN_SIZE) {
    return null
  }

  return { x, y, width, height }
}

function browserNativeBoundsSignature(bounds: BrowserNativeBounds | null): string {
  if (!bounds) {
    return 'hidden'
  }
  return `${bounds.x}:${bounds.y}:${bounds.width}:${bounds.height}`
}

function rectsIntersect(
  a: { left: number, top: number, right: number, bottom: number },
  b: { left: number, top: number, right: number, bottom: number },
): boolean {
  return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top
}

function setBrowserWebviewOccluded(webview: BrowserWebviewElement | null, occluded: boolean): void {
  if (!webview) {
    return
  }
  webview.style.visibility = occluded ? 'hidden' : 'visible'
  webview.style.pointerEvents = occluded ? 'none' : 'auto'
}

function hasRendererBrowserObscuringOverlay(viewportRect: DOMRect): boolean {
  if (typeof document === 'undefined') {
    return false
  }
  const overlays = document.querySelectorAll<HTMLElement>(
    BROWSER_RENDERER_OBSCURING_OVERLAY_SELECTOR,
  )
  for (const overlay of overlays) {
    const styles = window.getComputedStyle(overlay)
    if (styles.display === 'none' || styles.visibility === 'hidden' || styles.opacity === '0') {
      continue
    }
    for (const overlayRect of overlay.getClientRects()) {
      if (rectsIntersect(viewportRect, overlayRect)) {
        return true
      }
    }
  }
  return false
}

function applyBrowserNativeSurfaceOcclusions(
  bounds: BrowserNativeBounds,
  viewportRect: DOMRect,
): BrowserNativeBounds | null {
  if (typeof document === 'undefined') {
    return bounds
  }

  const left = bounds.x
  let top = bounds.y
  const right = bounds.x + bounds.width
  let bottom = bounds.y + bounds.height
  const viewportCenterY = bounds.y + bounds.height / 2
  const occluders = document.querySelectorAll<HTMLElement>(
    BROWSER_NATIVE_SURFACE_OCCLUSION_SELECTOR,
  )

  for (const occluder of occluders) {
    const rect = occluder.getBoundingClientRect()
    if (rect.width <= 0 || rect.height <= 0) {
      continue
    }

    const expandedRect = {
      left: rect.left - BROWSER_NATIVE_OCCLUSION_MARGIN,
      top: rect.top - BROWSER_NATIVE_OCCLUSION_MARGIN,
      right: rect.right + BROWSER_NATIVE_OCCLUSION_MARGIN,
      bottom: rect.bottom + BROWSER_NATIVE_OCCLUSION_MARGIN,
    }

    if (!rectsIntersect(viewportRect, expandedRect)) {
      continue
    }

    if ((expandedRect.top + expandedRect.bottom) / 2 >= viewportCenterY) {
      bottom = Math.min(bottom, expandedRect.top)
      continue
    }

    top = Math.max(top, expandedRect.bottom)
  }

  return browserNativeBoundsFromEdges({ left, top, right, bottom })
}

function formatBrowserActionError(error: unknown): string | null {
  if (!(error instanceof Error)) {
    return 'Browser action failed.'
  }
  if (/ERR_ABORTED|\(-3\)/i.test(error.message)) {
    return null
  }
  return error.message || 'Browser action failed.'
}

function getTabTitle(tab: BrowserTabState): string {
  if (tab.title && tab.title !== 'about:blank') {
    return tab.title
  }
  if (tab.url === 'about:blank') {
    return 'New tab'
  }
  return tab.url
}

function isBrowserPanelTab(tab: BrowserPanelTab): tab is BrowserWebTab {
  return tab.kind === 'browser'
}

function isPlanRefineEditorDirtyEvent(
  event: Event,
): event is CustomEvent<PlanRefineEditorDirtyDetail> {
  return (
    event instanceof CustomEvent
    && typeof event.detail === 'object'
    && event.detail !== null
    && typeof (event.detail as Partial<PlanRefineEditorDirtyDetail>).tabId === 'string'
    && typeof (event.detail as Partial<PlanRefineEditorDirtyDetail>).dirty === 'boolean'
  )
}

function isPlanRefineEditorSaveEvent(
  event: Event,
): event is CustomEvent<PlanRefineEditorSaveDetail> {
  return (
    event instanceof CustomEvent
    && typeof event.detail === 'object'
    && event.detail !== null
    && typeof (event.detail as Partial<PlanRefineEditorSaveDetail>).tabId === 'string'
    && typeof (event.detail as Partial<PlanRefineEditorSaveDetail>).markdown === 'string'
  )
}

function isBrowserBlankTab(tab: BrowserWebTab | null): boolean {
  return (tab?.url.trim() ?? '') === 'about:blank'
}

function bytesToBase64(bytes: Uint8Array): string {
  let binary = ''
  for (let offset = 0; offset < bytes.length; offset += BROWSER_SCREENSHOT_CHUNK_SIZE) {
    const chunk = bytes.subarray(offset, offset + BROWSER_SCREENSHOT_CHUNK_SIZE)
    binary += String.fromCharCode(...chunk)
  }
  return window.btoa(binary)
}

function createBrowserScreenshotFilePart(input: {
  name: string
  mimeType: 'image/png'
  bytes: Uint8Array
}): FileUIPart {
  return {
    type: 'file',
    filename: input.name,
    mediaType: input.mimeType,
    url: `data:${input.mimeType};base64,${bytesToBase64(input.bytes)}`,
  }
}

function createBrowserDataUrlFilePart(input: {
  name: string
  mimeType: 'image/png'
  dataUrl: string
}): FileUIPart {
  return {
    type: 'file',
    filename: input.name,
    mediaType: input.mimeType,
    url: input.dataUrl,
  }
}

function createBrowserBase64FilePart(input: {
  name: string
  mimeType: 'image/png'
  base64: string
}): FileUIPart {
  return createBrowserDataUrlFilePart({
    name: input.name,
    mimeType: input.mimeType,
    dataUrl: `data:${input.mimeType};base64,${input.base64}`,
  })
}

function inferBrowserPromptMediaType(url: string): string {
  const dataUrlMatch = /^data:([^;,]+)[;,]/i.exec(url)
  if (dataUrlMatch?.[1]) {
    return dataUrlMatch[1]
  }

  const normalizedUrl = url.toLowerCase()
  if (/\.(png)(?:[?#].*)?$/.test(normalizedUrl)) {
    return 'image/png'
  }
  if (/\.(jpe?g)(?:[?#].*)?$/.test(normalizedUrl)) {
    return 'image/jpeg'
  }
  if (/\.(webp)(?:[?#].*)?$/.test(normalizedUrl)) {
    return 'image/webp'
  }
  if (/\.(gif)(?:[?#].*)?$/.test(normalizedUrl)) {
    return 'image/gif'
  }
  if (/\.(pdf)(?:[?#].*)?$/.test(normalizedUrl)) {
    return 'application/pdf'
  }
  return 'application/octet-stream'
}

function createBrowserPromptFilePart(
  attachment: BrowserPromptAttachment,
  index: number,
): FileUIPart | null {
  const url = attachment.url.trim()
  if (!url) {
    return null
  }

  const filename = attachment.filename?.trim() || `browser-prompt-attachment-${index + 1}`
  const mediaType = attachment.mediaType?.trim() || inferBrowserPromptMediaType(url)
  return {
    type: 'file',
    filename,
    mediaType,
    url,
  }
}

function isBrowserPromptAttachment(value: unknown): value is BrowserPromptAttachment {
  return Boolean(
    value
    && typeof value === 'object'
    && typeof (value as BrowserPromptAttachment).url === 'string'
    && ((value as BrowserPromptAttachment).filename === undefined
      || typeof (value as BrowserPromptAttachment).filename === 'string')
    && ((value as BrowserPromptAttachment).mediaType === undefined
      || typeof (value as BrowserPromptAttachment).mediaType === 'string'),
  )
}

function isBrowserPromptRequest(value: unknown): value is BrowserPromptRequest {
  if (!value || typeof value !== 'object') {
    return false
  }
  const candidate = value as BrowserPromptRequest
  return (
    typeof candidate.threadId === 'string'
    && typeof candidate.tabId === 'string'
    && typeof candidate.text === 'string'
    && Array.isArray(candidate.attachments)
    && candidate.attachments.every(isBrowserPromptAttachment)
  )
}

function isBrowserAnnotationRuntimeEvent(value: unknown): value is BrowserAnnotationRuntimeEvent {
  if (!value || typeof value !== 'object') {
    return false
  }
  const candidate = value as BrowserAnnotationRuntimeEvent
  return (
    typeof candidate.threadId === 'string'
    && typeof candidate.tabId === 'string'
    && (candidate.type === 'ready'
      || candidate.type === 'selected-element'
      || candidate.type === 'save'
      || candidate.type === 'submit'
      || candidate.type === 'cancel'
      || candidate.type === 'closed'
      || candidate.type === 'toggle'
      || candidate.type === 'copy'
      || candidate.type === 'clear'
      || candidate.type === 'delete'
      || candidate.type === 'edit'
      || candidate.type === 'layout-sync')
  )
}

function isBrowserAnnotationAdjustmentApplyEvent(
  event: Event,
): event is CustomEvent<BrowserAnnotationAdjustmentApplyDetail> {
  if (!(event instanceof CustomEvent)) {
    return false
  }
  const detail = event.detail
  return Boolean(
    detail
    && typeof detail === 'object'
    && typeof (detail as BrowserAnnotationAdjustmentApplyDetail).ownerId === 'string'
    && typeof (detail as BrowserAnnotationAdjustmentApplyDetail).tabId === 'string',
  )
}

function toBrowserAnnotationRuntimeAnnotation(annotation: BrowserAnnotationRecord) {
  return {
    id: annotation.id,
    anchor: annotation.anchor,
    body: annotation.body,
    designChange: annotation.designChange,
    status: annotation.status,
  }
}

function screenshotFileNameForBrowserAnnotationUrl(url: string): string {
  try {
    const parsed = new URL(url)
    const host = parsed.hostname.replace(/[^a-z0-9.-]+/gi, '-').replace(/^-+|-+$/g, '')
    return `${host || 'browser'}-annotation.png`
  }
 catch {
    return 'browser-annotation.png'
  }
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === 'object'
}

function readCdpScreenshotData(result: unknown): string | null {
  if (!isRecord(result) || typeof result.data !== 'string' || result.data.length === 0) {
    return null
  }
  return result.data
}

async function captureBrowserAnnotationScreenshot(input: {
  bridge: NonNullable<ReturnType<typeof readBrowserBridge>>
  threadId: string
  tabId: string
  url: string
}): Promise<FileUIPart> {
  try {
    const screenshot = await input.bridge.captureScreenshot({
      threadId: input.threadId,
      tabId: input.tabId,
    })
    return createBrowserScreenshotFilePart({
      name: screenshot.name,
      mimeType: screenshot.mimeType,
      bytes: screenshot.bytes,
    })
  }
 catch {
    const result = await input.bridge.executeCdp({
      threadId: input.threadId,
      tabId: input.tabId,
      method: 'Page.captureScreenshot',
      params: {
        format: 'png',
        captureBeyondViewport: false,
        fromSurface: true,
      },
    })
    const data = readCdpScreenshotData(result)
    if (!data) {
      throw new Error('Couldn\'t capture a browser screenshot.')
    }
    return createBrowserBase64FilePart({
      name: screenshotFileNameForBrowserAnnotationUrl(input.url),
      mimeType: 'image/png',
      base64: data,
    })
  }
}

function getBrowserAnnotationCropRect(
  anchor: BrowserAnnotationAnchor,
): BrowserAnnotationCropRect | null {
  if (anchor.kind === 'point') {
    return null
  }
  if (anchor.kind === 'element') {
    return anchor.element.rect
  }
  if (anchor.kind === 'text') {
    return {
      x: anchor.x,
      y: anchor.y,
      width: anchor.width,
      height: anchor.height,
    }
  }
  return {
    x: anchor.x,
    y: anchor.y,
    width: anchor.width,
    height: anchor.height,
  }
}

async function createBrowserAnnotationCropFilePart(input: {
  imageDataUrl: string
  cropRect: BrowserAnnotationCropRect
  surfaceSize: { width: number, height: number }
}): Promise<FileUIPart | null> {
  if (
    input.cropRect.width <= 0
    || input.cropRect.height <= 0
    || input.surfaceSize.width <= 0
    || input.surfaceSize.height <= 0
  ) {
    return null
  }

  const image = new Image()
  const loaded = new Promise<void>((resolve, reject) => {
    image.onload = () => resolve()
    image.onerror = () => reject(new Error('Browser annotation crop image failed to load.'))
  })
  image.src = input.imageDataUrl
  await loaded

  const scaleX = image.naturalWidth / input.surfaceSize.width
  const scaleY = image.naturalHeight / input.surfaceSize.height
  const sourceX = Math.max(0, Math.floor(input.cropRect.x * scaleX))
  const sourceY = Math.max(0, Math.floor(input.cropRect.y * scaleY))
  const sourceWidth = Math.min(
    image.naturalWidth - sourceX,
    Math.max(1, Math.ceil(input.cropRect.width * scaleX)),
  )
  const sourceHeight = Math.min(
    image.naturalHeight - sourceY,
    Math.max(1, Math.ceil(input.cropRect.height * scaleY)),
  )

  const canvas = document.createElement('canvas')
  canvas.width = sourceWidth
  canvas.height = sourceHeight
  const context = canvas.getContext('2d')
  if (!context) {
    return null
  }
  context.drawImage(
    image,
    sourceX,
    sourceY,
    sourceWidth,
    sourceHeight,
    0,
    0,
    sourceWidth,
    sourceHeight,
  )

  return createBrowserDataUrlFilePart({
    name: 'browser-annotation-target.png',
    mimeType: 'image/png',
    dataUrl: canvas.toDataURL('image/png'),
  })
}

function formatBrowserAnnotationElementDetails(anchor: BrowserAnnotationAnchor): string[] {
  if (anchor.kind === 'text') {
    return [`Selected text: "${anchor.text}"`]
  }
  if (anchor.kind !== 'element') {
    return []
  }
  const element = anchor.element
  const styleRows = BROWSER_ANNOTATION_DESIGN_CSS_PROPERTIES
    .map(({ key, property }) => {
      const value = element.styles[key]
      if (!value || value === 'rgba(0, 0, 0, 0)') {
        return null
      }
      return `- ${property}: ${value}`
    })
    .filter((line): line is string => line !== null)
  return [
    `Element selector: ${element.selector}`,
    element.reactComponents ? `React components: ${element.reactComponents}` : null,
    element.label ? `Element text: ${element.label}` : null,
    element.description ? `Element description: ${element.description}` : null,
    element.role ? `Element role: ${element.role}` : null,
    element.attributes?.testId ? `Element test id: ${element.attributes.testId}` : null,
    element.attributes?.href ? `Element href: ${element.attributes.href}` : null,
    element.attributes?.placeholder
      ? `Element placeholder: ${element.attributes.placeholder}`
      : null,
    styleRows.length > 0 ? 'Element styles:' : null,
    ...styleRows,
  ].filter(line => line !== null)
}

function formatBrowserAnnotationDesignChange(
  designChange: BrowserAnnotationDesignChange | null,
): string[] {
  if (!designChange) {
    return []
  }

  const rows = [
    ...BROWSER_ANNOTATION_DESIGN_CSS_PROPERTIES.map(({ key, property }): [string, string | undefined] =>
      [property, designChange[key]]),
    ['comment', designChange.comment] as [string, string | undefined],
  ]
    .filter(([, value]) => Boolean(value))
    .map(([label, value]) => `- ${label}: ${value}`)

  return rows.length > 0 ? ['Requested design changes:', ...rows] : []
}

function hasBrowserAnnotationDesignChanges(
  designChange: BrowserAnnotationDesignChange | null | undefined,
): boolean {
  return countBrowserAnnotationDesignChanges(designChange ?? null) > 0
}

function createBrowserAnnotationPrompt(input: {
  body: string
  anchor: BrowserAnnotationAnchor
  attachedImageCount: number
  designChange: BrowserAnnotationDesignChange | null
  includesTargetCrop: boolean
  title: string
  url: string
  surfaceSize: { width: number, height: number }
}): string {
  return [
    `Browser annotation on "${input.title}".`,
    `URL: ${input.url}`,
    `Viewport: ${Math.round(input.surfaceSize.width)} x ${Math.round(input.surfaceSize.height)} px`,
    `Target: ${formatBrowserAnnotationAnchor(input.anchor)}`,
    input.includesTargetCrop
      ? 'Attached screenshots: full viewport and target crop'
      : 'Attached screenshots: full viewport',
    input.attachedImageCount > 0 ? `Additional attached images: ${input.attachedImageCount}` : null,
    ...formatBrowserAnnotationElementDetails(input.anchor),
    ...formatBrowserAnnotationDesignChange(input.designChange),
    '',
    input.body,
  ]
    .filter(line => line !== null)
    .join('\n')
}

export function BrowserPanel({
  ownerId = null,
  activeSessionId = null,
  activeSessionTitle = null,
  terminalCwd = null,
  nativeBoundsPaused = false,
  nativeSurfaceVisible = true,
  onCloseLastTab,
}: BrowserPanelProps) {
  const resolvedOwnerId = ownerId ?? DEFAULT_BROWSER_PANEL_OWNER_ID
  const selectBrowserState = selectOwnerBrowserState(resolvedOwnerId)
  const selectBrowserHistory = selectOwnerBrowserHistory(resolvedOwnerId)
  const selectBrowserAnnotations = selectOwnerBrowserAnnotations(resolvedOwnerId)
  const browserState = useBrowserPanelStore(selectBrowserState)
  const nativeSurfaceSuppressCount = useNativeBrowserSurfaceSuppressionStore(
    state => state.suppressCount,
  )
  const recentHistory = useBrowserPanelStore(selectBrowserHistory)
  const requestedTab = useBrowserPanelStore(
    state => state.owners[resolvedOwnerId]?.requestedTab ?? null,
  )
  const setActiveOwner = useBrowserPanelStore(state => state.setActiveOwner)
  const upsertOwnerState = useBrowserPanelStore(state => state.upsertOwnerState)
  const fulfillRequestedTab = useBrowserPanelStore(state => state.fulfillRequestedTab)
  const createBrowserTab = useBrowserPanelStore(state => state.createTab)
  const removeOwnerState = useBrowserPanelStore(state => state.removeOwnerState)
  const setActiveTab = useBrowserPanelStore(state => state.setActiveTab)
  const closePanelTab = useBrowserPanelStore(state => state.closeTab)
  const openWorkspaceFileTab = useBrowserPanelStore(state => state.openWorkspaceFileTab)
  const openLauncherTab = useBrowserPanelStore(state => state.openLauncherTab)
  const openTuiTab = useBrowserPanelStore(state => state.openTuiTab)
  const updateTuiTabTitle = useBrowserPanelStore(state => state.updateTuiTabTitle)
  const openContextUsageReportTab = useBrowserPanelStore(state => state.openContextUsageReportTab)
  const saveAnnotation = useBrowserPanelStore(state => state.saveAnnotation)
  const markAnnotationSent = useBrowserPanelStore(state => state.markAnnotationSent)
  const deleteAnnotation = useBrowserPanelStore(state => state.deleteAnnotation)
  const clearAnnotations = useBrowserPanelStore(state => state.clearAnnotations)
  const syncAnnotationLayoutHints = useBrowserPanelStore(state => state.syncAnnotationLayoutHints)
  const annotationAdjustmentSession = useBrowserPanelStore(
    state => state.annotationAdjustmentSession,
  )
  const setAnnotationAdjustmentSession = useBrowserPanelStore(
    state => state.setAnnotationAdjustmentSession,
  )
  const openAsideTab = useLayoutStore(state => state.openAsideTab)
  const setAnnotationTrayCollapsed = useBrowserPanelStore(
    state => state.setAnnotationTrayCollapsed,
  )
  const ownerAnnotations = useBrowserPanelStore(selectBrowserAnnotations)
  const ownerAnnotationLayoutHintsByTabId = useBrowserPanelStore(
    state =>
      state.owners[resolvedOwnerId]?.annotationLayoutHintsByTabId
      ?? EMPTY_BROWSER_ANNOTATION_LAYOUT_HINTS_BY_TAB_ID,
  )
  const annotationTrayCollapsed = useBrowserPanelStore(
    state => state.annotationTrayCollapsedByOwnerId[resolvedOwnerId] ?? true,
  )

  const viewportRef = useRef<HTMLDivElement | null>(null)
  const browserWebviewHostRef = useRef<HTMLDivElement | null>(null)
  const browserWebviewRef = useRef<BrowserWebviewElement | null>(null)
  const browserWebviewTabIdRef = useRef<string | null>(null)
  const browserWebviewAttachKeyRef = useRef<string | null>(null)
  const previousActiveTabIdRef = useRef<string | null>(null)
  const addressDraftByTabIdRef = useRef<Map<string, string>>(new Map())
  const lastSyncedAddressValueRef = useRef<string | undefined>(undefined)
  const previousAnnotationRuntimeTabIdRef = useRef<string | null>(null)
  const stableBoundsFrameCountRef = useRef(0)
  const animationFrameRef = useRef<number | null>(null)
  const lastNativeBoundsSignatureRef = useRef<string | null>(null)
  const newTabRequestInFlightRef = useRef(false)
  const pendingBrowserTabSelectionRef = useRef<string | null>(null)
  const [addressValue, setAddressValue] = useState('')
  const [isEditingAddress, setIsEditingAddress] = useState(false)
  const [localError, setLocalError] = useState<string | null>(null)
  const [suggestionsOpen, setSuggestionsOpen] = useState(false)
  const [annotationSession, setAnnotationSession]
    = useState<BrowserAnnotationRuntimeSession | null>(null)
  const [, setAnnotationSubmitting] = useState(false)
  const [newTabRequestPending, setNewTabRequestPending] = useState(false)
  const [dirtyPlanRefineTabIds, setDirtyPlanRefineTabIds] = useState<Set<string>>(() => new Set())
  const [discardPromptTabId, setDiscardPromptTabId] = useState<string | null>(null)
  const nativeBrowserAvailable = Boolean(readBrowserBridge())

  const tabs = useBrowserPanelStore(
    state => state.owners[resolvedOwnerId]?.tabs ?? EMPTY_BROWSER_PANEL_TABS,
  )
  const activePanelTabId = useBrowserPanelStore(
    state => state.owners[resolvedOwnerId]?.activeTabId ?? null,
  )
  const browserTabs = tabs.filter(isBrowserPanelTab)
  const activePanelTab = tabs.find(tab => tab.id === activePanelTabId) ?? tabs[0] ?? null
  const resolvedActivePanelTabId = activePanelTab?.id ?? null
  const activeBrowserTab = activePanelTab?.kind === 'browser' ? activePanelTab : null
  const activeBrowserTabId = activeBrowserTab?.id ?? null
  const activeBrowserTabUrl = activeBrowserTab?.lastCommittedUrl ?? activeBrowserTab?.url ?? null
  const activeBrowserTabIsBlank = isBrowserBlankTab(activeBrowserTab)
  const {
    servers: localServers,
    loading: localServersLoading,
    error: localServersError,
    refresh: refreshLocalServers,
  } = useLocalServers(activeBrowserTabIsBlank)
  const canCreateTuiTab = Boolean(terminalCwd)
  const activeBrowserAnnotations = ownerAnnotations.filter(
    annotation => annotation.tabId === activeBrowserTabId,
  )
  const activeBrowserAnnotationLayoutHints = activeBrowserTabId
    ? (ownerAnnotationLayoutHintsByTabId[activeBrowserTabId]
      ?? EMPTY_BROWSER_ANNOTATION_LAYOUT_HINTS)
    : EMPTY_BROWSER_ANNOTATION_LAYOUT_HINTS
  const activeAnnotationSession
    = annotationSession?.tabId === activeBrowserTabId ? annotationSession : null
  const hasActiveAnnotationSession = activeAnnotationSession !== null
  const suggestions = buildBrowserAddressSuggestions({
    query: addressValue,
    activeTabId: activeBrowserTabId,
    tabs: browserTabs,
    recentHistory,
  })
  const chromeStatus
    = activePanelTab?.kind === 'browser' || localError || browserState?.lastError
      ? resolveBrowserChromeStatus({
          localError,
          threadLastError: browserState?.lastError,
          activeTabStatus: activeBrowserTab?.status ?? 'suspended',
          hasActiveTab: Boolean(activeBrowserTab),
          workspaceReady: Boolean(browserState),
        })
      : null
  const chromeStatusLabel = chromeStatus?.label ?? null
  const chromeStatusTone = chromeStatus?.tone ?? null

  const applyBrowserState = useCallback(
    (state: ThreadBrowserState) => {
      const pendingTabId = pendingBrowserTabSelectionRef.current
      if (pendingTabId && state.activeTabId !== pendingTabId) {
        return
      }
      if (pendingTabId === state.activeTabId) {
        pendingBrowserTabSelectionRef.current = null
      }
      upsertOwnerState(state)
    },
    [upsertOwnerState],
  )

  useEffect(() => {
    if (resolvedActivePanelTabId && resolvedActivePanelTabId !== activePanelTabId) {
      setActiveTab(resolvedActivePanelTabId, resolvedOwnerId)
    }
  }, [activePanelTabId, resolvedActivePanelTabId, resolvedOwnerId, setActiveTab])

  useEffect(() => {
    if (!nativeSurfaceVisible) {
      return
    }
    setActiveOwner(resolvedOwnerId)
  }, [nativeSurfaceVisible, resolvedOwnerId, setActiveOwner])

  useEffect(() => {
    const bridge = readBrowserBridge()
    if (!bridge) {
      return
    }

    const unsubscribe = bridge.onState(applyBrowserState)

    void bridge
      .getState({ threadId: resolvedOwnerId })
      .then(applyBrowserState)
      .catch((error) => {
        setLocalError(formatBrowserActionError(error))
      })

    return () => {
      setAnnotationSession(null)
      setAnnotationSubmitting(false)
      unsubscribe()
      void bridge.hide({ threadId: resolvedOwnerId }).catch(() => {})
    }
  }, [applyBrowserState, resolvedOwnerId])

  useEffect(() => {
    if (!requestedTab) {
      return
    }

    const bridge = readBrowserBridge()
    if (!bridge) {
      createBrowserTab(
        requestedTab.url ?? 'about:blank',
        {
          sessionId: requestedTab.sessionId,
          sessionTitle: requestedTab.sessionTitle,
        },
        resolvedOwnerId,
      )
      fulfillRequestedTab(requestedTab.id, resolvedOwnerId)
      return
    }

    const url = requestedTab.url ?? 'about:blank'
    const action = browserState?.open
      ? bridge.newTab({ threadId: resolvedOwnerId, url, activate: true })
      : bridge.open({ threadId: resolvedOwnerId, initialUrl: url })

    void action
      .then((nextState) => {
        upsertOwnerState(nextState)
        if (nextState.activeTabId) {
          setActiveTab(nextState.activeTabId, resolvedOwnerId)
        }
      })
      .catch((error) => {
        setLocalError(formatBrowserActionError(error))
      })
      .finally(() => {
        fulfillRequestedTab(requestedTab.id, resolvedOwnerId)
      })
  }, [
    browserState?.open,
    createBrowserTab,
    fulfillRequestedTab,
    requestedTab,
    resolvedOwnerId,
    setActiveTab,
    upsertOwnerState,
  ])

  const detachRendererBrowserWebview = useCallback(
    (removeElement: boolean) => {
      const webview = browserWebviewRef.current
      const tabId = browserWebviewTabIdRef.current
      let webContentsId: number | undefined
      try {
        webContentsId = webview?.getWebContentsId?.()
      }
 catch {
        webContentsId = undefined
      }

      if (tabId && webContentsId && webContentsId > 0) {
        void readBrowserBridge()
          ?.detachWebview({
            threadId: resolvedOwnerId,
            tabId,
            webContentsId,
          })
          .catch(() => {})
      }

      browserWebviewAttachKeyRef.current = null
      browserWebviewTabIdRef.current = null
      if (!removeElement) {
        return
      }
      webview?.remove()
      browserWebviewRef.current = null
    },
    [resolvedOwnerId],
  )

  const hideNativeBrowserSurface = useCallback(() => {
    if (animationFrameRef.current !== null) {
      window.cancelAnimationFrame(animationFrameRef.current)
      animationFrameRef.current = null
      stableBoundsFrameCountRef.current = 0
    }
    const webview = browserWebviewRef.current
    setBrowserWebviewOccluded(webview, true)
    const surface = webview ? 'renderer' : 'native'
    const nextSignature = `${resolvedOwnerId}:${surface}:${browserNativeBoundsSignature(null)}`
    if (lastNativeBoundsSignatureRef.current === nextSignature) {
      return
    }
    lastNativeBoundsSignatureRef.current = nextSignature
    readBrowserBridge()?.setBounds({ threadId: resolvedOwnerId, bounds: null, surface })
  }, [resolvedOwnerId])

  const shouldShowNativeBrowserSurface = useCallback(
    () =>
      Boolean(
        nativeSurfaceVisible
        && browserState?.open
        && activePanelTab?.kind === 'browser'
        && !activeBrowserTabIsBlank
        && nativeSurfaceSuppressCount === 0,
      ),
    [
      activeBrowserTabIsBlank,
      activePanelTab?.kind,
      browserState?.open,
      nativeSurfaceVisible,
      nativeSurfaceSuppressCount,
    ],
  )

  useEffect(() => {
    if (shouldShowNativeBrowserSurface()) {
      return
    }
    hideNativeBrowserSurface()
  }, [hideNativeBrowserSurface, shouldShowNativeBrowserSurface])

  useEffect(() => {
    return () => {
      hideNativeBrowserSurface()
    }
  }, [hideNativeBrowserSurface])

  const syncBounds = useCallback(() => {
    if (!shouldShowNativeBrowserSurface()) {
      hideNativeBrowserSurface()
      return
    }
    if (nativeBoundsPaused) {
      return
    }

    const bridge = readBrowserBridge()
    const element = viewportRef.current
    if (!bridge || !element) {
      return
    }

    const viewportRect = element.getBoundingClientRect()
    const rawBounds = normalizeBrowserNativeBounds(viewportRect)
    if (!rawBounds) {
      hideNativeBrowserSurface()
      return
    }
    const webview = browserWebviewRef.current
    const rendererOccluded = Boolean(
      webview
      && (nativeSurfaceSuppressCount > 0 || hasRendererBrowserObscuringOverlay(viewportRect)),
    )
    setBrowserWebviewOccluded(webview, rendererOccluded)
    const surface = webview ? 'renderer' : 'native'
    const bounds = rendererOccluded
      ? null
      : webview
        ? rawBounds
        : applyBrowserNativeSurfaceOcclusions(rawBounds, viewportRect)
    if (!bounds) {
      if (!webview) {
        hideNativeBrowserSurface()
        return
      }
      const nextSignature = `${resolvedOwnerId}:${surface}:${browserNativeBoundsSignature(null)}`
      if (lastNativeBoundsSignatureRef.current === nextSignature) {
        return
      }
      lastNativeBoundsSignatureRef.current = nextSignature
      bridge.setBounds({ threadId: resolvedOwnerId, surface, bounds: null })
      return
    }

    const nextSignature = `${resolvedOwnerId}:${surface}:${activeBrowserTabId ?? 'none'}:${browserNativeBoundsSignature(bounds)}`
    if (lastNativeBoundsSignatureRef.current === nextSignature) {
      return
    }
    lastNativeBoundsSignatureRef.current = nextSignature
    bridge.setBounds({
      threadId: resolvedOwnerId,
      surface,
      bounds,
    })
  }, [
    hideNativeBrowserSurface,
    activeBrowserTabId,
    nativeBoundsPaused,
    nativeSurfaceSuppressCount,
    resolvedOwnerId,
    shouldShowNativeBrowserSurface,
  ])

  const scheduleStableBoundsSync = useCallback(() => {
    if (typeof window === 'undefined') {
      return
    }
    if (!shouldShowNativeBrowserSurface()) {
      hideNativeBrowserSurface()
      return
    }
    if (nativeBoundsPaused) {
      return
    }
    if (animationFrameRef.current !== null) {
      return
    }

    const tick = () => {
      if (typeof window === 'undefined') {
        animationFrameRef.current = null
        stableBoundsFrameCountRef.current = 0
        return
      }
      syncBounds()
      stableBoundsFrameCountRef.current += 1
      if (stableBoundsFrameCountRef.current < BROWSER_BOUNDS_SYNC_STABLE_FRAME_TARGET) {
        animationFrameRef.current = window.requestAnimationFrame(tick)
        return
      }
      animationFrameRef.current = null
      stableBoundsFrameCountRef.current = 0
    }

    animationFrameRef.current = window.requestAnimationFrame(tick)
  }, [hideNativeBrowserSurface, nativeBoundsPaused, shouldShowNativeBrowserSurface, syncBounds])

  useLayoutEffect(() => {
    const bridge = readBrowserBridge()
    const host = browserWebviewHostRef.current
    const canUseRendererWebview = Boolean(
      bridge
      && typeof bridge.getWebviewConfig === 'function'
      && typeof bridge.attachWebview === 'function'
      && typeof bridge.detachWebview === 'function',
    )
    if (
      !bridge
      || !host
      || !canUseRendererWebview
      || !nativeSurfaceVisible
      || !browserState?.open
      || activePanelTab?.kind !== 'browser'
      || !activeBrowserTabId
      || activeBrowserTabIsBlank
    ) {
      detachRendererBrowserWebview(true)
      return
    }

    let webview = browserWebviewRef.current
    if (!webview) {
      const config = bridge.getWebviewConfig({ threadId: resolvedOwnerId })
      webview = document.createElement('webview') as BrowserWebviewElement
      webview.className = 'size-full'
      webview.style.display = 'flex'
      webview.style.width = '100%'
      webview.style.height = '100%'
      webview.style.backgroundColor = 'var(--background)'
      webview.setAttribute('partition', config.partition)
      webview.setAttribute('preload', config.preloadUrl)
      webview.setAttribute('webpreferences', 'contextIsolation=yes,nodeIntegration=no,sandbox=yes')
      webview.setAttribute('allowpopups', 'true')
      browserWebviewRef.current = webview
      host.append(webview)
    }
 else if (webview.parentElement !== host) {
      host.append(webview)
    }

    if (browserWebviewTabIdRef.current !== activeBrowserTabId) {
      detachRendererBrowserWebview(false)
      browserWebviewTabIdRef.current = activeBrowserTabId
      browserWebviewAttachKeyRef.current = null
      webview.setAttribute('src', activeBrowserTabUrl || 'about:blank')
    }

    const attachVisibleWebview = () => {
      let webContentsId: number | undefined
      try {
        webContentsId = webview.getWebContentsId?.()
      }
 catch {
        return
      }
      if (!webContentsId || webContentsId <= 0) {
        return
      }

      const attachKey = `${activeBrowserTabId}:${webContentsId}`
      if (browserWebviewAttachKeyRef.current === attachKey) {
        return
      }
      browserWebviewAttachKeyRef.current = attachKey
      void bridge
        .attachWebview({
          threadId: resolvedOwnerId,
          tabId: activeBrowserTabId,
          webContentsId,
        })
        .then((nextState) => {
          if (browserWebviewAttachKeyRef.current === attachKey) {
            applyBrowserState(nextState)
          }
        })
        .catch((error) => {
          if (browserWebviewAttachKeyRef.current === attachKey) {
            browserWebviewAttachKeyRef.current = null
          }
          setLocalError(formatBrowserActionError(error))
        })
    }

    webview.addEventListener('dom-ready', attachVisibleWebview)
    webview.addEventListener('did-start-loading', attachVisibleWebview)
    const frameId = window.requestAnimationFrame(() => {
      attachVisibleWebview()
      scheduleStableBoundsSync()
    })

    return () => {
      window.cancelAnimationFrame(frameId)
      webview.removeEventListener('dom-ready', attachVisibleWebview)
      webview.removeEventListener('did-start-loading', attachVisibleWebview)
    }
  }, [
    activeBrowserTabId,
    activeBrowserTabIsBlank,
    activeBrowserTabUrl,
    activePanelTab?.kind,
    applyBrowserState,
    browserState?.open,
    detachRendererBrowserWebview,
    nativeSurfaceVisible,
    resolvedOwnerId,
    scheduleStableBoundsSync,
  ])

  useEffect(() => {
    return () => {
      detachRendererBrowserWebview(true)
    }
  }, [detachRendererBrowserWebview])

  const scheduleStableBoundsSyncFromObserver = useEffectEvent(() => {
    scheduleStableBoundsSync()
  })

  const hideNativeBrowserSurfaceFromObserver = useEffectEvent(() => {
    hideNativeBrowserSurface()
  })

  const scheduleStableBoundsSyncFromOcclusionObserver = useEffectEvent(() => {
    scheduleStableBoundsSync()
  })

  useLayoutEffect(() => {
    const element = viewportRef.current
    if (!element || !shouldShowNativeBrowserSurface()) {
      hideNativeBrowserSurfaceFromObserver()
      return
    }
    if (nativeBoundsPaused) {
      return
    }

    scheduleStableBoundsSyncFromObserver()
    const resizeObserver = new ResizeObserver(scheduleStableBoundsSyncFromObserver)
    resizeObserver.observe(element)
    window.addEventListener('resize', scheduleStableBoundsSyncFromObserver)
    window.addEventListener('scroll', scheduleStableBoundsSyncFromObserver, true)

    return () => {
      resizeObserver.disconnect()
      window.removeEventListener('resize', scheduleStableBoundsSyncFromObserver)
      window.removeEventListener('scroll', scheduleStableBoundsSyncFromObserver, true)
      if (animationFrameRef.current !== null) {
        window.cancelAnimationFrame(animationFrameRef.current)
        animationFrameRef.current = null
      }
    }
  }, [activePanelTab?.kind, nativeBoundsPaused, shouldShowNativeBrowserSurface])

  useEffect(() => {
    if (
      nativeBoundsPaused
      || !shouldShowNativeBrowserSurface()
      || typeof document === 'undefined'
      || typeof MutationObserver === 'undefined'
    ) {
      return
    }

    const observedOccluders = new Set<HTMLElement>()
    const resizeObserver
      = typeof ResizeObserver === 'undefined'
        ? null
        : new ResizeObserver(scheduleStableBoundsSyncFromOcclusionObserver)

    const syncObservedOccluders = () => {
      if (typeof document === 'undefined' || !document.body) {
        return
      }
      const nextOccluders = new Set(
        document.querySelectorAll<HTMLElement>(
          `${BROWSER_NATIVE_SURFACE_OCCLUSION_SELECTOR}, ${BROWSER_RENDERER_OBSCURING_OVERLAY_SELECTOR}`,
        ),
      )

      for (const occluder of observedOccluders) {
        if (nextOccluders.has(occluder)) {
          continue
        }
        resizeObserver?.unobserve(occluder)
        observedOccluders.delete(occluder)
      }

      for (const occluder of nextOccluders) {
        if (observedOccluders.has(occluder)) {
          continue
        }
        resizeObserver?.observe(occluder)
        observedOccluders.add(occluder)
      }

      scheduleStableBoundsSyncFromOcclusionObserver()
    }

    const mutationObserver = new MutationObserver(syncObservedOccluders)
    mutationObserver.observe(document.body, {
      attributes: true,
      attributeFilter: [BROWSER_NATIVE_SURFACE_OCCLUSION_ATTRIBUTE, 'aria-hidden', 'data-state'],
      childList: true,
      subtree: true,
    })
    syncObservedOccluders()

    return () => {
      mutationObserver.disconnect()
      resizeObserver?.disconnect()
      scheduleStableBoundsSyncFromOcclusionObserver()
    }
  }, [nativeBoundsPaused, shouldShowNativeBrowserSurface])

  useEffect(() => {
    const bridge = readBrowserBridge()
    const selectedElement = annotationAdjustmentSession?.selectedElement
    if (
      !bridge
      || activeAnnotationSession === null
      || !activeBrowserTab
      || !activeBrowserTabId
      || !selectedElement
      || annotationAdjustmentSession.ownerId !== resolvedOwnerId
      || annotationAdjustmentSession.tabId !== activeBrowserTabId
    ) {
      return
    }

    const designChange = annotationAdjustmentSession.designChanges

    void (async () => {
      if (hasBrowserAnnotationDesignChanges(designChange)) {
        await bridge.applyAnnotationDesign({
          threadId: resolvedOwnerId,
          tabId: activeBrowserTabId,
          selector: selectedElement.selector,
          designChange,
        })
      }
 else {
        await bridge.applyAnnotationDesign({
          threadId: resolvedOwnerId,
          tabId: activeBrowserTabId,
          selector: selectedElement.selector,
          designChange: {},
        })
      }
    })().catch((error) => {
      setLocalError(formatBrowserActionError(error))
    })
  }, [
    activeBrowserTab,
    activeBrowserTabId,
    annotationAdjustmentSession,
    activeAnnotationSession,
    resolvedOwnerId,
  ])

  useEffect(() => {
    scheduleStableBoundsSync()
  }, [
    activeBrowserTabIsBlank,
    activePanelTab?.id,
    hasActiveAnnotationSession,
    chromeStatusLabel,
    chromeStatusTone,
    scheduleStableBoundsSync,
    suggestionsOpen,
  ])

  useEffect(() => {
    const previousAnnotationRuntimeTabId = previousAnnotationRuntimeTabIdRef.current
    if (previousAnnotationRuntimeTabId) {
      void readBrowserBridge()
        ?.clearAnnotationDesign({
          threadId: resolvedOwnerId,
          tabId: previousAnnotationRuntimeTabId,
        })
        .catch(() => {})
    }
    previousAnnotationRuntimeTabIdRef.current = activeBrowserTabId
    setAnnotationAdjustmentSession(null)
  }, [activeBrowserTabId, resolvedOwnerId, setAnnotationAdjustmentSession])

  useEffect(() => {
    const nextDisplayValue = browserAddressDisplayValue(activeBrowserTab)
    const decision = resolveBrowserAddressSync({
      activeTabId: activeBrowserTabId,
      previousActiveTabId: previousActiveTabIdRef.current,
      savedDraft: activeBrowserTabId
        ? addressDraftByTabIdRef.current.get(activeBrowserTabId)
        : undefined,
      nextDisplayValue,
      lastSyncedValue: lastSyncedAddressValueRef.current,
      isEditing: isEditingAddress,
    })
    previousActiveTabIdRef.current = activeBrowserTabId

    if (decision.type === 'replace') {
      setAddressValue(decision.value)
      lastSyncedAddressValueRef.current = decision.syncedValue
    }
  }, [activeBrowserTab, activeBrowserTabId, isEditingAddress])

  const runBrowserAction = useCallback(async (action: () => Promise<unknown>) => {
    setLocalError(null)
    try {
      await action()
    }
 catch (error) {
      const message = formatBrowserActionError(error)
      if (message) {
        setLocalError(message)
      }
    }
  }, [])

  useEffect(() => {
    const bridge = readBrowserBridge()
    if (!bridge?.onPromptRequested) {
      return undefined
    }

    return bridge.onPromptRequested((request) => {
      if (!isBrowserPromptRequest(request) || request.threadId !== resolvedOwnerId) {
        return
      }

      const ownerState = useBrowserPanelStore.getState().owners[resolvedOwnerId]
      const sourceTab
        = ownerState?.tabs.find(
          (tab): tab is BrowserWebTab => tab.id === request.tabId && tab.kind === 'browser',
        ) ?? null
      const targetSessionId = sourceTab?.sessionId ?? activeSessionId
      if (!targetSessionId) {
        setLocalError('Open a chat session to receive browser page prompts.')
        return
      }

      const files = request.attachments
        .map(createBrowserPromptFilePart)
        .filter(file => file !== null)
      const sent = submitChatPromptIngress(targetSessionId, {
        text: request.text,
        files,
      })
      if (!sent) {
        setLocalError('The target composer is not ready for browser page prompts.')
      }
    })
  }, [activeSessionId, resolvedOwnerId])

  const handleNewTab = () => {
    openLauncherTab(resolvedOwnerId)
  }

  const closeLocalPanelTab = useCallback(
    (tabId: string) => {
      const tab = useBrowserPanelStore
        .getState()
        .owners[resolvedOwnerId]
?.tabs
.find(item => item.id === tabId)

      if (tab?.kind === 'side-conversation') {
        void releaseSideConversation(tab.sideConversationId)
      }

      const result = closePanelTab(tabId, resolvedOwnerId)
      setDirtyPlanRefineTabIds((previous) => {
        if (!previous.has(tabId)) {
          return previous
        }
        const next = new Set(previous)
        next.delete(tabId)
        return next
      })
      setDiscardPromptTabId(current => (current === tabId ? null : current))

      if (result.closedLastTab) {
        removeOwnerState(resolvedOwnerId)
        onCloseLastTab?.(resolvedOwnerId)
        return
      }
      const nextOwnerState = useBrowserPanelStore.getState().owners[resolvedOwnerId]
      const nextActiveBrowserTab = nextOwnerState?.tabs.find(
        item => item.id === nextOwnerState.activeTabId && item.kind === 'browser',
      )
      const bridge = readBrowserBridge()
      if (nextActiveBrowserTab && bridge) {
        void runBrowserAction(async () => {
          upsertOwnerState(
            await bridge.selectTab({
              threadId: resolvedOwnerId,
              tabId: nextActiveBrowserTab.id,
            }),
          )
        })
      }
    },
    [
      closePanelTab,
      onCloseLastTab,
      removeOwnerState,
      resolvedOwnerId,
      runBrowserAction,
      upsertOwnerState,
    ],
  )

  const handleCreateBrowserTab = (sourceTabId?: string) => {
    const bridge = readBrowserBridge()
    if (newTabRequestInFlightRef.current) {
      return
    }
    if (!bridge) {
      createBrowserTab(
        'about:blank',
        {
          sessionId: activeSessionId,
          sessionTitle: activeSessionTitle,
        },
        resolvedOwnerId,
      )
      if (sourceTabId) {
        closeLocalPanelTab(sourceTabId)
      }
      return
    }
    newTabRequestInFlightRef.current = true
    setNewTabRequestPending(true)
    void runBrowserAction(async () => {
      const nextState = browserState?.open
        ? await bridge.newTab({
            threadId: resolvedOwnerId,
            url: 'about:blank',
            activate: true,
          })
        : await bridge.open({ threadId: resolvedOwnerId, initialUrl: 'about:blank' })
      upsertOwnerState(nextState)
      if (nextState.activeTabId) {
        setActiveTab(nextState.activeTabId, resolvedOwnerId)
      }
      if (sourceTabId) {
        closeLocalPanelTab(sourceTabId)
      }
    }).finally(() => {
      newTabRequestInFlightRef.current = false
      setNewTabRequestPending(false)
    })
  }

  const handleCreateTuiTab = (sourceTabId?: string) => {
    if (!terminalCwd) {
      setLocalError('Open a workspace to create a terminal tab.')
      return
    }

    void loadBrowserTuiShellView()
    openTuiTab({
      cwd: terminalCwd,
      ownerId: resolvedOwnerId,
    })
    if (sourceTabId) {
      closeLocalPanelTab(sourceTabId)
    }
  }

  const handleTuiMetadata = (tabId: string, metadata: TerminalMetadata) => {
    if (metadata.title) {
      updateTuiTabTitle(tabId, metadata.title, resolvedOwnerId)
    }
  }

  const handleTuiExited = (tabId: string) => {
    closeLocalPanelTab(tabId)
  }

  const handleCloseTab = (tabId: string) => {
    const tab = tabs.find(item => item.id === tabId)
    if (!tab) {
      return
    }

    if (tab.kind === 'plan-refine' && dirtyPlanRefineTabIds.has(tabId)) {
      setDiscardPromptTabId(tabId)
      return
    }

    if (tab.kind !== 'browser') {
      closeLocalPanelTab(tabId)
      return
    }

    const bridge = readBrowserBridge()
    if (!bridge) {
      const result = closePanelTab(tabId, resolvedOwnerId)
      if (result.closedLastTab) {
        removeOwnerState(resolvedOwnerId)
        onCloseLastTab?.(resolvedOwnerId)
      }
      return
    }

    void runBrowserAction(async () => {
      const nextState = await bridge.closeTab({ threadId: resolvedOwnerId, tabId })
      upsertOwnerState(nextState)
      const remainingTabs
        = useBrowserPanelStore.getState().owners[resolvedOwnerId]?.tabs ?? EMPTY_BROWSER_PANEL_TABS
      if (remainingTabs.length === 0) {
        removeOwnerState(resolvedOwnerId)
        onCloseLastTab?.(resolvedOwnerId)
      }
    })
  }

  const handleSelectTab = (tabId: string) => {
    const tab = tabs.find(item => item.id === tabId)
    if (!tab) {
      return
    }
    if (tab.kind !== 'browser') {
      pendingBrowserTabSelectionRef.current = null
      hideNativeBrowserSurface()
      setActiveTab(tabId, resolvedOwnerId)
      return
    }

    const bridge = readBrowserBridge()
    if (!bridge) {
      setActiveTab(tabId, resolvedOwnerId)
      return
    }
    pendingBrowserTabSelectionRef.current = tabId
    setActiveTab(tabId, resolvedOwnerId)
    void runBrowserAction(async () => {
      applyBrowserState(await bridge.selectTab({ threadId: resolvedOwnerId, tabId }))
    }).finally(() => {
      if (pendingBrowserTabSelectionRef.current !== tabId) {
        return
      }
      pendingBrowserTabSelectionRef.current = null
      void bridge
        .getState({ threadId: resolvedOwnerId })
        .then(applyBrowserState)
        .catch(() => {})
    })
  }

  const navigateActiveTab = (url: string) => {
    if (!activeBrowserTabId) {
      return
    }
    const bridge = readBrowserBridge()
    if (!bridge) {
      return
    }
    void runBrowserAction(async () => {
      const normalizedUrl = normalizeBrowserAddressInput(url)
      upsertOwnerState(
        await bridge.navigate({
          threadId: resolvedOwnerId,
          tabId: activeBrowserTabId,
          url: normalizedUrl,
        }),
      )
      lastSyncedAddressValueRef.current = browserAddressDisplayValue({ url: normalizedUrl })
      addressDraftByTabIdRef.current.delete(activeBrowserTabId)
      setSuggestionsOpen(false)
    })
  }

  const handleSuggestion = (suggestion: BrowserAddressSuggestion) => {
    if (suggestion.kind === 'tab' && suggestion.tabId) {
      handleSelectTab(suggestion.tabId)
      setSuggestionsOpen(false)
      return
    }
    navigateActiveTab(suggestion.url)
  }

  const handleAddressSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    navigateActiveTab(addressValue)
  }

  const handleCaptureScreenshot = () => {
    if (!activeBrowserTabId) {
      return
    }
    const bridge = readBrowserBridge()
    if (!bridge) {
      return
    }
    void runBrowserAction(async () => {
      if (!activeSessionId) {
        throw new Error('Open a chat session to attach browser screenshots.')
      }
      const screenshot = await bridge.captureScreenshot({
        threadId: resolvedOwnerId,
        tabId: activeBrowserTabId,
      })
      const attached = submitChatComposerFileIngress(activeSessionId, [
        createBrowserScreenshotFilePart({
          name: screenshot.name,
          mimeType: screenshot.mimeType,
          bytes: screenshot.bytes,
        }),
      ])
      if (!attached) {
        throw new Error('The active composer is not ready for browser screenshots.')
      }
    })
  }

  const handleStartAnnotation = () => {
    if (!activeBrowserTabId) {
      return
    }
    const bridge = readBrowserBridge()
    const viewport = viewportRef.current
    if (!bridge || !viewport) {
      return
    }
    void runBrowserAction(async () => {
      await bridge
        .clearAnnotationDesign({
          threadId: resolvedOwnerId,
          tabId: activeBrowserTabId,
        })
        .catch(() => {})
      const rect = viewport.getBoundingClientRect()
      if (rect.width <= 0 || rect.height <= 0) {
        throw new Error('The browser viewport isn\'t ready for annotation.')
      }
      await bridge.startAnnotationRuntime({
        threadId: resolvedOwnerId,
        tabId: activeBrowserTabId,
        annotations: activeBrowserAnnotations.map(toBrowserAnnotationRuntimeAnnotation),
        layoutHints: activeBrowserAnnotationLayoutHints,
      })
      setAnnotationAdjustmentSession(null)
      setAnnotationSession({
        tabId: activeBrowserTabId,
        editingAnnotationId: null,
      })
    })
  }

  const buildBrowserAnnotationRecordInput = async (
    input: BrowserAnnotationRuntimeEvent,
  ): Promise<Omit<BrowserAnnotationRecord, 'id' | 'createdAt' | 'updatedAt' | 'status'> | null> => {
    if (!input.anchor) {
      return null
    }
    const bridge = readBrowserBridge()
    if (!bridge) {
      throw new Error('Browser annotations are available in the desktop app.')
    }
    const ownerState = useBrowserPanelStore.getState().owners[resolvedOwnerId]
    const sourceTab
      = ownerState?.tabs.find(
        (tab): tab is BrowserWebTab => tab.id === input.tabId && tab.kind === 'browser',
      ) ?? null
    const sourceUrl = input.sourceUrl ?? sourceTab?.lastCommittedUrl ?? sourceTab?.url ?? ''
    const screenshot = await captureBrowserAnnotationScreenshot({
      bridge,
      threadId: resolvedOwnerId,
      tabId: input.tabId,
      url: sourceUrl,
    })
    const attachedImages = (input.attachedImages ?? [])
      .map(createBrowserPromptFilePart)
      .filter(file => file !== null)
    const fallbackSurfaceRect = viewportRef.current?.getBoundingClientRect()
    const designChange = input.designChange ?? null
    return {
      ownerId: resolvedOwnerId,
      tabId: input.tabId,
      title: input.sourceTitle ?? (sourceTab ? getTabTitle(sourceTab) : sourceUrl),
      url: sourceUrl,
      body: input.body ?? '',
      anchor: input.anchor,
      designChange: hasBrowserAnnotationDesignChanges(designChange) ? designChange : null,
      attachedImages,
      screenshot,
      elements: input.elements ?? [],
      surfaceSize: input.surfaceSize ?? {
        width: fallbackSurfaceRect?.width ?? 0,
        height: fallbackSurfaceRect?.height ?? 0,
      },
    }
  }

  const sendBrowserAnnotationRecord = async (record: BrowserAnnotationRecord) => {
    if (!activeSessionId) {
      setLocalError('Open a chat session to send browser annotations.')
      return false
    }

    const cropRect = getBrowserAnnotationCropRect(record.anchor)
    const cropPart = cropRect
      ? await createBrowserAnnotationCropFilePart({
          imageDataUrl: record.screenshot.url,
          cropRect,
          surfaceSize: record.surfaceSize,
        }).catch(() => null)
      : null
    const files = cropPart
      ? [record.screenshot, cropPart, ...record.attachedImages]
      : [record.screenshot, ...record.attachedImages]
    const sent = submitChatPromptIngress(activeSessionId, {
      text: createBrowserAnnotationPrompt({
        body: record.body,
        anchor: record.anchor,
        attachedImageCount: record.attachedImages.length,
        designChange: record.designChange,
        includesTargetCrop: Boolean(cropPart),
        title: record.title,
        url: record.url,
        surfaceSize: record.surfaceSize,
      }),
      files,
    })
    if (!sent) {
      setLocalError('The active composer is not ready for browser annotations.')
    }
    return sent
  }

  const clearAnnotationRuntimeDraft = useCallback(() => {
    if (!activeBrowserTabId) {
      return
    }
    void readBrowserBridge()
      ?.clearAnnotationDesign({
        threadId: resolvedOwnerId,
        tabId: activeBrowserTabId,
      })
      .catch(() => {})
  }, [activeBrowserTabId, resolvedOwnerId])

  const stopAnnotationRuntime = useCallback(
    (tabId: string | null = activeBrowserTabId) => {
      if (!tabId) {
        return
      }
      void readBrowserBridge()
        ?.stopAnnotationRuntime({
          threadId: resolvedOwnerId,
          tabId,
        })
        .catch(() => {})
    },
    [activeBrowserTabId, resolvedOwnerId],
  )

  const closeAnnotationSession = useCallback(() => {
    clearAnnotationRuntimeDraft()
    setAnnotationAdjustmentSession(null)
    setAnnotationSession(null)
    setAnnotationSubmitting(false)
    scheduleStableBoundsSync()
  }, [clearAnnotationRuntimeDraft, scheduleStableBoundsSync, setAnnotationAdjustmentSession])

  const handleCancelAnnotation = useCallback(() => {
    stopAnnotationRuntime(activeAnnotationSession?.tabId ?? activeBrowserTabId)
    closeAnnotationSession()
  }, [
    activeAnnotationSession?.tabId,
    activeBrowserTabId,
    closeAnnotationSession,
    stopAnnotationRuntime,
  ])

  const handleRuntimeAnnotationCommit = (event: BrowserAnnotationRuntimeEvent) => {
    void (async () => {
      if (event.type === 'submit') {
        setAnnotationSubmitting(true)
      }
      const recordInput = await buildBrowserAnnotationRecordInput(event)
      if (!recordInput) {
        setAnnotationSubmitting(false)
        return
      }
      const annotationId = saveAnnotation(
        {
          ...recordInput,
          id: annotationSession?.editingAnnotationId ?? event.runtimeAnnotationId ?? undefined,
          status: 'saved',
        },
        resolvedOwnerId,
      )
      const nextAnnotations
        = useBrowserPanelStore
          .getState()
          .owners[resolvedOwnerId]
?.annotations
.filter(
            annotation => annotation.tabId === event.tabId,
          )
          .map(annotation => ({
            id: annotation.id,
            anchor: annotation.anchor,
            body: annotation.body,
            designChange: annotation.designChange,
            status: annotation.status,
          })) ?? []
      void readBrowserBridge()
        ?.startAnnotationRuntime({
          threadId: resolvedOwnerId,
          tabId: event.tabId,
          annotations: nextAnnotations,
          layoutHints: activeBrowserAnnotationLayoutHints,
        })
        .catch(() => {})
      if (event.type === 'save') {
        setAnnotationSubmitting(false)
        setAnnotationAdjustmentSession(null)
        return
      }
      const now = Date.now()
      const record: BrowserAnnotationRecord = {
        ...recordInput,
        id: annotationId,
        createdAt: now,
        updatedAt: now,
        status: 'saved',
      }
      const sent = await sendBrowserAnnotationRecord(record)
      if (!sent) {
        setAnnotationSubmitting(false)
        return
      }
      markAnnotationSent(annotationId, resolvedOwnerId)
      setAnnotationSubmitting(false)
      setAnnotationAdjustmentSession(null)
    })()
  }

  const handleApplyAnnotationAdjustment = (detail: BrowserAnnotationAdjustmentApplyDetail) => {
    if (detail.ownerId !== resolvedOwnerId || detail.tabId !== activeBrowserTabId) {
      return
    }
    if (
      !annotationAdjustmentSession
      || annotationAdjustmentSession.ownerId !== resolvedOwnerId
      || annotationAdjustmentSession.tabId !== activeBrowserTabId
      || !annotationAdjustmentSession.selectedElement
      || !hasBrowserAnnotationDesignChanges(annotationAdjustmentSession.designChanges)
    ) {
      return
    }

    const element = annotationAdjustmentSession.selectedElement
    const surfaceRect = viewportRef.current?.getBoundingClientRect()
    handleRuntimeAnnotationCommit({
      threadId: resolvedOwnerId,
      tabId: activeBrowserTabId,
      type: 'submit',
      anchor: { kind: 'element', element },
      selectedElement: element,
      body: '',
      attachedImages: [],
      designChange: annotationAdjustmentSession.designChanges,
      elements: [element],
      surfaceSize: {
        width: surfaceRect?.width ?? 0,
        height: surfaceRect?.height ?? 0,
      },
      sourceUrl: activeBrowserTabUrl,
      sourceTitle: activeBrowserTab ? getTabTitle(activeBrowserTab) : activeBrowserTabUrl,
    })
  }

  const handleToggleAnnotation = () => {
    if (hasActiveAnnotationSession) {
      handleCancelAnnotation()
      return
    }
    handleStartAnnotation()
  }

  const handleAnnotationAdjustmentApplyEvent = useEffectEvent((event: Event) => {
    if (!isBrowserAnnotationAdjustmentApplyEvent(event)) {
      return
    }
    handleApplyAnnotationAdjustment(event.detail)
  })

  useEffect(() => {
    const handleEvent = (event: Event) => {
      handleAnnotationAdjustmentApplyEvent(event)
    }
    window.addEventListener(BROWSER_ANNOTATION_ADJUSTMENT_APPLY_EVENT, handleEvent)
    return () => {
      window.removeEventListener(BROWSER_ANNOTATION_ADJUSTMENT_APPLY_EVENT, handleEvent)
    }
  }, [])

  const handleEditSavedAnnotation = useCallback(
    (annotation: BrowserAnnotationRecord) => {
      if (activeBrowserTabId !== annotation.tabId) {
        return
      }
      const bridge = readBrowserBridge()
      if (!bridge) {
        return
      }
      void runBrowserAction(async () => {
        await bridge.startAnnotationRuntime({
          threadId: resolvedOwnerId,
          tabId: annotation.tabId,
          annotations: activeBrowserAnnotations.map(toBrowserAnnotationRuntimeAnnotation),
          editAnnotationId: annotation.id,
          layoutHints: activeBrowserAnnotationLayoutHints,
        })
      })
      if (annotation.anchor.kind === 'element') {
        setAnnotationAdjustmentSession({
          ownerId: resolvedOwnerId,
          tabId: annotation.tabId,
          annotationId: annotation.id,
          selectedElement: annotation.anchor.element,
          designChanges: annotation.designChange ?? {},
        })
        openAsideTab('adjustment')
      }
 else {
        setAnnotationAdjustmentSession(null)
      }
      setAnnotationSession({
        tabId: annotation.tabId,
        editingAnnotationId: annotation.id,
      })
    },
    [
      activeBrowserAnnotations,
      activeBrowserAnnotationLayoutHints,
      activeBrowserTabId,
      resolvedOwnerId,
      runBrowserAction,
      openAsideTab,
      setAnnotationAdjustmentSession,
    ],
  )

  const handleAnnotationRuntimeBridgeEvent = useEffectEvent((event: unknown) => {
    if (!isBrowserAnnotationRuntimeEvent(event) || event.threadId !== resolvedOwnerId) {
      return
    }
    if (event.type === 'ready') {
      setAnnotationSession(
        previous =>
          previous ?? {
            tabId: event.tabId,
            editingAnnotationId: null,
          },
      )
      return
    }
    if (event.type === 'toggle') {
      if (event.tabId !== activeBrowserTabId) {
        return
      }
      handleToggleAnnotation()
      return
    }
    if (event.type === 'selected-element') {
      if (event.selectedElement) {
        setAnnotationAdjustmentSession({
          ownerId: resolvedOwnerId,
          tabId: event.tabId,
          annotationId: event.runtimeAnnotationId ?? annotationSession?.editingAnnotationId ?? null,
          selectedElement: event.selectedElement,
          designChanges: event.designChange ?? {},
        })
        openAsideTab('adjustment')
      }
 else {
        setAnnotationAdjustmentSession(null)
      }
      return
    }
    if (event.type === 'copy') {
      if (event.layoutHints) {
        syncAnnotationLayoutHints(
          {
            tabId: event.tabId,
            hints: event.layoutHints,
          },
          resolvedOwnerId,
        )
      }
      return
    }
    if (event.type === 'clear') {
      clearAnnotations({ ownerId: resolvedOwnerId, tabId: event.tabId })
      setAnnotationAdjustmentSession(null)
      return
    }
    if (event.type === 'layout-sync') {
      syncAnnotationLayoutHints(
        {
          tabId: event.tabId,
          hints: event.layoutHints ?? [],
        },
        resolvedOwnerId,
      )
      return
    }
    if (event.type === 'delete') {
      if (event.annotationId) {
        deleteAnnotation(event.annotationId, resolvedOwnerId)
      }
      return
    }
    if (event.type === 'edit') {
      const annotation = useBrowserPanelStore
        .getState()
        .owners[
          resolvedOwnerId
        ]
?.annotations
.find(candidate => candidate.id === event.annotationId && candidate.tabId === event.tabId)
      if (annotation && event.annotationId && event.anchor && typeof event.body === 'string') {
        saveAnnotation(
          {
            ...annotation,
            anchor: event.anchor,
            body: event.body,
            designChange: event.designChange ?? annotation.designChange,
            elements: event.elements ?? annotation.elements,
            surfaceSize: event.surfaceSize ?? annotation.surfaceSize,
          },
          resolvedOwnerId,
        )
        setAnnotationAdjustmentSession(null)
        return
      }
      if (annotation) {
        handleEditSavedAnnotation(annotation)
      }
      return
    }
    if (event.type === 'save' || event.type === 'submit') {
      handleRuntimeAnnotationCommit(event)
      return
    }
    if (event.type === 'cancel' || event.type === 'closed') {
      closeAnnotationSession()
    }
  })

  useEffect(() => {
    const bridge = readBrowserBridge()
    if (!bridge?.onAnnotationRuntimeEvent) {
      return undefined
    }

    return bridge.onAnnotationRuntimeEvent((event) => {
      handleAnnotationRuntimeBridgeEvent(event)
    })
  }, [])

  const handleSendSavedAnnotation = (annotation: BrowserAnnotationRecord) => {
    void (async () => {
      const sent = await sendBrowserAnnotationRecord(annotation)
      if (sent) {
        markAnnotationSent(annotation.id, resolvedOwnerId)
      }
    })()
  }

  const handlePanelKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {
    const isAnnotationToggle
      = (event.nativeEvent.metaKey || event.nativeEvent.ctrlKey)
        && event.nativeEvent.shiftKey
        && !event.nativeEvent.altKey
        && event.nativeEvent.key.toLowerCase() === 'f'
    if (isAnnotationToggle) {
      event.preventDefault()
      event.stopPropagation()
      event.nativeEvent.stopImmediatePropagation()
      handleToggleAnnotation()
      return
    }

    const isCommandOnly
      = event.nativeEvent.metaKey
        && !event.nativeEvent.altKey
        && !event.nativeEvent.ctrlKey
        && !event.nativeEvent.shiftKey
    if (!isCommandOnly) {
      return
    }

    const key = event.nativeEvent.key.toLowerCase()
    if (key === 'w' && activePanelTab) {
      event.preventDefault()
      event.stopPropagation()
      event.nativeEvent.stopImmediatePropagation()
      handleCloseTab(activePanelTab.id)
      return
    }

    if (!/^\d$/.test(key)) {
      return
    }

    const targetIndex = key === '0' ? 9 : Number.parseInt(key, 10) - 1
    const targetTab = tabs[targetIndex]
    if (!targetTab) {
      return
    }
    event.preventDefault()
    event.stopPropagation()
    event.nativeEvent.stopImmediatePropagation()
    handleSelectTab(targetTab.id)
  }

  const handleOpenContextUsageReport = () => {
    if (!activeSessionId) {
      return
    }
    openContextUsageReportTab({
      sessionId: activeSessionId,
      sessionTitle: activeSessionTitle,
      ownerId: resolvedOwnerId,
    })
  }

  const handleGoBack = () => {
    const bridge = readBrowserBridge()
    if (!bridge || !activeBrowserTabId) {
      return
    }
    void runBrowserAction(async () => {
      upsertOwnerState(
        await bridge.goBack({
          threadId: resolvedOwnerId,
          tabId: activeBrowserTabId,
        }),
      )
    })
  }

  const handleGoForward = () => {
    const bridge = readBrowserBridge()
    if (!bridge || !activeBrowserTabId) {
      return
    }
    void runBrowserAction(async () => {
      upsertOwnerState(
        await bridge.goForward({
          threadId: resolvedOwnerId,
          tabId: activeBrowserTabId,
        }),
      )
    })
  }

  const handleReload = () => {
    const bridge = readBrowserBridge()
    if (!bridge || !activeBrowserTabId) {
      return
    }
    void runBrowserAction(async () => {
      upsertOwnerState(
        await bridge.reload({
          threadId: resolvedOwnerId,
          tabId: activeBrowserTabId,
        }),
      )
    })
  }

  const handleAddressFocus = () => {
    setIsEditingAddress(true)
    setSuggestionsOpen(true)
  }

  const handleAddressBlur = () => {
    window.setTimeout(() => {
      setIsEditingAddress(false)
      setSuggestionsOpen(false)
    }, 120)
  }

  const handleAddressChange = (nextValue: string) => {
    setAddressValue(nextValue)
    if (activeBrowserTabId) {
      addressDraftByTabIdRef.current.set(activeBrowserTabId, nextValue)
    }
    setSuggestionsOpen(true)
  }

  useEffect(() => {
    const handleDirtyEvent = (event: Event) => {
      if (!isPlanRefineEditorDirtyEvent(event)) {
        return
      }
      const tabExists = useBrowserPanelStore
        .getState()
        .owners[
          resolvedOwnerId
        ]
?.tabs
.some(tab => tab.id === event.detail.tabId && tab.kind === 'plan-refine')
      if (!tabExists) {
        return
      }
      setDirtyPlanRefineTabIds((previous) => {
        const hasTab = previous.has(event.detail.tabId)
        if (event.detail.dirty === hasTab) {
          return previous
        }
        const next = new Set(previous)
        if (event.detail.dirty) {
          next.add(event.detail.tabId)
        }
 else {
          next.delete(event.detail.tabId)
        }
        return next
      })
      if (!event.detail.dirty && discardPromptTabId === event.detail.tabId) {
        setDiscardPromptTabId(null)
      }
    }

    window.addEventListener(PLAN_REFINE_EDITOR_DIRTY_EVENT, handleDirtyEvent)
    return () => {
      window.removeEventListener(PLAN_REFINE_EDITOR_DIRTY_EVENT, handleDirtyEvent)
    }
  }, [discardPromptTabId, resolvedOwnerId])

  useEffect(() => {
    const handleSaveEvent = (event: Event) => {
      if (!isPlanRefineEditorSaveEvent(event)) {
        return
      }
      const tab = useBrowserPanelStore
        .getState()
        .owners[
          resolvedOwnerId
        ]
?.tabs
.find(item => item.id === event.detail.tabId && item.kind === 'plan-refine')
      if (!tab) {
        return
      }
      queueMicrotask(() => {
        if (event.defaultPrevented) {
          closeLocalPanelTab(tab.id)
        }
      })
    }

    window.addEventListener(PLAN_REFINE_EDITOR_SAVE_EVENT, handleSaveEvent)
    return () => {
      window.removeEventListener(PLAN_REFINE_EDITOR_SAVE_EVENT, handleSaveEvent)
    }
  }, [closeLocalPanelTab, resolvedOwnerId])

  const handleDiscardPlanRefineTab = useCallback(
    (tabId: string) => {
      closeLocalPanelTab(tabId)
    },
    [closeLocalPanelTab],
  )

  return (
    <div
      className="flex min-h-0 flex-1 flex-col overflow-hidden bg-background"
      data-testid="browser-panel"
      data-browser-panel-ready="true"
      onKeyDownCapture={handlePanelKeyDown}
    >
      <BrowserPanelTabStripView
        tabs={tabs}
        activeTabId={resolvedActivePanelTabId}
        activeSessionId={activeSessionId}
        discardPromptTabId={discardPromptTabId}
        contextUsageAvailable={Boolean(activeSessionId)}
        onSelectTab={handleSelectTab}
        onRequestCloseTab={handleCloseTab}
        onDiscardPromptChange={setDiscardPromptTabId}
        onDiscardTab={handleDiscardPlanRefineTab}
        onNewTab={handleNewTab}
        onOpenContextUsage={handleOpenContextUsageReport}
      />

      {activeBrowserTab && (
        <BrowserNavigationToolbarView
          activeTab={activeBrowserTab}
          addressValue={addressValue}
          suggestions={suggestions}
          suggestionsOpen={suggestionsOpen}
          nativeBrowserAvailable={nativeBrowserAvailable}
          annotationActive={hasActiveAnnotationSession}
          onBack={handleGoBack}
          onForward={handleGoForward}
          onReload={handleReload}
          onAddressChange={handleAddressChange}
          onAddressFocus={handleAddressFocus}
          onAddressBlur={handleAddressBlur}
          onAddressSubmit={handleAddressSubmit}
          onSuggestionSelect={handleSuggestion}
          onCaptureScreenshot={handleCaptureScreenshot}
          onToggleAnnotation={
            hasActiveAnnotationSession ? handleCancelAnnotation : handleStartAnnotation
          }
        />
      )}

      {chromeStatus && <BrowserChromeStatusView status={chromeStatus} />}

      <div className="relative min-h-0 flex-1 overflow-hidden bg-background">
        {activePanelTab?.kind === 'browser' && (
          <div className="absolute inset-0 flex min-h-0 flex-col bg-background">
            <div ref={viewportRef} className="relative min-h-0 flex-1 bg-background">
              <div ref={browserWebviewHostRef} className="absolute inset-0" />
              {!nativeBrowserAvailable && (
                <div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-background px-6 text-center text-xs text-muted-foreground">
                  <GlobeIcon className="size-9 opacity-40" />
                  <p>Native browser pages are available in the desktop app.</p>
                </div>
              )}
              {nativeBrowserAvailable && activeBrowserTabIsBlank && (
                <BrowserNewTabSurfaceView
                  localServers={localServers}
                  localServersLoading={localServersLoading}
                  localServersError={localServersError}
                  onOpenUrl={navigateActiveTab}
                  onRefreshLocalServers={refreshLocalServers}
                />
              )}
              {!hasActiveAnnotationSession && (
                <BrowserAnnotationRailView
                  annotations={activeBrowserAnnotations}
                  collapsed={annotationTrayCollapsed}
                  onCollapsedChange={collapsed =>
                    setAnnotationTrayCollapsed(collapsed, resolvedOwnerId)}
                  onClear={() =>
                    clearAnnotations({ ownerId: resolvedOwnerId, tabId: activeBrowserTabId })}
                  onEdit={handleEditSavedAnnotation}
                  onDelete={annotationId => deleteAnnotation(annotationId, resolvedOwnerId)}
                  onSend={handleSendSavedAnnotation}
                />
              )}
            </div>
          </div>
        )}

        {activePanelTab?.kind === 'workspace-file' && activePanelTab.view === 'preview' && (
          <WorkspaceFilePreview
            workspaceId={activePanelTab.workspaceId}
            path={activePanelTab.path}
            onOpenEditor={(path) => {
              openWorkspaceFileTab({
                workspaceId: activePanelTab.workspaceId,
                path,
                view: 'editor',
                ownerId: resolvedOwnerId,
              })
            }}
            onAddLineComment={
              activeSessionId
                ? ({ workspaceId, path, lineNumber, comment }) => {
                    submitChatComposerContextIngress(activeSessionId, [
                      {
                        type: 'data-cradle-file-line-comment',
                        workspaceId,
                        path,
                        lineStart: lineNumber,
                        lineEnd: lineNumber,
                        comment,
                      },
                    ])
                  }
                : undefined
            }
          />
        )}

        {activePanelTab?.kind === 'workspace-file' && activePanelTab.view === 'editor' && (
          <WorkspaceFileEditor
            workspaceId={activePanelTab.workspaceId}
            path={activePanelTab.path}
          />
        )}

        {activePanelTab?.kind === 'workspace-diff' && (
          <WorkspaceDiffViewer
            ownerId={resolvedOwnerId}
            tabId={activePanelTab.id}
            workspaceId={activePanelTab.workspaceId}
            sessionId={activePanelTab.sessionId}
            repositoryPath={activePanelTab.repositoryPath}
            paths={activePanelTab.paths}
          />
        )}

        {activePanelTab?.kind === 'pull-request' && (
          <PullRequestDetailPanel
            owner={activePanelTab.owner}
            repo={activePanelTab.repo}
            number={activePanelTab.number}
            workId={activePanelTab.workId}
          />
        )}

        {activePanelTab?.kind === 'subagent' && (
          <SubagentOutputPanel
            sessionId={activePanelTab.sessionId}
            threadId={activePanelTab.threadId}
            agentName={activePanelTab.agentName}
            agentRole={activePanelTab.agentRole}
          />
        )}

        {activePanelTab?.kind === 'workflow' && (
          <WorkflowOutputPanel tab={activePanelTab} />
        )}

        {activePanelTab?.kind === 'side-conversation' && (
          <SideConversationPanel
            sideConversationId={activePanelTab.sideConversationId}
            parentSessionId={activePanelTab.parentSessionId}
            title={activePanelTab.title}
          />
        )}

        {activePanelTab?.kind === 'context-usage-report' && (
          <ContextUsageReport
            sessionId={activePanelTab.sessionId}
            sessionTitle={activePanelTab.sessionTitle}
          />
        )}

        {activePanelTab?.kind === 'launcher' && (
          <BrowserPanelCreateSurfaceView
            canCreateTui={canCreateTuiTab}
            browserPending={newTabRequestPending}
            onCreateBrowser={() => handleCreateBrowserTab(activePanelTab.id)}
            onCreateTui={() => handleCreateTuiTab(activePanelTab.id)}
          />
        )}

        {activePanelTab?.kind === 'tui' && (
          <div className="absolute inset-0 bg-background">
            <Suspense
              fallback={(
                <div className="flex h-full items-center justify-center text-xs text-muted-foreground">
                  Preparing terminal
                </div>
              )}
            >
              <BrowserTuiShellView
                ptyId={activePanelTab.ptyId}
                cwd={activePanelTab.cwd}
                visible={activePanelTab.id === resolvedActivePanelTabId}
                onMetadata={metadata => handleTuiMetadata(activePanelTab.id, metadata)}
                onExited={() => handleTuiExited(activePanelTab.id)}
              />
            </Suspense>
          </div>
        )}

        {activePanelTab?.kind === 'plan-document' && (
          <PlanDocumentViewer title={activePanelTab.title} text={activePanelTab.text} />
        )}

        {activePanelTab?.kind === 'plan-refine' && (
          <PlanRefineEditor
            tabId={activePanelTab.id}
            title={activePanelTab.title}
            text={activePanelTab.text}
          />
        )}

        {!activePanelTab && (
          <BrowserPanelCreateSurfaceView
            canCreateTui={canCreateTuiTab}
            browserPending={newTabRequestPending}
            onCreateBrowser={() => handleCreateBrowserTab()}
            onCreateTui={() => handleCreateTuiTab()}
          />
        )}
      </div>
    </div>
  )
}
