#!/usr/bin/env python3
"""静态扫描看板HTML中的JS潜在错误"""
import re, sys, json

HTML = "/Users/jugelizi/hftrading/mac-mini-deploy/trading_dashboard_source/ashare-dashboard.html"
with open(HTML, encoding="utf-8") as f:
    html = f.read()

scripts = re.findall(r'<script[^>]*>(.*?)</script>', html, re.S)
print(f"共 {len(scripts)} 个script块, 总JS字符数: {sum(len(s) for s in scripts)}")

js = "\n;\n".join(s for s in scripts)

issues = []

# 1. 未保护的 toFixed / toLocaleString / floor 调用（对象可能是undefined/null）
# 匹配 X.toFixed(...) 其中X不是 (xx||0) 形式
for m in re.finditer(r'([^\s()\[\]{};,+]\w*)\s*\.\s*toFixed\s*\(', js):
    obj = m.group(1)
    start = max(0, m.start() - 60)
    ctx = js[start:m.end() + 40].replace('\n', ' ')
    # 检查是否已有保护: ||0, ||'', ?., typeof, if(检查
    pre = js[max(0, m.start() - 100):m.start()]
    protected = any(p in pre[-40:] for p in ['|| 0', '||0', '?.', 'num', 'toNum'])
    if not protected:
        issues.append(('toFixed无保护', ctx.strip()[:100]))

# 2. 函数定义 vs 调用
defined = set(re.findall(r'function\s+([A-Za-z_$][\w$]*)\s*\(', js))
defined |= set(re.findall(r'\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:function|\()', js))
defined |= set(re.findall(r'window\.([A-Za-z_$][\w$]*)\s*=', js))
# DOM id 形成的全局
dom_ids = set(re.findall(r'id="([^"]+)"', html))

called = {}
for m in re.finditer(r'(?<![\w$.])([A-Za-z_$][\w$]{3,})\s*\(', js):
    name = m.group(1)
    if name in ('function', 'return', 'if', 'for', 'while', 'switch', 'catch', 'typeof', 'new', 'delete', 'void', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval', 'requestAnimationFrame', 'fetch', 'parseFloat', 'parseInt', 'encodeURIComponent', 'decodeURIComponent', 'Number', 'String', 'Boolean', 'Array', 'Object', 'JSON', 'Math', 'Date', 'Promise', 'RegExp', 'Error', 'TypeError', 'console', 'alert', 'prompt', 'confirm', 'localStorage', 'navigator', 'document', 'window', 'location', 'history', 'performance', 'Map', 'Set', 'WeakMap', 'Symbol', 'Proxy', 'Reflect', 'URL', 'URLSearchParams', 'FormData', 'Headers', 'Request', 'Response', 'AbortController', 'CustomEvent', 'Event', 'MutationObserver', 'IntersectionObserver', 'ResizeObserver', 'matchMedia', 'getComputedStyle', 'structuredClone', 'queueMicrotask', 'atob', 'btoa', 'crypto', 'eval', 'isNaN', 'isFinite', 'sendBeacon', 'now', 'toISOString', 'getElementById', 'querySelector', 'querySelectorAll', 'createElement', 'createTextNode', 'toFixed', 'addEventListener', 'removeEventListener', 'append', 'appendChild', 'prepend', 'insertBefore', 'removeChild', 'replaceChild', 'cloneNode', 'contains', 'closest', 'matches', 'getBoundingClientRect', 'scrollTo', 'scrollBy', 'focus', 'blur', 'click', 'submit', 'reset', 'select', 'remove', 'replace', 'split', 'join', 'slice', 'splice', 'concat', 'push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'indexOf', 'lastIndexOf', 'find', 'findIndex', 'filter', 'map', 'forEach', 'reduce', 'reduceRight', 'some', 'every', 'includes', 'flat', 'flatMap', 'fill', 'keys', 'values', 'entries', 'from', 'isArray', 'of', 'call', 'apply', 'bind', 'test', 'exec', 'toString', 'valueOf', 'trim', 'trimStart', 'trimEnd', 'padStart', 'padEnd', 'repeat', 'startsWith', 'endsWith', 'charAt', 'charCodeAt', 'codePointAt', 'fromCharCode', 'normalize', 'toLowerCase', 'toUpperCase', 'localeCompare', 'parse', 'stringify', 'abs', 'ceil', 'floor', 'round', 'trunc', 'sign', 'sqrt', 'pow', 'exp', 'log', 'sin', 'cos', 'tan', 'atan', 'atan2', 'asin', 'acos', 'min', 'max', 'hypot', 'random', 'getHours', 'getMinutes', 'getSeconds', 'getMilliseconds', 'getTime', 'getFullYear', 'getMonth', 'getDate', 'getDay', 'setHours', 'setMinutes', 'setSeconds', 'setDate', 'setMonth', 'setFullYear', 'getTimezoneOffset', 'toLocaleDateString', 'toLocaleTimeString', 'toLocaleString', 'open', 'close', 'postMessage', 'drawImage', 'fillRect', 'clearRect', 'strokeRect', 'beginPath', 'moveTo', 'lineTo', 'arc', 'fill', 'stroke', 'save', 'restore', 'translate', 'rotate', 'scale', 'setTransform', 'createLinearGradient', 'createRadialGradient', 'addColorStop', 'measureText', 'fillText', 'strokeText', 'getImageData', 'putImageData', 'createImageData', 'setLineDash', 'clip', 'isPointInPath', 'quadraticCurveTo', 'bezierCurveTo', 'closePath', 'setData', 'getData', 'preventDefault', 'stopPropagation', 'stopImmediatePropagation', 'initCustomEvent', 'composedPath', 'then', 'catch', 'finally', 'all', 'race', 'allSettled', 'any', 'resolve', 'reject', 'iterate', 'next', 'return', 'throw', 'canonicalize'):
        continue
    line_no = js[:m.start()].count('\n') + 1
    called.setdefault(name, line_no)

undefined_calls = {k: v for k, v in called.items() if k not in defined and k not in dom_ids and not k.startswith('api')}
# 排除局部变量（函数参数、内部var）——粗略：在js中出现 "name =" 或 "name:" 或 "name," 定义
maybe_local = set(re.findall(r'(?:var|let|const)\s+([A-Za-z_$][\w$]*)', js))
maybe_local |= set(re.findall(r'function\s*[\w$]*\s*\(([^)]*)\)', js)) if False else set()
params = set()
for pm in re.findall(r'function\s*[\w$]*\s*\(([^)]*)\)', js):
    for p in pm.split(','):
        p = p.strip().split('=')[0].strip()
        if p: params.add(p)
undefined_calls = {k: v for k, v in undefined_calls.items() if k not in maybe_local and k not in params}
undefined_calls = {k: v for k, v in undefined_calls.items() if not re.search(r'\b' + re.escape(k) + r'\s*[:=]', js) or k in called and js.count(k) < 3}

print(f"\n=== 未定义函数引用（前30个） ===")
for k, v in sorted(undefined_calls.items(), key=lambda x: x[1])[:30]:
    print(f"  L{v}: {k}")

print(f"\n=== toFixed无保护调用（{len(issues)}处，前40个） ===")
seen = set()
count = 0
for typ, ctx in issues:
    key = ctx[:60]
    if key in seen: continue
    seen.add(key)
    count += 1
    if count > 40: break
    print(f"  {ctx}")

# 3. 性能分析：script块数量、ECharts初始化数量、setInterval数量
print("\n=== 性能概况 ===")
n_init = len(re.findall(r'echarts\.init', js))
n_iv = len(re.findall(r'setInterval', js))
n_to = len(re.findall(r'setTimeout', js))
n_fetch = len(re.findall(r'fetch\s*\(', js))
n_xhr = len(re.findall(r'new XMLHttpRequest', js))
n_gbi = len(re.findall(r'getElementById', js))
n_al = len(re.findall(r'addEventListener', js))
print(f"script块: {len(scripts)}, echarts.init: {n_init}, setInterval: {n_iv}, setTimeout: {n_to}")
print(f"fetch: {n_fetch}, XHR: {n_xhr}, getElementById: {n_gbi}, addEventListener: {n_al}")
print(f"HTML总大小: {len(html)/1024:.0f} KB")
print(f"内联JS大小: {sum(len(s) for s in scripts)/1024:.0f} KB")
# 检查script src
srcs = re.findall(r'<script[^>]*src="([^"]+)"', html)
print(f"外部script: {srcs}")
links = re.findall(r'<link[^>]*href="([^"]+)"', html)
print(f"外部link: {links}")
imgs = re.findall(r'<img[^>]*src="([^"]+)"', html)
print(f"img标签: {len(imgs)}")
