#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""拜访与活动：控件收敛（去掉重叠的「全队」）+ KPI 四栏（总数/线下/线上/未标）+ 来源与可判定率。

替换区间同前：`var visitMonth = '';` … `function renderTodos(main) {`
"""
import io
import os
import shutil
import sys
import time

ROOT = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
FILES = [os.path.join(ROOT, '工具', 'index.html'), os.path.join(ROOT, '工具', 'app.html')]
START = "var visitMonth = '';"
END = "function renderTodos(main) {"

NEW = r'''var visitMonth = '';
var visitView = '';                      /* me | dept | cust —— 互斥单一维度，不再有「全队」按钮 */
var visitDrill = { dept: '', sales: '' };
var visitHubState = 'see';
var visitHubCustomer = '';
var visitQ = '';

function visitIsMgr() {
  var r = (state.user || {}).role;
  return r === 'admin' || r === 'gm' || r === 'manager';
}
function visitViewDefault() { return visitIsMgr() ? 'dept' : 'me'; }
function visitScopeOf(v) { return v === 'me' ? 'me' : 'team'; }
function visitViewLabel(v) { return v === 'me' ? '我的' : (v === 'dept' ? '按部门' : '按客户'); }
function visitSetView(v) {
  visitView = v;
  visitDrill = { dept: '', sales: '' };
  visitQ = '';
  render();
}
function visitSetMonth(m) { visitMonth = m; visitDrill = { dept: '', sales: '' }; render(); }
function visitSetState(st) { visitHubState = st; if (st === 'see') visitHubCustomer = ''; render(); }
function visitSetQ(v) { visitQ = v; }
function visitDrillTo(dept, sales) {
  visitDrill = { dept: dept || '', sales: sales || '' };
  if (window.__vj) renderVisitBody(window.__vj);
}
function visitPrepGo(cust) {
  try { sessionStorage.setItem('bior_vp_customer', cust || ''); } catch (e) {}
  switchTab('visitPrep');
  setTimeout(function () { if (typeof showToast === 'function') showToast('已切到「访前准备」｜本次要准备：' + (cust || '（未指定客户）')); }, 400);
}
function visitCrumb() {
  var parts = ['<a href="#" onclick="visitDrillTo(\'\',\'\');return false" style="color:var(--pri)">全部部门</a>'];
  if (visitDrill.dept) parts.push('<a href="#" onclick="visitDrillTo(\'' + esc(visitDrill.dept) + '\',\'\');return false" style="color:var(--pri)">' + esc(visitDrill.dept) + '</a>');
  if (visitDrill.sales) parts.push('<span style="font-weight:700">' + esc(visitDrill.sales) + '</span>');
  return '<div id="visitCrumb" class="text-muted" style="font-size:12.5px;margin:0 0 8px">' + parts.join(' <b>›</b> ') + '</div>';
}
function visitEmpty() {
  return '<div class="todo-empty"><span class="icon">📈</span>该月暂无活动' +
    '<br><span style="font-size:12px">点右上「＋ 记一次拜访/跟进」，或到「客户」里发起拜访</span></div>';
}
var visitActSeq = 0;
function visitCustTable(rows) {
  visitActSeq = 0;
  var body = '';
  rows.forEach(function (c, i) {
    body += '<tr><td>' + esc(c.customer) + '</td>' +
      '<td><b style="color:#1F4E9C">' + c.offline + '</b></td>' +
      '<td><b style="color:#002FA7">' + c.online + '</b></td>' +
      '<td>' + (c.total - c.offline - c.online > 0 ? (c.total - c.offline - c.online) : '—') + '</td>' +
      '<td>' + c.total + '</td>' +
      '<td style="white-space:nowrap">' +
        '<button class="btn btn-sm" onclick="visitToggle(' + i + ')">活动明细</button> ' +
        '<button class="btn btn-sm" onclick="visitPrepGo(&quot;' + esc(c.customer) + '&quot;)">去前准备</button>' +
      '</td></tr>' +
      '<tr id="visitActs' + i + '" style="display:none"><td colspan="6" style="background:#f8f9fa;padding:8px 12px">' +
      (c.acts || []).map(function (a) {
        var n = visitActSeq++;
        var ch = (a.channel === 'offline') ? '<span class="tag tag-blue">线下</span>'
               : (a.channel === 'online') ? '<span class="tag tag-gray">线上</span>'
               : '<span class="tag tag-orange">未标</span>';
        return '<div style="padding:5px 0;border-bottom:1px dashed #e5e7eb">' + ch +
          (a.src === 'feishu' ? ' <span class="tag tag-gray" style="font-size:11px">飞书历史</span>' : '') +
          ' <b>' + esc(a.date || '') + '</b> ｜ ' + esc(a.type || '') + ' ｜ ' + esc(a.sales || '') +
          ' <a href="#" onclick="visitDetail(&quot;' + esc(a.id || '') + '&quot;,' + n + ');return false" style="color:var(--pri)">看详情 →</a>' +
          '<div id="visitDetail' + n + '"></div></div>';
      }).join('') + '</td></tr>';
  });
  return '<div style="overflow-x:auto"><table><thead><tr><th>客户</th>' +
    '<th style="width:80px">线下</th><th style="width:80px">线上</th><th style="width:80px">未标</th>' +
    '<th style="width:70px">合计</th><th style="width:190px"></th></tr></thead><tbody>' + body + '</tbody></table></div>';
}

function renderVisitStats(main) {
  if (!state.user) { main.innerHTML = ''; return; }
  if (!visitMonth) visitMonth = today().slice(0, 7);
  if (!visitView) visitView = visitViewDefault();
  if (!visitIsMgr()) visitView = 'me';
  var mgr = visitIsMgr();

  var head = '<div class="card" style="padding:14px 16px 12px">' +
    '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">' +
      '<h2 style="font-size:19px;font-weight:900;margin:0">📈 拜访与活动</h2>' +
      '<span class="text-muted" style="font-size:12px">录一次、看一次，同一个页面；下钻 部门 › 销售 › 客户 › 明细</span>' +
      '<div class="todo-filters" style="margin-left:auto">' +
        '<button class="btn btn-sm ' + (visitHubState === 'see' ? 'active' : '') + '" onclick="visitSetState(\'see\')">👀 看</button>' +
        '<button class="btn btn-sm ' + (visitHubState === 'rec' ? 'active' : '') + '" onclick="visitSetState(\'rec\')">✍️ 录</button>' +
      '</div>' +
    '</div>';
  head += (visitHubState === 'rec')
    ? '<div class="text-muted" style="font-size:12.5px;margin-top:6px">结构化录入 → 提交后自动回到「看」并计入统计</div></div>'
    : '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:10px">' +
        '<input type="month" class="input" style="width:150px" value="' + esc(visitMonth) + '" onchange="visitSetMonth(this.value)">' +
        '<button class="btn btn-sm" onclick="visitSetMonth(today().slice(0,7));">本月</button>' +
        '<span style="width:8px"></span>' +
        '<div class="todo-filters">' +
          '<button class="btn btn-sm ' + (visitView === 'me' ? 'active' : '') + '" onclick="visitSetView(\'me\')">👤 我的</button>' +
          (mgr ? '<button class="btn btn-sm ' + (visitView === 'dept' ? 'active' : '') + '" onclick="visitSetView(\'dept\')">🏢 按部门</button>' +
                 '<button class="btn btn-sm ' + (visitView === 'cust' ? 'active' : '') + '" onclick="visitSetView(\'cust\')">📋 按客户</button>' : '') +
        '</div>' +
        '<button class="btn btn-primary btn-sm" style="margin-left:auto" onclick="visitSetState(\'rec\')">＋ 记一次拜访/跟进</button>' +
      '</div></div>';

  if (visitHubState === 'rec') {
    var q = visitHubCustomer ? ('&customer=' + encodeURIComponent(visitHubCustomer)) : '';
    main.innerHTML = head +
      '<div class="card" style="padding:0;overflow:hidden"><div style="height:calc(100vh - 240px);min-height:680px;background:#fff">' +
      '<iframe src="/工具/活动录入.html?v=20261007p3' + aeUserQS() + q + '" style="width:100%;height:100%;border:none" title="活动录入"></iframe>' +
      '</div></div>';
    return;
  }

  main.innerHTML = head + '<div class="card"><div class="card-body" id="visitBody">' +
    '<div class="text-muted" style="padding:14px">加载中…</div></div></div>';
  wbReq('/api/visit/stats?month=' + encodeURIComponent(visitMonth) + '&scope=' + encodeURIComponent(visitScopeOf(visitView)),
        { cache: 'no-store' })
    .then(function (j) { renderVisitBody(j); })
    .catch(function (e) {
      var b = document.getElementById('visitBody');
      if (b) b.innerHTML = '<div class="error-box" style="margin:12px">加载失败：' + esc(String((e && e.message) || e)) + '</div>';
    });
}

function visitTrend(j) {
  var t = (j && j.trend) || [];
  if (t.length < 2) return '';
  var mx = 1;
  t.forEach(function (x) { mx = Math.max(mx, x.total || ((x.offline || 0) + (x.online || 0))); });
  return '<div style="display:flex;gap:5px;align-items:flex-end;height:86px;margin:2px 0 14px">' +
    t.map(function (x) {
      var off = x.offline || 0, on = x.online || 0, un = Math.max(0, (x.total || 0) - off - on);
      var h = function (n) { return Math.round((n / mx) * 62); };
      return '<div title="' + esc(x.month) + '：合计 ' + (x.total || 0) + ' ｜ 线下 ' + off + ' ｜ 线上 ' + on + ' ｜ 未标 ' + un +
        '" style="flex:1;text-align:center">' +
        '<div style="height:62px;display:flex;flex-direction:column;justify-content:flex-end">' +
          (h(un) ? '<div style="height:' + h(un) + 'px;background:#cbd5e1;border-radius:3px 3px 0 0"></div>' : '') +
          (h(on) ? '<div style="height:' + h(on) + 'px;background:#002FA7"></div>' : '') +
          (h(off) ? '<div style="height:' + h(off) + 'px;background:#1F4E9C"></div>' : '') +
        '</div>' +
        '<div style="font-size:10px;color:#9aa0a6;margin-top:3px">' + esc(String(x.month || '').slice(5)) + '</div></div>';
    }).join('') +
    '<div style="font-size:10.5px;color:#9aa0a6;align-self:flex-start;margin-left:6px">' +
      '<div>■ <span style="color:#1F4E9C">线下</span></div><div>■ <span style="color:#002FA7">线上</span></div>' +
      '<div>■ <span style="color:#cbd5e1">未标</span></div></div></div>';
}

function kpiCard(label, val, color, sub) {
  return '<div class="stat-card" style="border-left:4px solid ' + color + '">' +
    '<div class="text-muted" style="font-size:12px">' + label + '</div>' +
    '<div class="value" style="color:' + color + '">' + val + '</div>' +
    '<div class="text-muted" style="font-size:12px">' + (sub || '') + '</div></div>';
}

function renderVisitBody(j) {
  var b = document.getElementById('visitBody'); if (!b) return;
  if (!j || !j.ok) {
    b.innerHTML = '<div class="error-box" style="margin:12px">' + esc((j && j.error) || '加载失败') + '</div>';
    return;
  }
  window.__vj = j;
  var vd = j.channel_judge || {};
  var sb = j.src_breakdown || {};

  var html = '<div class="stats-grid" style="margin-bottom:6px">' +
    kpiCard('当月活动总数', j.total, '#5f6368', (j.month || '')) +
    kpiCard('线下拜访', j.offline_count, '#1F4E9C', '按活动形式') +
    kpiCard('线上跟进', j.online_count, '#002FA7', '按活动形式') +
    kpiCard('形式未标', (vd.unknown || 0), '#cbd5e1', '不计入线下/线上') +
    '</div>';
  if (vd.rate !== undefined && vd.rate < 100) {
    html += '<div class="text-muted" style="font-size:12px;margin-bottom:8px">' +
      '形式可判定 <b>' + vd.rate + '%</b>（' + vd.judged + '/' + j.total + '）' +
      '；飞书历史活动无「活动形式」字段，由活动名称+描述文本推断，判不出的一律记「未标」不硬猜。' +
      ((sb.feishu || 0) ? ('　来源：飞书历史 ' + sb.feishu + ' 条' + ((sb.workbench || 0) ? (' + 工作台 ' + sb.workbench + ' 条') : '')) : '') +
      '</div>';
  }
  html += visitTrend(j);

  if (visitView === 'dept') {
    var deps = j.departments || [];
    html += visitCrumb();
    if (!visitDrill.dept) {
      if (!deps.length) { b.innerHTML = html + visitEmpty(); return; }
      b.innerHTML = html + '<div style="overflow-x:auto"><table><thead><tr><th>部门</th>' +
        '<th style="width:80px">线下</th><th style="width:80px">线上</th><th style="width:80px">未标</th>' +
        '<th style="width:70px">合计</th><th style="width:80px">销售数</th><th style="width:90px"></th></tr></thead><tbody>' +
        deps.map(function (d) {
          return '<tr><td><b>' + esc(d.dept) + '</b></td>' +
            '<td><b style="color:#1F4E9C">' + d.offline + '</b></td>' +
            '<td><b style="color:#002FA7">' + d.online + '</b></td>' +
            '<td class="text-muted">' + (d.other || 0) + '</td>' +
            '<td>' + d.total + '</td><td>' + (d.sales_count || 0) + '</td>' +
            '<td><button class="btn btn-sm" onclick="visitDrillTo(&quot;' + esc(d.dept) + '&quot;,&quot;&quot;)">下钻 →</button></td></tr>';
        }).join('') + '</tbody></table></div>';
      return;
    }
    var dep = null;
    for (var i = 0; i < deps.length; i++) { if (deps[i].dept === visitDrill.dept) { dep = deps[i]; break; } }
    if (!dep) { b.innerHTML = html + '<div class="error-box" style="margin:12px">该部门本月无数据</div>'; return; }
    if (!visitDrill.sales) {
      b.innerHTML = html + '<div style="overflow-x:auto"><table><thead><tr><th>销售</th>' +
        '<th style="width:80px">区域</th><th style="width:80px">线下</th><th style="width:80px">线上</th>' +
        '<th style="width:80px">未标</th><th style="width:70px">合计</th><th style="width:80px">客户数</th><th style="width:90px"></th></tr></thead><tbody>' +
        (dep.sales || []).map(function (s) {
          return '<tr><td><b>' + esc(s.name) + '</b></td>' +
            '<td class="text-muted">' + esc(s.region || '—') + '</td>' +
            '<td><b style="color:#1F4E9C">' + s.offline + '</b></td>' +
            '<td><b style="color:#002FA7">' + s.online + '</b></td>' +
            '<td class="text-muted">' + (s.other || 0) + '</td>' +
            '<td>' + s.total + '</td><td>' + (s.customer_count || 0) + '</td>' +
            '<td><button class="btn btn-sm" onclick="visitDrillTo(&quot;' + esc(visitDrill.dept) + '&quot;,&quot;' + esc(s.name) + '&quot;)">下钻 →</button></td></tr>';
        }).join('') + '</tbody></table></div>';
      return;
    }
    var sal = null;
    (dep.sales || []).forEach(function (s) { if (s.name === visitDrill.sales) { sal = s; } });
    if (!sal) { b.innerHTML = html + '<div class="error-box" style="margin:12px">该销售本月无数据</div>'; return; }
    b.innerHTML = html + visitCustTable(sal.customers || []);
    return;
  }

  /* 我的 / 按客户：扁平客户清单 */
  var all = j.customers || [];
  var rows = visitQ ? all.filter(function (c) { return (c.customer || '').indexOf(visitQ) >= 0; }) : all;
  html += '<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px;flex-wrap:wrap">' +
    '<input class="input" style="max-width:240px" placeholder="按客户名筛选…" value="' + esc(visitQ) + '" ' +
      'oninput="visitSetQ(this.value);clearTimeout(window.__vq);window.__vq=setTimeout(function(){renderVisitBody(window.__vj);},280)">' +
    '<span class="text-muted" style="font-size:12px">' + rows.length + ' 家客户</span></div>';
  if (!rows.length) { b.innerHTML = html + visitEmpty(); return; }
  b.innerHTML = html + visitCustTable(rows);
}

function visitToggle(i) {
  var el = document.getElementById('visitActs' + i);
  if (el) el.style.display = (el.style.display === 'none' ? '' : 'none');
}

function visitDetail(id, n) {
  var host = document.getElementById('visitDetail' + n); if (!host) return;
  host.innerHTML = '<div class="text-muted" style="font-size:12px;padding:4px 0">加载中…</div>';
  wbReq('/api/activity/detail?id=' + encodeURIComponent(id), { cache: 'no-store' })
    .then(function (j) {
      if (!j.ok) { host.innerHTML = '<div class="error-box" style="margin:6px 0">' + esc(j.error || '加载失败') + '</div>'; return; }
      var rec = j.activity || {};
      var ch = rec.channel === 'offline' ? '线下拜访' : (rec.channel === 'online' ? '线上跟进' : '未标形式');
      var ev = (rec.channel_ev || []).length ? ('（依据文本：' + esc(rec.channel_ev.join('、')) + '）') : '';
      host.innerHTML = '<div class="text-muted" style="font-size:12px;margin:6px 0 2px">' +
        '活动形式：' + esc(ch) + ev +
        (rec.src === 'feishu' ? ' ｜ <span class="tag tag-gray" style="font-size:11px">飞书历史</span>' : '') +
        ' ｜ 关联订单：' + ((rec.order_ref_nos || []).length ? esc(rec.order_ref_nos.join('、')) : '—') + '</div>' +
        '<pre style="white-space:pre-wrap;font-size:12px;margin:6px 0;max-height:340px;overflow:auto;' +
        'background:#fff;padding:10px;border:1px solid #e5e7eb;border-radius:6px">' + esc(rec.feishu_text || '') + '</pre>';
    })
    .catch(function (e) {
      host.innerHTML = '<div class="error-box" style="margin:6px 0">' + esc(String((e && e.message) || e)) + '</div>';
    });
}

'''


def main():
    apply = '--apply' in sys.argv
    ts = time.strftime('%Y%m%d-%H%M%S')
    bak = os.path.join(ROOT, '_bak_p2c_view_%s' % ts)
    print('控件收敛 + KPI 改造 —— %s\n' % ('APPLY' if apply else 'DRY-RUN'))
    allok = True
    for f in FILES:
        s = io.open(f, encoding='utf-8').read()
        i = s.find(START); j = s.find(END, i)
        if i < 0 or j < 0:
            print('  ❌ %s 区间未找到' % os.path.basename(f)); allok = False; continue
        print('  ✅ %s：替换 %d 字符 → %d 字符' % (os.path.basename(f), j - i, len(NEW)))
        out = s[:i] + NEW + s[j:]
        if apply:
            os.makedirs(bak, exist_ok=True)
            shutil.copy2(f, os.path.join(bak, os.path.basename(f)))
            io.open(f, 'w', encoding='utf-8').write(out)
    if apply and allok:
        print('\n  备份 → %s' % bak)
    print('\n%s' % ('全部命中 ✅' if allok else '有未命中 ❌'))
    return 0 if allok else 1


sys.exit(main())
