#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""patch_search_app_originals.py —— 给 search_app.py 的 /originals/ 加三个功能：

① 字段可点即搜索：id_code 变可点链接 → /originals/?q=<值>；再加「选中任意文字 →
   浮层给出搜索选项」的脚本（用于标题/正文里的公司名、产品名）。
② id_code 右侧加外链图标：纯数字 7 位 → ed.industrialinfo.com/NED/ui/#/ED/plant/<码>；
   9 位 → .../confirmedProject/<码>；其它位数不加。
③ 列表页显示正文访问次数：新库 /root/originals_views.db（与 originals.db 隔离，
   不会被同步覆盖），详情页打开时 +1，列表按 id_code 批量取。

安全：逐段 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_origfeat"

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


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


# ── 1. 辅助函数（插在 handle_originals_page 之前）──────────────────────
HELPERS = '''# ===== originals 增强：外链 / 访问计数 / 选中即搜（2026-09-24）=====
_ORIG_VIEWS_DB = '/root/originals_views.db'

def _orig_digits(s):
    """取纯数字（容忍 ._3085230 这类脏前缀）"""
    try:
        return re.sub(r'\\D', '', s or '')
    except Exception:
        return ''

def _orig_ext_url(code):
    """7 位 → plant；9 位 → confirmedProject；其它返回 None"""
    d = _orig_digits(code)
    if len(d) == 7:
        return 'https://ed.industrialinfo.com/NED/ui/#/ED/plant/' + d
    if len(d) == 9:
        return 'https://ed.industrialinfo.com/NED/ui/#/ED/confirmedProject/' + d
    return None

def _orig_vconn():
    c = sqlite3.connect(_ORIG_VIEWS_DB, timeout=15)
    c.execute('PRAGMA busy_timeout=15000')
    c.execute('CREATE TABLE IF NOT EXISTS view_count '
              '(id_code TEXT PRIMARY KEY, n INTEGER NOT NULL DEFAULT 0, last_view TEXT)')
    return c

def _orig_views_get(codes):
    out = {}
    codes = [c for c in codes if c]
    if not codes:
        return out
    try:
        cn = _orig_vconn()
        qs = ','.join('?' * len(codes))
        for k, n in cn.execute('SELECT id_code, n FROM view_count WHERE id_code IN (%s)' % qs, codes):
            out[k] = n
        cn.close()
    except Exception:
        pass
    return out

def _orig_views_inc(code):
    if not code:
        return 0
    try:
        cn = _orig_vconn()
        cn.execute('INSERT INTO view_count (id_code, n, last_view) VALUES (?,1,datetime("now","localtime")) '
                   'ON CONFLICT(id_code) DO UPDATE SET n=n+1, last_view=datetime("now","localtime")', (code,))
        cn.commit()
        row = cn.execute('SELECT n FROM view_count WHERE id_code=?', (code,)).fetchone()
        cn.close()
        return row[0] if row else 0
    except Exception:
        return 0

def _orig_search_link(text, cls=''):
    if not text:
        return ''
    return ('<a class="kw ' + cls + '" href="/originals/?q=' + urllib.parse.quote(str(text)) +
            '" title="按此关键词搜索">' + html.escape(str(text)) + '</a>')

def _orig_ext_icon(code):
    u = _orig_ext_url(code)
    if not u:
        return ''
    d = _orig_digits(code)
    return ('<a class="extlink" href="' + u + '" target="_blank" rel="noopener" '
            'title="在 industrialinfo 打开 ' + d + '"><svg width="12" height="12" viewBox="0 0 24 24" '
            'fill="none" stroke="#1a73e8" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">'
            '<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>'
            '<polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></a>')

_ORIG_EXTRA_CSS = (
    '.kw{color:inherit;border-bottom:1px dotted #9aa0a6}'
    '.kw:hover{color:#1a73e8;border-bottom-color:#1a73e8}'
    '.extlink{display:inline-flex;vertical-align:-1px;margin-left:4px;opacity:.6}'
    '.extlink:hover{opacity:1}'
    '.vc{display:inline-flex;align-items:center;font-size:11px;color:#5f6368;'
    'background:#f1f3f4;border-radius:9px;padding:1px 7px}'
    '.selbar{position:absolute;display:none;z-index:9999;align-items:center;gap:8px;'
    'background:#202124;color:#fff;border-radius:16px;padding:5px 10px;font-size:12px;'
    'box-shadow:0 2px 12px rgba(0,0,0,.35);font-family:sans-serif}'
    '.selbar .seltxt{max-width:220px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;opacity:.85}'
    '.selbar a{color:#8ab4f8;text-decoration:none;font-size:12px}'
    '.selbar .selclose{color:#9aa0a6}'
)

_ORIG_SELECT_JS = r"""<script>
(function(){
  var bar=document.createElement('div');
  bar.className='selbar';
  bar.innerHTML='<span class="seltxt"></span><a class="selbtn" href="#">\\u{1F50D} \\u641C\\u7D22</a><a class="selclose" href="#">\\u00D7</a>';
  document.body.appendChild(bar);
  function hide(){bar.style.display='none';}
  function show(){
    var s=window.getSelection();
    var t=s?s.toString().replace(/\\s+/g,' ').trim():'';
    if(!t||t.length<2||t.length>120){hide();return;}
    var r=s.getRangeAt(0).getBoundingClientRect();
    bar.querySelector('.seltxt').textContent=t.length>32?t.slice(0,32)+'\\u2026':t;
    bar.querySelector('.selbtn').href='/originals/?q='+encodeURIComponent(t);
    bar.style.display='inline-flex';
    var top=r.top-42; if(top<6) top=r.bottom+8;
    bar.style.top=(top+window.scrollY)+'px';
    bar.style.left=Math.max(8,r.left+window.scrollX)+'px';
  }
  document.addEventListener('mouseup',function(e){
    if(e.target.closest('.selbar'))return; setTimeout(show,10);
  });
  document.addEventListener('keyup',function(e){ if(e.key==='Escape') hide(); });
  document.addEventListener('mousedown',function(e){ if(!bar.contains(e.target)) hide(); });
  bar.querySelector('.selclose').addEventListener('click',function(e){
    e.preventDefault(); hide(); if(window.getSelection()) window.getSelection().removeAllRanges();
  });
})();
</script>"""


class SearchHandler(http.server.BaseHTTPRequestHandler):'''

repl("①辅助函数注入", "class SearchHandler(http.server.BaseHTTPRequestHandler):", HELPERS)

# ── 2. 列表页：样式追加 ───────────────────────────────────────────────
repl("②列表样式",
     ".count{font-size:14px;color:#70757a;margin:8px 0;text-align:center}</style></head><body>'",
     ".count{font-size:14px;color:#70757a;margin:8px 0;text-align:center}' + _ORIG_EXTRA_CSS + "
     "'</style></head><body>'")

# ── 3. 列表页：批量取访问次数 ─────────────────────────────────────────
repl("③列表取访问量",
     """            if results:
                h += '<div class=results>'""",
     """            if results:
                _vc = _orig_views_get([(str(r['id_code']).strip() or ('id:' + str(r['id']))) for r in results])
                h += '<div class=results>'""")

# ── 4. 列表页：渲染 id_code（可搜 + 外链）与访问次数 ──────────────────
repl("④列表行渲染",
     """                    h += '<div class=result-item>'
                    h += '<h3><a href="/originals/detail?id=' + str(r['id']) + '">' + html.escape(r['title']) + '</a></h3>'
                    h += '<div class=meta>'
                    h += '<span class=id_code>' + (html.escape(r['id_code']) if r['id_code'] else '-') + '</span>'
                    h += '<span class="src ' + sc_cls + '">' + sl + '</span>'
                    h += '</div></div>'""",
     """                    _key = str(r['id_code']).strip() or ('id:' + str(r['id']))
                    _vn = _vc.get(_key, 0)
                    h += '<div class=result-item>'
                    h += '<h3><a href="/originals/detail?id=' + str(r['id']) + '">' + html.escape(r['title']) + '</a></h3>'
                    h += '<div class=meta>'
                    if r['id_code']:
                        h += ('<span class=id_code>' + _orig_search_link(r['id_code']) +
                              _orig_ext_icon(r['id_code']) + '</span>')
                    else:
                        h += '<span class=id_code>-</span>'
                    h += '<span class="src ' + sc_cls + '">' + sl + '</span>'
                    h += '<span class=vc title="正文被打开的次数">\\U0001F441 ' + str(_vn) + '</span>'
                    h += '</div></div>'""")

# ── 5. 列表页：注入选中即搜脚本 ───────────────────────────────────────
repl("⑤列表选中脚本", "h += '</body></html>'", "h += _ORIG_SELECT_JS + '</body></html>'")

# ── 6. 详情页：计数 +1 ────────────────────────────────────────────────
repl("⑥详情计数",
     """        r = {'id': row[0], 'title': row[1], 'id_code': row[2] or '', 'source': row[3], 'content': row[4] or ''}
        sc = r['source']""",
     """        r = {'id': row[0], 'title': row[1], 'id_code': row[2] or '', 'source': row[3], 'content': row[4] or ''}
        _vkey = r['id_code'].strip() or ('id:' + str(r['id']))
        _vnow = _orig_views_inc(_vkey)
        sc = r['source']""")

# ── 7. 详情页：样式追加 ───────────────────────────────────────────────
repl("⑦详情样式",
     ".content{font-size:14px;line-height:1.8;color:#333;white-space:pre-wrap;word-break:break-word;overflow-x:auto}</style></head><body>'",
     ".content{font-size:14px;line-height:1.8;color:#333;white-space:pre-wrap;word-break:break-word;overflow-x:auto}' + _ORIG_EXTRA_CSS + '</style></head><body>'")

# ── 8. 详情页：渲染 id_code + 访问次数 ────────────────────────────────
repl("⑧详情行渲染",
     """        h += '<span class=id_code>' + (html.escape(r['id_code']) if r['id_code'] else '-') + '</span>'
        h += '<span class="src ' + sc_cls + '">' + sl + '</span>'
        h += '<span>ID: ' + str(r['id']) + '</span>'""",
     """        if r['id_code']:
            h += ('<span class=id_code>' + _orig_search_link(r['id_code']) +
                  _orig_ext_icon(r['id_code']) + '</span>')
        else:
            h += '<span class=id_code>-</span>'
        h += '<span class="src ' + sc_cls + '">' + sl + '</span>'
        h += '<span>ID: ' + str(r['id']) + '</span>'
        h += '<span class=vc title="正文被打开的次数">\\U0001F441 ' + str(_vnow) + ' 次</span>'""")

# ── 9. 详情页：注入选中即搜脚本 ───────────────────────────────────────
repl("⑨详情选中脚本",
     """        h += '<div class=content>' + html.escape(re.sub(r'\\n{3,}', '\\n\\n', r['content'] or '')) + '</div>'
        h += '</div></body></html>'""",
     """        h += '<div class=content>' + html.escape(re.sub(r'\\n{3,}', '\\n\\n', r['content'] or '')) + '</div>'
        h += '</div>' + _ORIG_SELECT_JS + '</body></html>'""")

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

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

try:
    ast.parse(src)
except SyntaxError as e:
    io.open("/tmp/search_app_candidate.py", "w", encoding="utf-8").write(src)
    print("❌ 语法错误，未落盘：%s" % e)
    print("   待检文本已存 /tmp/search_app_candidate.py（第 %s 行）" % e.lineno)
    ln = (e.lineno or 1)
    for i, l in enumerate(src.split("\n")[max(0, ln - 4):ln + 2], start=max(1, ln - 3)):
        print("   %5d|%s" % (i, l[:120]))
    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("✅ 已写入（+%d 字符）  备份 %s" % (len(src) - len(orig), BAK))
print("   改动前 %d 行 → 改动后 %d 行" % (orig.count("\n") + 1, src.count("\n") + 1))
