if (!window.Helper) { window.Helper = {} }
window.Helper = {
  /**
   * 将 emoney period 转换为可读文字
   * @param x emoney period, number 形式, 如 1, 5, 15, ..., 10000, ...
   */
  emoneyPeriodToName: x => x < 10000 && `${x} 分钟线` || [null, '日线', '周线', '月线', '季线', '半年线', '年线'][x / 10000],
  /**
   * 所有支持的 emoney periods
   */
  allEmoneyPeriods: [1, 5, 15, 30, 60, 120, 10000, 20000, 30000, 40000, 50000, 60000],
  /**
   * 显示指标详情弹层
   */
  showIndexDetailLayer: async function(obj, forceRefresh) {
    // obj: {indexCode: _, indexName: _}
    var layer = layui.layer;
    var load = layer.load(2);
    var url = !!forceRefresh ?
      '/admin/v1/manage/indexInfo/forceRefreshAndGetIndexDetail?indexCode=' :
      '/admin/v1/manage/indexInfo/getIndexDetail?indexCode=';
    url += obj.indexCode;
    var res = await (await fetch(url)).json();
    if (res.ok) {
      // build content
      var html = [];
      for (var i = 0; i < res.data.details.length; i++) {
        var detail = res.data.details[i];
        if (detail.type == 'TITLE') {
          html.push('<h3>');
          html.push(Helper.trimChars(detail.content, ':：'));
          html.push('</h3>');
        }
        else if (detail.type == 'IMAGE') {
          if (detail.content.indexOf('data:image/') == 0 &&
            detail.content.indexOf('base64') != -1) {
            html.push('<img src="');
            html.push(detail.content);
            html.push('">');
          }
        }
        else {
          html.push('<p>');
          html.push(detail.content.replaceAll(/\n+/g, '<br>'));
          html.push('</p>');
        }
      }
      console.log(res.data);
      top.layui.layer.open({
        title: obj.indexName + '指标说明',
        content: html.join(''),
        skin: 'layui-layer-indexDetail',
        area: ['520px', '320px'],
        btn: ['刷新', '确定'],
        btn1: function(index, _, _) {
          top.layui.layer.close(index);
          Helper.showIndexDetailLayer(obj, !0);
        },
        success: function(layero, _) {
          Helper.setLayerMainBtn(layero, -1);
        }
      })
    }
    else {
      Dog.error({ msg: res && res.message || '服务器错误' });
    }
    layer.close(load);
  },
  trimChars: function(str, chars) {
    const escaped = chars.split('').map(c => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('');
    const pattern = new RegExp(`^[${escaped}]+|[${escaped}]+$`, 'g');
    return str.replace(pattern, '');
  },
  /** 
   * 将指定弹层 (layero) 的指定索引的按钮设置为主按钮
   * @param layero 弹层对象
   * @param index 指定索引, 以 0 起始。当为负数时, 以 -1 为倒数第一个按钮
   */
  setLayerMainBtn: function(layero, index) {
    var btns = layero.find('.layui-layer-btn>*'), j = 1;
    if (index < 0) index = btns.length + index;
    for (let i = 0; i < btns.length; i++) {
      const btn = btns[i];
      const clazz = btn.getAttribute('class');
      const classes = clazz.split(' ');
      let filtered = classes.filter(str => !/^layui\-layer\-btn/gi.test(str));
      filtered.push('layui-layer-btn' + (index == i ? '0' : j++));
      btn.setAttribute('class', filtered.join(' '));
    }
  },
  /**
   * 在右侧展开弹层
   * @param option 与 layer 一致的选项
   */
  openR: function(option) {
    const defaultOption = {
      type: 1, area: '500px',
      skin: 'layui-anim layui-anim-rl layui-layer-adminRight',
      anim: -1, shadeClose: !0, closeBtn: !0, move: !1, offset: 'r'
    };
    option = $.extend(defaultOption, option);
    return layui.layer.open(option)
  },
  /**
   * 按照通用配置来渲染表格
   * @param option 和 table.render 选项基本一致, 但需要额外提供：
   * @param option.idName 该表格行对象 id 的名称
   * @param option.baseUrl 当前基本 URL, 将在此基础上拼接 list/updateBool/batchOp 等内容
   * @param option.batchOpEnum 批量操作枚举名，若不提供则不渲染批量操作控件和回调逻辑，若为 true 则仅渲染批量删除逻辑
   * <br>除此以外，cols 内列如果需要开关选项的, 在相应 col 内添加 switchTemplet: true
   */
  renderTable: (option) => {
    const defaultOption = {
      page: !0, skin: 'line'
    };
    option = $.extend(defaultOption, option);
    if (!option.idName) throw new Error('idName 不允许为空');
    if (!option.baseUrl) throw new Error('baseUrl 不允许为空');
    if (!option.baseUrl.endsWith('/')) option.baseUrl += '/';
    option.url = option.baseUrl + 'list';
    let tableSwitchTemplet = function() {
        // 以 elem 选择器 + '.' + switchFilter 作为 filter
        const filter = `${option.elem}.switchFilter`;
        layui.form.on(`switch(${filter})`, function(obj) {
          console.log(obj, obj.elem.checked);
          const data = {
            field: obj.elem.dataset.field,
            value: obj.elem.checked,
            id: obj.elem.dataset.id
          };
          $.ajax({
            url: option.baseUrl + 'updateBool', method: 'POST',
            data: data,
            success: () => {
              Dog.success({ time: 1000 });
              let colOption = option.cols[0].find(x => x.field === obj.elem.dataset.field);
              let refresh = colOption.refresh, mutex = colOption.mutex;
              let tableId = obj.elem.closest('[lay-table-id]').getAttribute('lay-table-id');
              let tableFilter = document.getElementById(tableId).getAttribute('lay-filter');
              if (refresh) {
                Dog.reloadTable(tableFilter);
              }
              else if (mutex) {
                // 互斥, 把当前表格内当前列的所有开关都设为与当前状态相反的状态
                // 先找到所有数据
                layui.table.getData(tableFilter).forEach((v, i) => {
                  if (v[option.idName] != obj.elem.dataset.id) {
                    // 非我行类，全部置反
                    $(`[lay-table-id="${tableFilter}"] tr[data-index="${i}"] [data-field="${obj.elem.dataset.field}"][lay-filter="${filter}"]`).removeAttr('checked')
                  }
                })
              }
            },
            error: function(res) {
              Dog.error({ msg: res })
              // 恢复 enabled 状态
              obj.elem.checked = !obj.elem.checked;
              layui.form.render('checkbox')
              return
            }
          })
        });
      return d => {
        var fieldName = d.LAY_COL.field;
        return `<input type="checkbox" lay-skin="switch" lay-text="|" 
                  data-field="${fieldName}" data-id="${d[option.idName]}" 
                  ${d[fieldName] ? 'checked' : ''} lay-filter="${filter}">`;
      }
    }
    let cols = option.cols[0];
    cols.forEach(col => {
      if (col.switchTemplet) {
        col.templet = tableSwitchTemplet()
      }
    })
    layui.table.render(option)
  },
  onSubmitForm: (submitButtonFilter, func) => {
    layui.form.on(`submit(${submitButtonFilter})`, _ => {
      const buttonEl = $(`[lay-filter="${submitButtonFilter}"]`);
      if (!buttonEl.length) {
        Dog.error({ msg: `找不到对应 filter 为 ${submitButtonFilter} 的 button` });
        return
      }
      const form = $(buttonEl.parents('.layui-form')[0] || buttonEl.parents('form')[0]);
      if (!form.length) {
        Dog.error({ msg: `找不到对应 filter 为 ${submitButtonFilter} 的 button 对应的表单` });
        return
      }
      // 获取 form 内所有表单
      const els = form.find('input[name], select[name], textarea[name], button[name]');
      let obj = { form: form[0], field: {} };
      $.each(els, (i, el) => {
        const name = el.name;
        if (!name) return true
        switch (el.type) {
          case 'checkbox':
            if (el.getAttribute('lay-skin')) {
              // 带 skin 当做二值简单表单
              obj.field[name] = el.value;
            }
            else {
              if (!obj.field.hasOwnProperty(name)) {
                obj.field[name] = [];
              }
              if (el.checked) {
                obj.field[name].push(el.value);
              }
            }
            break;

          case 'radio':
            if (el.checked) {
              obj.field[name] = el.value;
            }
            break;

          case 'select-multiple':
            obj.field[name] = Array.from(el.selectedOptions).map(opt => opt.value);
            break;

          default:
            obj.field[name] = el.value;
        }
      });
      if (func && typeof func === 'function') return func(obj);
      else if (func && typeof func === 'string') {
        // 按照默认来 post
        $.ajax({
          url: func,
          method: 'POST',
          contentType: 'application/json',
          data: JSON.stringify(obj.field),
          success: function(r) {
            Dog.success({
              onClose: () => {
                if (window.editLayer) layui.layer.close(window.editLayer);
                Dog.reloadTable()
              }
            })
          },
          error: res => Dog.error({ msg: res }),
        });
      }
    })
  },
  fillEditForm: (r, layero, layerIndex, extraSwitchFuncs) => {
    const el = $(layero);
    let switchFuncs = [];
    for (let key in r.data) {
      let val = r.data[key];
      const fieldEl = el[0].querySelector(`[name="${key}"]`);
      if (!fieldEl) continue;
      const type = fieldEl.type;
      switch (type) {
        case 'checkbox':
          const checked = fieldEl.value = val == 'true' || val == true;
          const laySkin = fieldEl.getAttribute('lay-skin');
          if (checked) fieldEl.setAttribute('checked', '');
          else fieldEl.removeAttribute('checked');
          if (laySkin) {
            switchFuncs[key] = function(obj) {
              obj.elem.value = obj.elem.checked;
              layui.form.render();
            }
            layui.form.on(`switch(${key})`, function(obj) {
              switchFuncs[obj.elem.name](obj);
            });
            layui.form.render();
            layui.event.call(this, 'form', `switch(${key})`, {
              elem: fieldEl,
              value: checked
            });
          }
          break;
        case 'radio':
          fieldEl.value = fieldEl.chceked = val == 'true' || val == true;
          break;
        case 'select-one':
          const options = fieldEl.querySelectorAll('option');
          options.forEach(option => {
            if (option.value == val) {
              option.selected = true;
              return false;
            }
          })
          break;
        default:
          fieldEl.value = val
      }
      if (type === 'text' || type === 'password' || type === 'hidden') {
        fieldEl.value = val;
      }
    }
    if (extraSwitchFuncs) {
      switchFuncs = $.extend(switchFuncs, extraSwitchFuncs)
    }
  },
  /**
   * @param option.elem dropdown 选择器
   * @param option.tableFilter 数据表格 filter，用以确定操作对哪个表格的数据生效
   * @param option.idName 数据表格 idName
   */
  renderDropdown: option => {
    const defaultOption = {
    };
    option = $.extend(defaultOption, option);
    if (!option.elem) throw new Error('elem 选择器不允许为空');
    if (!option.idName) throw new Error('idName 不允许为空');
    if (!option.tableFilter) throw new Error('数据表格 filter 不允许为空');
    if (!option.url) throw new Error('url 不允许为空');
    let click = (data, othis) => {
      var checked = layui.table.checkStatus(option.tableFilter), ids = [];
      if (!checked.data.length) {
        Dog.error('未选中任何项', { time: 1000 });
        return;
      }
      $.each(checked.data, function(i, row) {
        ids.push(row[option.idName]);
      });
      data = $.extend(data, { ids: ids });
      var op = async function() {
        $.ajax({
          url: option.url,
          method: 'POST',
          data: data,
          success: () => 
            Dog.success({
              msg: '批量操作成功',
              time: 1000,
              onClose: () => Dog.reloadTable(option.tableFilter)
            }),
          error: res => Dog.error({msg: res, time: 2000})
        })
      }
      data.op != 'DELETE' ? op() : layer.confirm('确认批量删除吗？该操作不可恢复', function() {
        op();
      })
    };
    option.click = click;
    layui.dropdown.render(option);
  },
  /**
   * 复制文本
   * @param text 欲复制的文本
   */
  copyText: text => {
    navigator.clipboard.writeText(text).then(() => {
      Dog.success({ msg: '复制成功' })
    }).catch(err => {
      console.error(err);
      Dog.error({ msg: '复制失败' })
    });
  },
  randomHexString: n => [...Array(n)].map(() => 'abcdef0123456789'[~~(Math.random() * 16)]).join(''),
  randomWordString: n => [...Array(n)].map(() => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'[~~(Math.random() * 62)]).join(''),
  /**
   * 监听所有的 input change 事件，包括未来生成的，通过脚本设置值的。只需要运行一次
   */
  listenAllInputChange: function() {
      if (window.Helper && window.Helper.__listenAllInputChangeFlag) return;
      window.Helper.__listenAllInputChangeFlag = !0;
      const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
      Object.defineProperty(HTMLInputElement.prototype, 'value', {
          get: descriptor.get,
          set: function(val) {
              const old = descriptor.get.call(this);
              descriptor.set.call(this, val);

              if (val !== old) {
                  this.dispatchEvent(new Event('input', { bubbles: true }));
              }
          }
      });
  },
  /**
   * 从指定根元素(root)下提取表单值
   * 覆盖：input(含 hidden)、textarea、select(单/多选)、checkbox、radio
   *
   * @param {Element|string} root - DOM 元素或选择器
   * @param {object} [opt]
   * @param {boolean} [opt.includeDisabled=false] - 是否包含 disabled 控件
   * @param {"auto"|"string"|"array"} [opt.checkboxMode="auto"]
   *        checkboxMode:
   *          - auto:      - 同名多个 => array；
   *                       - 单个 => boolean(无 value 时)或 string(有 value 时)
   *          - string:    - 永远输出 string（未选中则不输出该字段）
   *          - array:     - 永远输出 array（未选中则 []）
   *          - checked:   - 单个 checkbox => boolean
   *                       - 同名多个 checkbox => boolean[]（按 DOM 顺序）
   * @param {"last"|"array"} [opt.duplicateMode="array"]
   *        duplicateMode:
   *          - array: 同名非 checkbox/radio 也会聚合成 array
   *          - last: 取最后一个
   * @param {"byName"|"entries"} [opt.output="byName"]
   *        output:
   *          - byName: {name: value}
   *          - entries: [{name, type, value, el}]
   *
   * @returns {object|Array}
   */
  getFormValues(root, opt = {}) {
    
    function cssEscape(s) {
      if (window.CSS && typeof window.CSS.escape === "function") return window.CSS.escape(s);
      return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
    }
    
    const {
      includeDisabled = false,
      checkboxMode = "auto", // "auto" | "string" | "array" | "checked"
      duplicateMode = "array",
      output = "byName",     // "byName" | "entries"
    } = opt;

    const $root = typeof root === "string" ? document.querySelector(root) : root;
    if (!$root) throw new Error("root not found");

    const controls = Array.from($root.querySelectorAll("input, textarea, select"));

    const byName = Object.create(null);
    const entries = [];

    const pushValue = (name, value, meta) => {
      if (!name) return;

      if (output === "entries") {
        entries.push({ name, ...meta, value });
        return;
      }

      if (!(name in byName)) {
        byName[name] = value;
        return;
      }
      if (duplicateMode === "last") {
        byName[name] = value;
        return;
      }
      const cur = byName[name];
      byName[name] = Array.isArray(cur) ? cur.concat([value]) : [cur, value];
    };

    // 用于 checkboxMode="checked"：确保同名组只处理一次
    const processedCheckboxNames = new Set();

    for (const el of controls) {
      if (!includeDisabled && el.disabled) continue;

      const tag = el.tagName.toLowerCase();
      const name = el.name || el.getAttribute("name") || "";

      if (tag === "select") {
        const type = el.multiple ? "select-multiple" : "select-one";
        if (el.multiple) {
          const vals = Array.from(el.selectedOptions).map(o => o.value);
          pushValue(name, vals, { type, el });
        } else {
          pushValue(name, el.value, { type, el });
        }
        continue;
      }

      if (tag === "textarea") {
        pushValue(name, el.value, { type: "textarea", el });
        continue;
      }

      const inputType = (el.type || "text").toLowerCase();

      if (inputType === "radio") {
        if (!el.checked) continue;
        pushValue(name, el.value, { type: "radio", el });
        continue;
      }

      if (inputType === "checkbox") {
        // === 新增模式：checked ===
        if (checkboxMode === "checked") {
          if (!name) continue; // 没 name 没法聚合；如你要支持无 name，也可以改成 entries 模式收集
          if (processedCheckboxNames.has(name)) continue;

          const group = Array.from(
            $root.querySelectorAll(`input[type="checkbox"][name="${cssEscape(name)}"]`)
          ).filter(x => includeDisabled || !x.disabled);

          if (group.length <= 1) {
            const one = group[0] || el; // 理论上 group[0] 就是 el
            pushValue(name, !!one.checked, { type: "checkbox", el: one });
          } else {
            const states = group.map(x => !!x.checked);
            pushValue(name, states, { type: "checkbox", el: group[0] });
          }

          processedCheckboxNames.add(name);
          continue;
        }

        // === 原有模式逻辑 ===
        const hasValueAttr = el.hasAttribute("value");
        const valueAttr = el.value;
        const checked = el.checked;

        const sameNameCheckboxes = name
          ? $root.querySelectorAll(`input[type="checkbox"][name="${cssEscape(name)}"]`)
          : [];
        const isGroup = name && sameNameCheckboxes.length > 1;

        if (checkboxMode === "array" || (checkboxMode === "auto" && isGroup)) {
          if (checked) pushValue(name, valueAttr, { type: "checkbox", el });
          else if (checkboxMode === "array" && !(name in byName) && output !== "entries") {
            byName[name] = [];
          }
        } else if (checkboxMode === "string") {
          if (checked) pushValue(name, valueAttr, { type: "checkbox", el });
        } else {
          if (hasValueAttr) {
            if (checked) pushValue(name, valueAttr, { type: "checkbox", el });
          } else {
            pushValue(name, checked, { type: "checkbox", el });
          }
        }
        continue;
      }

      if (inputType === "file") {
        continue;
      }

      // hidden/text/number/password/date... 都取 value（包含 hidden）
      pushValue(name, el.value, { type: inputType, el });
    }

    return output === "entries" ? entries : byName;
  }
}