/*
Copyright (C) 2025 QuantumNous

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

For commercial licensing, please contact support@quantumnous.com
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
  Banner,
  Button,
  Card,
  Collapsible,
  Input,
  InputNumber,
  Radio,
  RadioGroup,
  Select,
  Tag,
  TextArea,
  Typography,
} from '@douyinfe/semi-ui';
import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
import { renderQuota } from '../../../../helpers/render';
import {
  createEmptyCondition,
  createEmptyTimeCondition,
  createEmptyRuleGroup,
  createEmptyTimeRuleGroup,
  getRequestRuleMatchOptions,
  normalizeCondition,
  tryParseRequestRuleExpr,
  buildRequestRuleExpr,
  combineBillingExpr,
  splitBillingExprAndRequestRules,
  MATCH_EQ,
  MATCH_EXISTS,
  MATCH_CONTAINS,
  MATCH_RANGE,
  SOURCE_HEADER,
  SOURCE_PARAM,
  SOURCE_TIME,
  TIME_FUNCS,
  COMMON_TIMEZONES,
} from './requestRuleExpr';

const { Text } = Typography;

const PRICE_SUFFIX = '$/1M tokens';

function unitCostToPrice(uc) {
  return Number(uc) || 0;
}
function priceToUnitCost(price) {
  return Number(price) || 0;
}

const OPS = ['<', '<=', '>', '>='];
const VAR_OPTIONS = [
  { value: 'p', label: 'p (输入)' },
  { value: 'c', label: 'c (输出)' },
];

const CACHE_MODE_TIMED = 'timed';
const CACHE_MODE_GENERIC = 'generic';

function formatTokenHint(n) {
  if (n == null || n === '' || Number.isNaN(Number(n))) return '';
  const v = Number(n);
  if (v === 0) return '= 0';
  if (v >= 1000000) return `= ${(v / 1000000).toLocaleString()}M tokens`;
  if (v >= 1000) return `= ${(v / 1000).toLocaleString()}K tokens`;
  return `= ${v.toLocaleString()} tokens`;
}

// ---------------------------------------------------------------------------
// Expr generation from visual config (multi-condition)
// ---------------------------------------------------------------------------

function buildConditionStr(conditions) {
  if (!conditions || conditions.length === 0) return '';
  return conditions
    .filter((c) => c.var && c.op && c.value != null && c.value !== '')
    .map((c) => `${c.var} ${c.op} ${c.value}`)
    .join(' && ');
}

// CACHE_VAR_MAP maps tier data fields to Expr variable names (cache + image + audio)
const CACHE_VAR_MAP = [
  { field: 'cache_read_unit_cost', exprVar: 'cr' },
  { field: 'cache_create_unit_cost', exprVar: 'cc' },
  { field: 'cache_create_1h_unit_cost', exprVar: 'cc1h' },
  { field: 'image_unit_cost', exprVar: 'img' },
  { field: 'audio_input_unit_cost', exprVar: 'ai' },
  { field: 'audio_output_unit_cost', exprVar: 'ao' },
];

function getTierCacheMode(tier) {
  if (tier?.cache_mode === CACHE_MODE_TIMED) {
    return CACHE_MODE_TIMED;
  }
  if (tier?.cache_mode === CACHE_MODE_GENERIC) {
    return CACHE_MODE_GENERIC;
  }
  return Number(tier?.cache_create_1h_unit_cost) > 0
    ? CACHE_MODE_TIMED
    : CACHE_MODE_GENERIC;
}

function normalizeVisualTier(tier = {}) {
  return {
    ...tier,
    conditions: Array.isArray(tier.conditions) ? tier.conditions : [],
    cache_mode: getTierCacheMode(tier),
  };
}

function createDefaultVisualConfig() {
  return {
    tiers: [
      normalizeVisualTier({
        conditions: [],
        input_unit_cost: 0,
        output_unit_cost: 0,
        label: 'base',
        cache_mode: CACHE_MODE_GENERIC,
      }),
    ],
  };
}

function normalizeVisualConfig(config) {
  if (!config || !Array.isArray(config.tiers) || config.tiers.length === 0) {
    return createDefaultVisualConfig();
  }
  return {
    ...config,
    tiers: config.tiers.map((tier) => normalizeVisualTier(tier)),
  };
}

function buildTierBodyExpr(tier) {
  const parts = [];
  const ic = Number(tier.input_unit_cost) || 0;
  const oc = Number(tier.output_unit_cost) || 0;
  parts.push(`p * ${ic}`);
  parts.push(`c * ${oc}`);
  for (const cv of CACHE_VAR_MAP) {
    const v = Number(tier[cv.field]) || 0;
    if (v !== 0) parts.push(`${cv.exprVar} * ${v}`);
  }
  return parts.join(' + ');
}

function generateExprFromVisualConfig(config) {
  if (!config || !config.tiers || config.tiers.length === 0)
    return 'p * 0 + c * 0';
  const tiers = config.tiers;

  if (tiers.length === 1) {
    const t = tiers[0];
    const label = t.label || 'default';
    return `tier("${label}", ${buildTierBodyExpr(t)})`;
  }

  const parts = [];
  for (let i = 0; i < tiers.length; i++) {
    const t = tiers[i];
    const label = t.label || `第${i + 1}档`;
    const body = `tier("${label}", ${buildTierBodyExpr(t)})`;
    const cond = buildConditionStr(t.conditions);

    if (i < tiers.length - 1 && cond) {
      parts.push(`${cond} ? ${body}`);
    } else {
      parts.push(body);
    }
  }
  return parts.join(' : ');
}

// ---------------------------------------------------------------------------
// Reverse-parse an Expr string back into visual config
// ---------------------------------------------------------------------------

function tryParseVisualConfig(exprStr) {
  if (!exprStr) return null;
  try {
    const cacheVarNames = CACHE_VAR_MAP.map((cv) => cv.exprVar);
    const optCacheStr = cacheVarNames
      .map((v) => `(?:\\s*\\+\\s*${v}\\s*\\*\\s*([\\d.eE+-]+))?`)
      .join('');

    // Body pattern: p * X + c * Y [+ cr * A] [+ cc * B] [+ cc1h * C]
    const bodyPat = `p\\s*\\*\\s*([\\d.eE+-]+)\\s*\\+\\s*c\\s*\\*\\s*([\\d.eE+-]+)${optCacheStr}`;

    // Single-tier: tier("label", body)
    const singleRe = new RegExp(`^tier\\("([^"]*)",\\s*${bodyPat}\\)$`);
    const simple = exprStr.match(singleRe);
    if (simple) {
      const tier = {
        conditions: [],
        input_unit_cost: Number(simple[2]),
        output_unit_cost: Number(simple[3]),
        label: simple[1],
      };
      CACHE_VAR_MAP.forEach((cv, i) => {
        const val = simple[4 + i];
        if (val != null) tier[cv.field] = Number(val);
      });
      return normalizeVisualConfig({ tiers: [normalizeVisualTier(tier)] });
    }

    // Multi-tier: cond1 ? tier(body) : cond2 ? tier(body) : tier(body)
    const condGroup = `((?:(?:p|c)\\s*(?:<|<=|>|>=)\\s*[\\d.eE+]+)(?:\\s*&&\\s*(?:p|c)\\s*(?:<|<=|>|>=)\\s*[\\d.eE+]+)*)`;
    const tierRe = new RegExp(
      `(?:${condGroup}\\s*\\?\\s*)?tier\\("([^"]*)",\\s*${bodyPat}\\)`,
      'g',
    );
    const tiers = [];
    let match;
    while ((match = tierRe.exec(exprStr)) !== null) {
      const condStr = match[1] || '';
      const conditions = [];
      if (condStr) {
        const condParts = condStr.split(/\s*&&\s*/);
        for (const cp of condParts) {
          const cm = cp.trim().match(/^(p|c)\s*(<|<=|>|>=)\s*([\d.eE+]+)$/);
          if (cm) {
            conditions.push({ var: cm[1], op: cm[2], value: Number(cm[3]) });
          }
        }
      }
      const tier = {
        conditions,
        input_unit_cost: Number(match[3]),
        output_unit_cost: Number(match[4]),
        label: match[2],
      };
      CACHE_VAR_MAP.forEach((cv, i) => {
        const val = match[5 + i];
        if (val != null) tier[cv.field] = Number(val);
      });
      tiers.push(normalizeVisualTier(tier));
    }
    if (tiers.length === 0) return null;

    const cfg = normalizeVisualConfig({ tiers });
    const regenerated = generateExprFromVisualConfig(cfg);
    if (regenerated.replace(/\s+/g, '') !== exprStr.replace(/\s+/g, ''))
      return null;
    return cfg;
  } catch {
    return null;
  }
}

// ---------------------------------------------------------------------------
// Condition editor row
// ---------------------------------------------------------------------------

function ConditionRow({ cond, onChange, onRemove, t }) {
  const hint = formatTokenHint(cond.value);
  return (
    <div style={{
      marginBottom: 6,
      display: 'grid',
      gridTemplateColumns: '1fr auto 1fr auto',
      gap: '4px 6px',
      alignItems: 'center',
    }}>
      <Select
        size='small'
        value={cond.var || 'p'}
        onChange={(val) => onChange({ ...cond, var: val })}
      >
        {VAR_OPTIONS.map((v) => (
          <Select.Option key={v.value} value={v.value}>
            {v.label}
          </Select.Option>
        ))}
      </Select>
      <Select
        size='small'
        value={cond.op || '<'}
        onChange={(val) => onChange({ ...cond, op: val })}
        style={{ width: 70 }}
      >
        {OPS.map((op) => (
          <Select.Option key={op} value={op}>
            {op}
          </Select.Option>
        ))}
      </Select>
      <InputNumber
        size='small'
        min={0}
        value={cond.value ?? ''}
        onChange={(val) => onChange({ ...cond, value: val })}
      />
      <Button
        icon={<IconDelete />}
        type='danger'
        theme='borderless'
        size='small'
        onClick={onRemove}
      />
      {hint ? (
        <Text
          size='small'
          style={{
            color: 'var(--semi-color-text-3)',
            gridColumn: '3 / 4',
          }}
        >
          = {hint}
        </Text>
      ) : null}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Price input that preserves intermediate text like "7." or "0.5"
// ---------------------------------------------------------------------------

function PriceInput({ unitCost, field, index, onUpdate, placeholder }) {
  const priceFromModel = unitCostToPrice(unitCost);
  const [text, setText] = useState(priceFromModel === 0 ? '' : String(priceFromModel));

  useEffect(() => {
    const current = Number(text);
    if (text === '' && priceFromModel === 0) return;
    if (!Number.isNaN(current) && current === priceFromModel) return;
    setText(priceFromModel === 0 ? '' : String(priceFromModel));
  }, [priceFromModel]);

  const handleChange = (val) => {
    setText(val);
    if (val === '') {
      onUpdate(index, field, 0);
      return;
    }
    const num = Number(val);
    if (!Number.isNaN(num)) {
      onUpdate(index, field, priceToUnitCost(num));
    }
  };

  return (
    <Input
      value={text}
      placeholder={placeholder || '0'}
      suffix={PRICE_SUFFIX}
      onChange={handleChange}
      style={{ width: '100%', marginTop: 2 }}
    />
  );
}

// ---------------------------------------------------------------------------
// Extended price block (cache fields) — collapsible per tier, with mode switch
// ---------------------------------------------------------------------------

const CACHE_FIELDS_TIMED = [
  { field: 'cache_read_unit_cost', labelKey: '缓存读取价格' },
  { field: 'cache_create_unit_cost', labelKey: '缓存创建价格（5分钟）' },
  { field: 'cache_create_1h_unit_cost', labelKey: '缓存创建价格（1小时）' },
];

const CACHE_FIELDS_GENERIC = [
  { field: 'cache_read_unit_cost', labelKey: '缓存读取价格' },
  { field: 'cache_create_unit_cost', labelKey: '缓存创建价格' },
];

function ExtendedPriceBlock({ tier, index, onUpdate, t }) {
  const hasAny = [...CACHE_FIELDS_TIMED, 'image_unit_cost', 'audio_input_unit_cost', 'audio_output_unit_cost'].some(
    (f) => Number(tier[typeof f === 'string' ? f : f.field]) > 0,
  );
  const [expanded, setExpanded] = useState(hasAny);
  const cacheMode = getTierCacheMode(tier);

  const handleCacheModeChange = (e) => {
    const mode = e.target.value;
    const patch = { cache_mode: mode };
    if (mode === CACHE_MODE_GENERIC) {
      patch.cache_create_1h_unit_cost = 0;
    }
    onUpdate(index, patch);
  };

  const activeFields =
    cacheMode === CACHE_MODE_TIMED ? CACHE_FIELDS_TIMED : CACHE_FIELDS_GENERIC;

  return (
    <div style={{ marginTop: 8 }}>
      <Button
        theme='borderless'
        size='small'
        onClick={() => setExpanded(!expanded)}
        style={{ padding: '2px 0', color: 'var(--semi-color-text-2)', fontSize: 12 }}
      >
        {expanded ? '▾' : '▸'} {t('扩展价格')}
      </Button>
      <Collapsible isOpen={expanded}>
        <div
          style={{
            marginTop: 4,
            padding: '8px 0',
          }}
        >
          <div className='text-xs text-gray-500 mb-2'>
            {t('这些价格都是可选项，不填也可以。')}
          </div>
          <div style={{ marginBottom: 8 }}>
            <RadioGroup
              type='button'
              size='small'
              value={cacheMode}
              onChange={handleCacheModeChange}
            >
              <Radio value={CACHE_MODE_GENERIC}>{t('通用缓存')}</Radio>
              <Radio value={CACHE_MODE_TIMED}>{t('分时缓存 (Claude)')}</Radio>
            </RadioGroup>
          </div>
          <div
            style={{
              display: 'grid',
              gridTemplateColumns: '1fr 1fr',
              gap: 8,
            }}
          >
            {activeFields.map((cf) => (
              <div key={cf.field}>
                <Text
                  size='small'
                  style={{ color: 'var(--semi-color-text-2)' }}
                >
                  {t(cf.labelKey)}
                </Text>
                <PriceInput
                  unitCost={tier[cf.field]}
                  field={cf.field}
                  index={index}
                  onUpdate={onUpdate}
                />
              </div>
            ))}
          </div>
          <div className='text-xs text-gray-500 mb-2 mt-3'>
            {t('图片/音频价格（可选）')}
          </div>
          <div
            style={{
              display: 'grid',
              gridTemplateColumns: '1fr 1fr 1fr',
              gap: 8,
            }}
          >
            {[
              { field: 'image_unit_cost', labelKey: '图片输入价格' },
              { field: 'audio_input_unit_cost', labelKey: '音频输入价格' },
              { field: 'audio_output_unit_cost', labelKey: '音频补全价格' },
            ].map((cf) => (
              <div key={cf.field}>
                <Text
                  size='small'
                  style={{ color: 'var(--semi-color-text-2)' }}
                >
                  {t(cf.labelKey)}
                </Text>
                <PriceInput
                  unitCost={tier[cf.field]}
                  field={cf.field}
                  index={index}
                  onUpdate={onUpdate}
                />
              </div>
            ))}
          </div>
        </div>
      </Collapsible>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Visual Tier Card (multi-condition)
// ---------------------------------------------------------------------------

function VisualTierCard({ tier, index, isLast, isOnly, onUpdate, onRemove, t }) {
  const conditions = tier.conditions || [];

  const varLabel = { p: t('输入'), c: t('输出') };
  const condSummary = useMemo(() => {
    if (conditions.length === 0) return t('无条件（兜底档）');
    return conditions
      .filter((c) => c.var && c.op && c.value != null)
      .map((c) => `${varLabel[c.var] || c.var} ${c.op} ${formatTokenHint(c.value)}`)
      .join(' && ');
  }, [conditions, t]);

  const updateCondition = (ci, newCond) => {
    const next = conditions.map((c, i) => (i === ci ? newCond : c));
    onUpdate(index, 'conditions', next);
  };

  const removeCondition = (ci) => {
    onUpdate(
      index,
      'conditions',
      conditions.filter((_, i) => i !== ci),
    );
  };

  const addCondition = () => {
    if (conditions.length >= 2) return;
    const usedVars = conditions.map((c) => c.var);
    const nextVar = usedVars.includes('p') ? 'c' : 'p';
    onUpdate(index, 'conditions', [
      ...conditions,
      { var: nextVar, op: '<', value: 200000 },
    ]);
  };

  return (
    <div
      style={{
        padding: '12px 16px',
        borderRadius: 8,
        border: '1px solid var(--semi-color-border)',
        background: 'var(--semi-color-bg-2)',
        marginBottom: 8,
      }}
    >
      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          marginBottom: 10,
        }}
      >
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <Tag color='blue' size='small'>
            {t('第 {{n}} 档', { n: index + 1 })}
          </Tag>
          {isLast && !isOnly ? (
            <Tag color='grey' size='small'>
              {t('兜底档')}
            </Tag>
          ) : null}
        </div>
        {!isOnly ? (
          <Button
            icon={<IconDelete />}
            type='danger'
            theme='borderless'
            size='small'
            onClick={() => onRemove(index)}
          />
        ) : null}
      </div>

      {/* Tier label */}
      <div style={{ marginBottom: 8 }}>
        <Text size='small' style={{ color: 'var(--semi-color-text-2)' }}>
          {t('档位名称')}
        </Text>
        <Input
          size='small'
          value={tier.label || ''}
          placeholder={t('第 {{n}} 档', { n: index + 1 })}
          onChange={(val) => onUpdate(index, 'label', val)}
          style={{ width: '100%', marginTop: 2 }}
        />
      </div>

      {/* Conditions */}
      {!isLast || isOnly ? (
        <div style={{ marginBottom: 10 }}>
          <Text
            size='small'
            style={{
              color: 'var(--semi-color-text-2)',
              display: 'block',
              marginBottom: 4,
            }}
          >
            {t('条件')}
          </Text>
          {conditions.map((cond, ci) => (
            <ConditionRow
              key={ci}
              cond={cond}
              onChange={(nc) => updateCondition(ci, nc)}
              onRemove={() => removeCondition(ci)}
              t={t}
            />
          ))}
          {conditions.length < 2 && (
            <Button
              icon={<IconPlus />}
              size='small'
              theme='borderless'
              onClick={addCondition}
              style={{ marginTop: 2 }}
            >
              {t('添加条件')}
            </Button>
          )}
        </div>
      ) : (
        <div
          style={{
            marginBottom: 10,
            padding: '4px 8px',
            borderRadius: 4,
            background: 'var(--semi-color-fill-1)',
          }}
        >
          <Text size='small' style={{ color: 'var(--semi-color-text-3)' }}>
            {condSummary}
          </Text>
        </div>
      )}

      {/* Prices */}
      <div
        style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}
      >
        <div>
          <Text size='small' style={{ color: 'var(--semi-color-text-2)' }}>
            {t('输入价格')}
          </Text>
          <PriceInput
            unitCost={tier.input_unit_cost}
            field='input_unit_cost'
            index={index}
            onUpdate={onUpdate}
          />
        </div>
        <div>
          <Text size='small' style={{ color: 'var(--semi-color-text-2)' }}>
            {t('输出价格')}
          </Text>
          <PriceInput
            unitCost={tier.output_unit_cost}
            field='output_unit_cost'
            index={index}
            onUpdate={onUpdate}
          />
        </div>
      </div>

      {/* Extended prices (cache) — collapsible */}
      <ExtendedPriceBlock tier={tier} index={index} onUpdate={onUpdate} t={t} />
    </div>
  );
}

// ---------------------------------------------------------------------------
// Visual editor
// ---------------------------------------------------------------------------

function VisualEditor({ visualConfig, onChange, t }) {
  const config = normalizeVisualConfig(visualConfig);
  const tiers = config.tiers || [];

  const updateTier = (index, field, value) => {
    const patch =
      typeof field === 'string' ? { [field]: value } : { ...field };
    const next = tiers.map((tier, i) =>
      i === index ? normalizeVisualTier({ ...tier, ...patch }) : tier,
    );
    onChange({ ...config, tiers: next });
  };

  const addTier = () => {
    const newTiers = [...tiers];
    if (
      newTiers.length > 0 &&
      (!newTiers[newTiers.length - 1].conditions ||
        newTiers[newTiers.length - 1].conditions.length === 0)
    ) {
      newTiers[newTiers.length - 1] = {
        ...newTiers[newTiers.length - 1],
        conditions: [{ var: 'p', op: '<', value: 200000 }],
      };
    }
    newTiers.push({
      conditions: [],
      input_unit_cost: 0,
      output_unit_cost: 0,
      label: `第${newTiers.length + 1}档`,
      cache_mode: CACHE_MODE_GENERIC,
    });
    onChange({ ...config, tiers: newTiers });
  };

  const removeTier = (index) => {
    if (tiers.length <= 1) return;
    const next = tiers.filter((_, i) => i !== index);
    if (next.length > 0) {
      next[next.length - 1] = {
        ...next[next.length - 1],
        conditions: [],
      };
    }
    onChange({ ...config, tiers: next });
  };

  return (
    <div>
      <Banner
        type='info'
        description={t('每个档位可设置 0~2 个条件（对 p 和 c），最后一档为兜底档无需条件。')}
        style={{ marginBottom: 12 }}
      />

      {tiers.map((tier, index) => (
        <VisualTierCard
          key={index}
          tier={tier}
          index={index}
          isLast={index === tiers.length - 1}
          isOnly={tiers.length === 1}
          onUpdate={updateTier}
          onRemove={removeTier}
          t={t}
        />
      ))}
      <Button
        icon={<IconPlus />}
        size='small'
        theme='light'
        onClick={addTier}
        style={{ marginTop: 4 }}
      >
        {t('添加更多档位')}
      </Button>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Raw Expr editor with preset templates
// ---------------------------------------------------------------------------

const PRESET_GROUPS = [
  {
    group: '固定价格',
    presets: [
      { key: 'flat', label: 'Flat', expr: 'tier("base", p * 2 + c * 4)' },
      { key: 'claude-opus', label: 'Claude Opus 4.6', expr: 'tier("base", p * 5 + c * 25 + cr * 0.5 + cc * 6.25 + cc1h * 10)' },
      { key: 'gpt-5.4', label: 'GPT-5.4', expr: 'tier("base", p * 2.5 + c * 15 + cr * 0.25)' },
    ],
  },
  {
    group: '阶梯计费',
    presets: [
      { key: 'claude-sonnet', label: 'Claude Sonnet 4.5', expr: 'p <= 200000 ? tier("standard", p * 3 + c * 15 + cr * 0.3 + cc * 3.75 + cc1h * 6) : tier("long_context", p * 6 + c * 22.5 + cr * 0.6 + cc * 7.5 + cc1h * 12)' },
      { key: 'qwen3-max', label: 'Qwen3-Max', expr: 'p <= 32000 ? tier("short", p * 1.2 + c * 6 + cr * 0.24 + cc * 1.5) : p <= 128000 ? tier("mid", p * 2.4 + c * 12 + cr * 0.48 + cc * 3) : tier("long", p * 3 + c * 15 + cr * 0.6 + cc * 3.75)' },
      { key: 'glm-4.5-air', label: 'GLM-4.5-Air', expr: 'p < 32000 && c < 200 ? tier("short_output", p * 0.8 + c * 2 + cr * 0.16) : p < 32000 && c >= 200 ? tier("long_output", p * 0.8 + c * 6 + cr * 0.16) : tier("mid_context", p * 1.2 + c * 8 + cr * 0.24)' },
    ],
  },
  {
    group: '多模态',
    presets: [
      { key: 'gpt-image-1-mini', label: 'GPT-Image-1-Mini', expr: 'tier("base", p * 2 + c * 8 + img * 2.5)' },
      { key: 'qwen3-omni-flash', label: 'Qwen3-Omni-Flash', expr: 'tier("base", p * 0.43 + c * 3.06 + img * 0.78 + ai * 3.81 + ao * 15.11)' },
    ],
  },
  {
    group: '请求条件',
    presets: [
      {
        key: 'claude-opus-fast', label: 'Claude Opus 4.6 Fast',
        expr: 'tier("base", p * 5 + c * 25 + cr * 0.5 + cc * 6.25 + cc1h * 10)',
        requestRules: [{ conditions: [{ source: SOURCE_HEADER, path: 'anthropic-beta', mode: MATCH_CONTAINS, value: 'fast-mode-2026-02-01' }], multiplier: '6' }],
      },
      {
        key: 'gpt-5.4-fast', label: 'GPT-5.4 Fast',
        expr: 'tier("base", p * 2.5 + c * 15 + cr * 0.25)',
        requestRules: [{ conditions: [{ source: SOURCE_PARAM, path: 'service_tier', mode: MATCH_EQ, value: 'fast' }], multiplier: '2' }],
      },
    ],
  },
  {
    group: '时间促销',
    presets: [
      {
        key: 'night-discount', label: '夜间半价',
        expr: 'tier("base", p * 3 + c * 15)',
        requestRules: [{ conditions: [{ source: SOURCE_TIME, timeFunc: 'hour', timezone: 'Asia/Shanghai', mode: MATCH_RANGE, rangeStart: '21', rangeEnd: '6' }], multiplier: '0.5' }],
      },
      {
        key: 'weekend-discount', label: '周末8折',
        expr: 'tier("base", p * 3 + c * 15)',
        requestRules: [
          { conditions: [{ source: SOURCE_TIME, timeFunc: 'weekday', timezone: 'Asia/Shanghai', mode: MATCH_EQ, value: '0' }], multiplier: '0.8' },
          { conditions: [{ source: SOURCE_TIME, timeFunc: 'weekday', timezone: 'Asia/Shanghai', mode: MATCH_EQ, value: '6' }], multiplier: '0.8' },
        ],
      },
      {
        key: 'new-year-promo', label: '新年促销',
        expr: 'tier("base", p * 3 + c * 15)',
        requestRules: [{ conditions: [
          { source: SOURCE_TIME, timeFunc: 'month', timezone: 'Asia/Shanghai', mode: MATCH_EQ, value: '1' },
          { source: SOURCE_TIME, timeFunc: 'day', timezone: 'Asia/Shanghai', mode: MATCH_EQ, value: '1' },
        ], multiplier: '0.5' }],
      },
    ],
  },
];

const PRESET_DEFAULT_VISIBLE = 2;

function PresetSection({ applyPreset, t }) {
  const [expanded, setExpanded] = useState(false);
  const visibleGroups = expanded ? PRESET_GROUPS : PRESET_GROUPS.slice(0, PRESET_DEFAULT_VISIBLE);
  const hasMore = PRESET_GROUPS.length > PRESET_DEFAULT_VISIBLE;

  return (
    <div style={{ marginBottom: 12 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
        <Text size='small' style={{ color: 'var(--semi-color-text-2)' }}>
          {t('预设模板')}
        </Text>
        {hasMore && (
          <Button
            theme='borderless'
            size='small'
            onClick={() => setExpanded(!expanded)}
            style={{ padding: '0 4px', fontSize: 12, color: 'var(--semi-color-primary)' }}
          >
            {expanded ? t('收起') : t('更多模板...')}
          </Button>
        )}
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
        {visibleGroups.map((g) => (
          <div key={g.group} style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
            <Tag size='small' color='grey' style={{ minWidth: 60, textAlign: 'center' }}>
              {t(g.group)}
            </Tag>
            {g.presets.map((p) => (
              <Button key={p.key} size='small' theme='light' onClick={() => applyPreset(p)}>
                {p.label}
              </Button>
            ))}
          </div>
        ))}
      </div>
    </div>
  );
}

function RawExprEditor({ exprString, onChange, t }) {
  return (
    <div>
      <Banner
        type='info'
        description={
          <div>
            <div>
              {t('变量')}: <code>p</code> ({t('输入 Token')}), <code>c</code> (
              {t('输出 Token')}), <code>cr</code> ({t('缓存读取')}),{' '}
              <code>cc</code> ({t('缓存创建')}),{' '}
              <code>cc1h</code> ({t('缓存创建-1小时')})
            </div>
            <div>
              {t('函数')}: <code>tier(name, value)</code>,{' '}
              <code>max(a, b)</code>, <code>min(a, b)</code>,{' '}
              <code>ceil(x)</code>, <code>floor(x)</code>,{' '}
              <code>abs(x)</code>, <code>header(name)</code>,{' '}
              <code>param(path)</code>, <code>has(source, text)</code>
            </div>
            <div>
              {t('也支持更好懂的别名')}: <code>prompt_tokens</code>,{' '}
              <code>completion_tokens</code>, <code>cache_read_tokens</code>,{' '}
              <code>cache_create_tokens</code>,{' '}
              <code>cache_create_1h_tokens</code>
            </div>
          </div>
        }
        style={{ marginBottom: 12 }}
      />

      <TextArea
        value={exprString}
        onChange={onChange}
        autosize={{ minRows: 3, maxRows: 12 }}
        style={{ fontFamily: 'monospace', fontSize: 13 }}
        placeholder={t('输入计费表达式...')}
      />
    </div>
  );
}

// ---------------------------------------------------------------------------
// Cache token inputs for estimator — auto-shown when expression uses cache vars
// ---------------------------------------------------------------------------

const EXTRA_ESTIMATOR_FIELDS = [
  { var: 'cr', stateKey: 'cacheReadTokens', labelKey: '缓存读取 Token (cr)' },
  { var: 'cc', stateKey: 'cacheCreateTokens', labelKey: '缓存创建 Token (cc)' },
  { var: 'cc1h', stateKey: 'cacheCreate1hTokens', labelKey: '缓存创建-1小时 (cc1h)' },
  { var: 'img', stateKey: 'imageTokens', labelKey: '图片输入 Token (img)' },
  { var: 'ai', stateKey: 'audioInputTokens', labelKey: '音频输入 Token (ai)' },
  { var: 'ao', stateKey: 'audioOutputTokens', labelKey: '音频补全 Token (ao)' },
];

function CacheTokenEstimatorInputs({
  effectiveExpr,
  cacheReadTokens, setCacheReadTokens,
  cacheCreateTokens, setCacheCreateTokens,
  cacheCreate1hTokens, setCacheCreate1hTokens,
  imageTokens, setImageTokens,
  audioInputTokens, setAudioInputTokens,
  audioOutputTokens, setAudioOutputTokens,
  t,
}) {
  const setters = {
    cacheReadTokens: setCacheReadTokens,
    cacheCreateTokens: setCacheCreateTokens,
    cacheCreate1hTokens: setCacheCreate1hTokens,
    imageTokens: setImageTokens,
    audioInputTokens: setAudioInputTokens,
    audioOutputTokens: setAudioOutputTokens,
  };
  const values = {
    cacheReadTokens,
    cacheCreateTokens,
    cacheCreate1hTokens,
    imageTokens,
    audioInputTokens,
    audioOutputTokens,
  };

  const usesExtra = useMemo(() => {
    if (!effectiveExpr) return false;
    return /\b(cr|cc1h|cc|img|ai|ao)\b/.test(effectiveExpr);
  }, [effectiveExpr]);

  if (!usesExtra) return null;

  return (
    <div
      style={{
        display: 'grid',
        gridTemplateColumns: '1fr 1fr',
        gap: 12,
        marginBottom: 12,
      }}
    >
      {EXTRA_ESTIMATOR_FIELDS.map((cf) => (
        <div key={cf.var}>
          <Text size='small' className='mb-1' style={{ display: 'block' }}>
            {t(cf.labelKey)}
          </Text>
          <InputNumber
            value={values[cf.stateKey]}
            min={0}
            onChange={(val) => setters[cf.stateKey](val ?? 0)}
            style={{ width: '100%' }}
          />
        </div>
      ))}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Cost estimator (works with any Expr string)
// ---------------------------------------------------------------------------

function evalExprLocally(exprStr, p, c, cr, cc, cc1h, img, ai, ao) {
  try {
    let matchedTier = '';
    const tierFn = (name, value) => {
      matchedTier = name;
      return value;
    };
    const env = {
      p,
      c,
      cr: cr || 0,
      cc: cc || 0,
      cc1h: cc1h || 0,
      img: img || 0,
      ai: ai || 0,
      ao: ao || 0,
      prompt_tokens: p,
      completion_tokens: c,
      cache_read_tokens: cr || 0,
      cache_create_tokens: cc || 0,
      cache_create_1h_tokens: cc1h || 0,
      image_tokens: img || 0,
      audio_input_tokens: ai || 0,
      audio_output_tokens: ao || 0,
      tier: tierFn,
      max: Math.max,
      min: Math.min,
      abs: Math.abs,
      ceil: Math.ceil,
      floor: Math.floor,
    };
    const fn = new Function(
      ...Object.keys(env),
      `"use strict"; return (${exprStr});`,
    );
    return { cost: fn(...Object.values(env)), matchedTier, error: null };
  } catch (e) {
    return { cost: 0, matchedTier: '', error: e.message };
  }
}

// ---------------------------------------------------------------------------
// Request condition rule row (moved from RequestMultiplierEditor)
// ---------------------------------------------------------------------------

const TIME_FUNC_LABELS = {
  hour: '小时',
  minute: '分钟',
  weekday: '星期',
  month: '月份',
  day: '日期',
};

const TIME_FUNC_HINTS = {
  hour: '0~23',
  minute: '0~59',
  weekday: '0=周日 1=周一 2=周二 3=周三 4=周四 5=周五 6=周六',
  month: '1=一月 ... 12=十二月',
  day: '1~31',
};

const TIME_FUNC_PLACEHOLDERS = {
  hour: '0-23',
  minute: '0-59',
  weekday: '0-6',
  month: '1-12',
  day: '1-31',
};

function RuleConditionRow({ cond, onChange, onRemove, t }) {
  const normalized = normalizeCondition(cond);
  const isTime = normalized.source === SOURCE_TIME;
  const matchOptions = getRequestRuleMatchOptions(normalized.source, t);

  const sourceSelect = (
    <Select
      size='small'
      value={normalized.source}
      onChange={(value) => {
        if (value === SOURCE_TIME) {
          onChange(normalizeCondition({ source: SOURCE_TIME, timeFunc: 'hour', timezone: 'Asia/Shanghai', mode: MATCH_GTE }));
        } else {
          onChange(normalizeCondition({ source: value, path: '', mode: MATCH_EQ }));
        }
      }}
      style={{ width: 110 }}
    >
      <Select.Option value={SOURCE_PARAM}>{t('请求参数')}</Select.Option>
      <Select.Option value={SOURCE_HEADER}>{t('请求头')}</Select.Option>
      <Select.Option value={SOURCE_TIME}>{t('时间条件')}</Select.Option>
    </Select>
  );

  const removeBtn = (
    <Button icon={<IconDelete />} type='danger' theme='borderless' size='small' onClick={onRemove} />
  );

  if (isTime) {
    const isRange = normalized.mode === MATCH_RANGE;
    const ph = TIME_FUNC_PLACEHOLDERS[normalized.timeFunc] || '';
    const hint = TIME_FUNC_HINTS[normalized.timeFunc] || '';
    return (
      <div style={{
        marginBottom: 8,
        padding: '8px 10px',
        borderRadius: 6,
        background: 'var(--semi-color-fill-0)',
        display: 'flex',
        flexDirection: 'column',
        gap: 6,
      }}>
        <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
          {sourceSelect}
          <Select
            size='small'
            value={normalized.timeFunc}
            onChange={(value) => onChange({ ...normalized, timeFunc: value })}
            style={{ flex: 1 }}
          >
            {TIME_FUNCS.map((fn) => (
              <Select.Option key={fn} value={fn}>{t(TIME_FUNC_LABELS[fn] || fn)}</Select.Option>
            ))}
          </Select>
          {removeBtn}
        </div>
        <Select
          size='small'
          value={normalized.timezone}
          onChange={(value) => onChange({ ...normalized, timezone: value })}
          filter
          allowCreate
          placeholder={t('时区')}
        >
          {COMMON_TIMEZONES.map((tz) => (
            <Select.Option key={tz.value} value={tz.value}>{tz.label}</Select.Option>
          ))}
        </Select>
        <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
          <Select
            size='small'
            value={normalized.mode}
            onChange={(value) => onChange(normalizeCondition({ ...normalized, mode: value }))}
            style={{ flex: 1 }}
          >
            {matchOptions.map((item) => (
              <Select.Option key={item.value} value={item.value}>{item.label}</Select.Option>
            ))}
          </Select>
          {isRange ? (
            <div style={{ display: 'flex', gap: 4, alignItems: 'center', flex: 1 }}>
              <Input size='small' value={normalized.rangeStart} placeholder={ph} style={{ flex: 1 }} onChange={(value) => onChange({ ...normalized, rangeStart: value })} />
              <span>~</span>
              <Input size='small' value={normalized.rangeEnd} placeholder={ph} style={{ flex: 1 }} onChange={(value) => onChange({ ...normalized, rangeEnd: value })} />
            </div>
          ) : (
            <Input size='small' value={normalized.value} placeholder={ph} style={{ flex: 1 }} onChange={(value) => onChange({ ...normalized, value })} />
          )}
        </div>
        {hint && (
          <Text size='small' style={{ color: 'var(--semi-color-text-3)' }}>
            {t(hint)}
          </Text>
        )}
      </div>
    );
  }

  const showValue = normalized.mode !== MATCH_EXISTS;
  return (
    <div style={{
      marginBottom: 8,
      padding: '8px 10px',
      borderRadius: 6,
      background: 'var(--semi-color-fill-0)',
      display: 'grid',
      gridTemplateColumns: '1fr 1fr auto',
      gap: '6px 8px',
    }}>
      {sourceSelect}
      <Input
        size='small'
        value={normalized.path}
        placeholder={normalized.source === SOURCE_HEADER ? t('例如 anthropic-beta') : t('例如 service_tier')}
        onChange={(value) => onChange({ ...normalized, path: value })}
      />
      {removeBtn}
      <Select
        size='small'
        value={normalized.mode}
        onChange={(value) => onChange(normalizeCondition({ ...normalized, mode: value, value: value === MATCH_EXISTS ? '' : normalized.value }))}
      >
        {matchOptions.map((item) => (
          <Select.Option key={item.value} value={item.value}>{item.label}</Select.Option>
        ))}
      </Select>
      <Input
        size='small'
        value={normalized.value}
        placeholder={normalized.mode === MATCH_CONTAINS ? t('匹配内容') : normalized.mode === MATCH_EXISTS ? '' : t('匹配值')}
        disabled={!showValue}
        onChange={(value) => onChange({ ...normalized, value })}
      />
      <div />
    </div>
  );
}

function RuleGroupCard({ group, index, onChange, onRemove, t }) {
  const conditions = group.conditions || [];

  const updateCondition = (ci, newCond) => {
    const next = conditions.map((c, i) => (i === ci ? newCond : c));
    onChange({ ...group, conditions: next });
  };
  const removeCondition = (ci) => {
    const next = conditions.filter((_, i) => i !== ci);
    onChange({ ...group, conditions: next.length > 0 ? next : [createEmptyCondition()] });
  };
  const addCondition = (cond) => {
    onChange({ ...group, conditions: [...conditions, cond] });
  };

  return (
    <div
      style={{
        padding: '12px 16px',
        borderRadius: 8,
        border: '1px solid var(--semi-color-border)',
        background: 'var(--semi-color-bg-2)',
        marginBottom: 8,
      }}
    >
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
        <Tag color='blue' size='small'>
          {t('第 {{n}} 组', { n: index + 1 })}
        </Tag>
        <Button icon={<IconDelete />} type='danger' theme='borderless' size='small' onClick={onRemove} />
      </div>

      <div style={{ marginBottom: 8 }}>
        <Text size='small' style={{ color: 'var(--semi-color-text-2)', display: 'block', marginBottom: 4 }}>
          {t('条件')}{conditions.length > 1 ? ` (${t('同时满足')})` : ''}
        </Text>
        {conditions.map((cond, ci) => (
          <RuleConditionRow
            key={ci}
            cond={cond}
            onChange={(nc) => updateCondition(ci, nc)}
            onRemove={() => removeCondition(ci)}
            t={t}
          />
        ))}
        <div style={{ display: 'flex', gap: 6 }}>
          <Button icon={<IconPlus />} size='small' theme='borderless' onClick={() => addCondition(createEmptyCondition())}>
            {t('添加条件')}
          </Button>
          <Button icon={<IconPlus />} size='small' theme='borderless' onClick={() => addCondition(createEmptyTimeCondition())}>
            {t('添加时间条件')}
          </Button>
        </div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <Text size='small' style={{ color: 'var(--semi-color-text-2)', whiteSpace: 'nowrap' }}>
          {t('倍率')}
        </Text>
        <Input
          size='small'
          value={group.multiplier || ''}
          placeholder={t('例如 0.5 或 2')}
          suffix='x'
          onChange={(value) => onChange({ ...group, multiplier: value })}
          style={{ width: 160 }}
        />
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Main component
// ---------------------------------------------------------------------------

export default function TieredPricingEditor({ model, onExprChange, requestRuleExpr, onRequestRuleExprChange, t }) {
  const currentExpr = model?.billingExpr || '';

  const [editorMode, setEditorMode] = useState('visual');
  const [visualConfig, setVisualConfig] = useState(null);
  const [rawExpr, setRawExpr] = useState('');
  const [promptTokens, setPromptTokens] = useState(200000);
  const [completionTokens, setCompletionTokens] = useState(10000);
  const [cacheReadTokens, setCacheReadTokens] = useState(0);
  const [cacheCreateTokens, setCacheCreateTokens] = useState(0);
  const [cacheCreate1hTokens, setCacheCreate1hTokens] = useState(0);
  const [imageTokens, setImageTokens] = useState(0);
  const [audioInputTokens, setAudioInputTokens] = useState(0);
  const [audioOutputTokens, setAudioOutputTokens] = useState(0);

  const currentRequestRuleExpr = requestRuleExpr || '';
  const parsedRequestRuleGroups = useMemo(
    () => tryParseRequestRuleExpr(currentRequestRuleExpr),
    [currentRequestRuleExpr],
  );
  const canUseVisualRules = parsedRequestRuleGroups !== null;
  const [requestRuleGroups, setRequestRuleGroups] = useState(parsedRequestRuleGroups || []);

  useEffect(() => {
    if (parsedRequestRuleGroups) {
      setRequestRuleGroups(parsedRequestRuleGroups);
    } else {
      setRequestRuleGroups([]);
    }
  }, [currentRequestRuleExpr, parsedRequestRuleGroups]);

  const handleRequestRuleGroupsChange = useCallback((nextGroups) => {
    setRequestRuleGroups(nextGroups);
    onRequestRuleExprChange(buildRequestRuleExpr(nextGroups));
  }, [onRequestRuleExprChange]);

  useEffect(() => {
    const parsed = tryParseVisualConfig(currentExpr);
    if (parsed) {
      setEditorMode('visual');
      setVisualConfig(parsed);
      setRawExpr(currentExpr);
    } else if (currentExpr) {
      setEditorMode('raw');
      setRawExpr(currentExpr);
      setVisualConfig(null);
    } else {
      setEditorMode('visual');
      setVisualConfig(createDefaultVisualConfig());
      setRawExpr('');
    }
  }, [model?.name]);

  const effectiveExpr = useMemo(() => {
    if (editorMode === 'visual') {
      return generateExprFromVisualConfig(visualConfig);
    }
    const { billingExpr } = splitBillingExprAndRequestRules(rawExpr);
    return billingExpr;
  }, [editorMode, visualConfig, rawExpr]);

  useEffect(() => {
    if (effectiveExpr !== currentExpr) {
      onExprChange(effectiveExpr);
    }
  }, [effectiveExpr]);

  const handleVisualChange = useCallback((newConfig) => {
    setVisualConfig(newConfig);
  }, []);

  const handleRawChange = useCallback((val) => {
    setRawExpr(val);
    const { requestRuleExpr: ruleStr } = splitBillingExprAndRequestRules(val);
    onRequestRuleExprChange(ruleStr);
  }, [onRequestRuleExprChange]);

  const handleModeSwitch = useCallback(
    (e) => {
      const newMode = e.target.value;
      if (newMode === 'visual') {
        const { billingExpr, requestRuleExpr: ruleStr } = splitBillingExprAndRequestRules(rawExpr);
        const parsed = tryParseVisualConfig(billingExpr);
        if (parsed) {
          setVisualConfig(parsed);
        } else {
          setVisualConfig(createDefaultVisualConfig());
        }
        const parsedGroups = tryParseRequestRuleExpr(ruleStr);
        setRequestRuleGroups(parsedGroups || []);
        onRequestRuleExprChange(ruleStr);
      } else {
        const expr = generateExprFromVisualConfig(visualConfig);
        const ruleExpr = buildRequestRuleExpr(requestRuleGroups);
        setRawExpr(combineBillingExpr(expr, ruleExpr) || expr);
      }
      setEditorMode(newMode);
    },
    [rawExpr, visualConfig, requestRuleGroups, onRequestRuleExprChange],
  );

  const applyPreset = useCallback(
    (preset) => {
      const presetGroups = preset.requestRules || [];
      const ruleExpr = buildRequestRuleExpr(presetGroups);
      const combined = combineBillingExpr(preset.expr, ruleExpr) || preset.expr;
      setRawExpr(combined);
      const parsed = tryParseVisualConfig(preset.expr);
      if (parsed) {
        setVisualConfig(parsed);
      } else {
        setEditorMode('raw');
        setVisualConfig(null);
      }
      setRequestRuleGroups(presetGroups);
      onRequestRuleExprChange(ruleExpr);
    },
    [onRequestRuleExprChange],
  );

  const evalResult = useMemo(
    () => evalExprLocally(
      effectiveExpr, promptTokens, completionTokens,
      cacheReadTokens, cacheCreateTokens, cacheCreate1hTokens,
      imageTokens, audioInputTokens, audioOutputTokens,
    ),
    [effectiveExpr, promptTokens, completionTokens,
      cacheReadTokens, cacheCreateTokens, cacheCreate1hTokens,
      imageTokens, audioInputTokens, audioOutputTokens],
  );

  return (
    <div>
      <div style={{ marginBottom: 12 }}>
        <RadioGroup
          type='button'
          size='small'
          value={editorMode}
          onChange={handleModeSwitch}
        >
          <Radio value='visual'>{t('可视化编辑')}</Radio>
          <Radio value='raw'>{t('表达式编辑')}</Radio>
        </RadioGroup>
      </div>

      <PresetSection applyPreset={applyPreset} t={t} />

      <Card
        bodyStyle={{ padding: 16 }}
        style={{ marginBottom: 12, background: 'var(--semi-color-fill-0)' }}
      >
        {editorMode === 'visual' ? (
          <VisualEditor
            visualConfig={visualConfig}
            onChange={handleVisualChange}
            t={t}
          />
        ) : (
          <RawExprEditor exprString={rawExpr} onChange={handleRawChange} t={t} />
        )}

        {editorMode === 'visual' && (
          <>
            <div style={{ borderTop: '1px solid var(--semi-color-border)', margin: '16px 0' }} />

            <div className='font-medium mb-2'>{t('请求条件调价')}</div>
            <div style={{ marginBottom: 12 }}>
              <Text type='secondary' size='small'>
                {t('满足条件时，整单价格乘以 X；如果有多条同时命中，会继续相乘。')}
              </Text>
              <div style={{ marginTop: 2 }}>
                <Text type='secondary' size='small'>
                  {t('X 也可以小于 1，当折扣用。想做"只给输出加价"或"额外加固定费用"，请直接写完整计费公式。')}
                </Text>
              </div>
            </div>

            {currentRequestRuleExpr && !canUseVisualRules ? (
              <Banner
                type='warning'
                bordered
                fullMode={false}
                closeIcon={null}
                style={{ marginBottom: 12 }}
                title={t('这个公式比较复杂，下面的简化表单没法完整还原，请在表达式编辑模式下修改。')}
              />
            ) : (
              <>
                {requestRuleGroups.map((group, gi) => (
                  <RuleGroupCard
                    key={`rule-group-${gi}`}
                    group={group}
                    index={gi}
                    t={t}
                    onChange={(nextGroup) => {
                      const next = [...requestRuleGroups];
                      next[gi] = nextGroup;
                      handleRequestRuleGroupsChange(next);
                    }}
                    onRemove={() => {
                      handleRequestRuleGroupsChange(requestRuleGroups.filter((_, i) => i !== gi));
                    }}
                  />
                ))}
                <Button
                  icon={<IconPlus />}
                  size='small'
                  theme='light'
                  onClick={() => handleRequestRuleGroupsChange([...requestRuleGroups, createEmptyRuleGroup()])}
                  style={{ marginTop: 4 }}
                >
                  {t('添加条件组')}
                </Button>
              </>
            )}
          </>
        )}
      </Card>

      <Card
        bodyStyle={{ padding: 16 }}
        style={{ marginBottom: 12, background: 'var(--semi-color-fill-0)' }}
      >
        <div className='font-medium mb-2'>{t('Token 估算器')}</div>
        <div className='text-xs text-gray-500 mb-3'>
          {t('输入 Token 数量，查看按当前配置的预计费用（不含分组倍率）。')}
        </div>
        <div
          style={{
            display: 'grid',
            gridTemplateColumns: '1fr 1fr',
            gap: 12,
            marginBottom: 12,
          }}
        >
          <div>
            <Text size='small' className='mb-1' style={{ display: 'block' }}>
              {t('输入 Token 数')} (p)
            </Text>
            <InputNumber
              value={promptTokens}
              min={0}
              onChange={(val) => setPromptTokens(val ?? 0)}
              style={{ width: '100%' }}
            />
          </div>
          <div>
            <Text size='small' className='mb-1' style={{ display: 'block' }}>
              {t('输出 Token 数')} (c)
            </Text>
            <InputNumber
              value={completionTokens}
              min={0}
              onChange={(val) => setCompletionTokens(val ?? 0)}
              style={{ width: '100%' }}
            />
          </div>
        </div>
        {/* Cache token inputs — shown when expression uses cache variables */}
        <CacheTokenEstimatorInputs
          effectiveExpr={effectiveExpr}
          cacheReadTokens={cacheReadTokens}
          setCacheReadTokens={setCacheReadTokens}
          cacheCreateTokens={cacheCreateTokens}
          setCacheCreateTokens={setCacheCreateTokens}
          cacheCreate1hTokens={cacheCreate1hTokens}
          setCacheCreate1hTokens={setCacheCreate1hTokens}
          imageTokens={imageTokens}
          setImageTokens={setImageTokens}
          audioInputTokens={audioInputTokens}
          setAudioInputTokens={setAudioInputTokens}
          audioOutputTokens={audioOutputTokens}
          setAudioOutputTokens={setAudioOutputTokens}
          t={t}
        />
        <div
          style={{
            padding: '10px 14px',
            borderRadius: 8,
            background: evalResult.error
              ? 'var(--semi-color-danger-light-default)'
              : 'var(--semi-color-primary-light-default)',
            border: `1px solid ${evalResult.error ? 'var(--semi-color-danger)' : 'var(--semi-color-primary)'}`,
          }}
        >
          {evalResult.error ? (
            <Text type='danger'>
              {t('表达式错误')}: {evalResult.error}
            </Text>
          ) : (
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <Text strong style={{ fontSize: 15 }}>
                  {t('预计费用')}：{renderQuota(evalResult.cost, 4)}
                </Text>
                {evalResult.matchedTier && (
                  <Tag size='small' color='blue' type='light'>
                    {t('命中档位')}：{evalResult.matchedTier}
                  </Tag>
                )}
              </div>
              <Text
                size='small'
                style={{
                  display: 'block',
                  marginTop: 2,
                  color: 'var(--semi-color-text-3)',
                }}
              >
                {t('原始额度')}：{evalResult.cost.toLocaleString()}
              </Text>
            </div>
          )}
        </div>
      </Card>

    </div>
  );
}
