import React from 'react';

type LookupItem = {
  display: string;
  value: string;
  isManual: boolean;
  source: 'selected' | 'manual';
  listDataId?: string;
};

type LookupOption = {
  display: string;
  value: string;
  listDataId: string;
  raw: any;
};

type WriteTargetInfo = {
  paramName: string;
  raw: any;
  candidates: string[];
  displayValue: string;
};

export class CodeInApplication implements CodeInComp {
  description() {
    return "在易企办表单中按关键词查找和选择数据列表记录。";
  }

  requiredFields(params?: CodeInParams) {
    const required: string[] = [];
    const safeParams: any = params || {};
    const outputTargetMap: any = {};
    ['saveToField', 'selectedItemsField', 'newItemsField'].forEach((key) => {
      const candidates = this.collectRequiredFieldCandidates(safeParams[key]).filter((value) => this.isLikelyWritableTargetName(value));
      if (candidates.length > 0) {
        outputTargetMap[key] = candidates[0];
      }
      candidates.forEach((value) => {
        if (value && required.indexOf(value) === -1) {
          required.push(value);
        }
      });
    });
    /* 选择器参数可能在 render() 前被解析为当前值。requiredFields(params) 先从原始配置取得可写字段或临时变量的标识。 */
    (this as any).smartLookupOutputTargetMap = outputTargetMap;
    return required;
  }

  inputParameters(): InputParameter[] {
    return [
      { id: 'dataListId', name: "数据列表 ID", type: 'variable', desc: "提供候选记录的易企办数据列表 ID。" },
      { id: 'displayField', name: "查找显示字段", type: 'variable', desc: "用于关键词匹配和显示选项的字段标识。" },
      { id: 'valueField', name: "保存值字段", type: 'variable', desc: "用于写入已选值的字段标识。" },
      { id: 'saveToField', name: "完整结果保存目标", type: 'variable', desc: "从变量选择器绑定可写表单字段或页面临时变量，保存完整 JSON。" },
      { id: 'selectedItemsField', name: "匹配结果保存目标", type: 'variable', desc: "从选择器绑定可写目标，仅保存已匹配记录的值。" },
      { id: 'newItemsField', name: "手工值保存目标", type: 'variable', desc: "从选择器绑定可写目标，仅保存手工录入的值。" },
      { id: 'multiSelect', name: "允许多选", type: 'variable', desc: "使用 true 或 false 控制能否选多项。" },
      { id: 'allowManualEntry', name: "允许手工录入", type: 'variable', desc: "使用 true 或 false 控制能否添加手工输入的文字。" },
      { id: 'maxResults', name: "候选条数上限", type: 'string', desc: "候选列表最多显示的匹配记录数。" },
      { id: 'placeholderText', name: "输入提示", type: 'string', desc: "搜索框中的提示文字。" },
      { id: 'labelText', name: "控件标题", type: 'string', desc: "控件上方的可选标题。" },
      { id: 'noResultText', name: "无匹配提示", type: 'string', desc: "未找到匹配记录时的提示。" },
      { id: 'manualTagText', name: "手工录入标记", type: 'string', desc: "用于标记手工录入的标签文字。" },
      { id: 'minSearchChars', name: "最少搜索字符", type: 'string', desc: "发起查询所需的最少字符数，默认为 1。" },
      { id: 'debounceMs', name: "搜索等待毫秒数", type: 'string', desc: "停止输入后等待多少毫秒再发起查询，默认为 260。" },
    ];
  }

  render(context: CodeInContext, fieldsValues: any, readonly: boolean) {
    return (
      <SmartLookupPicker
        context={context}
        fieldsValues={fieldsValues}
        readonly={readonly}
        params={(context && context.params) || {}}
        configuredOutputTargets={(this as any).smartLookupOutputTargetMap || {}}
      />
    );
  }

  safeTrim(value: any) {
    return value === undefined || value === null ? '' : String(value).trim();
  }

  normalizeExpressionText(value: any) {
    if (value === undefined || value === null) {
      return '';
    }
    if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
      return String(value).trim();
    }
    if (Array.isArray(value)) {
      return value.map((item) => this.normalizeExpressionText(item)).filter(Boolean).join(',');
    }
    if (typeof value === 'object') {
      return this.normalizeExpressionText(
        value.value !== undefined ? value.value :
        value.Value !== undefined ? value.Value :
        value.key !== undefined ? value.key :
        value.Key !== undefined ? value.Key :
        value.id !== undefined ? value.id :
        value.Id !== undefined ? value.Id :
        value.fieldId !== undefined ? value.fieldId :
        value.FieldId !== undefined ? value.FieldId :
        value.name !== undefined ? value.name :
        value.Name !== undefined ? value.Name :
        value.label !== undefined ? value.label :
        value.Label !== undefined ? value.Label :
        value.title !== undefined ? value.title :
        value.Title !== undefined ? value.Title : ''
      );
    }
    return String(value).trim();
  }

  collectRequiredFieldCandidates(value: any) {
    const candidates: string[] = [];
    const seen: any = {};
    const add = (candidate: any) => {
      const cleaned = this.normalizeExpressionText(candidate);
      if (cleaned && !seen[cleaned]) {
        seen[cleaned] = true;
        candidates.push(cleaned);
      }
    };
    const visit = (current: any, depth: number) => {
      if (current === undefined || current === null || depth > 3) {
        return;
      }
      if (typeof current === 'string' || typeof current === 'number' || typeof current === 'boolean') {
        add(current);
        return;
      }
      if (Array.isArray(current)) {
        current.forEach((item) => visit(item, depth + 1));
        return;
      }
      if (typeof current !== 'object') {
        return;
      }
      [
        'fieldId', 'FieldId', 'fieldName', 'FieldName',
        'variableId', 'VariableId', 'variableName', 'VariableName',
        'tempVariableId', 'TempVariableId', 'tempVariableName', 'TempVariableName',
        'tempVarId', 'TempVarId', 'id', 'Id', 'key', 'Key',
        'name', 'Name', 'code', 'Code', 'label', 'Label', 'title', 'Title',
        'path', 'Path', 'value', 'Value',
      ].forEach((key) => {
        if (current[key] !== undefined && current[key] !== null) {
          add(current[key]);
        }
      });
      ['target', 'Target', 'binding', 'Binding', 'field', 'Field', 'variable', 'Variable', 'tempVariable', 'TempVariable', 'data', 'Data', 'meta', 'Meta', 'metadata', 'Metadata'].forEach((key) => {
        if (current[key] !== undefined && current[key] !== null) {
          visit(current[key], depth + 1);
        }
      });
    };
    visit(value, 0);
    return candidates;
  }

  isLikelyWritableTargetName(candidate: any) {
    const value = this.safeTrim(candidate);
    if (!value || value === '[]' || value === '{}') {
      return false;
    }
    if (value.charAt(0) === '[' || value.charAt(0) === '{') {
      return false;
    }
    if (value.indexOf('":') !== -1 || value.indexOf('","') !== -1) {
      return false;
    }
    return value.length <= 160;
  }
}

class SmartLookupPicker extends React.Component<any, any> {
  searchTimer: any;
  activeRequestId: number;
  wrapperRef: any;

  constructor(props: any) {
    super(props);
    this.searchTimer = null;
    this.activeRequestId = 0;
    this.wrapperRef = null;
    this.state = {
      keyword: '',
      selectedItems: this.getInitialSelectedItems(props),
      options: [],
      loading: false,
      dropdownOpen: false,
      errorText: '',
      lastLoadedRawValue: this.readRawCombinedValue(props),
    };
  }

  componentDidMount() {
    document.addEventListener('mousedown', this.handleDocumentMouseDown);
  }

  componentDidUpdate(prevProps: any) {
    const previousRawValue = this.readRawCombinedValue(prevProps);
    const nextRawValue = this.readRawCombinedValue(this.props);
    if (previousRawValue !== nextRawValue && nextRawValue !== this.state.lastLoadedRawValue) {
      this.setState({
        selectedItems: this.getInitialSelectedItems(this.props),
        lastLoadedRawValue: nextRawValue,
      });
    }
  }

  componentWillUnmount() {
    this.cancelSearch();
    document.removeEventListener('mousedown', this.handleDocumentMouseDown);
  }

  getConfig() {
    const params = this.props.params || {};
    const saveToTarget = this.resolveWriteTarget('saveToField', params.saveToField);
    const selectedItemsTarget = this.resolveWriteTarget('selectedItemsField', params.selectedItemsField);
    const newItemsTarget = this.resolveWriteTarget('newItemsField', params.newItemsField);
    return {
      dataListId: this.toCleanString(params.dataListId),
      displayField: this.toCleanString(params.displayField),
      valueField: this.toCleanString(params.valueField),
      saveToField: saveToTarget.displayValue,
      selectedItemsField: selectedItemsTarget.displayValue,
      newItemsField: newItemsTarget.displayValue,
      saveToTarget: saveToTarget,
      selectedItemsTarget: selectedItemsTarget,
      newItemsTarget: newItemsTarget,
      multiSelect: this.toBoolean(params.multiSelect, false),
      allowManualEntry: this.toBoolean(params.allowManualEntry, false),
      maxResults: this.toPositiveInt(params.maxResults, 8),
      placeholderText: this.toCleanString(params.placeholderText) || "输入关键词查找",
      labelText: this.toCleanString(params.labelText),
      noResultText: this.toCleanString(params.noResultText) || "未找到匹配记录",
      manualTagText: this.toCleanString(params.manualTagText) || "手工录入",
      minSearchChars: this.toPositiveInt(params.minSearchChars, 1),
      debounceMs: this.toPositiveInt(params.debounceMs, 260),
    };
  }

  getInitialSelectedItems(props: any) {
    const config = this.getConfigFromProps(props);
    const combinedRaw = this.readCurrentFieldValue(props, config.saveToField);
    const combinedItems = this.parseCombinedItems(combinedRaw);
    if (combinedItems.length > 0) {
      return this.dedupeItems(combinedItems);
    }

    const matchedValues = this.parseArrayValue(this.readCurrentFieldValue(props, config.selectedItemsField));
    const manualValues = this.parseArrayValue(this.readCurrentFieldValue(props, config.newItemsField));
    const rebuilt = matchedValues.map((value: any) => ({
      display: this.toCleanString(value),
      value: this.toCleanString(value),
      isManual: false,
      source: 'selected' as 'selected',
    })).concat(manualValues.map((value: any) => ({
      display: this.toCleanString(value),
      value: this.toCleanString(value),
      isManual: true,
      source: 'manual' as 'manual',
    })));
    return this.dedupeItems(rebuilt);
  }

  getConfigFromProps(props: any) {
    const params = (props && props.params) || {};
    const saveToTarget = this.resolveWriteTargetFromProps(props, 'saveToField', params.saveToField);
    const selectedItemsTarget = this.resolveWriteTargetFromProps(props, 'selectedItemsField', params.selectedItemsField);
    const newItemsTarget = this.resolveWriteTargetFromProps(props, 'newItemsField', params.newItemsField);
    return {
      saveToField: saveToTarget.displayValue,
      selectedItemsField: selectedItemsTarget.displayValue,
      newItemsField: newItemsTarget.displayValue,
      saveToTarget: saveToTarget,
      selectedItemsTarget: selectedItemsTarget,
      newItemsTarget: newItemsTarget,
    };
  }

  readRawCombinedValue(props: any) {
    const config = this.getConfigFromProps(props);
    return this.toCleanString(this.readCurrentFieldValue(props, config.saveToField));
  }

  readCurrentFieldValue(props: any, fieldName: string) {
    if (!fieldName) {
      return '';
    }
    const fieldsValues = (props && props.fieldsValues) || {};
    if (fieldsValues && fieldsValues[fieldName] !== undefined && fieldsValues[fieldName] !== null) {
      return fieldsValues[fieldName];
    }
    const context = props && props.context;
    if (context && typeof context.getFieldValue === 'function') {
      try {
        const value = context.getFieldValue(fieldName);
        if (value !== undefined && value !== null) {
          return value;
        }
      } catch (error) {
        return '';
      }
    }
    return '';
  }

  parseCombinedItems(rawValue: any) {
    const parsed = this.parseJsonValue(rawValue);
    if (!Array.isArray(parsed)) {
      return [];
    }
    return parsed
      .map((item: any) => {
        if (!item) {
          return null;
        }
        const isManual = item.isManual === true || item.source === 'manual';
        const display = this.toCleanString(item.display || item.value);
        const value = this.toCleanString(item.value || item.display);
        if (!display && !value) {
          return null;
        }
        return {
          display: display || value,
          value: value || display,
          isManual: isManual,
          source: isManual ? 'manual' : 'selected',
          listDataId: this.toCleanString(item.listDataId || item.ListDataID),
        };
      })
      .filter(Boolean);
  }

  parseArrayValue(rawValue: any) {
    const parsed = this.parseJsonValue(rawValue);
    if (Array.isArray(parsed)) {
      return parsed.map((value) => this.toCleanString(value)).filter(Boolean);
    }
    const cleaned = this.toCleanString(rawValue);
    if (!cleaned) {
      return [];
    }
    return cleaned.split(',').map((value) => this.toCleanString(value)).filter(Boolean);
  }

  parseJsonValue(rawValue: any) {
    if (Array.isArray(rawValue) || (rawValue && typeof rawValue === 'object')) {
      return rawValue;
    }
    const cleaned = this.toCleanString(rawValue);
    if (!cleaned) {
      return null;
    }
    try {
      return JSON.parse(cleaned);
    } catch (error) {
      return null;
    }
  }

  handleDocumentMouseDown = (event: any) => {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
      this.cancelSearch();
      this.setState({ dropdownOpen: false, loading: false });
    }
  };

  handleInputChange = (event: any) => {
    const keyword = event.target.value || '';
    this.setState({ keyword: keyword, dropdownOpen: true, errorText: '' });
    this.scheduleSearch(keyword);
  };

  handleInputFocus = () => {
    if (this.state.options.length > 0 || this.state.keyword) {
      this.setState({ dropdownOpen: true });
    }
  };

  handleInputKeyDown = (event: any) => {
    if (event.nativeEvent && (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)) return;
    const config = this.getConfig();
    if (event.key === 'Enter') {
      event.preventDefault();
      const keyword = this.toCleanString(this.state.keyword);
      if (config.allowManualEntry && keyword) {
        this.addManualItem(keyword);
      } else if (!config.multiSelect && this.state.options.length > 0) {
        this.addMatchedItem(this.state.options[0]);
      }
    }
    if (event.key === 'Escape') {
      this.cancelSearch();
      this.setState({ dropdownOpen: false, loading: false });
    }
  };

  cancelSearch() {
    this.activeRequestId += 1;
    if (this.searchTimer) {
      clearTimeout(this.searchTimer);
      this.searchTimer = null;
    }
  }

  scheduleSearch(keyword: string) {
    const config = this.getConfig();
    this.cancelSearch();
    const cleaned = this.toCleanString(keyword);
    if (!cleaned || cleaned.length < config.minSearchChars) {
      this.setState({ options: [], loading: false, dropdownOpen: Boolean(cleaned) });
      return;
    }
    this.setState({ options: [], loading: false });
    this.searchTimer = setTimeout(() => {
      this.runSearch(cleaned);
    }, config.debounceMs);
  }

  async runSearch(keyword: string) {
    const config = this.getConfig();
    const client = this.props.context && this.props.context.modules && this.props.context.modules.yeeSDKClient;
    const canQuery = client && client.lists && typeof client.lists.queryItems === 'function';
    const requestId = this.activeRequestId + 1;
    this.activeRequestId = requestId;

    if (!canQuery || !config.dataListId || !config.displayField || !config.valueField) {
      this.setState({
        options: [],
        loading: false,
        dropdownOpen: true,
        errorText: canQuery ? '' : "无法访问查找服务",
      });
      return;
    }

    this.setState({ loading: true, dropdownOpen: true, errorText: '' });

    try {
      /* 先尝试宿主常见的包含查询。如无匹配，读取有限数量的候选记录，并在本地按显示字段匹配。 */
      let response: any = null;
      let options: LookupOption[] = [];
      let receivedResponse = false;
      const searchPayloads = this.buildSearchQueryPayloads(config, keyword);

      for (let payloadIndex = 0; payloadIndex < searchPayloads.length; payloadIndex += 1) {
        try {
          response = await this.callQueryItems(client, config.dataListId, searchPayloads[payloadIndex]);
          if (requestId !== this.activeRequestId) return;
          receivedResponse = true;
          options = this.extractOptions(response, config, keyword);
          if (options.length > 0) {
            break;
          }
        } catch (searchError) {
          if (requestId !== this.activeRequestId) return;
        }
      }

      if (options.length === 0) {
        try {
          const fallbackResponse = await this.callQueryItems(client, config.dataListId, this.buildFallbackQueryPayload(config));
          receivedResponse = true;
          const fallbackOptions = this.extractOptions(fallbackResponse, config, keyword);
          if (fallbackOptions.length > 0) {
            options = fallbackOptions;
          }
        } catch (fallbackError) {
          /* 宿主无法执行后备查询时，保留已有的筛选结果。 */
        }
      }

      if (requestId !== this.activeRequestId) {
        return;
      }

      if (!receivedResponse) throw new Error('查询失败');
      this.setState({
        options: options.slice(0, config.maxResults),
        loading: false,
        dropdownOpen: true,
        errorText: '',
      });
    } catch (error) {
      if (requestId !== this.activeRequestId) {
        return;
      }
      this.setState({
        options: [],
        loading: false,
        dropdownOpen: true,
        errorText: "无法加载匹配记录",
      });
    }
  }

  buildFilteredQueryPayload(config: any, keyword: string) {
    return this.buildSearchQueryPayloads(config, keyword)[0];
  }

  async callQueryItems(client: any, dataListId: string, payload: any) {
    /* 依次尝试 queryItems 的常用签名，兼容不支持的签名返回空页的情况。 */
    const payloadWithoutListId = Object.assign({}, payload);
    delete payloadWithoutListId.listId;
    delete payloadWithoutListId.dataListId;
    const attempts = [
      () => client.lists.queryItems(payload),
      () => client.lists.queryItems(dataListId, payloadWithoutListId),
      () => client.lists.queryItems(dataListId, payload),
      () => client.lists.queryItems(dataListId, payloadWithoutListId, {}),
      () => client.lists.queryItems(dataListId, payload, {}),
    ];
    let lastResponse: any = null;
    let lastError: any = null;
    for (let index = 0; index < attempts.length; index += 1) {
      try {
        const response = await attempts[index]();
        const rows = this.extractRows(response);
        if (rows.length > 0) {
          return response;
        }
        if (lastResponse === null) {
          lastResponse = response;
        }
      } catch (error) {
        lastError = error;
      }
    }
    if (lastResponse !== null) {
      return lastResponse;
    }
    throw lastError;
  }

  buildSearchQueryPayloads(config: any, keyword: string) {
    const fields = this.uniqueArray(['ListDataID', config.displayField, config.valueField]);

    /* 用显示字段做包含查询，用值字段标识选中项。筛选格式不兼容时，仅在首批候选记录中本地匹配。 */
    const basePayload: any = {
      listId: config.dataListId,
      dataListId: config.dataListId,
      fields: fields,
      fieldIds: fields,
      selectedFields: fields,
      pageIndex: 1,
      pageNo: 1,
      pageSize: Math.max(config.maxResults, 20),
    };

    return [
      Object.assign({}, basePayload, {
        filters: [
          {
            fieldId: config.displayField,
            field: config.displayField,
            operator: 'contains',
            compare: 'contains',
            value: keyword,
          },
        ],
      }),
      Object.assign({}, basePayload, {
        filters: [
          {
            fieldId: config.displayField,
            field: config.displayField,
            op: 'contains',
            method: 'contains',
            values: [keyword],
            value: keyword,
          },
        ],
      }),
      Object.assign({}, basePayload, {
        filter: {
          fieldId: config.displayField,
          field: config.displayField,
          operator: 'contains',
          compare: 'contains',
          value: keyword,
        },
      }),
      Object.assign({}, basePayload, {
        query: {
          fieldId: config.displayField,
          field: config.displayField,
          operator: 'contains',
          value: keyword,
        },
      }),
      Object.assign({}, basePayload, {
        where: [
          {
            fieldId: config.displayField,
            field: config.displayField,
            operator: 'contains',
            value: keyword,
          },
        ],
      }),
      Object.assign({}, basePayload, {
        condition: {
          fieldId: config.displayField,
          field: config.displayField,
          operator: 'contains',
          value: keyword,
        },
      }),
      Object.assign({}, basePayload, {
        conditions: [
          {
            fieldId: config.displayField,
            field: config.displayField,
            operator: 'contains',
            value: keyword,
          },
        ],
      }),
      Object.assign({}, basePayload, {
        filterItems: [
          {
            fieldId: config.displayField,
            field: config.displayField,
            operator: 'contains',
            value: keyword,
          },
        ],
      }),
      Object.assign({}, basePayload, {
        query: keyword,
        keyword: keyword,
        search: keyword,
        searchField: config.displayField,
        searchFieldId: config.displayField,
        searchKey: keyword,
      }),
    ];
  }

  buildFallbackQueryPayload(config: any) {
    return {
      listId: config.dataListId,
      dataListId: config.dataListId,
      fields: this.uniqueArray(['ListDataID', config.displayField, config.valueField]),
      fieldIds: this.uniqueArray(['ListDataID', config.displayField, config.valueField]),
      selectedFields: this.uniqueArray(['ListDataID', config.displayField, config.valueField]),
      pageIndex: 1,
      pageNo: 1,
      pageSize: Math.max(config.maxResults, 50),
    };
  }

  extractOptions(response: any, config: any, keyword: string) {
    const rows = this.extractRows(response);
    const normalizedKeyword = this.normalizeText(keyword);
    return rows
      .map((row: any) => {
        const display = this.toCleanString(this.readItemValue(row, config.displayField));
        const value = this.toCleanString(this.readItemValue(row, config.valueField) || this.readItemValue(row, 'ListDataID') || this.readItemValue(row, 'ListDataId'));
        const listDataId = this.toCleanString(this.readItemValue(row, 'ListDataID') || this.readItemValue(row, 'ListDataId') || this.readItemValue(row, 'id'));
        if (!display || !value) {
          return null;
        }
        return {
          display: display,
          value: value,
          listDataId: listDataId,
          raw: row,
        };
      })
      .filter(Boolean)
      .filter((option: LookupOption) => {
        if (!normalizedKeyword) {
          return true;
        }
        return this.normalizeText(option.display).indexOf(normalizedKeyword) !== -1;
      })
      .filter((option: LookupOption) => !this.isSelected(option.value, false));
  }

  extractRows(response: any) {
    if (!response) {
      return [];
    }
    if (Array.isArray(response)) {
      return response;
    }
    const candidates = [
      response.items,
      response.Items,
      response.rows,
      response.Rows,
      response.records,
      response.Records,
      response.listData,
      response.ListData,
      response.data,
      response.Data,
      response.result,
      response.Result,
      response.data && response.data.items,
      response.data && response.data.Items,
      response.data && response.data.data,
      response.data && response.data.Data,
      response.data && response.data.rows,
      response.data && response.data.Rows,
      response.data && response.data.list,
      response.data && response.data.List,
      response.data && response.data.listData,
      response.data && response.data.ListData,
      response.data && response.data.records,
      response.data && response.data.Records,
      response.result && response.result.items,
      response.result && response.result.Items,
      response.result && response.result.data,
      response.result && response.result.Data,
      response.result && response.result.rows,
      response.result && response.result.Rows,
      response.result && response.result.listData,
      response.result && response.result.ListData,
      response.result && response.result.records,
      response.result && response.result.Records,
      response.Data && response.Data.items,
      response.Data && response.Data.Items,
      response.Data && response.Data.data,
      response.Data && response.Data.Data,
      response.Data && response.Data.rows,
      response.Data && response.Data.Rows,
      response.Result && response.Result.items,
      response.Result && response.Result.Items,
      response.Result && response.Result.data,
      response.Result && response.Result.Data,
      response.Result && response.Result.rows,
      response.Result && response.Result.Rows,
    ];
    for (let index = 0; index < candidates.length; index += 1) {
      if (Array.isArray(candidates[index])) {
        return candidates[index];
      }
    }
    return this.findFirstRowArray(response, 0);
  }

  findFirstRowArray(value: any, depth: number): any[] {
    if (!value || depth > 5) {
      return [];
    }
    if (Array.isArray(value)) {
      if (value.length === 0) {
        return value;
      }
      const first = value[0];
      if (first && typeof first === 'object') {
        return value;
      }
      return [];
    }
    if (typeof value !== 'object') {
      return [];
    }
    const preferredKeys = [
      'items', 'Items', 'rows', 'Rows', 'records', 'Records',
      'listData', 'ListData', 'data', 'Data', 'result', 'Result',
      'list', 'List',
    ];
    for (let keyIndex = 0; keyIndex < preferredKeys.length; keyIndex += 1) {
      const child = value[preferredKeys[keyIndex]];
      const found = this.findFirstRowArray(child, depth + 1);
      if (found.length > 0) {
        return found;
      }
    }
    const keys = Object.keys(value);
    for (let index = 0; index < keys.length; index += 1) {
      const found = this.findFirstRowArray(value[keys[index]], depth + 1);
      if (found.length > 0) {
        return found;
      }
    }
    return [];
  }

  readItemValue(row: any, fieldName: string) {
    if (!row || !fieldName) {
      return '';
    }
    const directValue = this.readObjectValueByKey(row, fieldName);
    if (directValue !== undefined && directValue !== null) {
      return this.normalizeCellValue(directValue);
    }
    const fieldMaps = [row.values, row.Values, row.fields, row.Fields, row.data, row.Data, row.item, row.Item];
    for (let mapIndex = 0; mapIndex < fieldMaps.length; mapIndex += 1) {
      const map = fieldMaps[mapIndex];
      if (map && !Array.isArray(map)) {
        const mappedValue = this.readObjectValueByKey(map, fieldName);
        if (mappedValue !== undefined && mappedValue !== null) {
          return this.normalizeCellValue(mappedValue);
        }
      }
    }
    const fieldArrays = [
      row.fieldValues,
      row.FieldValues,
      row.fields,
      row.Fields,
      row.values,
      row.Values,
      row.data,
      row.Data,
      row.cells,
      row.Cells,
      row.columns,
      row.Columns,
    ];
    for (let groupIndex = 0; groupIndex < fieldArrays.length; groupIndex += 1) {
      const group = fieldArrays[groupIndex];
      if (!Array.isArray(group)) {
        continue;
      }
      for (let itemIndex = 0; itemIndex < group.length; itemIndex += 1) {
        const field = group[itemIndex] || {};
        const fieldId = this.toCleanString(
          field.fieldId !== undefined ? field.fieldId :
          field.FieldId !== undefined ? field.FieldId :
          field.FieldID !== undefined ? field.FieldID :
          field.id !== undefined ? field.id :
          field.Id !== undefined ? field.Id :
          field.ID !== undefined ? field.ID :
          field.name !== undefined ? field.name :
          field.Name !== undefined ? field.Name :
          field.fieldName !== undefined ? field.fieldName :
          field.FieldName !== undefined ? field.FieldName :
          field.fieldCode !== undefined ? field.fieldCode :
          field.FieldCode !== undefined ? field.FieldCode :
          field.code !== undefined ? field.code :
          field.Code !== undefined ? field.Code :
          field.key !== undefined ? field.key :
          field.Key !== undefined ? field.Key : ''
        );
        if (this.sameFieldName(fieldId, fieldName)) {
          return this.normalizeCellValue(
            field.value !== undefined ? field.value :
            field.Value !== undefined ? field.Value :
            field.fieldValue !== undefined ? field.fieldValue :
            field.FieldValue !== undefined ? field.FieldValue :
            field.dataValue !== undefined ? field.dataValue :
            field.DataValue !== undefined ? field.DataValue :
            field.valueText !== undefined ? field.valueText :
            field.ValueText !== undefined ? field.ValueText :
            field.text !== undefined ? field.text :
            field.Text !== undefined ? field.Text :
            field.display !== undefined ? field.display :
            field.Display !== undefined ? field.Display :
            field.label !== undefined ? field.label :
            field.Label !== undefined ? field.Label :
            field.name !== undefined ? field.name :
            field.Name !== undefined ? field.Name : ''
          );
        }
      }
    }
    return '';
  }

  readObjectValueByKey(source: any, wantedKey: string) {
    if (!source || typeof source !== 'object' || Array.isArray(source) || !wantedKey) {
      return undefined;
    }
    if (source[wantedKey] !== undefined) {
      return source[wantedKey];
    }
    const keys = Object.keys(source);
    for (let index = 0; index < keys.length; index += 1) {
      if (this.sameFieldName(keys[index], wantedKey)) {
        return source[keys[index]];
      }
    }
    return undefined;
  }

  sameFieldName(left: any, right: any) {
    const leftText = this.toCleanString(left);
    const rightText = this.toCleanString(right);
    if (!leftText || !rightText) {
      return false;
    }
    return leftText === rightText || leftText.toLowerCase() === rightText.toLowerCase();
  }

  normalizeCellValue(value: any) {
    if (value === undefined || value === null) {
      return '';
    }
    if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
      return value;
    }
    if (Array.isArray(value)) {
      return value.map((item) => this.normalizeCellValue(item)).filter(Boolean).join(', ');
    }
    if (typeof value === 'object') {
      return (
        value.value !== undefined ? value.value :
        value.Value !== undefined ? value.Value :
        value.fieldValue !== undefined ? value.fieldValue :
        value.FieldValue !== undefined ? value.FieldValue :
        value.dataValue !== undefined ? value.dataValue :
        value.DataValue !== undefined ? value.DataValue :
        value.valueText !== undefined ? value.valueText :
        value.ValueText !== undefined ? value.ValueText :
        value.text !== undefined ? value.text :
        value.Text !== undefined ? value.Text :
        value.display !== undefined ? value.display :
        value.Display !== undefined ? value.Display :
        value.label !== undefined ? value.label :
        value.Label !== undefined ? value.Label :
        value.name !== undefined ? value.name :
        value.Name !== undefined ? value.Name :
        value.title !== undefined ? value.title :
        value.Title !== undefined ? value.Title : ''
      );
    }
    return value;
  }

  addMatchedItem(option: LookupOption) {
    const config = this.getConfig();
    if (this.props.readonly || !option) {
      return;
    }
    const item = {
      display: option.display,
      value: option.value,
      isManual: false,
      source: 'selected' as 'selected',
      listDataId: option.listDataId,
    };
    const nextItems = config.multiSelect ? this.dedupeItems(this.state.selectedItems.concat([item])) : [item];
    this.cancelSearch();
    this.setState({
      selectedItems: nextItems,
      loading: false,
      keyword: '',
      options: [],
      dropdownOpen: false,
      errorText: '',
    }, () => this.persistSelectedItems(nextItems));
  }

  addManualItem(text: string) {
    const config = this.getConfig();
    const cleaned = this.toCleanString(text);
    if (this.props.readonly || !config.allowManualEntry || !cleaned) {
      return;
    }
    const item = {
      display: cleaned,
      value: cleaned,
      isManual: true,
      source: 'manual' as 'manual',
    };
    const nextItems = config.multiSelect ? this.dedupeItems(this.state.selectedItems.concat([item])) : [item];
    this.cancelSearch();
    this.setState({
      selectedItems: nextItems,
      loading: false,
      keyword: '',
      options: [],
      dropdownOpen: false,
      errorText: '',
    }, () => this.persistSelectedItems(nextItems));
  }

  removeItem(indexToRemove: number) {
    if (this.props.readonly) {
      return;
    }
    const nextItems = this.state.selectedItems.filter((item: LookupItem, index: number) => index !== indexToRemove);
    this.setState({ selectedItems: nextItems }, () => this.persistSelectedItems(nextItems));
  }

  persistSelectedItems(items: LookupItem[]) {
    const config = this.getConfig();
    const normalizedItems = this.dedupeItems(items).map((item: LookupItem) => ({
      display: item.display,
      value: item.value,
      isManual: item.isManual === true,
      source: item.isManual === true ? 'manual' : 'selected',
    }));
    const matchedValues = normalizedItems
      .filter((item: LookupItem) => item.isManual !== true)
      .map((item: LookupItem) => item.value);
    const manualValues = normalizedItems
      .filter((item: LookupItem) => item.isManual === true)
      .map((item: LookupItem) => item.value);

    if (config.saveToTarget.candidates.length > 0) {
      this.writeTargetValue(config.saveToTarget, JSON.stringify(normalizedItems));
    }
    if (config.selectedItemsTarget.candidates.length > 0) {
      this.writeTargetValue(config.selectedItemsTarget, JSON.stringify(matchedValues));
    }
    if (config.newItemsTarget.candidates.length > 0) {
      this.writeTargetValue(config.newItemsTarget, JSON.stringify(manualValues));
    }
  }

  writeTargetValue(target: WriteTargetInfo, value: any) {
    const candidates = target && target.candidates ? target.candidates : [];
    let saved = false;
    for (let index = 0; index < candidates.length; index += 1) {
      const candidate = candidates[index];
      if (this.writeFieldValue(candidate, value)) {
        saved = true;
        break;
      }
    }
    return saved;
  }

  writeFieldValue(fieldName: string, value: any) {
    const context = this.props.context || {};
    const fieldsValues = this.props.fieldsValues || {};

    if (!fieldName) {
      return false;
    }

    const writerNames = [
      'setFieldValue',
      'setFormFieldValue',
      'setFieldData',
      'setValue',
      'setVariableValue',
      'setVariable',
      'setTempVariableValue',
      'setTempVariable',
      'setTemporaryVariableValue',
      'setTemporaryVariable',
      'setDashboardVariableValue',
      'setPageVariableValue',
      'setFilterVariableValue',
      'setFilterValue',
      'updateFieldValue',
      'updateVariableValue',
    ];
    const setterHosts = this.getSetterHosts(context);

    for (let hostIndex = 0; hostIndex < setterHosts.length; hostIndex += 1) {
      const hostInfo = setterHosts[hostIndex];
      const host = hostInfo.host;
      for (let writerIndex = 0; writerIndex < writerNames.length; writerIndex += 1) {
        const writerName = writerNames[writerIndex];
        const writer = host && host[writerName];
        if (typeof writer === 'function') {
          try {
            const result = writer.call(host, fieldName, value);
            if (result === false) {
              continue;
            }
            return true;
          } catch (error) {
            /* 尝试其他可写入口。 */
          }
        }
      }
    }

    const objectSetterNames = ['setFieldsValue', 'setFieldValues', 'setValues', 'setVariables', 'setTempVariables'];
    for (let hostIndex = 0; hostIndex < setterHosts.length; hostIndex += 1) {
      const hostInfo = setterHosts[hostIndex];
      const host = hostInfo.host;
      for (let writerIndex = 0; writerIndex < objectSetterNames.length; writerIndex += 1) {
        const writerName = objectSetterNames[writerIndex];
        const writer = host && host[writerName];
        if (typeof writer === 'function') {
          try {
            const payload: any = {};
            payload[fieldName] = value;
            const result = writer.call(host, payload);
            if (result === false) {
              continue;
            }
            return true;
          } catch (error) {
            /* 尝试其他可写入口。 */
          }
        }
      }
    }

    fieldsValues[fieldName] = value;
    return true;
  }

  getSetterHosts(context: any) {
    const hosts: any[] = [];
    const addHost = (name: string, host: any) => {
      if (host && hosts.filter((item) => item.host === host).length === 0) {
        hosts.push({ name: name, host: host });
      }
    };
    addHost('context', context);
    addHost('context.formContext', context && context.formContext);
    addHost('context.pageContext', context && context.pageContext);
    addHost('context.dashboardContext', context && context.dashboardContext);
    addHost('context.variableContext', context && context.variableContext);
    addHost('context.variables', context && context.variables);
    addHost('context.tempVariables', context && context.tempVariables);
    addHost('context.runtimeContext', context && context.runtimeContext);
    addHost('context.dataContext', context && context.dataContext);
    return hosts;
  }

  dedupeItems(items: LookupItem[]) {
    const seen: any = {};
    const result: LookupItem[] = [];
    (items || []).forEach((item: LookupItem) => {
      if (!item) {
        return;
      }
      const display = this.toCleanString(item.display || item.value);
      const value = this.toCleanString(item.value || item.display);
      if (!display && !value) {
        return;
      }
      const isManual = item.isManual === true || item.source === 'manual';
      const key = (isManual ? 'manual|' : 'selected|') + this.normalizeText(value || display);
      if (seen[key]) {
        return;
      }
      seen[key] = true;
      result.push({
        display: display || value,
        value: value || display,
        isManual: isManual,
        source: isManual ? 'manual' : 'selected',
        listDataId: this.toCleanString(item.listDataId),
      });
    });
    return result;
  }

  isSelected(value: string, isManual: boolean) {
    const key = (isManual ? 'manual|' : 'selected|') + this.normalizeText(value);
    return this.state.selectedItems.some((item: LookupItem) => {
      const itemKey = (item.isManual ? 'manual|' : 'selected|') + this.normalizeText(item.value);
      return itemKey === key;
    });
  }

  resolveWriteTarget(paramName: string, rawValue: any): WriteTargetInfo {
    return this.resolveWriteTargetFromProps(this.props, paramName, rawValue);
  }

  resolveWriteTargetFromProps(props: any, paramName: string, rawValue: any): WriteTargetInfo {
    const configuredTargets = (props && props.configuredOutputTargets) || {};
    const candidates = this.collectWritableTargetCandidates(configuredTargets[paramName] || rawValue)
      .filter((candidate) => this.isLikelyWritableTargetName(candidate));
    const displayValue = candidates.length > 0 ? candidates[0] : this.toCleanString(rawValue);
    const target = {
      paramName: paramName,
      raw: rawValue,
      candidates: candidates,
      displayValue: displayValue,
    };

    return target;
  }

  isLikelyWritableTargetName(candidate: any) {
    const value = this.toCleanString(candidate);
    if (!value) {
      return false;
    }
    if (value === '[]' || value === '{}') {
      return false;
    }
    if (value.charAt(0) === '[' || value.charAt(0) === '{') {
      return false;
    }
    if (value.indexOf('":') !== -1 || value.indexOf('","') !== -1) {
      return false;
    }
    return value.length <= 160;
  }

  collectWritableTargetCandidates(rawValue: any) {
    const candidates: string[] = [];
    const seen: any = {};
    const addCandidate = (value: any) => {
      const cleaned = this.toCleanString(value);
      if (!cleaned || seen[cleaned]) {
        return;
      }
      seen[cleaned] = true;
      candidates.push(cleaned);
    };

    const visit = (value: any, depth: number) => {
      if (value === undefined || value === null || depth > 4) {
        return;
      }
      if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
        addCandidate(value);
        this.extractExpressionTokens(value).forEach(addCandidate);
        return;
      }
      if (Array.isArray(value)) {
        value.forEach((item) => visit(item, depth + 1));
        return;
      }
      if (typeof value !== 'object') {
        return;
      }

      /* 选择器对象的 value 可能是变量当前值。优先读取标识或名称等元数据来定位写入目标。 */
      [
        'fieldId',
        'FieldId',
        'fieldName',
        'FieldName',
        'variableId',
        'VariableId',
        'variableName',
        'VariableName',
        'tempVariableId',
        'TempVariableId',
        'tempVariableName',
        'TempVariableName',
        'tempVarId',
        'TempVarId',
        'id',
        'Id',
        'key',
        'Key',
        'name',
        'Name',
        'code',
        'Code',
        'label',
        'Label',
        'title',
        'Title',
        'path',
        'Path',
        'value',
        'Value',
      ].forEach((key) => {
        if (value[key] !== undefined && value[key] !== null) {
          addCandidate(value[key]);
          this.extractExpressionTokens(value[key]).forEach(addCandidate);
        }
      });

      [
        'target',
        'Target',
        'binding',
        'Binding',
        'bind',
        'Bind',
        'field',
        'Field',
        'variable',
        'Variable',
        'tempVariable',
        'TempVariable',
        'expression',
        'Expression',
        'data',
        'Data',
        'meta',
        'Meta',
        'metadata',
        'Metadata',
        'props',
        'Props',
      ].forEach((key) => {
        if (value[key] !== undefined && value[key] !== null) {
          visit(value[key], depth + 1);
        }
      });
    };

    visit(rawValue, 0);
    return candidates;
  }

  extractExpressionTokens(value: any) {
    const text = this.toCleanString(value);
    if (!text) {
      return [];
    }
    const tokens: string[] = [];
    const patterns = [
      /\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g,
      /@\{\s*([A-Za-z0-9_.-]+)\s*\}/g,
      /\$\{\s*([A-Za-z0-9_.-]+)\s*\}/g,
      /\b(?:TempVariables|tempVariables|Variables|variables|Fields|fields)\.([A-Za-z0-9_.-]+)\b/g,
    ];
    patterns.forEach((pattern) => {
      let match = pattern.exec(text);
      while (match) {
        if (match[1]) {
          tokens.push(match[1]);
          const parts = match[1].split('.');
          if (parts.length > 1) {
            tokens.push(parts[parts.length - 1]);
          }
        }
        match = pattern.exec(text);
      }
    });
    return tokens;
  }

  toCleanString(value: any) {
    return this.normalizeExpressionValue(value);
  }

  normalizeText(value: any) {
    return this.toCleanString(value).toLowerCase();
  }

  toBoolean(value: any, fallback: boolean) {
    if (value === true || value === false) {
      return value;
    }
    if (Array.isArray(value) && value.length > 0) {
      return this.toBoolean(value[0], fallback);
    }
    if (value && typeof value === 'object') {
      const objectValue =
        value.value !== undefined ? value.value :
        value.Value !== undefined ? value.Value :
        value.key !== undefined ? value.key :
        value.Key !== undefined ? value.Key :
        value.checked !== undefined ? value.checked :
        value.Checked !== undefined ? value.Checked :
        value.selected !== undefined ? value.selected :
        value.Selected !== undefined ? value.Selected :
        value.label !== undefined ? value.label :
        value.Label !== undefined ? value.Label : undefined;
      if (objectValue !== undefined) {
        return this.toBoolean(objectValue, fallback);
      }
    }
    const cleaned = this.normalizeText(value);
    if (['true', '1', 'yes', 'y', 'on'].indexOf(cleaned) !== -1) {
      return true;
    }
    if (['false', '0', 'no', 'n', 'off'].indexOf(cleaned) !== -1) {
      return false;
    }
    return fallback;
  }

  normalizeExpressionValue(value: any) {
    if (value === undefined || value === null) {
      return '';
    }
    if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
      return String(value).trim();
    }
    if (Array.isArray(value)) {
      if (value.length === 0) {
        return '';
      }
      return value.map((item) => this.normalizeExpressionValue(item)).filter(Boolean).join(',');
    }
    if (typeof value === 'object') {
      const resolved =
        value.value !== undefined ? value.value :
        value.Value !== undefined ? value.Value :
        value.key !== undefined ? value.key :
        value.Key !== undefined ? value.Key :
        value.id !== undefined ? value.id :
        value.Id !== undefined ? value.Id :
        value.fieldId !== undefined ? value.fieldId :
        value.FieldId !== undefined ? value.FieldId :
        value.name !== undefined ? value.name :
        value.Name !== undefined ? value.Name :
        value.label !== undefined ? value.label :
        value.Label !== undefined ? value.Label :
        value.title !== undefined ? value.title :
        value.Title !== undefined ? value.Title : undefined;
      if (resolved !== undefined) {
        return this.normalizeExpressionValue(resolved);
      }
      try {
        return JSON.stringify(value);
      } catch (error) {
        return '';
      }
    }
    return String(value).trim();
  }

  toPositiveInt(value: any, fallback: number) {
    const parsed = parseInt(String(value || ''), 10);
    return parsed > 0 ? parsed : fallback;
  }

  uniqueArray(values: string[]) {
    const seen: any = {};
    return values.filter((value) => {
      const cleaned = this.toCleanString(value);
      if (!cleaned || seen[cleaned]) {
        return false;
      }
      seen[cleaned] = true;
      return true;
    });
  }

  renderSelectedChips(config: any) {
    if (!this.state.selectedItems.length) {
      return null;
    }
    return (
      <div className="slp-chip-row">
        {this.state.selectedItems.map((item: LookupItem, index: number) => (
          <span className={item.isManual ? 'slp-chip slp-chip-manual' : 'slp-chip'} key={(item.isManual ? 'm-' : 's-') + item.value + '-' + index}>
            <span className="slp-chip-text">{item.display}</span>
            {item.isManual ? <span className="slp-chip-badge">{config.manualTagText}</span> : null}
            {!this.props.readonly ? (
              <button type="button" className="slp-chip-remove" onClick={() => this.removeItem(index)} aria-label={"移除 " + item.display}>
                ×
              </button>
            ) : null}
          </span>
        ))}
      </div>
    );
  }

  renderDropdown(config: any) {
    if (!this.state.dropdownOpen || this.props.readonly) {
      return null;
    }
    const hasKeyword = Boolean(this.toCleanString(this.state.keyword));
    return (
      <div className="slp-dropdown">
        {this.state.loading ? <div className="slp-status">正在搜索…</div> : null}
        {!this.state.loading && this.state.errorText ? <div className="slp-status slp-status-error">{this.state.errorText}</div> : null}
        {!this.state.loading && !this.state.errorText && hasKeyword && this.state.options.length === 0 ? (
          <div className="slp-status">{config.noResultText}</div>
        ) : null}
        {!this.state.loading && !this.state.errorText && this.state.options.map((option: LookupOption) => (
          <button type="button" className="slp-option" key={option.value + '-' + option.listDataId} onMouseDown={(event) => event.preventDefault()} onClick={() => this.addMatchedItem(option)}>
            {config.multiSelect ? <span className="slp-checkbox" aria-hidden="true" /> : null}
            <span className="slp-option-main">
              <span className="slp-option-title">{option.display}</span>
              <span className="slp-option-subtitle">{option.value}</span>
            </span>
          </button>
        ))}
        {!this.state.loading && !this.state.errorText && config.allowManualEntry && hasKeyword ? (
          <button type="button" className="slp-manual-row" onMouseDown={(event) => event.preventDefault()} onClick={() => this.addManualItem(this.state.keyword)}>
            手工添加「{this.toCleanString(this.state.keyword)}」
          </button>
        ) : null}
      </div>
    );
  }

  render() {
    const config = this.getConfig();
    const readonly = this.props.readonly === true;
    const isConfigMissing = !config.dataListId || !config.displayField || !config.valueField;
    return (
      <div className={readonly ? 'slp-root slp-readonly' : 'slp-root'} ref={(node) => { this.wrapperRef = node; }}>
        <style dangerouslySetInnerHTML={{ __html: smartLookupPickerStyles }} />
        {config.labelText ? <div className="slp-label">{config.labelText}</div> : null}
        <div className={isConfigMissing ? 'slp-shell slp-shell-warning' : 'slp-shell'}>
          {this.renderSelectedChips(config)}
          {!readonly ? (
            <input
              className="slp-input"
              aria-label={config.labelText || "查找记录"}
              value={this.state.keyword}
              placeholder={config.placeholderText}
              onChange={this.handleInputChange}
              onFocus={this.handleInputFocus}
              onKeyDown={this.handleInputKeyDown}
              disabled={readonly}
            />
          ) : null}
          {readonly && !this.state.selectedItems.length ? <div className="slp-readonly-empty">尚未选择记录</div> : null}
        </div>
        {isConfigMissing && !readonly ? (
          <div className="slp-help">配置数据列表 ID、查找显示字段和值字段后，即可搜索。</div>
        ) : null}
        {this.renderDropdown(config)}
      </div>
    );
  }
}

const smartLookupPickerStyles = [
  '.slp-root{position:relative;width:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;color:#172033;}',
  '.slp-label{margin-bottom:8px;color:#172033;font-size:13px;font-weight:600;line-height:1.35;}',
  '.slp-shell{min-height:42px;display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:7px 9px;background:#ffffff;border:1px solid #d8e1ee;border-radius:8px;transition:border-color .16s ease,box-shadow .16s ease,background .16s ease;}',
  '.slp-shell:focus-within{border-color:#146ff6;box-shadow:0 0 0 3px rgba(20,111,246,.1);}',
  '.slp-shell-warning{border-color:#f2c36b;background:#fffaf0;}',
  '.slp-input{flex:1 1 180px;min-width:120px;height:28px;border:0;outline:none;background:transparent;color:#172033;font-size:14px;line-height:28px;}',
  '.slp-input::placeholder{color:#8a97aa;}',
  '.slp-chip-row{display:flex;flex-wrap:wrap;gap:6px;}',
  '.slp-chip{display:inline-flex;max-width:100%;align-items:center;gap:6px;min-height:28px;padding:3px 6px 3px 10px;border:1px solid #cfe0ff;border-radius:999px;background:#edf4ff;color:#0d4fb3;font-size:12px;font-weight:500;line-height:18px;}',
  '.slp-chip-manual{border-color:#d9dee7;background:#f5f7fa;color:#4f5d73;}',
  '.slp-chip-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
  '.slp-chip-badge{padding:1px 5px;border-radius:999px;background:#ffffff;color:#6b778c;font-size:10px;font-weight:600;}',
  '.slp-chip-remove{width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer;font-size:13px;line-height:1;}',
  '.slp-chip-remove:hover{background:rgba(20,111,246,.12);}',
  '.slp-dropdown{position:absolute;z-index:30;left:0;right:0;top:calc(100% + 6px);max-height:280px;overflow:auto;padding:6px;border:1px solid #d8e1ee;border-radius:8px;background:#ffffff;box-shadow:0 10px 24px rgba(21,33,51,.12);}',
  '.slp-option,.slp-manual-row{width:100%;display:flex;align-items:center;gap:10px;padding:9px 10px;border:0;border-radius:6px;background:transparent;color:#172033;text-align:left;cursor:pointer;}',
  '.slp-option:hover,.slp-manual-row:hover{background:#f3f7ff;}',
  '.slp-checkbox{width:15px;height:15px;flex:0 0 15px;border:1px solid #aebbd0;border-radius:4px;background:#ffffff;}',
  '.slp-option-main{min-width:0;display:flex;flex-direction:column;gap:2px;}',
  '.slp-option-title{overflow:hidden;color:#172033;font-size:13px;font-weight:600;line-height:18px;text-overflow:ellipsis;white-space:nowrap;}',
  '.slp-option-subtitle{overflow:hidden;color:#7a879a;font-size:12px;line-height:16px;text-overflow:ellipsis;white-space:nowrap;}',
  '.slp-manual-row{margin-top:4px;border-top:1px solid #eef2f7;border-radius:0 0 6px 6px;color:#4f5d73;font-size:13px;}',
  '.slp-status{padding:11px 10px;color:#6b778c;font-size:13px;line-height:18px;}',
  '.slp-status-error{color:#b42318;}',
  '.slp-help{margin-top:6px;color:#8a6200;font-size:12px;line-height:16px;}',
  '.slp-readonly .slp-shell{background:#f8fafc;}',
  '.slp-readonly-empty{color:#8a97aa;font-size:13px;line-height:24px;}',
  '@media (max-width:520px){.slp-shell{padding:7px;}.slp-input{flex-basis:100%;}.slp-dropdown{max-height:240px;}}',
].join('');
