#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""patch_search_app_crawler_views.py

① 访问次数：从 /originals/ 移除，改为显示在 crawler 列表页
   —— 复用库内**既有**的 project_visits（session.db，project_type='crawler'），不另起一套
② 选中即搜（跳 /originals/?q=…）：加到 crawler 列表页
③ 关键修正：抽屉有**预取**(prefetchItem) 也会命中详情处理器 → 会把预取当访问、
   虚增计数。改为只认真实打开（抽屉调用带 view=1；直接访问详情页无 _detail_frag）

安全：逐段 anchor 替换，命中数必须为 1；改前备份；改后 ast 校验。
"""
import ast
import io
import os
import shutil
import sys

P = "/root/gov_crawler/search_app.py"
BAK = "/root/gov_crawler/Archive/search_app.py.bak_20260924_views2crawler"

src = io.open(P, encoding="utf-8").read()
orig = src
edits = []


def repl(name, old, new, expect=1):
    global src
    n = src.count(old)
    if n != expect:
        edits.append((name, "❌ 命中 %d 次（期望 %d）" % (n, expect)))
        return False
    src = src.replace(old, new)
    edits.append((name, "✅"))
    return True


# ── A. 新辅助函数（挂在既有 helper 之后）────────────────────────────
repl("A 新辅助函数",
     "_ORIG_SELECT_JS = r\"\"\"<script>",
     '''def _crawler_views_map(ids):
    """crawler 列表页的「访问次数」——复用既有 project_visits（type='crawler'）。"""
    out = {}
    ids = [str(i) for i in (ids or []) if str(i).strip()]
    if not ids:
        return out
    try:
        cn = sqlite3.connect(SESSION_DB, timeout=15)
        cn.execute('PRAGMA busy_timeout=15000')
        qs = ','.join('?' * len(ids))
        for pid, n in cn.execute(
                "SELECT project_id, COUNT(*) FROM project_visits "
                "WHERE project_type='crawler' AND project_id IN (%s) "
                "GROUP BY project_id" % qs, ids):
            out[str(pid)] = n
        cn.close()
    except Exception:
        pass
    return out


def _crawler_attach_views(d):
    """给列表 items 附上访问次数 v（供前端 rowEl 渲染）"""
    try:
        items = d.get('items') or []
        m = _crawler_views_map([it.get('id') for it in items])
        for it in items:
            it['v'] = m.get(str(it.get('id')), 0)
    except Exception:
        pass
    return d


_ORIG_SELECT_JS = r"""<script>''')

# ── B. 前端 rowEl：meta 行加访问次数 ─────────────────────────────────
repl("B 列表行显示访问次数",
     """      + (it.date ? '<span>' + esc(it.date) + '</span>' : '')
      + tags + '</div>'""",
     """      + (it.date ? '<span>' + esc(it.date) + '</span>' : '')
      + (it.v ? '<span title="被打开次数" style="font-size:11px;color:#5f6368;'
                + 'background:#f1f3f4;border-radius:9px;padding:0 6px">'
                + '\\U0001F441 ' + it.v + '</span>' : '')
      + tags + '</div>'""")

# ── C. 抽屉真实打开 → 带 view=1（预取不带，不计入）──────────────────
repl("C 抽屉标记真实打开",
     """  api('/api/item', {db:'__ROUTE__', id:id}).then(function(d){
    if (!d || !d.ok) return;""",
     """  api('/api/item', {db:'__ROUTE__', id:id, view:1}).then(function(d){
    if (!d || !d.ok) return;""")

# ── D. 详情处理器：预取不算访问 ─────────────────────────────────────
repl("D 预取不计入访问",
     "        _track_project_visit(db_type, pid, getattr(self, 'username', 'anonymous'))",
     """        # 2026-09-24: 抽屉的 prefetchItem 也走这里 → 不能算「打开」。仅当调用方带
        #   view=1（抽屉真实打开）或本身不是抽屉片段（直接访问详情页）才计数。
        _is_frag = getattr(self, '_detail_frag', False)
        try:
            _qp = urllib.parse.parse_qs(urllib.parse.urlparse(self.path or '').query)
        except Exception:
            _qp = {}
        if (not _is_frag) or (_qp.get('view') or [''])[0] == '1':
            _track_project_visit(db_type, pid, getattr(self, 'username', 'anonymous'))""")

# ── E. /api/list 附访问次数 ─────────────────────────────────────────
repl("E /api/list 附计数",
     """                                and (params.get('db') or ['crawler'])[0] != 'crawler'))
            d['ok'] = True""",
     """                                and (params.get('db') or ['crawler'])[0] != 'crawler'))
            if (params.get('db') or ['crawler'])[0] == 'crawler':
                _crawler_attach_views(d)
            d['ok'] = True""")

# ── F. 首屏 boot 数据附访问次数 ─────────────────────────────────────
repl("F 首屏附计数",
     "    boot['range'] = daterange",
     """    if route == 'crawler':
        _crawler_attach_views(boot)
    boot['range'] = daterange""")

# ── G. crawler 列表页注入「选中即搜」────────────────────────────────
repl("G 列表页选中即搜",
     """            + LIST_UI_JS.replace('__ROUTE__', route).replace('__BASE__', meta['base']) +
            '</body></html>')""",
     """            + LIST_UI_JS.replace('__ROUTE__', route).replace('__BASE__', meta['base']) +
            '<style>' + _ORIG_EXTRA_CSS + '</style>' + _ORIG_SELECT_JS +
            '</body></html>')""")

# ── H. /originals/ 列表：移除访问次数 ───────────────────────────────
repl("H originals 列表去计数",
     """                _vc = _orig_views_get([(str(r['id_code']).strip() or ('id:' + str(r['id']))) for r in results])
                h += '<div class=results>'""",
     """                h += '<div class=results>'""")

repl("H2 originals 列表行去计数",
     """                    _key = str(r['id_code']).strip() or ('id:' + str(r['id']))
                    _vn = _vc.get(_key, 0)
                    h += '<div class=result-item>'""",
     """                    h += '<div class=result-item>'""")

repl("H3 originals 列表去计数span",
     """                    h += '<span class=vc title="正文被打开的次数">\\U0001F441 ' + str(_vn) + '</span>'
""",
     "")

# ── I. /originals/ 详情：移除计数与自增 ─────────────────────────────
repl("I originals 详情去自增",
     """        _vkey = r['id_code'].strip() or ('id:' + str(r['id']))
        _vnow = _orig_views_inc(_vkey)
        sc = r['source']""",
     """        sc = r['source']""")

repl("I2 originals 详情去计数span",
     """        h += '<span class=vc title="正文被打开的次数">\\U0001F441 ' + str(_vnow) + ' 次</span>'
""",
     "")

# ── 汇总 ────────────────────────────────────────────────────────────
print("=" * 88)
for n, r in edits:
    print("  %-24s %s" % (n, r))
print("=" * 88)

if [n for n, r in edits if not r.startswith("✅")]:
    print("❌ 有锚点未命中，未改动任何内容")
    sys.exit(1)

try:
    ast.parse(src)
except SyntaxError as e:
    io.open("/tmp/search_app_cand_views.py", "w", encoding="utf-8").write(src)
    print("❌ 语法错误，未落盘：%s（待检 /tmp/search_app_cand_views.py）" % e)
    sys.exit(1)

os.makedirs(os.path.dirname(BAK), exist_ok=True)
if not os.path.exists(BAK):
    shutil.copy2(P, BAK)
io.open(P, "w", encoding="utf-8").write(src)
print("✅ 已写入  备份 %s" % BAK)
print("   行数 %d → %d" % (orig.count("\n") + 1, src.count("\n") + 1))
