import React,{useCallback,useEffect,useMemo,useRef,useState} from 'react';
import {createRoot} from 'react-dom/client';
import cytoscape from 'cytoscape';
import 'antd/dist/reset.css';
import {Layout,Typography,Card,Form,Input,Button,Select,InputNumber,Alert,Divider,List,Popconfirm,Modal,Tabs,Switch,Space,Tag,Empty,Descriptions,Timeline,ConfigProvider,theme,notification,AutoComplete,Row,Col,Statistic,Badge} from 'antd';

const API=import.meta.env.VITE_API_BASE_URL||'http://localhost:18574/api';
const WS=API.replace(/^http/,'ws')+'/ws';
type Graph={nodes:any[];edges:any[]};
type Filters={search?:string;college?:string;grade?:string;organization_id?:number;position_id?:number;relationship_type?:string;tag_id?:number};

const request=async(path:string,options?:RequestInit)=>{
  const headers=new Headers(options?.headers);
  const token=localStorage.getItem('chronicle_token');
  if(token)headers.set('Authorization','Bearer '+token);
  const response=await fetch(API+path,{...options,headers});
  const text=await response.text();
  if(!response.ok)throw new Error(text||`HTTP ${response.status}`);
  return text?JSON.parse(text):null;
};
const queryString=(values:Record<string,any>)=>{
  const query=new URLSearchParams();
  Object.entries(values).forEach(([key,value])=>{if(value!==undefined&&value!==null&&value!=='')query.set(key,String(value))});
  return query.toString();
};

const App=()=>{
  const graphRef=useRef<HTMLDivElement>(null);
  const cyRef=useRef<any>(null);
  const socketRef=useRef<WebSocket|null>(null);
  const loadRef=useRef<()=>Promise<void>>(async()=>{});
  const [graph,setGraph]=useState<Graph>({nodes:[],edges:[]});
  const [people,setPeople]=useState<any[]>([]);
  const [allPeople,setAllPeople]=useState<any[]>([]);
  const [orgs,setOrgs]=useState<any[]>([]);
  const [positions,setPositions]=useState<any[]>([]);
  const [rels,setRels]=useState<any[]>([]);
  const [events,setEvents]=useState<any[]>([]);
  const [assignments,setAssignments]=useState<any[]>([]);
  const [tags,setTags]=useState<any[]>([]);
  const [relationshipTypes,setRelationshipTypes]=useState<string[]>([]);
  const [analysis,setAnalysis]=useState<any>({});
  const [users,setUsers]=useState<any[]>([]);
  const [logs,setLogs]=useState<any[]>([]);
  const [versions,setVersions]=useState<any[]>([]);
  const [auth,setAuth]=useState<any>(()=>{try{return JSON.parse(localStorage.getItem('chronicle_user')||'null')}catch{return null}});
  const [tab,setTab]=useState('graph');
  const [dark,setDark]=useState(false);
  const [filters,setFilters]=useState<Filters>({});
  const [graphCenter,setGraphCenter]=useState<number|undefined>();
  const [graphDepth,setGraphDepth]=useState(1);
  const [selectedNode,setSelectedNode]=useState<string|null>(null);
  const [detail,setDetail]=useState<any>(null);
  const [edgeDetail,setEdgeDetail]=useState<any>(null);
  const [pathResult,setPathResult]=useState<any>(null);
  const [editState,setEditState]=useState<{kind:string;item:any}|null>(null);
  const [personForm]=Form.useForm();
  const [relationForm]=Form.useForm();
  const [orgForm]=Form.useForm();
  const [positionForm]=Form.useForm();
  const [assignmentForm]=Form.useForm();
  const [eventForm]=Form.useForm();
  const [tagForm]=Form.useForm();
  const [userForm]=Form.useForm();
  const [editForm]=Form.useForm();

  const load=useCallback(async()=>{
    if(!auth)return;
    try{
      const personQuery=queryString({search:filters.search,college:filters.college,grade:filters.grade,organization_id:filters.organization_id,position_id:filters.position_id,tag_id:filters.tag_id});
      const graphQuery=queryString({...filters,person_id:graphCenter,depth:graphCenter?graphDepth:undefined});
      const [g,p,all,o,po,r,e,a,t,rt,an,us,lg,vs]=await Promise.all([
        request('/graph'+(graphQuery?'?'+graphQuery:'')),
        request('/people'+(personQuery?'?'+personQuery:'')),
        request('/people'),request('/organizations'),request('/positions'),request('/relationships'),request('/events'),request('/assignments'),request('/tags'),request('/relationship-types'),request('/analysis'),
        auth.role==='admin'?request('/users'):Promise.resolve([]),
        auth.role==='admin'?request('/logs'):Promise.resolve([]),
        auth.role==='admin'?request('/versions'):Promise.resolve([])
      ]);
      setGraph(g);setPeople(p);setAllPeople(all);setOrgs(o);setPositions(po);setRels(r);setEvents(e);setAssignments(a);setTags(t);setRelationshipTypes(rt);setAnalysis(an);setUsers(us);setLogs(lg);setVersions(vs);
    }catch(error){notification.error({message:'加载失败',description:String(error)})}
  },[auth,filters,graphCenter,graphDepth]);

  useEffect(()=>{loadRef.current=load},[load]);
  useEffect(()=>{
    if(!auth)return;
    loadRef.current();
    const socket=new WebSocket(WS);socketRef.current=socket;socket.onmessage=()=>loadRef.current();
    return()=>{socket.close();socketRef.current=null};
  },[auth]);

  const showDetail=useCallback(async(id:number)=>{
    try{setDetail(await request('/person/'+id+'/detail'))}catch(error){notification.error({message:'人物详情加载失败',description:String(error)})}
  },[]);
  useEffect(()=>{
    if(!graphRef.current)return;
    cyRef.current?.destroy();
    const instance=cytoscape({container:graphRef.current,elements:graph,style:[
      {selector:'node',style:{'label':'data(label)','background-color':'#1677ff','color':'#fff','text-valign':'center','text-halign':'center','width':'mapData(degree,0,12,30,68)','height':'mapData(degree,0,12,30,68)','font-size':12,'border-width':1,'border-color':'#0958d9'}},
      {selector:'node[leader = "true"]',style:{'background-color':'#fa8c16','border-width':3,'border-color':'#d46b08'}},
      {selector:'node.selected',style:{'background-color':'#722ed1','border-width':4,'border-color':'#391085'}},
      {selector:'edge',style:{'label':'data(label)','width':'mapData(strength,1,5,1,6)','line-color':'#8c8c8c','font-size':10,'color':'#595959','curve-style':'bezier'}},
      {selector:'edge[directed = "true"]',style:{'target-arrow-color':'#8c8c8c','target-arrow-shape':'triangle'}}
    ],layout:{name:'cose',animate:true,fit:true,padding:30}});
    instance.on('tap','node',(event:any)=>{const node=event.target;instance.$('node').removeClass('selected');node.addClass('selected');setSelectedNode(node.id());showDetail(Number(node.id()))});
    instance.on('tap','edge',(event:any)=>setEdgeDetail(event.target.data()));
    cyRef.current=instance;
    return()=>{instance.destroy();cyRef.current=null};
  },[graph,showDetail]);

  const post=async(path:string,data:any,form?:any)=>{
    try{await request(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});form?.resetFields();await load();notification.success({message:'保存成功'})}
    catch(error){notification.error({message:'操作失败',description:String(error)})}
  };
  const remove=async(path:string)=>{
    try{await request(path,{method:'DELETE'});await load();notification.success({message:'删除成功'})}
    catch(error){notification.error({message:'操作失败',description:String(error)})}
  };
  const download=async(format:'json'|'csv'|'xlsx')=>{
    try{
      const token=localStorage.getItem('chronicle_token');const response=await fetch(API+'/export?format='+format,{headers:token?{Authorization:'Bearer '+token}:{}});
      if(!response.ok)throw new Error(await response.text());
      const blob=await response.blob();const url=URL.createObjectURL(blob);const anchor=document.createElement('a');anchor.href=url;anchor.download='chronicle-export.'+format;anchor.click();URL.revokeObjectURL(url);
    }catch(error){notification.error({message:'导出失败',description:String(error)})}
  };
  const downloadBackup=async()=>{
    try{
      const token=localStorage.getItem('chronicle_token');const response=await fetch(API+'/backup',{headers:token?{Authorization:'Bearer '+token}:{}});
      if(!response.ok)throw new Error(await response.text());
      const blob=await response.blob();const url=URL.createObjectURL(blob);const anchor=document.createElement('a');anchor.href=url;anchor.download='chronicle-backup.json';anchor.click();URL.revokeObjectURL(url);
    }catch(error){notification.error({message:'备份失败',description:String(error)})}
  };
  const restoreBackup=()=>{
    if(auth.role!=='admin'||!window.confirm('恢复备份会替换当前业务数据，但不会覆盖用户账户和操作日志，确认继续吗？'))return;
    const input=document.createElement('input');input.type='file';input.accept='.json';input.onchange=async()=>{const file=input.files?.[0];if(!file)return;try{const backup=JSON.parse(await file.text());const result=await request('/restore',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(backup)});notification.success({message:'恢复完成',description:Object.entries(result).map(([key,value])=>`${key}: ${value}`).join('，')});await load()}catch(error){notification.error({message:'恢复失败',description:String(error)})}};input.click();
  };
  const createVersion=async()=>{
    const label=window.prompt('版本说明（可选）');if(label===null)return;
    try{await request('/versions',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({label})});await load();notification.success({message:'历史版本已创建'})}
    catch(error){notification.error({message:'创建版本失败',description:String(error)})}
  };
  const downloadVersion=async(id:number)=>{
    try{const version=await request('/versions/'+id);const blob=new Blob([JSON.stringify(version,null,2)],{type:'application/json;charset=utf-8'});const url=URL.createObjectURL(blob);const anchor=document.createElement('a');anchor.href=url;anchor.download='chronicle-version-'+id+'.json';anchor.click();URL.revokeObjectURL(url)}
    catch(error){notification.error({message:'版本导出失败',description:String(error)})}
  };
  const restoreVersion=async(id:number)=>{
    if(!window.confirm('恢复该历史版本会替换当前业务数据，但不会覆盖用户账户和操作日志；恢复操作本身也会生成新的历史版本。确认继续吗？'))return;
    try{const result=await request('/versions/'+id+'/restore',{method:'POST'});await load();notification.success({message:'历史版本已恢复',description:Object.entries(result).map(([key,value])=>`${key}: ${value}`).join('，')})}
    catch(error){notification.error({message:'版本恢复失败',description:String(error)})}
  };
  const downloadGraphImage=(format:'png'|'svg')=>{
    const cy=cyRef.current;if(!cy||!cy.nodes().length){notification.warning({message:'当前没有可导出的图谱'});return}
    if(format==='png'){
      const url=cy.png({full:true,bg:'#ffffff',scale:2});const anchor=document.createElement('a');anchor.href=url;anchor.download='chronicle-graph.png';anchor.click();return;
    }
    const padding=40,bbox=cy.elements().boundingBox();const width=Math.max(320,bbox.w+padding*2),height=Math.max(240,bbox.h+padding*2);const point=(position:any)=>({x:position.x-bbox.x+padding,y:position.y-bbox.y+padding});const escape=(value:any)=>String(value||'').replace(/[&<>"']/g,(char:string)=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&apos;'}[char]||char));
    const lines=cy.edges().map((edge:any)=>{const source=point(edge.source().position()),target=point(edge.target().position());return `<line x1="${source.x}" y1="${source.y}" x2="${target.x}" y2="${target.y}" stroke="#8c8c8c" stroke-width="${Math.max(1,Number(edge.data('strength')||1))}"/>`}).join('');
    const nodes=cy.nodes().map((node:any)=>{const position=point(node.position());return `<circle cx="${position.x}" cy="${position.y}" r="${Math.max(15,Number(node.renderedWidth())/2)}" fill="${node.data('leader')?'#fa8c16':'#1677ff'}"/><text x="${position.x}" y="${position.y+4}" text-anchor="middle" fill="#ffffff" font-size="12">${escape(node.data('label'))}</text>`}).join('');
    const blob=new Blob([`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect width="100%" height="100%" fill="#ffffff"/>${lines}${nodes}</svg>`],{type:'image/svg+xml;charset=utf-8'});const url=URL.createObjectURL(blob);const anchor=document.createElement('a');anchor.href=url;anchor.download='chronicle-graph.svg';anchor.click();URL.revokeObjectURL(url);
  };
  const downloadGraphPdf=async()=>{
    const cy=cyRef.current;if(!cy||!cy.nodes().length){notification.warning({message:'当前没有可导出的图谱'});return}
    try{
      const payload={nodes:cy.nodes().map((node:any)=>({id:node.id(),label:node.data('label'),leader:node.data('leader'),x:node.position().x,y:node.position().y})),edges:cy.edges().map((edge:any)=>({source:edge.source().id(),target:edge.target().id(),strength:edge.data('strength'),label:edge.data('label')}))};
      const token=localStorage.getItem('chronicle_token');const response=await fetch(API+'/graph-export',{method:'POST',headers:{'Content-Type':'application/json',...(token?{Authorization:'Bearer '+token}:{})},body:JSON.stringify(payload)});
      if(!response.ok)throw new Error(await response.text());
      const blob=await response.blob();const url=URL.createObjectURL(blob);const anchor=document.createElement('a');anchor.href=url;anchor.download='chronicle-graph.pdf';anchor.click();URL.revokeObjectURL(url);
    }catch(error){notification.error({message:'PDF 导出失败',description:String(error)})}
  };
  const importDataFile=()=>{
    const input=document.createElement('input');input.type='file';input.accept='.json,.csv,.xlsx';
    input.onchange=async()=>{const file=input.files?.[0];if(!file)return;try{const formData=new FormData();formData.append('file',file);const result=await request('/import-file',{method:'POST',body:formData});notification.success({message:'导入完成',description:Object.entries(result).map(([key,value])=>`${key}: ${value}`).join('，')});await load()}catch(error){notification.error({message:'导入失败',description:String(error)})}};
    input.click();
  };
  const login=async(values:any)=>{try{const result=await request('/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(values)});localStorage.setItem('chronicle_token',result.token);localStorage.setItem('chronicle_user',JSON.stringify(result));setAuth(result);notification.success({message:'登录成功'})}catch(error){notification.error({message:'登录失败',description:String(error)})}};
  const personOptions=useMemo(()=>allPeople.map(p=>({value:p.id,label:p.name})),[allPeople]);
  const orgOptions=useMemo(()=>orgs.map(o=>({value:o.id,label:o.name})),[orgs]);
  const positionOptions=useMemo(()=>positions.map(p=>({value:p.id,label:p.name})),[positions]);
  const tagOptions=useMemo(()=>tags.map(t=>({value:t.id,label:t.name})),[tags]);
  const relationOptions=useMemo(()=>relationshipTypes.map(t=>({value:t,label:t})),[relationshipTypes]);
  const collegeOptions=useMemo(()=>Array.from(new Set(allPeople.map(p=>p.college).filter(Boolean))).map(x=>({value:x,label:x})),[allPeople]);
  const gradeOptions=useMemo(()=>Array.from(new Set(allPeople.map(p=>p.grade).filter(Boolean))).map(x=>({value:x,label:x})),[allPeople]);

  const openEdit=(kind:string,item:any)=>{setEditState({kind,item});editForm.resetFields();};
  const editInitial=useMemo(()=>{
    if(!editState)return {};
    const item=editState.item;
    if(editState.kind==='event')return {...item,participants:(item.participants||[]).map((x:any)=>x.person_id)};
    return {...item,password:''};
  },[editState]);
  const saveEdit=async(values:any)=>{
    if(!editState)return;
    let path='';let payload=values;
    if(editState.kind==='person')path='/people/'+editState.item.id;
    if(editState.kind==='organization')path='/organizations/'+editState.item.id;
    if(editState.kind==='position')path='/positions/'+editState.item.id;
    if(editState.kind==='assignment')path='/assignments/'+editState.item.id;
    if(editState.kind==='relationship')path='/relationships/'+editState.item.id;
    if(editState.kind==='event'){path='/events/'+editState.item.id;payload={...values,participants:(values.participants||[]).map((id:number)=>({person_id:id}))};}
    if(editState.kind==='user')path='/users/'+editState.item.id;
    try{await request(path,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});setEditState(null);await load();notification.success({message:'修改成功'})}catch(error){notification.error({message:'修改失败',description:String(error)})}
  };
  const findPath=async(values:any)=>{try{setPathResult(await request('/paths?'+queryString({source_id:values.source_id,target_id:values.target_id,max_hops:values.max_hops||6})))}catch(error){notification.error({message:'路径查询失败',description:String(error)})}};

  const filterBar=<Space wrap style={{marginBottom:12}}>
    <Input.Search allowClear placeholder="搜索姓名/学院/组织/职位/标签" value={filters.search} onChange={e=>setFilters(x=>({...x,search:e.target.value}))} onSearch={value=>setFilters(x=>({...x,search:value}))} style={{width:230}}/>
    <Select allowClear placeholder="学院" value={filters.college} options={collegeOptions} onChange={value=>setFilters(x=>({...x,college:value}))} style={{width:130}}/>
    <Select allowClear placeholder="年级" value={filters.grade} options={gradeOptions} onChange={value=>setFilters(x=>({...x,grade:value}))} style={{width:110}}/>
    <Select allowClear placeholder="组织" value={filters.organization_id} options={orgOptions} onChange={value=>setFilters(x=>({...x,organization_id:value}))} style={{width:140}}/>
    <Select allowClear placeholder="职位" value={filters.position_id} options={positionOptions} onChange={value=>setFilters(x=>({...x,position_id:value}))} style={{width:120}}/>
    <Select allowClear placeholder="关系类型" value={filters.relationship_type} options={relationOptions} onChange={value=>setFilters(x=>({...x,relationship_type:value}))} style={{width:130}}/>
    <Select allowClear placeholder="标签" value={filters.tag_id} options={tagOptions} onChange={value=>setFilters(x=>({...x,tag_id:value}))} style={{width:120}}/>
    <Button onClick={()=>setFilters({})}>清空筛选</Button>
  </Space>;

  const graphTools=<>
    {filterBar}
    <Space wrap style={{marginBottom:12}}><Select allowClear showSearch optionFilterProp="label" placeholder="中心人物" value={graphCenter} options={personOptions} onChange={value=>setGraphCenter(value)} style={{width:150}}/><Select disabled={!graphCenter} value={graphDepth} options={[{value:1,label:'展开一级关系'},{value:2,label:'展开二级关系'},{value:3,label:'展开三级关系'}]} onChange={value=>setGraphDepth(value)} style={{width:150}}/><Button onClick={()=>{setGraphCenter(undefined);setGraphDepth(1)}}>显示完整图谱</Button></Space>
    <Space wrap style={{marginBottom:12}}><Button onClick={()=>cyRef.current?.fit(undefined,30)}>适应屏幕</Button><Button onClick={()=>cyRef.current?.zoom(cyRef.current.zoom()*1.2)}>放大</Button><Button onClick={()=>cyRef.current?.zoom(cyRef.current.zoom()/1.2)}>缩小</Button><Button onClick={()=>{cyRef.current?.elements().unselect();setSelectedNode(null)}}>取消选中</Button><Button onClick={()=>downloadGraphImage('png')}>导出 PNG</Button><Button onClick={()=>downloadGraphImage('svg')}>导出 SVG</Button><Button onClick={downloadGraphPdf}>导出 PDF</Button></Space>
    <Typography.Paragraph type="secondary">可按中心人物展开一级、二级或三级关系；节点大小表示直接关系数量，橙色节点表示组织负责人。点击节点查看人物档案，点击关系查看来源和推导信息。</Typography.Paragraph>
    <Form layout="inline" onFinish={findPath} style={{marginBottom:12}}><Form.Item name="source_id" rules={[{required:true,message:'请选择起点'}]}><Select placeholder="起点" style={{width:120}} options={personOptions}/></Form.Item><Form.Item name="target_id" rules={[{required:true,message:'请选择终点'}]}><Select placeholder="终点" style={{width:120}} options={personOptions}/></Form.Item><Form.Item name="max_hops" initialValue={6}><InputNumber min={1} max={12} placeholder="最多跳数"/></Form.Item><Button htmlType="submit">查找路径</Button></Form>
    <Space wrap><Button disabled={auth.role==='viewer'} onClick={()=>download('json')}>导出 JSON</Button><Button disabled={auth.role==='viewer'} onClick={()=>download('csv')}>导出 CSV</Button><Button disabled={auth.role==='viewer'} onClick={()=>download('xlsx')}>导出 Excel</Button><Button disabled={auth.role==='viewer'} onClick={importDataFile}>导入数据</Button><Button disabled={auth.role!=='admin'} onClick={downloadBackup}>备份数据</Button><Button danger disabled={auth.role!=='admin'} onClick={restoreBackup}>恢复备份</Button></Space>
    <Divider/><Row gutter={8}><Col span={6}><Statistic title="人物" value={analysis.people_count||0}/></Col><Col span={6}><Statistic title="关系" value={analysis.relationship_count||0}/></Col><Col span={6}><Statistic title="事件" value={analysis.event_count||0}/></Col><Col span={6}><Statistic title="组织" value={analysis.organization_count||0}/></Col></Row>
    <Row gutter={8} style={{marginTop:12}}><Col span={8}><Statistic title="网络密度" value={Number(analysis.network?.density||0)*100} precision={2} suffix="%"/></Col><Col span={8}><Statistic title="连通分量" value={analysis.network?.component_count||0}/></Col><Col span={8}><Statistic title="平均路径" value={analysis.network?.average_path_length||0} precision={2}/></Col></Row>
    <Divider/><Typography.Text strong>关系中心人物（按直接关联人数）</Typography.Text><List size="small" dataSource={analysis.top_people||[]} renderItem={(item:any)=><List.Item>{item.name}<Badge count={item.degree} style={{backgroundColor:'#1677ff'}}/></List.Item>} locale={{empty:<Empty description="暂无分析数据"/>}}/>
    <Divider/><Typography.Text strong>中介中心性排行</Typography.Text><List size="small" dataSource={(analysis.top_betweenness||[]).slice(0,5)} renderItem={(item:any)=><List.Item>{item.name}<Typography.Text type="secondary">{item.betweenness}</Typography.Text></List.Item>} locale={{empty:<Empty description="暂无分析数据"/>}}/>
  </>;

  const peoplePanel=<><Typography.Text strong>新增人物</Typography.Text><Form form={personForm} layout="vertical" onFinish={values=>post('/people',values,personForm)} style={{marginTop:12}}><Form.Item name="name" label="姓名" rules={[{required:true}]}><Input placeholder="例如：张三"/></Form.Item><Form.Item name="grade" label="年级"><Input/></Form.Item><Form.Item name="college" label="学院"><Input/></Form.Item><Form.Item name="major" label="专业"><Input/></Form.Item><Form.Item name="bio" label="简介"><Input.TextArea rows={2}/></Form.Item><Form.Item name="tag_ids" label="标签"><Select mode="multiple" options={tagOptions} placeholder="可多选标签"/></Form.Item><Button type="primary" htmlType="submit" block>添加人物</Button></Form><Divider/><List size="small" dataSource={people} locale={{empty:<Empty description="暂无人物"/>}} renderItem={p=><List.Item actions={[<Button size="small" onClick={()=>openEdit('person',p)}>编辑</Button>,<Popconfirm title="确认删除人物及其关系？" onConfirm={()=>remove('/people/'+p.id)}><Button danger size="small">删除</Button></Popconfirm>]}><Space>{p.name}<Typography.Text type="secondary">{p.college||''}</Typography.Text>{(p.tags||[]).map((t:any)=><Tag key={t.id}>{t.name}</Tag>)}</Space></List.Item>}/></>;

  const relationPanel=<><Typography.Text strong>新增关系</Typography.Text><Form form={relationForm} layout="vertical" onFinish={values=>post('/relationships',values,relationForm)} style={{marginTop:12}}><Form.Item name="source_id" label="人物 A" rules={[{required:true}]}><Select showSearch optionFilterProp="label" options={personOptions}/></Form.Item><Form.Item name="target_id" label="人物 B" rules={[{required:true}]}><Select showSearch optionFilterProp="label" options={personOptions}/></Form.Item><Form.Item name="type" label="关系类型" initialValue="合作" rules={[{required:true}]}><AutoComplete options={relationOptions} placeholder="可选择或输入自定义类型"/></Form.Item><Form.Item name="strength" label="强度" initialValue={3}><InputNumber min={1} max={5} style={{width:'100%'}}/></Form.Item><Form.Item name="event_id" label="关联事件"><Select allowClear options={events.map(e=>({value:e.id,label:e.name}))}/></Form.Item><Form.Item name="description" label="描述"><Input.TextArea rows={2}/></Form.Item><Form.Item name="source" label="关系来源"><Input/></Form.Item><Form.Item name="confidence" label="置信度" initialValue={100}><InputNumber min={0} max={100} style={{width:'100%'}}/></Form.Item><Button type="primary" htmlType="submit" block>添加关系</Button></Form><Divider/><List size="small" dataSource={rels} locale={{empty:<Empty description="暂无关系"/>}} renderItem={r=><List.Item actions={[<Button size="small" onClick={()=>openEdit('relationship',r)}>编辑</Button>,<Popconfirm title="确认删除？" onConfirm={()=>remove('/relationships/'+r.id)}><Button danger size="small">删除</Button></Popconfirm>]}><Space>{r.source_name} → {r.target_name}<Tag color={r.origin==='event'?'orange':'blue'}>{r.type}</Tag>{r.event_name&&<Typography.Text type="secondary">· {r.event_name}</Typography.Text>}</Space></List.Item>}/></>;

  const orgPanel=<><Typography.Text strong>新增组织</Typography.Text><Form form={orgForm} layout="vertical" onFinish={values=>post('/organizations',values,orgForm)} style={{marginTop:12}}><Form.Item name="name" label="名称" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="type" label="类型"><Input placeholder="学生组织/社团/项目组"/></Form.Item><Form.Item name="description" label="描述"><Input.TextArea rows={2}/></Form.Item><Button type="primary" htmlType="submit" block>添加组织</Button></Form><Divider/><List size="small" dataSource={orgs} renderItem={o=><List.Item actions={[<Button size="small" onClick={()=>openEdit('organization',o)}>编辑</Button>,<Popconfirm title="确认删除？" onConfirm={()=>remove('/organizations/'+o.id)}><Button danger size="small">删除</Button></Popconfirm>]}>{o.name} <Typography.Text type="secondary">{o.type||''}</Typography.Text></List.Item>}/></>;

  const positionPanel=<><Typography.Text strong>新增职位</Typography.Text><Form form={positionForm} layout="vertical" onFinish={values=>post('/positions',values,positionForm)} style={{marginTop:12}}><Form.Item name="name" label="名称" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="level" label="层级" initialValue={1}><InputNumber min={1} max={10} style={{width:'100%'}}/></Form.Item><Form.Item name="description" label="描述"><Input.TextArea rows={2}/></Form.Item><Button type="primary" htmlType="submit" block>添加职位</Button></Form><Divider/><List size="small" dataSource={positions} renderItem={p=><List.Item actions={[<Button size="small" onClick={()=>openEdit('position',p)}>编辑</Button>,<Popconfirm title="确认删除？" onConfirm={()=>remove('/positions/'+p.id)}><Button danger size="small">删除</Button></Popconfirm>]}>{p.name} <Typography.Text type="secondary">层级 {p.level}</Typography.Text></List.Item>}/></>;

  const assignmentPanel=<><Typography.Text strong>分配组织/职位</Typography.Text><Form form={assignmentForm} layout="vertical" onFinish={values=>post('/assignments',values,assignmentForm)} style={{marginTop:12}}><Form.Item name="person_id" label="人物" rules={[{required:true}]}><Select showSearch optionFilterProp="label" options={personOptions}/></Form.Item><Form.Item name="organization_id" label="组织" rules={[{required:true}]}><Select options={orgOptions}/></Form.Item><Form.Item name="position_id" label="职位"><Select allowClear options={positionOptions}/></Form.Item><Form.Item name="start_date" label="开始时间"><Input placeholder="2026-03-01"/></Form.Item><Form.Item name="end_date" label="结束时间"><Input placeholder="可留空"/></Form.Item><Form.Item name="note" label="备注"><Input.TextArea rows={2}/></Form.Item><Button type="primary" htmlType="submit" block>保存任职</Button></Form><Divider/><List size="small" dataSource={assignments} locale={{empty:<Empty description="暂无任职记录"/>}} renderItem={a=><List.Item actions={[<Button size="small" onClick={()=>openEdit('assignment',a)}>编辑</Button>,<Popconfirm title="确认删除？" onConfirm={()=>remove('/assignments/'+a.id)}><Button danger size="small">删除</Button></Popconfirm>]}>{a.person_name} · {a.organization_name} {a.position_name&&<Tag>{a.position_name}</Tag>}<Typography.Text type="secondary"> {a.start_date||''}{a.end_date?` ~ ${a.end_date}`:''}</Typography.Text></List.Item>}/></>;

  const eventPanel=<><Typography.Text strong>新增事件</Typography.Text><Form form={eventForm} layout="vertical" onFinish={values=>post('/events',{...values,participants:(values.participants||[]).map((id:number)=>({person_id:id}))},eventForm)} style={{marginTop:12}}><Form.Item name="name" label="事件名称" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="event_date" label="发生时间"><Input placeholder="2026-05-01"/></Form.Item><Form.Item name="participants" label="参与人物"><Select mode="multiple" showSearch optionFilterProp="label" options={personOptions}/></Form.Item><Form.Item name="description" label="描述"><Input.TextArea rows={3}/></Form.Item><Form.Item name="source" label="来源"><Input/></Form.Item><Button type="primary" htmlType="submit" block>添加事件</Button></Form><Divider/><List size="small" dataSource={events} renderItem={e=><List.Item actions={[<Button size="small" onClick={()=>openEdit('event',e)}>编辑</Button>,<Popconfirm title="删除事件会移除其自动推导关系，确认继续？" onConfirm={()=>remove('/events/'+e.id)}><Button danger size="small">删除</Button></Popconfirm>]}><Space>{e.name}<Typography.Text type="secondary">{e.event_date||''}</Typography.Text>{e.derived_relationships>0&&<Tag color="orange">推导关系 {e.derived_relationships}</Tag>}</Space></List.Item>}/></>;

  const tagPanel=<><Typography.Text strong>新增标签</Typography.Text><Form form={tagForm} layout="inline" onFinish={values=>post('/tags',values,tagForm)} style={{marginTop:12}}><Form.Item name="name" rules={[{required:true}]}><Input placeholder="例如：核心成员"/></Form.Item><Form.Item name="color"><Input placeholder="颜色（可选）"/></Form.Item><Button type="primary" htmlType="submit">添加</Button></Form><Divider/><List size="small" dataSource={tags} locale={{empty:<Empty description="暂无标签"/>}} renderItem={t=><List.Item actions={[<Popconfirm title="确认删除标签？" onConfirm={()=>remove('/tags/'+t.id)}><Button danger size="small">删除</Button></Popconfirm>]}><Tag color={t.color||'blue'}>{t.name}</Tag><Typography.Text type="secondary">{t.people_count} 人</Typography.Text></List.Item>}/></>;

  const userPanel=<><Typography.Text strong>新增用户</Typography.Text><Form form={userForm} layout="vertical" onFinish={values=>post('/users',values,userForm)} style={{marginTop:12}}><Form.Item name="username" label="用户名" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="password" label="密码" rules={[{required:true}]}><Input.Password/></Form.Item><Form.Item name="role" label="角色" initialValue="viewer"><Select options={[{value:'admin',label:'管理员'},{value:'editor',label:'编辑者'},{value:'viewer',label:'浏览者'}]}/></Form.Item><Button type="primary" htmlType="submit" block>添加用户</Button></Form><Divider/><List size="small" dataSource={users} renderItem={u=><List.Item actions={[<Button size="small" onClick={()=>openEdit('user',u)}>修改</Button>,u.username!=='admin'&&<Popconfirm title="确认删除？" onConfirm={()=>remove('/users/'+u.id)}><Button danger size="small">删除</Button></Popconfirm>]}>{u.username} <Tag>{u.role}</Tag></List.Item>}/></>;
  const logsPanel=<List size="small" dataSource={logs} locale={{empty:<Empty description="暂无日志"/>}} renderItem={(x:any)=><List.Item>{x.created_at} · {x.actor||'系统'} · {x.entity} · {x.action}</List.Item>}/>;
  const versionsPanel=<><Space style={{marginBottom:12}}><Button type="primary" onClick={createVersion}>创建当前版本</Button><Typography.Text type="secondary">业务数据自动变更后也会生成版本</Typography.Text></Space><List size="small" dataSource={versions} locale={{empty:<Empty description="暂无历史版本"/>}} renderItem={(x:any)=><List.Item actions={[<Button size="small" onClick={()=>downloadVersion(x.id)}>导出</Button>,<Popconfirm title="确认恢复此版本？" onConfirm={()=>restoreVersion(x.id)}><Button danger size="small">恢复</Button></Popconfirm>]}><Space direction="vertical" size={0}><Typography.Text strong>{x.label||`${x.action} · ${x.entity}`}</Typography.Text><Typography.Text type="secondary">{x.created_at} · {x.actor||'系统'} · 校验码 {String(x.checksum||'').slice(0,12)}</Typography.Text></Space></List.Item>}/></>;

  const renderEditFields=()=>{
    const kind=editState?.kind;
    if(kind==='person')return <><Form.Item name="name" label="姓名" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="grade" label="年级"><Input/></Form.Item><Form.Item name="college" label="学院"><Input/></Form.Item><Form.Item name="major" label="专业"><Input/></Form.Item><Form.Item name="bio" label="简介"><Input.TextArea rows={3}/></Form.Item><Form.Item name="tag_ids" label="标签"><Select mode="multiple" options={tagOptions}/></Form.Item></>;
    if(kind==='organization')return <><Form.Item name="name" label="名称" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="type" label="类型"><Input/></Form.Item><Form.Item name="description" label="描述"><Input.TextArea rows={3}/></Form.Item></>;
    if(kind==='position')return <><Form.Item name="name" label="名称" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="level" label="层级"><InputNumber min={1} max={10} style={{width:'100%'}}/></Form.Item><Form.Item name="description" label="描述"><Input.TextArea rows={3}/></Form.Item></>;
    if(kind==='assignment')return <><Form.Item name="person_id" label="人物" rules={[{required:true}]}><Select options={personOptions}/></Form.Item><Form.Item name="organization_id" label="组织" rules={[{required:true}]}><Select options={orgOptions}/></Form.Item><Form.Item name="position_id" label="职位"><Select allowClear options={positionOptions}/></Form.Item><Form.Item name="start_date" label="开始时间"><Input/></Form.Item><Form.Item name="end_date" label="结束时间"><Input/></Form.Item><Form.Item name="note" label="备注"><Input.TextArea rows={2}/></Form.Item></>;
    if(kind==='relationship')return <><Form.Item name="type" label="关系类型" rules={[{required:true}]}><AutoComplete options={relationOptions}/></Form.Item><Form.Item name="strength" label="强度"><InputNumber min={1} max={5} style={{width:'100%'}}/></Form.Item><Form.Item name="event_id" label="关联事件"><Select allowClear options={events.map(e=>({value:e.id,label:e.name}))}/></Form.Item><Form.Item name="description" label="描述"><Input.TextArea rows={2}/></Form.Item><Form.Item name="source" label="关系来源"><Input/></Form.Item><Form.Item name="confidence" label="置信度"><InputNumber min={0} max={100} style={{width:'100%'}}/></Form.Item></>;
    if(kind==='event')return <><Form.Item name="name" label="事件名称" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="event_date" label="发生时间"><Input/></Form.Item><Form.Item name="participants" label="参与人物"><Select mode="multiple" options={personOptions}/></Form.Item><Form.Item name="description" label="描述"><Input.TextArea rows={3}/></Form.Item><Form.Item name="source" label="来源"><Input/></Form.Item></>;
    if(kind==='user')return <><Form.Item name="role" label="角色"><Select options={[{value:'admin',label:'管理员'},{value:'editor',label:'编辑者'},{value:'viewer',label:'浏览者'}]}/></Form.Item><Form.Item name="password" label="新密码"><Input.Password placeholder="留空表示不修改"/></Form.Item></>;
    return null;
  };

  const detailModal=<Modal open={!!detail} title="人物详情" width={650} footer={null} onCancel={()=>setDetail(null)}>{detail&&<><Descriptions column={2} bordered size="small"><Descriptions.Item label="姓名">{detail.person.name}</Descriptions.Item><Descriptions.Item label="年级">{detail.person.grade||'-'}</Descriptions.Item><Descriptions.Item label="学院">{detail.person.college||'-'}</Descriptions.Item><Descriptions.Item label="专业">{detail.person.major||'-'}</Descriptions.Item><Descriptions.Item label="简介" span={2}>{detail.person.bio||'-'}</Descriptions.Item><Descriptions.Item label="标签" span={2}>{(detail.person.tags||[]).map((t:any)=><Tag key={t.id}>{t.name}</Tag>)}</Descriptions.Item></Descriptions><Divider orientation="left">组织经历</Divider><List size="small" dataSource={detail.assignments} locale={{empty:<Empty description="暂无任职记录"/>}} renderItem={(a:any)=><List.Item>{a.organization} {a.position&&<Tag>{a.position}</Tag>}<Typography.Text type="secondary">{a.start_date||''}{a.end_date?` ~ ${a.end_date}`:''}{a.note?` · ${a.note}`:''}</Typography.Text></List.Item>}/><Divider orientation="left">事件时间线</Divider><Timeline items={(detail.events||[]).map((e:any)=>({children:<><Typography.Text strong>{e.event_date||'未注明时间'} · {e.name}</Typography.Text><div>{e.description||'暂无描述'}{e.role&&`（${e.role}）`}</div></>}))}/><Divider orientation="left">人物关系</Divider><List size="small" dataSource={detail.relationships} locale={{empty:<Empty description="暂无关系"/>}} renderItem={(r:any)=><List.Item>{r.person_name} · <Tag>{r.type}</Tag> 强度 {r.strength}{r.event_name&&<Typography.Text type="secondary"> · {r.event_name}</Typography.Text>}</List.Item>}/></>}</Modal>;
  const edgeModal=<Modal open={!!edgeDetail} title="关系详情" footer={null} onCancel={()=>setEdgeDetail(null)}>{edgeDetail&&<Descriptions column={1} bordered size="small"><Descriptions.Item label="关系类型">{edgeDetail.label}</Descriptions.Item><Descriptions.Item label="关系强度">{edgeDetail.strength}</Descriptions.Item><Descriptions.Item label="来源事件">{edgeDetail.eventName||'-'}</Descriptions.Item><Descriptions.Item label="事件时间">{edgeDetail.eventDate||'-'}</Descriptions.Item><Descriptions.Item label="描述">{edgeDetail.description||'-'}</Descriptions.Item><Descriptions.Item label="来源">{edgeDetail.sourceText||'-'}</Descriptions.Item><Descriptions.Item label="置信度">{edgeDetail.confidence}%</Descriptions.Item><Descriptions.Item label="关系来源">{edgeDetail.origin==='event'?'事件推导':'管理员/编辑者手动创建'}</Descriptions.Item></Descriptions>}</Modal>;
  const pathModal=<Modal open={!!pathResult} title="关系路径" footer={null} onCancel={()=>setPathResult(null)}>{pathResult&&(<>{pathResult.found?<Timeline items={pathResult.nodes.map((node:any,index:number)=>({children:<span>{node.name}{index<pathResult.edges.length&&<> → <Tag>{pathResult.edges[index].type}</Tag></>}</span>}))}/>:<Empty description="未找到关系路径"/>}{pathResult.found&&<Typography.Text type="secondary">共 {pathResult.hops} 跳</Typography.Text>}</>)}</Modal>;
  const editModal=<Modal open={!!editState} title={`编辑${editState?.kind==='person'?'人物':editState?.kind==='organization'?'组织':editState?.kind==='position'?'职位':editState?.kind==='assignment'?'任职':editState?.kind==='relationship'?'关系':editState?.kind==='event'?'事件':'用户'}`} onCancel={()=>setEditState(null)} onOk={()=>editForm.submit()} destroyOnClose><Form key={editState?`${editState.kind}-${editState.item.id}`:'edit'} form={editForm} layout="vertical" initialValues={editInitial} onFinish={saveEdit}>{renderEditFields()}</Form></Modal>;

  if(!auth)return <ConfigProvider theme={{algorithm:dark?theme.darkAlgorithm:theme.defaultAlgorithm}}><Layout style={{height:'100vh'}}><Layout.Header><Typography.Title level={3} style={{color:'#fff',margin:0}}>岁月史书 · 人际关系图谱</Typography.Title></Layout.Header><Layout.Content style={{display:'grid',placeItems:'center'}}><Card title="登录系统" style={{width:380}}><Form onFinish={login}><Form.Item name="username" label="用户名" initialValue="admin" rules={[{required:true}]}><Input/></Form.Item><Form.Item name="password" label="密码" initialValue="admin123" rules={[{required:true}]}><Input.Password/></Form.Item><Button type="primary" htmlType="submit" block>登录</Button></Form><Typography.Text type="secondary">初始管理员：admin / admin123</Typography.Text></Card></Layout.Content></Layout></ConfigProvider>;
  const graphPanel=<div style={{flex:1,minWidth:0,position:'relative'}}><div ref={graphRef} style={{height:'calc(100vh - 110px)',minHeight:500,background:dark?'#141414':'#f5f5f5',borderRadius:8}}/>{!graph.nodes.length&&<Alert message="暂无符合条件的图谱数据" description="请添加人物和关系，或清空当前筛选条件" type="info" showIcon style={{position:'absolute',top:16,left:16,right:16}}/>}</div>;
  const items:any[]=[{key:'graph',label:'图谱',children:graphTools},...(auth.role==='viewer'?[]:[{key:'people',label:'人物',children:peoplePanel},{key:'relations',label:'关系',children:relationPanel},{key:'orgs',label:'组织',children:orgPanel},{key:'positions',label:'职位',children:positionPanel},{key:'assignments',label:'任职',children:assignmentPanel},{key:'events',label:'事件',children:eventPanel},{key:'tags',label:'标签',children:tagPanel}])];
  if(auth.role==='admin')items.push({key:'users',label:'用户',children:userPanel},{key:'logs',label:'日志',children:logsPanel},{key:'versions',label:'版本',children:versionsPanel});
  return <ConfigProvider theme={{algorithm:dark?theme.darkAlgorithm:theme.defaultAlgorithm}}>{detailModal}{edgeModal}{pathModal}{editModal}<Layout style={{height:'100vh'}}><Layout.Header style={{display:'flex',alignItems:'center',justifyContent:'space-between'}}><Typography.Title level={3} style={{color:'#fff',margin:0}}>岁月史书 · 人际关系图谱</Typography.Title><Space><Typography.Text style={{color:'#fff'}}>{auth.username}（{auth.role}）</Typography.Text><Button size="small" onClick={()=>{localStorage.clear();setAuth(null)}}>退出</Button><Typography.Text style={{color:'#fff'}}>深色模式</Typography.Text><Switch checked={dark} onChange={setDark}/></Space></Layout.Header><Layout.Content style={{display:'flex',gap:16,padding:16}}>{graphPanel}<Card title="管理中心" style={{width:440,overflowY:'auto'}}><Tabs activeKey={tab} onChange={setTab} items={items}/></Card></Layout.Content></Layout></ConfigProvider>;
};

createRoot(document.getElementById('root')!).render(<App/>);
