const app = document.getElementById('app');
let page = null;
let currentResult = null;
let runState = null;

const labels = {
  zh: {
    input: '任务输入',
    result: '结果',
    run: '运行',
    rerun: '再次运行',
    chooseFile: '选择文件',
    clear: '清空',
    copyResult: '复制结果',
    copyRaw: '复制原始输出',
    copyJson: '复制 JSON',
    raw: '原始输出',
    details: '运行详情',
    options: '更多选项',
    blocks: '结果块',
    lines: '行数',
    chars: '字符',
    mode: '模式',
    status: '状态',
    source: '来源',
    open: '安全浏览器',
    openSystem: '系统浏览器',
    openFolder: '打开文件夹',
    on: '开启',
    off: '关闭',
    ready: '就绪',
    running: '正在运行',
    empty: '暂无结果',
    error: '运行失败',
    canceled: '已取消',
    preview: '预览',
    noResult: '运行后会在这里显示结构化结果。',
    noInput: '此工具无需输入，可直接运行或等待自动加载。',
    copy: '复制',
    toolInfo: '工具说明'
  },
  en: {
    input: 'Task input',
    result: 'Result',
    run: 'Run',
    rerun: 'Run again',
    chooseFile: 'Choose file',
    clear: 'Clear',
    copyResult: 'Copy result',
    copyRaw: 'Copy raw output',
    copyJson: 'Copy JSON',
    raw: 'Raw output',
    details: 'Run details',
    options: 'More options',
    blocks: 'Blocks',
    lines: 'Lines',
    chars: 'Chars',
    mode: 'Mode',
    status: 'Status',
    source: 'Source',
    open: 'Safe browser',
    openSystem: 'System browser',
    openFolder: 'Open folder',
    on: 'On',
    off: 'Off',
    ready: 'Ready',
    running: 'Running',
    empty: 'No result yet',
    error: 'Run failed',
    canceled: 'Canceled',
    preview: 'Preview',
    noResult: 'Structured results will appear here after the tool runs.',
    noInput: 'This tool does not require input. Run it or wait for auto load.',
    copy: 'Copy',
    toolInfo: 'Tool info'
  }
};

window.chrome?.webview?.addEventListener('message', event => receive(event.data));
window.addEventListener('message', event => receive(event.data));

function receive(message) {
  if (!message) return;
  try {
    if (message.type === 'performanceMode') {
      document.documentElement.classList.toggle('drag-light', !!message.lightMode);
      document.documentElement.classList.toggle('drag-strong', !!message.lightMode && message.level === 'strong');
      return;
    }

    if (message.type === 'toolPagePayload') {
      page = message.payload;
      currentResult = page.result || null;
      runState = page.runState || null;
      render();
      return;
    }

    if (message.type === 'toolRunState') {
      runState = message.runState;
      render();
      return;
    }

    if (message.type === 'toolResult') {
      currentResult = message.document;
      runState = message.runState;
      render();
      return;
    }

    if (message.type === 'settingsChanged' && page) {
      page.theme = message.theme || page.theme;
      page.language = message.language || page.language;
      render();
      return;
    }

    if (message.type === 'fileSelected' && page) {
      setPrimaryText(message.path || '');
      render();
    }
  } catch (error) {
    renderFatal(error);
  }
}

function isEnglish() {
  return (page?.language || '').toLowerCase().startsWith('en');
}

function t(key) {
  return (isEnglish() ? labels.en : labels.zh)[key] || labels.en[key] || key;
}

function post(action, value, payload) {
  window.chrome?.webview?.postMessage({ action, value, payload });
}

function text(value) {
  return value === null || value === undefined ? '' : String(value);
}

function el(tag, className, content) {
  const node = document.createElement(tag);
  if (className) node.className = className;
  if (content !== undefined) node.textContent = content;
  return node;
}

function render() {
  try {
    if (!page) return;
    const theme = (page.theme || 'Light').toLowerCase().includes('dark') ? 'dark' : 'light';
    document.documentElement.dataset.theme = theme;
    document.documentElement.lang = isEnglish() ? 'en-US' : 'zh-CN';
    document.documentElement.style.setProperty('--accent', page.experience?.accent || '#2563eb');

    app.innerHTML = '';
    const root = el('section', `page ${page.experience?.inputLayout || ''} ${page.experience?.resultLayout || ''}`);
    root.append(toolbar());

    const workbench = el('section', 'workbench');
    workbench.append(inputPanel(), resultPanel());
    root.append(workbench);
    app.append(root);
  } catch (error) {
    renderFatal(error);
  }
}

function renderFatal(error) {
  app.innerHTML = '';
  const root = el('section', 'empty-state fatal');
  root.append(el('h2', '', t('error')));
  root.append(el('p', 'muted', error?.message || String(error || 'Render failed')));
  app.append(root);
}

function toolbar() {
  const root = el('section', 'tool-chrome');
  const identity = el('div', 'tool-identity');
  const mark = el('div', 'tool-mark', (page.toolName || page.toolId || 'T').trim().slice(0, 2).toUpperCase());
  const title = el('div', 'tool-title');
  title.append(el('h1', '', page.toolName || page.toolId || 'Tool'));
  title.append(chromeMeta());
  if (page.toolDescription) {
    title.title = page.toolDescription;
  }
  identity.append(mark, title);

  const actions = el('div', 'tool-actions');
  actions.append(actionButton(primaryActionText(), 'primary', runTool));
  if (page.availableActions?.includes('chooseFile')) {
    actions.append(actionButton(t('chooseFile'), '', () => post('chooseFile')));
  }
  actions.append(actionButton(t('copyResult'), 'ghost', () => post('copyResult')));

  root.append(identity, actions);
  return root;
}

function chromeMeta() {
  const row = el('div', 'meta-row');
  row.append(statusBadge());
  const category = page.runtimeMetadata?.category;
  if (category) row.append(el('span', 'quiet-badge', category));
  const layout = resultLayoutLabel();
  if (layout) row.append(el('span', 'quiet-badge', layout));
  return row;
}

function inputPanel() {
  const root = el('section', 'surface input-surface');
  const head = el('div', 'surface-head');
  const title = el('div');
  title.append(el('h2', '', t('input')));
  title.append(el('p', 'muted compact-copy', inputHint()));
  head.append(title, statusBadge());
  root.append(head);

  const grid = el('div', 'input-grid');
  appendFields(grid);
  const options = optionsSurface();
  if (options) grid.append(options);

  const actions = el('div', 'action-row');
  actions.append(actionButton(primaryActionText(), 'primary', runTool));
  if (page.availableActions?.includes('chooseFile')) {
    actions.append(actionButton(t('chooseFile'), '', () => post('chooseFile')));
  }
  if (page.availableActions?.includes('clear')) {
    actions.append(actionButton(t('clear'), 'ghost', clearInputs));
  }
  grid.append(actions);
  root.append(grid);
  return root;
}

function appendFields(root) {
  const fields = page.input?.fields || [];
  if (!fields.length) {
    root.append(el('p', 'muted inline-note', page.spec?.autoRunOnOpen ? t('ready') : t('noInput')));
    return;
  }

  const primary = fields.find(field => field.key === 'input' || field.key === 'query' || field.key === 'path' || field.kind === 'textarea') || fields[0];
  const primaryKinds = ['textarea', 'text', 'url', 'token', 'file', 'color'];

  if (primary && primaryKinds.includes(primary.kind)) {
    root.append(fieldElement(primary, true));
    const secondary = fields.filter(field => field !== primary);
    if (secondary.length) {
      const secondaryGrid = el('div', 'field-grid');
      for (const field of secondary) secondaryGrid.append(fieldElement(field, false));
      root.append(secondaryGrid);
    }
    return;
  }

  const fieldGrid = el('div', 'field-grid');
  for (const field of fields) fieldGrid.append(fieldElement(field, false));
  root.append(fieldGrid);
}

function fieldElement(field, primary) {
  const wrap = el('div', `field ${primary ? 'primary-field' : ''}`);
  const label = el('label', '', field.label || field.key);
  label.htmlFor = `field-${field.key}`;
  wrap.append(label);

  let control;
  if (field.kind === 'textarea' || (primary && page.spec?.primaryInput === 'MultilineText')) {
    control = el('textarea', primary ? 'editor' : '');
    control.value = field.value || '';
  } else if (field.kind === 'select') {
    control = el('select');
    for (const option of field.options || []) {
      const item = el('option', '', option.label || option.value);
      item.value = option.value || '';
      item.selected = item.value === (field.value || '');
      control.append(item);
    }
  } else {
    control = el('input');
    control.type = field.kind === 'number'
      ? 'number'
      : field.kind === 'date'
        ? 'date'
        : field.kind === 'color'
          ? 'color'
          : field.kind === 'token'
            ? 'password'
            : 'text';
    control.value = field.value || '';
    if (field.minimum !== null && field.minimum !== undefined) control.min = field.minimum;
    if (field.maximum !== null && field.maximum !== undefined) control.max = field.maximum;
  }

  control.id = `field-${field.key}`;
  control.name = field.key;
  control.placeholder = field.placeholder || '';
  control.addEventListener('input', () => updateField(field.key, control.value));
  control.addEventListener('change', () => updateField(field.key, control.value));
  wrap.append(control);
  return wrap;
}

function optionsSurface() {
  const rules = page.spec?.rules || [];
  const description = page.toolDescription || page.input?.metadata?.inputDescription || '';
  if (!rules.length && !description) return null;

  const content = el('div', rules.length <= 2 ? 'options-inline' : 'options-stack');
  for (const rule of rules) content.append(ruleElement(rule));
  if (description) {
    const info = el('p', 'muted option-help', localizeHint(description));
    content.append(info);
  }

  if (rules.length <= 1 && !description) {
    return content;
  }

  const drawer = el('details', 'options-drawer');
  const summary = el('summary');
  summary.append(el('span', '', t('options')));
  summary.append(el('span', 'muted', rules.length ? `${rules.length}` : t('toolInfo')));
  drawer.append(summary, content);
  return drawer;
}

function ruleElement(rule) {
  const wrap = el('div', 'rule-field');
  wrap.append(el('label', '', localized(rule.localizedLabel, rule.label || rule.key)));
  const options = rule.options || [];
  const current = (page.input?.rules || {})[rule.key] ?? options[0]?.value ?? 'false';

  if (!options.length) {
    const toggle = actionButton(current === 'true' ? t('on') : t('off'), `toggle-chip ${current === 'true' ? 'active' : ''}`, () => {
      page.input.rules = { ...(page.input.rules || {}), [rule.key]: current === 'true' ? 'false' : 'true' };
      render();
    });
    wrap.append(toggle);
    return wrap;
  }

  if (options.length <= 4) {
    const group = el('div', 'segmented');
    for (const option of options) {
      const value = option.value || '';
      const label = localized(option.localizedLabel, option.label || value);
      group.append(actionButton(label, current === value ? 'active' : '', () => {
        page.input.rules = { ...(page.input.rules || {}), [rule.key]: value };
        render();
      }));
    }
    wrap.append(group);
    return wrap;
  }

  const select = el('select');
  for (const option of options) {
    const item = el('option', '', localized(option.localizedLabel, option.label || option.value));
    item.value = option.value || '';
    item.selected = item.value === current;
    select.append(item);
  }
  select.addEventListener('change', () => {
    page.input.rules = { ...(page.input.rules || {}), [rule.key]: select.value };
    render();
  });
  wrap.append(select);
  return wrap;
}

function resultPanel() {
  const root = el('section', 'result-surface');
  root.append(resultToolbar());

  if (currentResult) {
    const blocks = Array.isArray(currentResult.blocks) ? currentResult.blocks : [];
    const ordered = prioritizeBlocks(blocks);
    if (ordered.length) {
      for (const block of ordered) root.append(renderBlockSafe(block));
    } else {
      root.append(emptyResult());
    }
    root.append(resultDetails(blocks));
  } else {
    root.append(emptyResult(runState?.state === 'error' ? t('error') : t('empty'), runState?.message || t('noResult')));
  }

  return root;
}

function resultToolbar() {
  const root = el('section', 'result-toolbar');
  const title = el('div');
  title.append(el('h2', '', t('result')));
  title.append(el('p', 'muted compact-copy', resultLayoutLabel()));
  const actions = el('div', 'result-actions');
  actions.append(statusBadge());
  actions.append(actionButton(t('copyRaw'), 'ghost compact', () => post('copy', currentResult?.rawText || '')));
  root.append(title, actions);
  return root;
}

function emptyResult(title = t('empty'), message = t('noResult')) {
  const empty = el('section', 'empty-state');
  empty.append(el('h2', '', title));
  empty.append(el('p', 'muted', message));
  return empty;
}

function resultDetails(blocks) {
  const root = el('details', 'result-details');
  const summary = el('summary');
  summary.append(el('span', '', t('details')));
  summary.append(el('span', 'muted', `${blocks.length} ${t('blocks')}`));
  root.append(summary);
  root.append(runtimeMetrics(blocks));
  root.append(rawCard());
  return root;
}

function statusBadge() {
  const state = runState?.state || currentResult?.status || 'ready';
  const status = el('div', 'status');
  status.append(el('span', `dot ${state}`));
  status.append(el('span', '', statusText(state)));
  return status;
}

function runtimeMetrics(blocks) {
  const raw = currentResult?.rawText || '';
  const values = [
    [t('status'), statusText(runState?.state || currentResult?.status || 'success')],
    [t('blocks'), blocks.length],
    [t('lines'), raw ? raw.replace(/\r\n/g, '\n').split('\n').length : 0],
    [t('mode'), primitiveLabel(page.experience?.resultPrimitives?.[0] || page.resultKind || 'result')]
  ];
  const root = el('section', 'metrics runtime-metrics');
  for (const [label, value] of values) {
    const metric = el('div', 'metric');
    metric.append(el('span', 'muted', label), el('strong', '', value));
    root.append(metric);
  }
  return root;
}

function prioritizeBlocks(blocks) {
  const score = block => {
    const kind = String(block?.kind || '').toLowerCase();
    const meta = block?.metadata || {};
    const mode = String(meta.displayMode || '').toLowerCase();
    const priority = String(meta.priority || '').toLowerCase();
    if (priority === 'high') return 0;
    if (kind === 'metric') return 1;
    if (['rankedlist', 'newslist', 'cardlist'].includes(kind)) return 2;
    if (kind === 'table' || kind === 'linechart') return 3;
    if (['image', 'media', 'color'].includes(kind)) return 4;
    if (['keyvalue', 'status', 'timeline', 'diff'].includes(kind)) return 5;
    if (mode === 'source' || priority === 'low') return 8;
    if (['raw', 'text'].includes(kind)) return 7;
    return 6;
  };
  return blocks.map((block, index) => ({ block, index }))
    .sort((a, b) => score(a.block) - score(b.block) || a.index - b.index)
    .map(item => item.block);
}

function renderBlockSafe(block) {
  try {
    return renderBlock(block || {});
  } catch (error) {
    const root = el('article', 'result-card error-card');
    root.append(el('h2', '', t('error')));
    root.append(el('p', 'muted', error?.message || String(error || 'Block render failed')));
    if (block?.text) root.append(code(redact(block.text)));
    return root;
  }
}

function renderBlock(block) {
  const kind = String(block.kind || '').toLowerCase();
  const metadata = block.metadata || {};
  const mode = String(metadata.displayMode || '').toLowerCase();
  const priority = String(metadata.priority || '').toLowerCase();
  const root = el('article', `result-card kind-${kind || 'block'} mode-${mode || 'default'} priority-${priority || 'normal'}`);
  const head = el('div', 'card-head');
  const title = el('div', 'card-title');
  title.append(blockIcon(kind), el('h2', '', block.title || kindLabel(kind) || t('result')));
  head.append(title, el('span', 'badge', kindLabel(kind)));
  root.append(head);

  const pairs = Array.isArray(block.pairs) ? block.pairs : [];
  const rows = Array.isArray(block.rows) ? block.rows : [];
  const items = Array.isArray(block.items) ? block.items : [];
  if (kind === 'keyvalue' || kind === 'metric') root.append(kvGrid(pairs, kind, metadata));
  else if (kind === 'table' || kind === 'linechart') root.append(table(rows));
  else if (['rankedlist', 'newslist', 'cardlist'].includes(kind)) root.append(rankedList(items, kind));
  else if (['diff', 'status', 'timeline'].includes(kind)) root.append(statusList(items, kind));
  else if (kind === 'text') root.append(items.length ? statusList(items, kind) : code(redact(block.text || '')));
  else if (['code', 'json', 'jsontree', 'raw'].includes(kind)) root.append(code(redact(block.text || '')));
  else if (kind === 'file') root.append(fileBlock(block));
  else if (kind === 'link') root.append(linkBlock(block));
  else if (kind === 'image' || kind === 'media') root.append(mediaBlock(block));
  else if (kind === 'color') root.append(colorBlock(block));
  else root.append(el('p', '', redact(block.text || '')));
  return root;
}

function kvGrid(pairs, kind, metadata) {
  const grid = el('div', `kv-grid ${kind === 'metric' ? 'metric-grid' : ''}`);
  const visible = pairs.slice(0, 80);
  for (const [index, pair] of visible.entries()) {
    const item = el('div', `kv ${kind === 'metric' && index === 0 ? 'primary-kv' : ''} ${metadata?.displayMode === 'source' ? 'source-kv' : ''}`);
    item.append(el('span', '', pair.key || ''), el('strong', '', redact(pair.value || '')));
    grid.append(item);
  }
  if (!visible.length) grid.append(el('p', 'muted', t('noResult')));
  return grid;
}

function table(rows) {
  const wrap = el('div', 'table-wrap');
  const tableNode = el('table');
  const visible = rows.slice(0, 180);
  for (const [rowIndex, row] of visible.entries()) {
    const tr = el('tr');
    const cells = Array.isArray(row) ? row : Object.values(row || {});
    for (const cell of cells.slice(0, 8)) tr.append(el(rowIndex === 0 ? 'th' : 'td', '', redact(cell)));
    tableNode.append(tr);
  }
  if (!visible.length) {
    const tr = el('tr');
    tr.append(el('td', 'muted', t('noResult')));
    tableNode.append(tr);
  }
  wrap.append(tableNode);
  return wrap;
}

function rankedList(items, kind) {
  const root = el('div', `list ${kind === 'rankedlist' ? 'ranked-list' : 'card-list'} ${kind}`);
  for (const [index, item] of items.slice(0, 220).entries()) {
    const normalized = normalizeListItem(item, index);
    const row = el('div', `list-item ${kind === 'rankedlist' ? 'ranked-item' : ''}`);
    row.append(rankBadge(normalized.rank, normalized.leading, kind));
    const body = el('div', 'item-body');
    body.append(el('strong', '', redact(normalized.title)));
    if (normalized.subtitle) body.append(el('span', 'item-subtitle', redact(normalized.subtitle)));
    row.append(body);
    makeCardLink(row, normalized.uri, normalized.title);
    root.append(row);
  }
  if (!items.length) root.append(el('p', 'muted', t('noResult')));
  return root;
}

function statusList(items, kind) {
  const root = el('div', `list status-list ${kind}`);
  for (const [index, item] of items.slice(0, 220).entries()) {
    const normalized = normalizeListItem(item, index);
    const row = el('div', `list-item status-${normalized.status || 'info'}`);
    row.append(el('span', 'badge', normalized.leading));
    const body = el('div', 'item-body');
    body.append(el('strong', '', redact(normalized.title)));
    if (normalized.subtitle) body.append(el('span', 'item-subtitle', redact(normalized.subtitle)));
    row.append(body);
    if (normalized.uri) row.append(linkActions(normalized.uri));
    root.append(row);
  }
  if (!items.length) root.append(el('p', 'muted', t('noResult')));
  return root;
}

function normalizeListItem(item, index) {
  let title = text(item?.title || item?.text || '');
  let subtitle = text(item?.subtitle || '');
  const uri = text(item?.uri || '');
  const status = text(item?.status || '');
  const leading = text(item?.leading || status || index + 1);
  const rank = Number.parseInt(leading, 10) || index + 1;
  const parts = title.split(/\s+\/\s+/).filter(Boolean);
  if (parts.length > 1) {
    title = parts[0];
    subtitle = subtitle ? `${subtitle}\n${parts.slice(1).join(' / ')}` : parts.slice(1).join(' / ');
  }
  const heat = title.match(/^(?<title>.*?)\s+(?<meta>(?:热度|播放|指数|人数|票房|状态)[:：]?.+)$/);
  if (heat?.groups?.title) {
    title = heat.groups.title;
    subtitle = subtitle ? `${subtitle}\n${heat.groups.meta}` : heat.groups.meta;
  }
  if (!title) title = t('noResult');
  return { rank, leading, title, subtitle, uri, status };
}

function rankBadge(rank, label, kind) {
  const badge = el('span', `rank-badge rank-${rank <= 3 && kind === 'rankedlist' ? rank : 'other'} ${kind !== 'rankedlist' ? 'plain' : ''}`);
  badge.append(el('span', 'rank-core', rank <= 3 && kind === 'rankedlist' ? String(rank) : label));
  return badge;
}

function linkActions(uri) {
  const actions = el('div', 'link-actions');
  actions.append(actionButton(t('open'), '', () => post('openLink', uri)));
  actions.append(actionButton(t('openSystem'), 'ghost compact', () => post('openSystemBrowser', uri)));
  return actions;
}

function makeCardLink(row, uri, label) {
  if (!uri) return;
  row.classList.add('clickable');
  row.tabIndex = 0;
  row.role = 'link';
  row.title = uri;
  row.setAttribute('aria-label', `${t('open')} ${label || uri}`);
  row.addEventListener('click', event => {
    event.preventDefault();
    post('openLink', uri);
  });
  row.addEventListener('keydown', event => {
    if (event.key === 'Enter' || event.key === ' ') {
      event.preventDefault();
      post('openLink', uri);
    }
  });
}

function code(value) {
  const wrap = el('div', 'code-wrap');
  const pre = el('pre');
  pre.textContent = value;
  wrap.append(pre);
  return wrap;
}

function fileBlock(block) {
  const row = el('div', 'list-item file-card');
  row.append(el('span', 'badge', 'FILE'));
  const body = el('div', 'item-body');
  body.append(el('strong', '', block.text || block.path || 'File'));
  body.append(el('span', '', redact(block.path || '')));
  row.append(body, actionButton(t('openFolder'), '', () => post('openFolder', block.path)));
  return row;
}

function linkBlock(block) {
  const row = el('div', 'list-item link-card');
  row.append(el('span', 'badge', 'URL'));
  const body = el('div', 'item-body');
  body.append(el('strong', '', redact(block.text || block.uri || 'Link')));
  body.append(el('span', '', redact(block.uri || '')));
  row.append(body, linkActions(block.uri));
  return row;
}

function mediaBlock(block) {
  const metadata = block.metadata || {};
  const uri = block.uri || block.text || '';
  const pngBase64 = metadata.pngBase64 || metadata.pngbase64 || '';
  const rawSvg = metadata.rawSvg || metadata.rawsvg || (String(uri).trim().startsWith('<svg') ? uri : '');
  const source = pngBase64
    ? `data:image/png;base64,${pngBase64}`
    : rawSvg
      ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(rawSvg)}`
      : uri;
  if (/\.(png|jpe?g|gif|webp|bmp|svg)(\?|$)/i.test(source) || source.startsWith('data:image')) {
    const frame = el('div', 'media-frame');
    const img = new Image();
    img.alt = block.title || t('preview');
    img.loading = 'eager';
    img.decoding = 'async';
    img.src = source;
    frame.append(img);
    return frame;
  }
  return linkBlock(block);
}

function colorBlock(block) {
  const swatch = el('div', 'swatch');
  swatch.style.background = block.text || block.uri || page.experience?.accent || '#2563eb';
  return swatch;
}

function rawCard() {
  const root = el('section', 'raw-card');
  const head = el('div', 'card-head');
  head.append(el('h2', '', t('raw')), actionButton(t('copyRaw'), 'ghost compact', () => post('copy', currentResult?.rawText || '')));
  root.append(head, code(redact(currentResult?.rawText || '')));
  return root;
}

function runTool() {
  const input = buildInputText();
  post('run', input, {
    text: input,
    fields: page.input?.fields || [],
    rules: page.input?.rules || {}
  });
}

function buildInputText() {
  const fields = page.input?.fields || [];
  const spec = page.spec || {};
  const byKey = Object.fromEntries(fields.map(field => [field.key, field.value || '']));
  if (spec.primaryInput === 'DateRange') {
    const start = byKey.start || byKey.date || new Date().toISOString().slice(0, 10);
    return byKey.end ? `${start} ${byKey.end}` : start;
  }
  if (['Number', 'NumberPair', 'NumberTriple', 'NumberQuad'].includes(spec.primaryInput)) {
    return fields.filter(field => field.kind === 'number').map(field => field.value || '0').join(' ');
  }
  const preset = fields.find(field => field.key === 'preset');
  if (preset && spec.primaryInput === 'FixedOptions') return preset.value || page.input?.text || '';
  const primary = fields.find(field => ['input', 'query', 'path', 'token'].includes(field.key)) || fields[0];
  return primary?.value || page.input?.text || '';
}

function clearInputs() {
  for (const field of page.input?.fields || []) field.value = '';
  page.input.text = '';
  post('clear');
  render();
}

function updateField(key, value) {
  for (const field of page.input?.fields || []) {
    if (field.key === key) field.value = value;
  }
  if (['input', 'query', 'path', 'token'].includes(key)) page.input.text = value;
}

function setPrimaryText(value) {
  const fields = page.input?.fields || [];
  const primary = fields.find(field => ['input', 'query', 'path', 'token'].includes(field.key)) || fields[0];
  if (primary) primary.value = value;
  page.input.text = value;
}

function redact(value) {
  let output = text(value);
  return output.replace(/https?:\/\/[^\s\]\)"'<>]+/gi, (match, offset, source) => {
    return shouldRedactUrl(match, offset, source) ? redactedLabel() : match;
  });
}

function redactedLabel() {
  return isEnglish() ? '[YMhut endpoint hidden]' : '[YMhut 接口已隐藏]';
}

function shouldRedactUrl(url, offset, source) {
  const host = safeHost(url);
  const hints = page?.privacyPolicy?.redactedHostHints || [];
  if (host && hints.some(hint => host.toLowerCase().includes(String(hint).toLowerCase()))) return true;
  const context = source.slice(Math.max(0, offset - 64), offset);
  return /(?:api[_\-\s]*url|request[_\-\s]*url|api\s+request|endpoint|接口地址|请求地址)\s*[:=：]?\s*$/i.test(context);
}

function safeHost(url) {
  try {
    return new URL(url).host;
  } catch {
    return '';
  }
}

function resultLayoutLabel() {
  const layout = page.experience?.resultLayout || page.experience?.experienceId || '';
  const primitive = page.experience?.resultPrimitives?.[0] || page.resultKind || '';
  const zh = {
    'data-table': '数据表',
    'ranked-lane': '排行榜',
    'news-board': '新闻卡片',
    'json-tree': 'JSON',
    'code-panel': '代码预览',
    'metric-grid': '指标',
    'media-stage': '媒体预览',
    'media-wall': '媒体',
    'file-rail': '文件',
    'status-timeline': '状态',
    'color-lab': '颜色',
    'text-stream': '文本',
    'generator-strip': '生成结果'
  };
  const en = {
    'data-table': 'Data table',
    'ranked-lane': 'Ranking',
    'news-board': 'News cards',
    'json-tree': 'JSON',
    'code-panel': 'Code preview',
    'metric-grid': 'Metrics',
    'media-stage': 'Media preview',
    'media-wall': 'Media',
    'file-rail': 'Files',
    'status-timeline': 'Status',
    'color-lab': 'Color',
    'text-stream': 'Text',
    'generator-strip': 'Generated'
  };
  const key = Object.keys(zh).find(item => layout.includes(item) || String(primitive).includes(item));
  const lang = isEnglish() ? en : zh;
  return key ? lang[key] : primitiveLabel(primitive || layout);
}

function primitiveLabel(value) {
  const key = String(value || '').toLowerCase();
  const zh = {
    'data-table': '数据表',
    'ranked-lane': '排行榜',
    'news-board': '新闻',
    'summary-metrics': '摘要',
    'raw-drawer': '原始输出',
    'virtual-list': '列表',
    'column-scan': '列扫描',
    'keyvaluecards': '键值卡片',
    'calculatortable': '计算表',
    'rankedlist': '排行榜',
    'newscards': '新闻卡片',
    'table': '表格'
  };
  const en = {
    'data-table': 'Data table',
    'ranked-lane': 'Ranking',
    'news-board': 'News',
    'summary-metrics': 'Summary',
    'raw-drawer': 'Raw output',
    'virtual-list': 'List',
    'column-scan': 'Column scan',
    'keyvaluecards': 'Key-value cards',
    'calculatortable': 'Calculation table',
    'rankedlist': 'Ranking',
    'newscards': 'News cards',
    'table': 'Data table'
  };
  const lang = isEnglish() ? en : zh;
  return lang[key] || value || t('result');
}

function kindLabel(kind) {
  const key = String(kind || '').toLowerCase();
  const zh = {
    keyvalue: '详情',
    metric: '指标',
    table: '表格',
    linechart: '趋势',
    rankedlist: '榜单',
    newslist: '新闻',
    cardlist: '卡片',
    timeline: '时间线',
    status: '状态',
    diff: '差异',
    text: '文本',
    code: '代码',
    json: 'JSON',
    jsontree: 'JSON',
    raw: '原始',
    file: '文件',
    link: '链接',
    image: '图像',
    media: '媒体',
    color: '颜色'
  };
  const en = {
    keyvalue: 'Details',
    metric: 'Metric',
    table: 'Table',
    linechart: 'Trend',
    rankedlist: 'Rank',
    newslist: 'News',
    cardlist: 'Cards',
    timeline: 'Timeline',
    status: 'Status',
    diff: 'Diff',
    text: 'Text',
    code: 'Code',
    json: 'JSON',
    jsontree: 'JSON',
    raw: 'Raw',
    file: 'File',
    link: 'Link',
    image: 'Image',
    media: 'Media',
    color: 'Color'
  };
  const lang = isEnglish() ? en : zh;
  return lang[key] || key || t('result');
}

function blockIcon(kind) {
  const node = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  node.setAttribute('class', 'block-icon');
  node.setAttribute('viewBox', '0 0 24 24');
  node.setAttribute('aria-hidden', 'true');
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  path.setAttribute('fill', 'none');
  path.setAttribute('stroke', 'currentColor');
  path.setAttribute('stroke-width', '1.8');
  path.setAttribute('stroke-linecap', 'round');
  path.setAttribute('stroke-linejoin', 'round');
  const key = String(kind || '').toLowerCase();
  path.setAttribute('d', {
    metric: 'M4 19V5m0 14h16M8 16V9m4 7V6m4 10v-4',
    keyvalue: 'M5 7h5M5 12h14M5 17h10',
    table: 'M4 5h16v14H4zM4 11h16M10 5v14',
    linechart: 'M4 17l5-5 4 3 7-8',
    rankedlist: 'M7 17h10M8 13h8M10 9h4M12 4l2 3 3 .5-2.2 2.1.5 3.2L12 11l-3.3 1.8.5-3.2L7 7.5 10 7z',
    newslist: 'M5 5h14v14H5zM8 9h8M8 13h8M8 17h5',
    cardlist: 'M5 6h14M5 12h14M5 18h14',
    timeline: 'M12 5v14M8 7h8M8 17h8',
    status: 'M5 12l4 4L19 6',
    diff: 'M7 8h10M7 16h10M12 4v8',
    code: 'M9 18l-6-6 6-6M15 6l6 6-6 6',
    json: 'M8 5H6v14h2M16 5h2v14h-2',
    jsontree: 'M8 5H6v14h2M16 5h2v14h-2',
    file: 'M6 3h8l4 4v14H6zM14 3v5h5',
    link: 'M10 13a5 5 0 0 0 7 0l2-2a5 5 0 0 0-7-7l-1 1M14 11a5 5 0 0 0-7 0l-2 2a5 5 0 0 0 7 7l1-1',
    image: 'M4 5h16v14H4zM8 13l3-3 3 4 2-2 4 5',
    media: 'M5 5l14 7-14 7z',
    color: 'M12 3a9 9 0 0 0 0 18 3 3 0 0 0 3-3c0-1.7-1.3-3-3-3H9a6 6 0 0 1 3-12z'
  }[key] || 'M5 6h14M5 12h14M5 18h14');
  node.append(path);
  return node;
}

function localized(pair, fallback) {
  if (!pair) return fallback || '';
  return isEnglish() ? (pair.en || fallback || '') : (pair.zh || fallback || '');
}

function inputHint() {
  const input = page.spec?.primaryInput || '';
  if (input === 'None') return page.spec?.autoRunOnOpen ? t('ready') : t('run');
  if (input === 'FilePath') return t('chooseFile');
  return localizeHint(page.input?.metadata?.hint || page.spec?.emptyState || '');
}

function localizeHint(value) {
  if (isEnglish()) return value;
  return {
    'Open this tool to refresh and render live data.': '打开工具后会刷新并展示实时数据。',
    'Enter values or choose options, then run the tool.': '输入内容或选择选项后运行工具。',
    'The tool could not finish. Check the input and try again.': '工具未能完成，请检查输入后重试。'
  }[value] || value;
}

function primaryActionText() {
  if (runState?.state === 'success') return t('rerun');
  const label = page.spec?.primaryActionLabel || t('run');
  return isEnglish() ? mapActionEn(label) : label;
}

function mapActionEn(label) {
  const map = {
    '格式化': 'Format',
    '转换': 'Convert',
    '生成': 'Generate',
    '查询': 'Query',
    '计算': 'Calculate',
    '处理': 'Process',
    '重新加载': 'Reload',
    '打开': 'Open',
    '扫描': 'Scan'
  };
  return map[label] || label || t('run');
}

function statusText(state) {
  if (runState?.message) return runState.message;
  return {
    running: t('running'),
    loading: t('running'),
    success: t('ready'),
    ok: t('ready'),
    error: t('error'),
    canceled: t('canceled'),
    ready: t('ready')
  }[state] || t('ready');
}

function actionButton(label, className, onClick) {
  const node = el('button', className || '', label);
  node.type = 'button';
  node.addEventListener('click', onClick);
  return node;
}
