const {useState, useEffect, useRef, useCallback, useMemo, createContext, useContext} = React;


// ── Context ──────────────────────────────────────────
const ToastContext = createContext();

function ToastProvider({children}) {
  const [toasts, setToasts] = useState([]);
  const addToast = useCallback((msg, type='info') => {
    const id = Date.now();
    setToasts(prev => [...prev, {id, msg, type}]);
    setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 3000);
  }, []);
  return (
    <ToastContext.Provider value={addToast}>
      {children}
      <div className="fixed top-4 right-4 z-50 flex flex-col gap-2.5 pointer-events-none">
        {toasts.map(t => (
          <div key={t.id} className={`pointer-events-auto animate-toast-in px-4 py-3 rounded-xl text-sm font-medium shadow-2xl border backdrop-blur-xl flex items-center gap-2.5
            ${t.type==='success'?'bg-emerald-500/15 border-emerald-500/20 text-emerald-300 shadow-emerald-500/5':
              t.type==='error'?'bg-red-500/15 border-red-500/20 text-red-300 shadow-red-500/5':
              'bg-brand-500/15 border-brand-500/20 text-brand-400 shadow-brand-500/5'}`}>
            <span className={`w-5 h-5 rounded-full flex items-center justify-center text-xs flex-shrink-0 ${
              t.type==='success'?'bg-emerald-500/20':'bg-red-500/20'
            }`}>{t.type==='success'?'✓':t.type==='error'?'✗':'ℹ'}</span>
            <span>{t.msg}</span>
          </div>
        ))}
      </div>
    </ToastContext.Provider>
  );
}

// ── Auth Context ─────────────────────────────────────
const AuthContext = createContext(null);

const ROLE_LABELS = {admin:'管理员', user:'用户', viewer:'访客'};
const ROLE_COLORS = {admin:'text-rose-400', user:'text-brand-400', viewer:'text-zinc-400'};

// ── Agent Metadata (known agents, fallback for unknown) ──
const AGENT_META = {
  // ── 核心智能 (策略 → 执行 主链路) ──
  orchestrator:{icon:'\ud83e\udde0',label:'RR-Agent',desc:'对话运行时核心引擎',cat:'core'},
  strategist:{icon:'\ud83d\udcc8',label:'策略师',desc:'投资策略研判 · 信号合成',cat:'core'},
  trading:{icon:'\ud83d\udcbc',label:'执行',desc:'算法订单 · 仓位管理（用户自主）',cat:'core'},
  // ── 量化研究 (数据 + 分析) ──
  market:{icon:'\ud83d\udcca',label:'行情',desc:'实时行情数据',cat:'quant'},
  analysis:{icon:'\ud83d\udd2c',label:'分析师',desc:'市场深度分析',cat:'quant'},
  intraday:{icon:'\u23f1\ufe0f',label:'盘中',desc:'盘中实时扫描',cat:'quant'},
  news:{icon:'\ud83d\udcf0',label:'资讯',desc:'财经资讯监控',cat:'quant'},
  backtest:{icon:'\ud83d\udcd0',label:'回测',desc:'量化策略回测',cat:'quant'},
};

const CATEGORIES = [
  {id:'core',  label:'核心智能', icon:'\ud83e\udde0', color:'brand',   desc:'策略 → 执行 主链路'},
  {id:'quant', label:'量化研究', icon:'\ud83d\udcca', color:'emerald', desc:'数据 · 分析 · 回测'},
];

function getAgentMeta(name) {
  if (AGENT_META[name]) return AGENT_META[name];
  return {icon:'🤖', label:name, desc:'Agent', cat:'ops'};
}

const CHAT_TARGETS = {
  manager:{icon:'🎯',label:'Manager',desc:'智能管家 · 自动分发'},
  analysis:{icon:'🔬',label:'分析师',desc:'市场深度分析'},
  market:{icon:'📊',label:'行情',desc:'实时行情数据'},
  news:{icon:'📰',label:'新闻',desc:'财经资讯监控'},
  strategist:{icon:'📈',label:'策略师',desc:'投资策略研判'},
  general:{icon:'🧠',label:'通用助手',desc:'知识问答 · 翻译写作'},
  backtest:{icon:'📐',label:'回测',desc:'量化策略回测'},
};

function getChatMeta(target) {
  return CHAT_TARGETS[target] || {icon:'🤖', label:target, desc:'Agent'};
}

const QUICK = [
  {cmd:'zt',label:'涨停板',icon:'📊',desc:'今日涨停股票'},
  {cmd:'lb',label:'连板股',icon:'🔗',desc:'连续涨停个股'},
  {cmd:'bk',label:'板块',icon:'🏷️',desc:'热门板块排行'},
  {cmd:'hot',label:'热股',icon:'🔥',desc:'资金关注热股'},
  {cmd:'summary',label:'市场摘要',icon:'📋',desc:'全市场概览'},
  {cmd:'news',label:'最新新闻',icon:'📰',desc:'财经资讯'},
  {cmd:'strategy',label:'策略建议',icon:'📈',desc:'AI 投资建议'},
  {cmd:'quant 今日热点策略',label:'量化研发',icon:'📐',desc:'量化回测流水线'},
];

const PRESETS = [
  {id:'morning_prep',name:'盘前准备',icon:'🌅',desc:'新闻→行情→分析→策略→推送',steps:5},
  {id:'close_review',name:'收盘复盘',icon:'🌆',desc:'行情→新闻→分析→复盘→风险',steps:7},
  {id:'deep_research',name:'深度研究',icon:'🔬',desc:'全量行情→新闻→深度分析',steps:4},
  {id:'quant_research',name:'量化策略研发',icon:'📐',desc:'Alpha→Coder→Backtest→Risk→PM',steps:4},
  {id:'memory_maintenance',name:'记忆维护',icon:'🧹',desc:'健康→提醒→SOUL→卫生',steps:5},
];

// ── Auth Helpers ─────────────────────────────────────
function getToken() { return localStorage.getItem('rragent_token') || ''; }
function setToken(t) { if (t) localStorage.setItem('rragent_token', t); else localStorage.removeItem('rragent_token'); }
function authHeaders() {
  const t = getToken();
  const h = {'Content-Type':'application/json'};
  if (t) h['Authorization'] = 'Bearer ' + t;
  return h;
}

async function apiPost(url, body)  { return window.__mockApi(url, 'POST', body); }
async function apiGet(url)         { return window.__mockApi(url, 'GET', null); }
async function apiPut(url, body)   { return window.__mockApi(url, 'PUT', body); }
async function apiDelete(url)      { return window.__mockApi(url, 'DELETE', null); }

// Demo: 流式对话用脚本化演示回复 (无后端)
function streamChat(message, target, onEvent) {
  const reply = window.__mockChat ? window.__mockChat(message, target) : '这是演示环境。';
  let i = 0;
  return new Promise(function(resolve) {
    const timer = setInterval(function() {
      if (i >= reply.length) { clearInterval(timer); onEvent({type:'done'}); resolve(); return; }
      onEvent({type:'chunk', content: reply.slice(i, i+2)});
      i += 2;
    }, 18);
  });
}

// ── Token/Cost tracking helpers ─────────────────────
function createTokenSession() {
  return { inputTokens: 0, outputTokens: 0, model: '' };
}

function updateTokenSession(session, usage) {
  return {
    inputTokens: session.inputTokens + (usage.input_tokens || 0),
    outputTokens: session.outputTokens + (usage.output_tokens || 0),
    model: usage.model || session.model,
  };
}

function formatTokenCost(session) {
  // Rough estimate: qwen ~0.002 yuan per 1k tokens
  const totalTokens = session.inputTokens + session.outputTokens;
  const costYuan = totalTokens * 0.000002;
  return {
    inStr: session.inputTokens.toLocaleString(),
    outStr: session.outputTokens.toLocaleString(),
    costStr: costYuan < 0.01 ? costYuan.toFixed(4) : costYuan.toFixed(2),
    model: session.model || 'unknown',
  };
}

function genId() { return 'c_' + Date.now().toString(36) + Math.random().toString(36).slice(2,6); }

function useIsMobile() {
  const [mobile, setMobile] = React.useState(window.innerWidth < 768);
  React.useEffect(() => {
    const handler = () => setMobile(window.innerWidth < 768);
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);
  return mobile;
}

// ── Shared Components ────────────────────────────────

function Spinner({size}) {
  const s = size || 4;
  return (
    <div className="relative inline-flex">
      <div className={`w-${s} h-${s} border-2 border-brand-500/20 border-t-brand-500 rounded-full animate-spin`}></div>
      <div className={`absolute inset-0 w-${s} h-${s} border-2 border-transparent border-b-brand-400/30 rounded-full animate-spin`} style={{animationDirection:'reverse',animationDuration:'1.5s'}}></div>
    </div>
  );
}

function StatusDot({status, size}) {
  const s = size || 2;
  const colors = {
    online:'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,.5)]',
    slow:'bg-amber-400 shadow-[0_0_6px_rgba(251,191,36,.4)]',
    sleeping:'bg-blue-500/60',
    offline:'bg-zinc-600',error:'bg-red-500'
  };
  return <span className={`inline-block w-${s} h-${s} rounded-full ${colors[status]||colors.offline} flex-shrink-0`}></span>;
}

// FreshnessBadge — small pill showing how stale a dataset is.
//   props.ts:   epoch seconds of when the data was produced (required)
//   props.fresh: threshold (sec) for green (default 60)
//   props.slow:  threshold (sec) for amber (default 3600)
//   props.label: optional override (default derives "刚刚 / 2分钟前 / 37分钟前 / 1天前")
// A live ticker re-renders every 15s so the badge stays accurate.
// F6 — tiny inline SVG sparkline. Takes `points` (numeric array) and
// renders it at fixed pixel dimensions. Null/undef points are skipped
// to keep line continuous. Zero-width when fewer than 2 points.
function Sparkline({points, width = 72, height = 20, color = '#818cf8', className}) {
  const valid = (points || []).map(Number).filter(v => Number.isFinite(v));
  if (valid.length < 2) {
    return <div className={`inline-block ${className||''}`} style={{width, height}} />;
  }
  const min = Math.min(...valid);
  const max = Math.max(...valid);
  const span = (max - min) || 1;
  const stepX = width / (valid.length - 1);
  const pathD = valid.map((v, i) => {
    const x = i * stepX;
    const y = height - ((v - min) / span) * (height - 2) - 1;
    return (i === 0 ? 'M' : 'L') + x.toFixed(1) + ',' + y.toFixed(1);
  }).join(' ');
  return (
    <svg className={`inline-block ${className||''}`} width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
      <path d={pathD} fill="none" stroke={color} strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}


function FreshnessBadge({ts, fresh = 60, slow = 3600, className}) {
  const [, setTick] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setTick(t => t + 1), 15000);
    return () => clearInterval(id);
  }, []);
  if (!ts) return null;
  const age = Math.max(0, (Date.now() / 1000) - ts);
  const tone = age < fresh ? 'green' : (age < slow ? 'amber' : 'red');
  const toneCls = tone === 'green'
    ? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/25'
    : tone === 'amber'
    ? 'bg-amber-500/10 text-amber-400 border-amber-500/25'
    : 'bg-red-500/10 text-red-400 border-red-500/25';
  const dotCls = tone === 'green' ? 'bg-emerald-400' : tone === 'amber' ? 'bg-amber-400' : 'bg-red-400';
  const fmt = (sec) => {
    if (sec < 10) return '刚刚';
    if (sec < 60) return `${Math.floor(sec)}s前`;
    if (sec < 3600) return `${Math.floor(sec/60)}分前`;
    if (sec < 86400) return `${Math.floor(sec/3600)}小时前`;
    return `${Math.floor(sec/86400)}天前`;
  };
  return (
    <span className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-mono border ${toneCls} ${className||''}`}
          title={`data produced ${new Date(ts*1000).toLocaleString('zh-CN')}`}>
      <span className={`w-1.5 h-1.5 rounded-full ${dotCls}`}></span>
      {fmt(age)}
    </span>
  );
}

function Card({children, className, onClick, glow}) {
  return (
    <div onClick={onClick}
      className={`bg-surface-2 rounded-2xl border border-border p-5 card-hover gradient-border ${onClick?'cursor-pointer active:scale-[.98]':''} ${glow?'glow-brand':''} ${className||''}`}>
      {children}
    </div>
  );
}

function LoadingBlock({text}) {
  return (
    <div className="flex flex-col items-center gap-3 py-10 justify-center text-zinc-500 animate-fade-in">
      <Spinner size={5} />
      <span className="text-sm font-medium">{text||'加载中...'}</span>
    </div>
  );
}

function SkeletonBlock({lines, className}) {
  const n = lines || 3;
  return (
    <div className={`space-y-3 animate-fade-in ${className||''}`}>
      {Array.from({length: n}).map((_, i) => (
        <div key={i} className="skeleton h-4 rounded-lg" style={{width: `${85 - i * 15}%`, animationDelay: `${i * 0.1}s`}}></div>
      ))}
    </div>
  );
}

function ViewWrapper({children, className}) {
  return <div className={`view-enter flex-1 h-full overflow-auto ${className||''}`}>{children}</div>;
}

class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { hasError: false, error: null }; }
  static getDerivedStateFromError(error) { return { hasError: true, error }; }
  componentDidCatch(error, info) { console.error('ErrorBoundary caught:', error, info); }
  render() {
    if (this.state.hasError) {
      return React.createElement('div', {className:'flex-1 flex flex-col items-center justify-center p-8 text-red-400'},
        React.createElement('h2', {className:'text-lg font-bold mb-2'}, '页面渲染错误'),
        React.createElement('pre', {className:'text-sm text-red-300 bg-red-900/20 rounded-xl p-4 max-w-lg overflow-auto'}, String(this.state.error)),
        React.createElement('button', {className:'mt-4 px-4 py-2 bg-surface-3 rounded-lg text-zinc-300 text-sm hover:bg-surface-4', onClick:()=>this.setState({hasError:false,error:null})}, '重试')
      );
    }
    return this.props.children;
  }
}

function DataBlock({data, loading, placeholder}) {
  if (loading) return <LoadingBlock />;
  if (!data) return <div className="text-zinc-600 text-sm py-4 text-center">{placeholder||'暂无数据'}</div>;
  return <pre className="text-[13px] text-zinc-300 leading-[1.7] font-mono overflow-x-auto max-w-full break-words">{data}</pre>;
}

function ExpandableCode({code, label, defaultExpanded = true}) {
  const lines = code ? code.split('\n') : [];
  const [expanded, setExpanded] = React.useState(defaultExpanded);
  if (!code) return null;
  const copyCode = () => { navigator.clipboard.writeText(code).catch(()=>{}); };
  return (
    <div className="mt-1">
      <div className="flex items-center gap-2 mb-1">
        {label && <span className="text-[9px] text-zinc-600">{label}</span>}
        <span className="text-[9px] text-zinc-600">{lines.length} 行 · {code.length} 字符</span>
        <button onClick={copyCode} className="text-[9px] text-zinc-500 hover:text-zinc-200 transition px-1 py-0.5 rounded bg-surface-3 border border-border/50">复制</button>
        <button onClick={() => setExpanded(!expanded)} className="text-[9px] text-brand-400 hover:text-brand-300 transition">
          {expanded ? '收起' : '展开代码'}
        </button>
      </div>
      {expanded && (
        <pre className="text-[10px] text-zinc-400 font-mono leading-[1.6] overflow-auto bg-surface-0/50 rounded-lg p-2 border border-border/50 max-h-[500px]">
          {code}
        </pre>
      )}
      {!expanded && (
        <div className="text-[9px] text-zinc-600 italic pl-1">代码已折叠（{lines.length} 行）</div>
      )}
    </div>
  );
}

function ExpandableDetail({text, label}) {
  const [expanded, setExpanded] = useState(false);
  if (!text) return null;
  const isLong = text.length > 300;
  return (
    <div className="mt-1">
      {label && <span className="text-[9px] text-zinc-600 mb-1 block">{label}</span>}
      <pre className={`text-[10px] text-red-300/70 font-mono leading-[1.5] overflow-auto bg-red-950/20 rounded-lg p-2 border border-red-900/20 ${expanded ? 'max-h-[400px]' : 'max-h-20'}`}>
        {expanded ? text : text.slice(0, 300)}{!expanded && isLong ? '...' : ''}
      </pre>
      {isLong && (
        <button onClick={() => setExpanded(!expanded)} className="text-[9px] text-red-400 hover:text-red-300 mt-1 transition">
          {expanded ? '收起' : `展开全部 (${text.length} 字符)`}
        </button>
      )}
    </div>
  );
}
