import './index.css';
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { localeManager, t } from '../../services/locale';
import { themeManager } from '../../services/theme';
import type { MeasureConfig, MeasureMode, MeasurePanelOptions, MeasurePrecision, MeasureResult, MeasureUnit } from './types';

/**
 * 测量方式图标（SVG）
 *
 * 说明：
 * - 你上传的 SVG 原文件放在 `src/assets/icons/` 目录
 * - 原始 SVG 含 defs/clipPath/style/背景 rect，直接内联时容易出现渲染/裁剪异常（尤其多个图标同时出现）
 * - 这里把图标“瘦身”为纯 path，并统一使用 currentColor，确保稳定渲染
 */
const MEASURE_MODE_ICON_SVGS: Record<MeasureMode, string> = {
  distance: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(0 4.197)"><path fill="currentColor" d="M29.692,3.03,27.55.919a.529.529,0,0,1-.014-.756A.549.549,0,0,1,28.3.15l.014.013,3.067,3.023a.529.529,0,0,1,0,.756L28.317,6.966a.549.549,0,0,1-.767.013.529.529,0,0,1-.014-.756l.014-.013L29.692,4.1H2.31L4.452,6.21a.528.528,0,0,1,.013.756.547.547,0,0,1-.766.013l-.014-.013L.616,3.942a.531.531,0,0,1,0-.756L3.685.163a.548.548,0,0,1,.767.014.528.528,0,0,1,0,.742L2.31,3.03ZM24.136,15.055H23.051V18H21.966v-2.94H20.882V18H19.8v-2.94H18.712V18H17.627v-2.94H16.543v5.078H15.458V15.055H14.373V18H13.288v-2.94H12.2V18H11.119v-2.94H10.034V18H8.949v-2.94H7.865V18H6.78v-2.94H5.7v5.078H4.61V15.055H1.9a.27.27,0,0,0-.272.268v6.413A.269.269,0,0,0,1.9,22H30.1a.268.268,0,0,0,.271-.267V15.323a.269.269,0,0,0-.271-.268H27.39v5.078H26.305V15.055H25.221V18H24.136Zm5.966-1.6A1.884,1.884,0,0,1,32,15.323v6.413a1.885,1.885,0,0,1-1.9,1.871H1.9A1.885,1.885,0,0,1,0,21.736V15.323a1.885,1.885,0,0,1,1.9-1.871Z"/></g></svg>`,
  minDistance: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M-5.839,24.8H-34.16A1.875,1.875,0,0,1-36,22.933V16.52a1.887,1.887,0,0,1,1.9-1.871H-5.9A1.887,1.887,0,0,1-4,16.52v6.412A1.875,1.875,0,0,1-5.839,24.8ZM-34.1,16.252a.27.27,0,0,0-.272.268v6.412a.27.27,0,0,0,.272.267H-5.9a.269.269,0,0,0,.271-.267V16.52a.27.27,0,0,0-.271-.268H-8.61V21.33H-9.695V16.252h-1.085v2.939h-1.085V16.252h-1.085v2.939h-1.085V16.252h-1.084v2.939H-16.2V16.252h-1.085v2.939h-1.085V16.252h-1.084V21.33h-1.084V16.252h-1.085v2.939h-1.085V16.252H-23.8v2.939h-1.085V16.252h-1.085v2.939h-1.085V16.252h-1.084v2.939H-29.22V16.252H-30.3V21.33H-31.39V16.252Z" transform="translate(36 2)"/><path fill="currentColor" d="M23.716,7.947V4.875c0-.8-.232-1.085-.765-1.085a1.573,1.573,0,0,0-1.133.585V7.947H20.4V2.75h1.163l.1.687H21.7a2.547,2.547,0,0,1,1.763-.817c1.172,0,1.676.78,1.676,2.089V7.947Zm-7.26,0V2.62h1.58V7.947Zm-3.8,0V4.875c0-.8-.243-1.085-.76-1.085a1.606,1.606,0,0,0-1.049.585V7.947H9.421V4.875c0-.8-.243-1.085-.758-1.085a1.608,1.608,0,0,0-1.05.585V7.947H6.194V2.75H7.36l.1.7H7.5A2.326,2.326,0,0,1,9.169,2.62a1.486,1.486,0,0,1,1.5.91A2.445,2.445,0,0,1,12.4,2.62c1.156,0,1.691.78,1.691,2.089V7.947Zm3.8-6.849a.79.79,0,0,1,1.58,0,.79.79,0,0,1-1.58,0Z" transform="translate(0.333 3.053)"/></svg>`,
  angle: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M39.587,50.766h13.7a1,1,0,0,1,0,2H23.171a1,1,0,0,1,0-2h1.418l6.582-7.006v-.006a.517.517,0,0,1,.14-.357.456.456,0,0,1,.337-.144l12.1-12.876a.451.451,0,0,1,.665,0,.524.524,0,0,1,0,.708L32.883,43.355a8.3,8.3,0,0,1,6.7,7.411Zm-.949,0a7.254,7.254,0,0,0-6.611-6.5l-6.108,6.5Z" transform="translate(-22.229 -26.489)"/></svg>`,
  elevation: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M84.131,193.119a1.056,1.056,0,0,1,1.116.982v7.857a1.056,1.056,0,0,1-1.116.982H54.367a1.056,1.056,0,0,1-1.116-.982V194.1a1.056,1.056,0,0,1,1.116-.982Zm-1.116,1.964H55.483v5.893H83.015Zm1.116-13.749a1.064,1.064,0,0,1,1.114.935,1.032,1.032,0,0,1-1.007,1.025l-.107,0H71.2l-7.858,6.914a1.227,1.227,0,0,1-1.578,0l-8.185-7.2-.018-.016-.032-.031.049.047a1.107,1.107,0,0,1-.092-.092l-.011-.014a.869.869,0,0,1-.182-.857l0-.008L53.31,182l.012-.029.02-.045.019-.035a1.1,1.1,0,0,1,.891-.552h.007q.053,0,.107,0ZM68.043,183.3H57.06l5.492,4.831Z" transform="translate(-53.247 -176.136)"/></svg>`,
  volume: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M94.74,86.658V71.189a.371.371,0,0,1,.2-.329l13.869-7.22a.371.371,0,0,1,.344,0l13.053,6.891h0l.819.431a.371.371,0,0,1,.2.328v15.3a.371.371,0,0,1-.2.328l-13.872,7.255a.371.371,0,0,1-.342,0L94.94,86.987a.371.371,0,0,1-.2-.329Zm2.119-.837,11.2,5.8a.024.024,0,0,0,.035-.022V79.483a.371.371,0,0,0-.2-.328l-11.2-5.909a.024.024,0,0,0-.035.021V85.492A.371.371,0,0,0,96.859,85.821Zm13.151-6.459v12a.12.12,0,0,0,.176.106L114,89.474l3.334-1.745,3.771-1.978a.371.371,0,0,0,.2-.328V73.5a.193.193,0,0,0-.284-.171l-10.812,5.708A.371.371,0,0,0,110.01,79.362ZM97.925,71.725l10.839,5.72a.371.371,0,0,0,.346,0L119.8,71.808a.214.214,0,0,0,0-.378l-10.649-5.621a.371.371,0,0,0-.344,0L97.925,71.47A.144.144,0,0,0,97.925,71.725Z" transform="translate(-92.982 -62.907)"/></svg>`,
  laserDistance: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(0 -1.293)"><path fill="currentColor" d="M0,1.293v31.96H32V1.293ZM30.97,32.182H1.03V2.323H30.97Z"/><path fill="currentColor" d="M160.026,291.9l1.6,1.6,7.305-7.305-7.305-7.305-1.6,1.6,4.794,4.566h-6.392v2.283h6.392Zm-5.251,0-4.566-4.566h6.164v-2.283H150.21l4.566-4.566-1.37-1.6L146.1,286.19l7.305,7.305Z" transform="translate(-141.535 -268.917)"/></g></svg>`,
  slope: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M202.1,188.337l2.629-2.191-8.447-3.106,1.533,8.871,2.629-2.194,9.341,11.209,1.656-1.379Zm-13.726-.435a1.075,1.075,0,0,0-1.07-.341,1.057,1.057,0,0,0-.5.277l-5.11,4.08a1.08,1.08,0,0,0-.406.84l-.007,17.386a1.079,1.079,0,0,0,1.077,1.077L205.7,211.2a1.078,1.078,0,0,0,.822-1.774Zm-4.934,21.164.007-15.788,3.968-3.171,15.974,18.941Z" transform="translate(-180.36 -181.131)"/></svg>`,
  spaceVolume: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(-106.35 -97.661)"><path fill="currentColor" d="M125.977,128.829l13.076-7.363v-13.6l-13.076,6.8Zm-3.126-15.655a.565.565,0,0,1-.258-.064L109.3,106.323a.567.567,0,0,1-.011-1L122.578,98a.567.567,0,0,1,.55,0l13.288,7.325a.567.567,0,0,1-.011,1l-13.292,6.79A.63.63,0,0,1,122.851,113.174ZM110.773,105.8l12.078,6.172,12.078-6.172-12.078-6.657Z" transform="translate(-1.922)"/><path fill="currentColor" d="M120.649,322.52a.58.58,0,0,1-.262-.064l-13.08-6.8a.573.573,0,0,1-.307-.5V301a.566.566,0,0,1,.273-.486.573.573,0,0,1,.558-.019l13.076,6.8a.573.573,0,0,1,.307.5v14.161a.57.57,0,0,1-.565.569Zm-12.511-7.708,11.942,6.206V308.136l-11.942-6.206Zm15.917,9.408a.585.585,0,0,1-.288-.076.567.567,0,0,1-.281-.489V309.49a.562.562,0,0,1,.307-.5l13.076-6.8a.573.573,0,0,1,.558.019.562.562,0,0,1,.273.486v13.6a.568.568,0,0,1-.288.493l-13.076,7.359A.557.557,0,0,1,124.055,324.22Zm.569-14.385V322.68l11.942-6.722V303.629Z" transform="translate(0 -194.822)"/></g></svg>`
};

/**
 * 测量面板组件（只做 UI，不实现真实测量）
 *
 * 组件职责：
 * - 展示 8 种测量方式按钮（默认 4 个，可展开/收起）
 * - 维护当前选中的测量方式（current mode）
 * - 展示测量结果（由外部 setResult 注入）
 * - 提供 “删除全部 / 设置” 的 UI 与对外方法（暂不实现真实逻辑，仅回调/占位）
 *
 * 注意：
 * - 所有用户可见文本必须通过 t(key) 获取（国际化强制要求）
 * - 组件需要订阅主题/语言变更，并在 destroy 时清理订阅
 */
export class MeasurePanel implements IBimComponent {
  public element: HTMLElement;

  private options: MeasurePanelOptions;
  private activeMode: MeasureMode;
  private isExpanded: boolean;
  private result: MeasureResult | null = null;

  /**
   * 测量配置（单位/精度）
   * 说明：
   * - 你要求：创建 MeasurePanel 不传入单位和精度
   * - 默认值维护在组件内部
   * - 初始化时优先读取缓存（localStorage），否则使用默认值
   */
  private config: MeasureConfig;

  /** 设置面板的临时配置（用于“取消”回滚） */
  private draftConfig: MeasureConfig | null = null;

  /** 当前视图：主面板 / 设置面板 */
  private view: 'main' | 'settings' = 'main';

  /** 缓存 key（默认全局） */
  private static readonly CONFIG_CACHE_KEY = 'bim-engine:measure:config';

  /** 默认配置（由组件内部维护） */
  private static readonly DEFAULT_CONFIG: MeasureConfig = {
    unit: 'mm',
    precision: 2
  };

  // DOM 引用（便于局部更新，减少频繁 querySelector）
  private toolButtons: Map<MeasureMode, HTMLButtonElement> = new Map();
  private toggleBtn!: HTMLButtonElement;
  private toggleTextEl!: HTMLElement;
  private mainValueValueEl!: HTMLElement;
  private mainValueLabelEl!: HTMLElement;
  private mainNumberEl!: HTMLElement;
  private mainUnitEl!: HTMLElement;
  private xyzBoxEl!: HTMLElement;
  private xyzXEl!: HTMLElement;
  private xyzYEl!: HTMLElement;
  private xyzZEl!: HTMLElement;
  private clearBtn!: HTMLButtonElement;
  private settingsBtn!: HTMLButtonElement;

  // Settings DOM
  private mainViewEl!: HTMLElement;
  private settingsViewEl!: HTMLElement;
  private unitSelectEl!: HTMLSelectElement;
  private precisionSelectEl!: HTMLSelectElement;
  private saveSettingsBtn!: HTMLButtonElement;
  private cancelSettingsBtn!: HTMLButtonElement;

  // 订阅清理
  private unsubscribeLocale: (() => void) | null = null;
  private unsubscribeTheme: (() => void) | null = null;

  /**
   * 构造函数
   * @param options 组件配置
   */
  constructor(options: MeasurePanelOptions = {}) {
    this.options = options;
    this.activeMode = options.defaultMode ?? 'distance';
    this.isExpanded = options.defaultExpanded ?? false;

    // 读取配置：优先缓存，否则默认
    this.config = this.loadConfigFromCache() ?? { ...MeasurePanel.DEFAULT_CONFIG };

    this.element = this.createDom();
  }

  /**
   * 初始化组件（实现 IBimComponent）
   */
  public init(): void {
    // 订阅语言变更：更新所有文本/提示
    this.unsubscribeLocale = localeManager.subscribe(() => {
      this.setLocales();
    });

    // 订阅主题变更：更新 CSS 变量（如需要）
    this.unsubscribeTheme = themeManager.subscribe((theme) => {
      this.setTheme(theme);
    });

    // 初始应用
    this.setLocales();
    this.setTheme(themeManager.getTheme());

    // 初始渲染状态（按钮显隐、选中态、结果区）
    this.applyExpandedState();
    this.applyActiveModeState();
    this.applyViewState();
    this.renderResult();
  }

  /**
   * 设置主题（实现 IBimComponent）
   * @param theme 主题配置
   */
  public setTheme(theme: ThemeConfig): void {
    // 为了可读性：这里显式写出映射，不做过度抽象
    const style = this.element.style;

    // 这些变量不会强制覆盖外部（Dialog）已有变量，只做兜底
    style.setProperty('--bim-measure-border', theme.border ?? 'rgba(255, 255, 255, 0.12)');
    style.setProperty('--bim-measure-divider', theme.border ?? 'rgba(255, 255, 255, 0.10)');
    style.setProperty('--bim-measure-icon-color', theme.icon ?? '#ddd');
    style.setProperty('--bim-measure-label-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.70)');
    style.setProperty('--bim-measure-value-color', theme.textPrimary ?? 'rgba(255, 255, 255, 0.90)');

    // “删除全部”颜色：截图中偏绿色，这里用 primary 做一个合理映射
    style.setProperty('--bim-measure-danger', theme.primary ?? '#46d369');
    // 设置面板“保存设置”按钮用主题色
    style.setProperty('--bim-measure-primary', theme.primary ?? '#0078d4');
    style.setProperty('--bim-measure-primary-hover', theme.primaryHover ?? '#0063b1');
    style.setProperty('--bim-measure-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
    style.setProperty('--bim-measure-btn-hover-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
    style.setProperty('--bim-measure-btn-active-bg', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
  }

  /**
   * 设置语言（实现 IBimComponent）
   */
  public setLocales(): void {
    // 1) 更新按钮 tooltip（图标占位时，tooltip 是主要的可读文本）
    for (const [mode, btn] of this.toolButtons.entries()) {
      btn.title = t(this.getModeI18nKey(mode));
      btn.setAttribute('aria-label', btn.title);
    }

    // 2) 更新展开/收起按钮 tooltip
    this.toggleBtn.title = this.isExpanded ? t('measure.actions.collapse') : t('measure.actions.expand');
    this.toggleBtn.setAttribute('aria-label', this.toggleBtn.title);

    // 2.1) 更新展开/收起按钮可见文本（你要求的“文字提示”）
    if (this.toggleTextEl) {
      this.toggleTextEl.textContent = this.toggleBtn.title;
    }

    // 3) 更新底部按钮文本/tooltip
    this.clearBtn.textContent = t('measure.actions.clearAll');
    this.settingsBtn.title = t('measure.actions.settings');
    this.settingsBtn.setAttribute('aria-label', this.settingsBtn.title);

    // 4) 主值 label（随模式变化）
    this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));

    // 6) XYZ label（使用 key）
    // 这里 label 在 createDom 已经是固定文本节点，直接用 setText 更新更直观
    // 但为了减少 DOM 结构复杂度，我们把 label 写在 createDom 里，通过 data-key 更新
    const labelNodes = this.element.querySelectorAll<HTMLElement>('[data-i18n-key]');
    labelNodes.forEach((node) => {
      const key = node.dataset.i18nKey;
      if (key) node.textContent = t(key);
    });

    // 7) 设置面板文本
    this.saveSettingsBtn.textContent = t('measure.settings.save');
    this.cancelSettingsBtn.textContent = t('measure.settings.cancel');
  }

  /**
   * 销毁组件（实现 IBimComponent）
   */
  public destroy(): void {
    // 清理订阅
    if (this.unsubscribeLocale) {
      this.unsubscribeLocale();
      this.unsubscribeLocale = null;
    }
    if (this.unsubscribeTheme) {
      this.unsubscribeTheme();
      this.unsubscribeTheme = null;
    }

    // 清理事件监听：由于本组件的监听都绑定在创建时的具体按钮上，
    // 且按钮会随 element 一起被 GC，这里不做逐个 removeEventListener（可读性优先）

    // 移除 DOM
    this.element.remove();
  }

  // ==========================
  // 对外 API（给 Manager / 外部业务调用）
  // ==========================

  /**
   * 获取当前测量方式
   */
  public getActiveMode(): MeasureMode {
    return this.activeMode;
  }

  /**
   * 切换测量方式（你要求的“切换类型的方法”）
   * @param mode 目标测量方式
   */
  public switchMode(mode: MeasureMode): void {
    this.setActiveMode(mode);
  }

  /**
   * 设置当前测量方式
   * @param mode 目标测量方式
   */
  public setActiveMode(mode: MeasureMode): void {
    if (this.activeMode === mode) return;
    this.activeMode = mode;
    this.applyActiveModeState();

    // 切换方式后，主值 label 也需要更新
    this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));

    // 通知外部（如果需要）
    if (this.options.onModeChange) {
      this.options.onModeChange(mode);
    }

    // 模式切换后，结果展示也应刷新（例如某些字段显示为 --）
    this.renderResult();

    // 切换模式会影响结果区高度（例如 distance 显示 xyz，其它不显示）
    // 复用 onExpandedChange 来通知外部重新计算 Dialog 高度（不额外扩展回调，保持接口简单）
    if (this.options.onExpandedChange) {
      this.options.onExpandedChange(this.isExpanded);
    }
  }

  /**
   * 设置测量结果（由外部注入）
   * @param result 测量结果；传 null 表示清空
   */
  public setResult(result: MeasureResult | null): void {
    this.result = result;
    this.renderResult();
  }

  /**
   * 删除全部（只做 UI 状态清空 + 回调）
   */
  public clearAll(): void {
    // 先清空结果显示
    this.result = null;
    this.renderResult();

    // 通知外部
    if (this.options.onClearAll) {
      this.options.onClearAll();
    }
  }

  /**
   * 打开设置（本次只预留方法/回调）
   */
  public openSettings(): void {
    // 进入设置面板（组件内部逻辑）
    this.enterSettingsView();

    // 仍然保留回调（如果外部想监听）
    if (this.options.onSettings) {
      this.options.onSettings();
    }
  }

  /**
   * 获取当前测量配置
   */
  public getConfig(): MeasureConfig {
    return { ...this.config };
  }

  /**
   * 设置测量配置（可选对外调用）
   * @param partial 部分更新
   * @param persist 是否写入缓存（默认 false）
   */
  public setConfig(partial: Partial<MeasureConfig>, persist: boolean = false): void {
    const next: MeasureConfig = {
      unit: partial.unit ?? this.config.unit,
      precision: partial.precision ?? this.config.precision
    };
    this.config = next;
    if (persist) {
      this.saveConfigToCache(next);
    }

    // 配置变化会影响数值显示（单位/精度）
    this.renderResult();

    // 如果当前在设置面板，表单也需要同步
    if (this.view === 'settings') {
      this.syncSettingsFormFromConfig(next);
    }
  }

  /**
   * 展开 / 收起（可选对外调用）
   * @param expanded 是否展开
   */
  public setExpanded(expanded: boolean): void {
    if (this.isExpanded === expanded) return;
    this.isExpanded = expanded;
    this.applyExpandedState();
    this.setLocales(); // 更新 tooltip（展开/收起）

    // 通知外部：用于重新计算 Dialog 高度
    if (this.options.onExpandedChange) {
      this.options.onExpandedChange(this.isExpanded);
    }
  }

  /**
   * 获取是否展开
   */
  public getExpanded(): boolean {
    return this.isExpanded;
  }

  // ==========================
  // 内部实现
  // ==========================

  private createDom(): HTMLElement {
    const root = document.createElement('div');
    root.className = 'bim-measure-panel';

    // 主视图容器（默认显示）
    this.mainViewEl = document.createElement('div');
    this.mainViewEl.className = 'bim-measure-main';

    // 顶部：工具按钮区
    const toolsBox = document.createElement('div');
    toolsBox.className = 'bim-measure-tools';

    const grid = document.createElement('div');
    grid.className = 'bim-measure-tool-grid';

    // 8 种测量方式（顺序严格按你给的）
    const modes: MeasureMode[] = [
      'distance',
      'minDistance',
      'angle',
      'elevation',
      'volume',
      'laserDistance',
      'slope',
      'spaceVolume'
    ];

    // 图标：优先使用你上传的 SVG 文件内容（已内联到 MEASURE_MODE_ICON_SVGS）
    // 兜底：如果某个 mode 没有配置图标，则使用圆形占位（防止页面空白）
    const fallbackCircleIconSvg = `
      <svg viewBox="0 0 24 24" aria-hidden="true">
        <circle cx="12" cy="12" r="9"></circle>
      </svg>
    `;

    // 逐个创建按钮
    for (let i = 0; i < modes.length; i++) {
      const mode = modes[i];
      const btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'bim-measure-tool-btn';
      btn.dataset.mode = mode;

      // icon
      const icon = document.createElement('span');
      icon.className = 'bim-measure-tool-icon';
      icon.innerHTML = MEASURE_MODE_ICON_SVGS[mode] || fallbackCircleIconSvg;
      btn.appendChild(icon);

      // 点击切换模式
      btn.addEventListener('click', () => {
        this.setActiveMode(mode);
      });

      // 先不在这里设置 title/text（统一交给 setLocales）
      this.toolButtons.set(mode, btn);
      grid.appendChild(btn);
    }

    toolsBox.appendChild(grid);

    // 展开/收起按钮（箭头）
    const toggleBox = document.createElement('div');
    toggleBox.className = 'bim-measure-toggle';

    this.toggleBtn = document.createElement('button');
    this.toggleBtn.type = 'button';
    this.toggleBtn.className = 'bim-measure-toggle-btn';
    // 展开/收起按钮：更小，并带文字提示（展开/收起）
    // 注意：文本内容由 setLocales() 统一更新，这里先放一个占位容器
    this.toggleTextEl = document.createElement('span');
    this.toggleTextEl.className = 'bim-measure-toggle-text';
    const toggleIconEl = document.createElement('span');
    toggleIconEl.className = 'bim-measure-toggle-icon';
    toggleIconEl.innerHTML = `
      <svg viewBox="0 0 24 24" aria-hidden="true">
        <path d="M7 10l5 5 5-5z"></path>
      </svg>
    `;
    this.toggleBtn.appendChild(this.toggleTextEl);
    this.toggleBtn.appendChild(toggleIconEl);
    this.toggleBtn.addEventListener('click', () => {
      this.isExpanded = !this.isExpanded;
      this.applyExpandedState();
      this.setLocales(); // 更新 tooltip（展开/收起）

      // 通知外部：用于重新计算 Dialog 高度
      if (this.options.onExpandedChange) {
        this.options.onExpandedChange(this.isExpanded);
      }
    });

    toggleBox.appendChild(this.toggleBtn);
    toolsBox.appendChild(toggleBox);
    this.mainViewEl.appendChild(toolsBox);

    // 中部：结果区
    const resultBox = document.createElement('div');
    resultBox.className = 'bim-measure-result';

    // 主结果值（随模式变化）
    const mainValueRow = document.createElement('div');
    mainValueRow.className = 'bim-measure-row';
    const mainValueLabel = document.createElement('span');
    mainValueLabel.className = 'label';
    this.mainValueLabelEl = mainValueLabel;
    const mainValueValue = document.createElement('span');
    mainValueValue.className = 'value';
    this.mainValueValueEl = mainValueValue;

    // 主值拆分：数值（黄色）+ 单位（普通色）
    // 这样可以满足：
    // 1) 只让“数据”变黄，单位不变色
    // 2) 没有数据时展示 `-- 单位`
    this.mainNumberEl = document.createElement('span');
    this.mainNumberEl.className = 'bim-measure-main-number';
    this.mainUnitEl = document.createElement('span');
    this.mainUnitEl.className = 'bim-measure-main-unit';
    this.mainValueValueEl.appendChild(this.mainNumberEl);
    this.mainValueValueEl.appendChild(document.createTextNode(' '));
    this.mainValueValueEl.appendChild(this.mainUnitEl);
    mainValueRow.appendChild(mainValueLabel);
    mainValueRow.appendChild(mainValueValue);
    resultBox.appendChild(mainValueRow);

    // XYZ
    const xyzBox = document.createElement('div');
    xyzBox.className = 'bim-measure-xyz';
    this.xyzBoxEl = xyzBox;

    const makeXyzRow = (labelKey: string, valueClassName: string, valueElSetter: (el: HTMLElement) => void) => {
      const row = document.createElement('div');
      row.className = 'bim-measure-row';
      const label = document.createElement('span');
      label.className = 'label';
      label.dataset.i18nKey = labelKey;
      const value = document.createElement('span');
      value.className = `value ${valueClassName}`;
      valueElSetter(value);
      row.appendChild(label);
      row.appendChild(value);
      return row;
    };

    xyzBox.appendChild(makeXyzRow('measure.labels.x', 'bim-measure-xyz-x', (el) => (this.xyzXEl = el)));
    xyzBox.appendChild(makeXyzRow('measure.labels.y', 'bim-measure-xyz-y', (el) => (this.xyzYEl = el)));
    xyzBox.appendChild(makeXyzRow('measure.labels.z', 'bim-measure-xyz-z', (el) => (this.xyzZEl = el)));
    resultBox.appendChild(xyzBox);

    this.mainViewEl.appendChild(resultBox);

    // 底部：删除全部 + 设置
    const footer = document.createElement('div');
    footer.className = 'bim-measure-footer';

    this.clearBtn = document.createElement('button');
    this.clearBtn.type = 'button';
    this.clearBtn.className = 'bim-measure-clear-btn';
    this.clearBtn.addEventListener('click', () => {
      this.clearAll();
    });

    this.settingsBtn = document.createElement('button');
    this.settingsBtn.type = 'button';
    this.settingsBtn.className = 'bim-measure-settings-btn';
    this.settingsBtn.innerHTML = `
      <svg viewBox="0 0 24 24" aria-hidden="true">
        <path d="M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58a.5.5 0 0 0 .12-.64l-1.92-3.32a.5.5 0 0 0-.6-.22l-2.39.96a7.27 7.27 0 0 0-1.63-.94l-.36-2.54A.5.5 0 0 0 13.9 1h-3.8a.5.5 0 0 0-.49.42l-.36 2.54c-.58.23-1.12.54-1.63.94l-2.39-.96a.5.5 0 0 0-.6.22L2.71 7.48a.5.5 0 0 0 .12.64l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94L2.83 14.52a.5.5 0 0 0-.12.64l1.92 3.32c.13.22.39.3.6.22l2.39-.96c.5.4 1.05.71 1.63.94l.36 2.54c.04.24.25.42.49.42h3.8c.24 0 .45-.18.49-.42l.36-2.54c.58-.23 1.12-.54 1.63-.94l2.39.96c.22.09.47 0 .6-.22l1.92-3.32a.5.5 0 0 0-.12-.64l-2.03-1.58zM12 15.5A3.5 3.5 0 1 1 12 8a3.5 3.5 0 0 1 0 7.5z"></path>
      </svg>
    `;
    this.settingsBtn.addEventListener('click', () => {
      this.openSettings();
    });

    footer.appendChild(this.clearBtn);
    footer.appendChild(this.settingsBtn);
    this.mainViewEl.appendChild(footer);

    // 设置视图容器（默认隐藏）
    this.settingsViewEl = this.createSettingsDom();

    root.appendChild(this.mainViewEl);
    root.appendChild(this.settingsViewEl);

    return root;
  }

  /**
   * 创建“设置面板”DOM
   */
  private createSettingsDom(): HTMLElement {
    const box = document.createElement('div');
    box.className = 'bim-measure-settings';

    // 标题
    const title = document.createElement('div');
    title.className = 'bim-measure-settings-title';
    title.dataset.i18nKey = 'measure.settings.title';
    box.appendChild(title);

    // 单位
    const unitRow = document.createElement('div');
    unitRow.className = 'bim-measure-settings-row';
    const unitLabel = document.createElement('div');
    unitLabel.className = 'label';
    unitLabel.dataset.i18nKey = 'measure.settings.unit';
    this.unitSelectEl = document.createElement('select');
    this.unitSelectEl.className = 'bim-measure-settings-select';
    this.unitSelectEl.appendChild(this.makeOption('m'));
    this.unitSelectEl.appendChild(this.makeOption('cm'));
    this.unitSelectEl.appendChild(this.makeOption('mm'));
    this.unitSelectEl.appendChild(this.makeOption('km'));
    unitRow.appendChild(unitLabel);
    unitRow.appendChild(this.unitSelectEl);
    box.appendChild(unitRow);

    // 提示文本：你要求放在“单位”下面
    const hint = document.createElement('div');
    hint.className = 'bim-measure-settings-hint';
    hint.dataset.i18nKey = 'measure.settings.hint';
    box.appendChild(hint);

    // 精度
    const precisionRow = document.createElement('div');
    precisionRow.className = 'bim-measure-settings-row';
    const precisionLabel = document.createElement('div');
    precisionLabel.className = 'label';
    precisionLabel.dataset.i18nKey = 'measure.settings.precision';
    this.precisionSelectEl = document.createElement('select');
    this.precisionSelectEl.className = 'bim-measure-settings-select';
    this.precisionSelectEl.appendChild(this.makePrecisionOption(0));
    this.precisionSelectEl.appendChild(this.makePrecisionOption(1));
    this.precisionSelectEl.appendChild(this.makePrecisionOption(2));
    this.precisionSelectEl.appendChild(this.makePrecisionOption(3));
    precisionRow.appendChild(precisionLabel);
    precisionRow.appendChild(this.precisionSelectEl);
    box.appendChild(precisionRow);

    // 底部按钮
    const actions = document.createElement('div');
    actions.className = 'bim-measure-settings-actions';

    this.saveSettingsBtn = document.createElement('button');
    this.saveSettingsBtn.type = 'button';
    this.saveSettingsBtn.className = 'bim-measure-settings-save';
    this.saveSettingsBtn.addEventListener('click', () => {
      this.saveSettings();
    });

    this.cancelSettingsBtn = document.createElement('button');
    this.cancelSettingsBtn.type = 'button';
    this.cancelSettingsBtn.className = 'bim-measure-settings-cancel';
    this.cancelSettingsBtn.addEventListener('click', () => {
      this.cancelSettings();
    });

    actions.appendChild(this.saveSettingsBtn);
    actions.appendChild(this.cancelSettingsBtn);
    box.appendChild(actions);

    // 初次同步表单值
    this.syncSettingsFormFromConfig(this.config);

    return box;
  }

  private makeOption(unit: MeasureUnit): HTMLOptionElement {
    const opt = document.createElement('option');
    opt.value = unit;
    // 选项显示内容：直接显示单位字符串
    opt.textContent = unit;
    return opt;
  }

  private makePrecisionOption(precision: MeasurePrecision): HTMLOptionElement {
    const opt = document.createElement('option');
    opt.value = String(precision);
    // 显示：0 / 0.0 / 0.00 / 0.000
    opt.textContent = precision === 0 ? '0' : `0.${'0'.repeat(precision)}`;
    return opt;
  }

  /**
   * 进入设置视图：保存一份当前配置作为草稿基线
   */
  private enterSettingsView(): void {
    this.draftConfig = { ...this.config };
    this.view = 'settings';
    this.syncSettingsFormFromConfig(this.config);
    this.applyViewState();
  }

  /**
   * 保存设置：写入 config + 写缓存 + 返回主视图
   */
  private saveSettings(): void {
    const unit = (this.unitSelectEl.value as MeasureUnit) || this.config.unit;
    const precision = (Number(this.precisionSelectEl.value) as MeasurePrecision);
    const next: MeasureConfig = {
      unit,
      precision: this.isValidPrecision(precision) ? precision : this.config.precision
    };

    this.config = next;
    this.saveConfigToCache(next);
    this.draftConfig = null;
    this.view = 'main';
    this.applyViewState();

    // 配置变化会影响显示
    this.renderResult();

    // 高度变化（设置面板 -> 主面板）也需要通知外部
    if (this.options.onExpandedChange) {
      this.options.onExpandedChange(this.isExpanded);
    }
  }

  /**
   * 取消设置：回滚到进入设置前的配置，并返回主视图
   */
  private cancelSettings(): void {
    if (this.draftConfig) {
      this.config = { ...this.draftConfig };
    }
    this.draftConfig = null;
    this.view = 'main';
    this.applyViewState();
    this.renderResult();

    // 高度变化（设置面板 -> 主面板）也需要通知外部
    if (this.options.onExpandedChange) {
      this.options.onExpandedChange(this.isExpanded);
    }
  }

  private syncSettingsFormFromConfig(config: MeasureConfig): void {
    this.unitSelectEl.value = config.unit;
    this.precisionSelectEl.value = String(config.precision);
  }

  private applyViewState(): void {
    if (this.view === 'settings') {
      this.mainViewEl.style.display = 'none';
      // 注意：CSS 里 `.bim-measure-settings { display: none; }` 是默认隐藏
      // 因此这里必须显式设置为可见（否则会出现“进入设置页后什么都不显示”的问题）
      this.settingsViewEl.style.display = 'block';
    } else {
      // 显式恢复主视图显示（避免外部样式干扰）
      this.mainViewEl.style.display = 'block';
      this.settingsViewEl.style.display = 'none';
    }
  }

  /**
   * 从缓存读取配置
   * - 有缓存：返回解析后的配置
   * - 无缓存/解析失败：返回 null
   */
  private loadConfigFromCache(): MeasureConfig | null {
    try {
      const raw = localStorage.getItem(MeasurePanel.CONFIG_CACHE_KEY);
      if (!raw) return null;
      const parsed = JSON.parse(raw) as Partial<MeasureConfig>;
      if (!parsed || typeof parsed !== 'object') return null;

      const unit = parsed.unit;
      const precision = parsed.precision;

      if (!this.isValidUnit(unit) || !this.isValidPrecision(precision as number)) return null;

      return {
        unit,
        precision: precision as MeasurePrecision
      };
    } catch (_e) {
      // localStorage 可能被禁用或 JSON 格式不正确，直接忽略
      return null;
    }
  }

  /**
   * 写入缓存（localStorage）
   */
  private saveConfigToCache(config: MeasureConfig): void {
    try {
      localStorage.setItem(MeasurePanel.CONFIG_CACHE_KEY, JSON.stringify(config));
    } catch (_e) {
      // localStorage 可能被禁用：忽略即可，不影响功能
    }
  }

  private isValidUnit(unit: any): unit is MeasureUnit {
    return unit === 'm' || unit === 'cm' || unit === 'mm' || unit === 'km';
  }

  private isValidPrecision(precision: any): precision is MeasurePrecision {
    return precision === 0 || precision === 1 || precision === 2 || precision === 3;
  }

  /**
   * 应用“展开/收起”状态：默认只显示前 4 个按钮
   */
  private applyExpandedState(): void {
    let index = 0;
    for (const btn of this.toolButtons.values()) {
      // 默认展示前四个，其余根据展开状态显示/隐藏
      if (index >= 4) {
        btn.style.display = this.isExpanded ? '' : 'none';
      } else {
        btn.style.display = '';
      }
      index++;
    }

    // toggle 样式（旋转箭头）
    if (this.isExpanded) {
      this.toggleBtn.classList.add('is-expanded');
    } else {
      this.toggleBtn.classList.remove('is-expanded');
    }
  }

  /**
   * 应用“当前选中按钮”样式
   */
  private applyActiveModeState(): void {
    for (const [mode, btn] of this.toolButtons.entries()) {
      if (mode === this.activeMode) {
        btn.classList.add('is-active');
      } else {
        btn.classList.remove('is-active');
      }
    }
  }

  /**
   * 渲染结果区（根据 activeMode 从 result 里取对应字段）
   */
  private renderResult(): void {
    // 1) 根据模式决定结果区显示规则
    // 你给的规则：
    // - 距离：显示数值 + xyz
    // - 最小距离：只显示数值
    // - 角度：--°
    // - 标高：--m（固定 m）
    // - 体积：--mm³（单位随设置变动，即 unit³）
    // - 激光测距：不显示任何数值/xyz，只显示“激光测距”文字
    // - 坡度：--%
    // - 空间体积：--mm³（单位随设置变动，即 unit³）

    // 1.1) 主行：默认显示 label + value（数值/单位拆分）
    // 激光测距：只显示文字，因此隐藏 label/单位
    if (this.activeMode === 'laserDistance') {
      this.mainValueLabelEl.style.display = 'none';
      this.mainNumberEl.textContent = t(this.getModeI18nKey('laserDistance'));
      this.mainUnitEl.textContent = '';
      // 激光测距：你要求不使用黄色主数据
      this.mainNumberEl.classList.add('is-laser-text');
    } else {
      this.mainValueLabelEl.style.display = '';
      this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
      const parts = this.formatMainValueParts(this.activeMode, this.result);
      this.mainNumberEl.textContent = parts.numberText;
      this.mainUnitEl.textContent = parts.unitText;
      // 其它模式：恢复黄色主数据
      this.mainNumberEl.classList.remove('is-laser-text');
    }

    // 1.2) XYZ：只有“距离”需要展示
    if (this.activeMode === 'distance') {
      this.xyzBoxEl.style.display = '';
      const xyz = this.result?.xyz;
      if (!xyz) {
        this.xyzXEl.textContent = '--';
        this.xyzYEl.textContent = '--';
        this.xyzZEl.textContent = '--';
        return;
      }
      this.xyzXEl.textContent = this.formatNumberWithPrecision(xyz.x, this.config.precision);
      this.xyzYEl.textContent = this.formatNumberWithPrecision(xyz.y, this.config.precision);
      this.xyzZEl.textContent = this.formatNumberWithPrecision(xyz.z, this.config.precision);
      return;
    }

    // 非 distance：隐藏 xyz
    this.xyzBoxEl.style.display = 'none';
  }

  /**
   * 获取模式名称的国际化 key
   */
  private getModeI18nKey(mode: MeasureMode): string {
    return `measure.modes.${mode}`;
  }

  /**
   * 获取“主值 label”的国际化 key（随模式变化）
   */
  private getModeValueLabelI18nKey(mode: MeasureMode): string {
    return `measure.labels.value.${mode}`;
  }

  // 注意：旧的 formatMainValue/formatWithFixedUnit 已被 formatMainValueParts 替代，
  // 以支持“数值与单位分色显示”和“无数据时仍展示单位”。

  /**
   * 基础数字格式化（按精度显示）
   */
  private formatNumberWithPrecision(value: number, precision: MeasurePrecision): string {
    // 你要求精度可选：0 / 0.0 / 0.00 / 0.000，因此这里不做 trim，严格按 toFixed 输出
    return value.toFixed(precision);
  }

  // 注意：旧的 formatLengthWithConfig 已被 formatLengthParts 替代。

  private convertMmToUnit(mm: number, unit: MeasureUnit): number {
    switch (unit) {
      case 'mm':
        return mm;
      case 'cm':
        return mm / 10;
      case 'm':
        return mm / 1000;
      case 'km':
        return mm / 1_000_000;
      default:
        return mm;
    }
  }

  private getUnitI18nKey(unit: MeasureUnit): string {
    return `measure.units.${unit}`;
  }

  // 注意：旧的 formatElevationFixedMeters / formatVolumeWithConfig 已被 formatMainValueParts 替代。

  private convertMm3ToUnit3(mm3: number, unit: MeasureUnit): number {
    // 先把 mm³ -> 对应 unit³
    // mm -> cm: /10，因此 mm³ -> cm³: /1000
    // mm -> m : /1000，因此 mm³ -> m³ : /1e9
    // mm -> km: /1e6，因此 mm³ -> km³: /1e18
    switch (unit) {
      case 'mm':
        return mm3;
      case 'cm':
        return mm3 / 1000;
      case 'm':
        return mm3 / 1_000_000_000;
      case 'km':
        return mm3 / 1_000_000_000_000_000_000;
      default:
        return mm3;
    }
  }

  /**
   * 主数据拆分：返回 { 数值文本, 单位文本 }
   * 规则：
   * - 没数据时：必须展示 `-- 单位`（而不是只展示 `--`）
   * - 单位随模式变化：
   *   - 距离/最小距离：单位随设置变动
   *   - 角度：°
   *   - 标高：固定 m
   *   - 体积/空间体积：单位³（随设置变动）
   *   - 坡度：%
   */
  private formatMainValueParts(mode: MeasureMode, result: MeasureResult | null): { numberText: string; unitText: string } {
    if (mode === 'laserDistance') return { numberText: t(this.getModeI18nKey('laserDistance')), unitText: '' };

    // 没有数据：显示 `-- 单位`
    if (!result) {
      return this.getEmptyValuePartsByMode(mode);
    }

    switch (mode) {
      case 'distance':
        return this.formatLengthParts(result.distanceMm);
      case 'minDistance':
        return this.formatLengthParts(result.minDistanceMm);
      case 'angle':
        return this.formatFixedUnitParts(result.angleDeg, t('measure.units.deg'));
      case 'elevation':
        // 标高固定 m（外部注入值约定为 mm）
        return this.formatFixedUnitParts(
          result.elevationMm === undefined ? undefined : result.elevationMm / 1000,
          t('measure.units.m')
        );
      case 'volume':
        return this.formatVolumeParts(result.volumeM3);
      case 'slope':
        return this.formatFixedUnitParts(result.slopePercent, t('measure.units.percent'));
      case 'spaceVolume':
        return this.formatVolumeParts(result.spaceVolumeM3);
      default:
        return { numberText: '--', unitText: '' };
    }
  }

  private getEmptyValuePartsByMode(mode: MeasureMode): { numberText: string; unitText: string } {
    switch (mode) {
      case 'distance':
      case 'minDistance':
        return { numberText: '--', unitText: t(this.getUnitI18nKey(this.config.unit)) };
      case 'angle':
        return { numberText: '--', unitText: t('measure.units.deg') };
      case 'elevation':
        return { numberText: '--', unitText: t('measure.units.m') };
      case 'volume':
      case 'spaceVolume':
        return { numberText: '--', unitText: `${this.config.unit}³` };
      case 'slope':
        return { numberText: '--', unitText: t('measure.units.percent') };
      default:
        return { numberText: '--', unitText: '' };
    }
  }

  private formatFixedUnitParts(value: number | undefined, unitText: string): { numberText: string; unitText: string } {
    if (value === null || value === undefined || Number.isNaN(value)) {
      return { numberText: '--', unitText };
    }
    return { numberText: this.formatNumberWithPrecision(value, this.config.precision), unitText };
  }

  private formatLengthParts(valueMm: number | undefined): { numberText: string; unitText: string } {
    const unitText = t(this.getUnitI18nKey(this.config.unit));
    if (valueMm === null || valueMm === undefined || Number.isNaN(valueMm)) {
      return { numberText: '--', unitText };
    }
    const converted = this.convertMmToUnit(valueMm, this.config.unit);
    return { numberText: this.formatNumberWithPrecision(converted, this.config.precision), unitText };
  }

  private formatVolumeParts(valueMm3: number | undefined): { numberText: string; unitText: string } {
    const unitText = `${this.config.unit}³`;
    if (valueMm3 === null || valueMm3 === undefined || Number.isNaN(valueMm3)) {
      return { numberText: '--', unitText };
    }
    const converted = this.convertMm3ToUnit3(valueMm3, this.config.unit);
    return { numberText: this.formatNumberWithPrecision(converted, this.config.precision), unitText };
  }
}


