import { useEffect, useMemo, useRef, useState } from 'react'
import {
  Tree, Card, Descriptions, Tag, Table, Select, Button, Space, message, Alert,
  Segmented, Empty, Typography, Input, Spin, Tooltip, InputNumber, Checkbox, Popconfirm, Popover,
} from 'antd'
import {
  SaveOutlined, ReloadOutlined, ApartmentOutlined, CodeOutlined, EditOutlined, EyeOutlined,
  PlusOutlined, DeleteOutlined,
} from '@ant-design/icons'
import { api } from '../api'
import { loadOntology } from '../ontology/build'
import type { Ontology, OntoObject, OntoBehavior, OntoRule, OntoEvent } from '../ontology/types'
import { EditSession, type Path } from './editSession'

const { Text, Paragraph } = Typography

const ATTR_TYPES = ['string', 'int', 'decimal', 'boolean', 'date', 'datetime', 'enum']
const EFFECTS = ['REJECT', 'ALERT', 'COMPENSATE']
const EDIT_LAYERS = ['M1', 'MetaRule', 'ME'] // 结构化可编辑的层（对象/规则/事件）

/**
 * 模型编辑器：models/*.yaml 是唯一事实源。既可在「结构预览」里可视化浏览 / 结构化编辑，
 * 也可在「源码编辑」里改 YAML 原文。两种编辑都保存即写盘并热重载。等价 onto-app 的「本体模型」页。
 */
export function ModelEditor({ principal, onReloaded }: { principal: string; onReloaded?: () => void }) {
  const [mode, setMode] = useState<'structure' | 'source'>('structure')
  return (
    <div>
      <Segmented
        value={mode}
        onChange={(v) => setMode(v as any)}
        style={{ marginBottom: 12 }}
        options={[
          { label: '结构预览 / 编辑', value: 'structure', icon: <ApartmentOutlined /> },
          { label: '源码编辑', value: 'source', icon: <CodeOutlined /> },
        ]}
      />
      {mode === 'structure' ? <StructureBrowser principal={principal} onReloaded={onReloaded} /> : <SourceEditor principal={principal} onReloaded={onReloaded} />}
    </div>
  )
}

// ============================================================
// 结构预览 + 结构化编辑：本体导航树 + 详情（可切换编辑）
// ============================================================
function StructureBrowser({ principal, onReloaded }: { principal: string; onReloaded?: () => void }) {
  const [onto, setOnto] = useState<Ontology | null>(null)
  const [err, setErr] = useState('')
  const [sel, setSel] = useState<string>('')
  const [editing, setEditing] = useState(false)
  const [entering, setEntering] = useState(false)
  const [saving, setSaving] = useState(false)
  const sessionRef = useRef(new EditSession())
  const [, setVer] = useState(0)
  const bump = () => setVer((v) => v + 1)

  const reloadOnto = () => loadOntology().then(setOnto).catch((e) => setErr(String(e?.message || e)))
  useEffect(() => { reloadOnto() }, [])

  const treeData = useMemo(() => (onto ? buildTree(onto) : []), [onto])
  const dirty = [...sessionRef.current.dirty]

  async function enterEdit() {
    setEntering(true)
    try {
      await Promise.all(EDIT_LAYERS.map((c) => sessionRef.current.ensure(c)))
      setEditing(true); bump()
    } catch (e: any) { message.error('进入编辑失败：' + e.message) } finally { setEntering(false) }
  }

  async function save() {
    setSaving(true)
    try {
      const saved = await sessionRef.current.saveAll(principal)
      message.success('已保存并热重载：' + (saved.join(', ') || '（无改动）'))
      await reloadOnto()
      onReloaded?.()
      bump()
    } catch (e: any) { message.error('保存失败：' + e.message) } finally { setSaving(false) }
  }

  function exitEdit() {
    if (dirty.length && !window.confirm('有未保存的结构改动，退出将丢弃。确定？')) return
    sessionRef.current.reset()
    setEditing(false); bump()
  }

  if (err) return <Alert type="error" showIcon message="加载本体失败" description={err} />
  if (!onto) return <div style={{ padding: 40, textAlign: 'center' }}><Spin /></div>

  return (
    <div>
      <Space style={{ marginBottom: 10 }} wrap>
        <Segmented
          value={editing ? 'edit' : 'view'}
          onChange={(v) => (v === 'edit' ? enterEdit() : exitEdit())}
          options={[
            { label: '预览', value: 'view', icon: <EyeOutlined /> },
            { label: '编辑', value: 'edit', icon: <EditOutlined /> },
          ]}
        />
        {entering && <Spin size="small" />}
        {editing && (
          <>
            <Button type="primary" icon={<SaveOutlined />} loading={saving} disabled={!dirty.length} onClick={save}>
              保存并热重载
            </Button>
            {dirty.length > 0 && <span>改动层：{dirty.map((c) => <Tag key={c} color="orange">{c}</Tag>)}</span>}
            <Text type="secondary" style={{ fontSize: 12 }}>结构化编辑对象(M1)/规则(MetaRule)/事件(ME)；只改被编辑的节点，其余原文（含注释）不动。保存需管理员（当前 {principal}）。</Text>
          </>
        )}
      </Space>
      <div style={{ display: 'flex', gap: 16, alignItems: 'flex-start' }}>
        <Card size="small" style={{ width: 300, flex: 'none', maxHeight: 560, overflow: 'auto' }} styles={{ body: { padding: 8 } }}>
          <Tree
            treeData={treeData}
            defaultExpandedKeys={['g-obj', 'g-beh', 'g-rule', 'g-evt', 'g-scene']}
            selectedKeys={sel ? [sel] : []}
            onSelect={(k) => setSel((k[0] as string) || '')}
            showLine
            blockNode
          />
        </Card>
        <Card size="small" style={{ flex: 1, minHeight: 300 }}>
          {editing
            ? <EditDetail session={sessionRef.current} sel={sel} bump={bump} />
            : <Detail onto={onto} sel={sel} />}
        </Card>
      </div>
    </div>
  )
}

const TYPE_TAG: Record<string, { color: string; text: string }> = {
  REJECT: { color: 'red', text: 'REJECT' }, ALERT: { color: 'orange', text: 'ALERT' },
  COMPENSATE: { color: 'purple', text: 'COMPENSATE' },
}

function buildTree(onto: Ontology): any[] {
  const roots = onto.objects.filter((o) => o.is_aggregate_root)
  const objChildren = roots.map((r) => ({
    key: `obj:${r.id}`,
    title: <span>{r.name} <Text type="secondary" style={{ fontSize: 11 }}>{r.alias}</Text></span>,
    children: onto.objects.filter((o) => o.parent_id === r.id).map((e) => ({
      key: `obj:${e.id}`,
      title: <span>{e.name} <Text type="secondary" style={{ fontSize: 11 }}>子实体</Text></span>,
    })),
  }))
  const behByOwner = new Map<string, OntoBehavior[]>()
  onto.behaviors.forEach((b) => { const k = b.owner_object_id; behByOwner.set(k, [...(behByOwner.get(k) || []), b]) })
  const behChildren = [...behByOwner.entries()].map(([owner, list]) => ({
    key: `bg:${owner}`, selectable: false, title: <Text type="secondary">{owner}</Text>,
    children: list.map((b) => ({ key: `beh:${b.id}`, title: b.name })),
  }))
  const evtByAgg = new Map<string, OntoEvent[]>()
  onto.events.forEach((e) => { const k = e.aggregate || '其他'; evtByAgg.set(k, [...(evtByAgg.get(k) || []), e]) })
  const evtChildren = [...evtByAgg.entries()].map(([agg, list]) => ({
    key: `eg:${agg}`, selectable: false, title: <Text type="secondary">{agg}</Text>,
    children: list.map((e) => ({ key: `evt:${e.id}`, title: e.name })),
  }))
  return [
    { key: 'g-obj', selectable: false, title: <b>对象模型 ({onto.objects.length})</b>, children: objChildren },
    { key: 'g-beh', selectable: false, title: <b>行为模型 ({onto.behaviors.length})</b>, children: behChildren },
    { key: 'g-rule', selectable: false, title: <b>规则模型 ({onto.rules.length})</b>, children: onto.rules.map((r) => ({ key: `rule:${r.id}`, title: r.name })) },
    { key: 'g-evt', selectable: false, title: <b>事件模型 ({onto.events.length})</b>, children: evtChildren },
    { key: 'g-scene', selectable: false, title: <b>场景流程 ({onto.processModels.length})</b>, children: onto.processModels.map((p) => ({ key: `scene:${p.id}`, title: p.name })) },
  ]
}

function splitSel(sel: string): [string, string] {
  const i = sel.indexOf(':')
  return [sel.slice(0, i), sel.slice(i + 1)]
}

// ============================================================
// 只读详情
// ============================================================
function Detail({ onto, sel }: { onto: Ontology; sel: string }) {
  if (!sel) return <Empty description="从左侧选择一个对象 / 行为 / 规则 / 事件 / 场景查看结构" />
  const [kind, id] = splitSel(sel)
  if (kind === 'obj') { const o = onto.objects.find((x) => x.id === id); return o ? <ObjectDetail o={o} /> : null }
  if (kind === 'beh') { const b = onto.behaviors.find((x) => x.id === id); return b ? <BehaviorDetail b={b} onto={onto} /> : null }
  if (kind === 'rule') { const r = onto.rules.find((x) => x.id === id); return r ? <RuleDetail r={r} /> : null }
  if (kind === 'evt') { const e = onto.events.find((x) => x.id === id); return e ? <EventDetail e={e} /> : null }
  if (kind === 'scene') {
    const p = onto.processModels.find((x) => x.id === id)
    if (!p) return null
    return (
      <div>
        <Space style={{ marginBottom: 8 }}><b style={{ fontSize: 16 }}>{p.name}</b><Tag color="purple">{p.id}</Tag></Space>
        <Paragraph type="secondary">流程节点（由 M4 flowSteps 合成，完整可视化见「场景流程图」视图）：</Paragraph>
        <Table size="small" pagination={false} rowKey="id" dataSource={p.nodes}
          columns={[
            { title: '节点', dataIndex: 'name' },
            { title: '类型', dataIndex: 'type', render: (t: string) => <Tag>{t}</Tag> },
            { title: '关联命令', dataIndex: 'behaviorId', render: (v: string) => v || '—' },
          ]} />
      </div>
    )
  }
  return null
}

function ObjectDetail({ o }: { o: OntoObject }) {
  return (
    <div>
      <Space style={{ marginBottom: 10 }}>
        <b style={{ fontSize: 16 }}>{o.name}</b>
        <Tag color="blue">{o.alias}</Tag>
        <Tag color={o.is_aggregate_root ? 'gold' : 'default'}>{o.is_aggregate_root ? '聚合根' : `子实体 · 属于 ${o.parent_id}`}</Tag>
      </Space>
      {o.description && <Paragraph type="secondary">{o.description}</Paragraph>}
      <Text strong>属性（{o.attributes.length}）</Text>
      <Table size="small" pagination={false} rowKey="name" style={{ marginTop: 6 }} dataSource={o.attributes}
        columns={[
          {
            title: '字段', dataIndex: 'name', render: (v: string, a: any) => (
              <Space size={4}>
                {a.is_primary_key && <Tooltip title="主键"><span>🔑</span></Tooltip>}
                {a.is_foreign_key && <Tooltip title="外键/引用"><span>🔗</span></Tooltip>}
                <span className="om-mono">{v}</span>
              </Space>
            ),
          },
          { title: '类型', dataIndex: 'type' },
          { title: '引用', dataIndex: 'ref', render: (r: any) => (r ? <Tag color="purple">→ {r.root}.{r.identifier}</Tag> : '—') },
          {
            title: '约束/标注', key: 'c', render: (_: any, a: any) => (
              <Space size={4} wrap>
                {a.transient && <Tag color="magenta">瞬态</Tag>}
                {a.unique && <Tag>唯一</Tag>}
                {a.constraints && Object.entries(a.constraints).filter(([k]) => k !== 'patternMessage').map(([k, v]) => <Tag key={k}>{k}={String(v)}</Tag>)}
              </Space>
            ),
          },
        ]} />
      {o.relationships.length > 0 && (
        <>
          <Text strong style={{ display: 'block', marginTop: 12 }}>关系（{o.relationships.length}）</Text>
          <Space direction="vertical" style={{ marginTop: 6, width: '100%' }}>
            {o.relationships.map((r, i) => (
              <div key={i}>
                <Tag color={r.refType === 'composition' ? 'purple' : 'blue'}>{r.refType === 'composition' ? '组合' : '引用'}</Tag>
                <Text>{r.fk_field ? `${r.fk_field} → ` : ''}{r.target_object_id}</Text>
                <Text type="secondary" style={{ marginLeft: 8 }}>{r.kind}</Text>
              </div>
            ))}
          </Space>
        </>
      )}
    </div>
  )
}

function BehaviorDetail({ b, onto }: { b: OntoBehavior; onto: Ontology }) {
  const item = (label: string, node: any) => <Descriptions.Item label={label}>{node}</Descriptions.Item>
  return (
    <div>
      <Space style={{ marginBottom: 10 }}><b style={{ fontSize: 16 }}>{b.name}</b><Tag color="green">COMMAND</Tag><Tag>{b.commandType}</Tag></Space>
      <Descriptions size="small" column={1} bordered>
        {item('归属对象', b.owner_object_id)}
        {b.description && item('说明', b.description)}
        {b.produced_events.length > 0 && item('产出事件', b.produced_events.map((e) => <Tag key={e} color="magenta">{e}</Tag>))}
        {b.applied_rules.length > 0 && item('适用规则', b.applied_rules.map((r) => <Tag key={r} color="red">{onto.rules.find((x) => x.id === r)?.name || r}</Tag>))}
        {b.preconditions.length > 0 && item('前置校验', <ul style={{ margin: 0, paddingLeft: 18 }}>{b.preconditions.map((p, i) => <li key={i} className="om-mono">{p}</li>)}</ul>)}
        {b.effects.length > 0 && item('派生/副作用', <ul style={{ margin: 0, paddingLeft: 18 }}>{b.effects.map((e, i) => <li key={i} className="om-mono">{e}</li>)}</ul>)}
      </Descriptions>
    </div>
  )
}

function RuleDetail({ r }: { r: OntoRule }) {
  const t = TYPE_TAG[r.type] || { color: 'default', text: r.type }
  return (
    <div>
      <Space style={{ marginBottom: 10 }}><b style={{ fontSize: 16 }}>{r.name}</b><Tag color={t.color}>{t.text}</Tag><Text code>{r.id}</Text></Space>
      <Descriptions size="small" column={1} bordered>
        <Descriptions.Item label="匹配条件"><span className="om-mono">{r.expression}</span></Descriptions.Item>
        <Descriptions.Item label="提示信息">{r.description}</Descriptions.Item>
        <Descriptions.Item label="绑定场景">{r.bindScene || '—'}</Descriptions.Item>
      </Descriptions>
    </div>
  )
}

function EventDetail({ e }: { e: OntoEvent }) {
  return (
    <div>
      <Space style={{ marginBottom: 10 }}><b style={{ fontSize: 16 }}>{e.name}</b><Tag color="magenta">事件</Tag></Space>
      <Descriptions size="small" column={1} bordered>
        <Descriptions.Item label="归属聚合">{e.aggregate}</Descriptions.Item>
        <Descriptions.Item label="topic"><span className="om-mono">{e.topic}</span></Descriptions.Item>
        <Descriptions.Item label="载荷">{(e.payload || []).map((p) => <Tag key={p}>{p}</Tag>)}</Descriptions.Item>
        <Descriptions.Item label="消费方">{(e.consumers || []).map((c) => <Tag key={c} color="blue">{c}</Tag>)}</Descriptions.Item>
      </Descriptions>
    </div>
  )
}

// ============================================================
// 结构化编辑详情：对象(M1) / 规则(MetaRule) / 事件(ME)
// ============================================================
function EditDetail({ session, sel, bump }: { session: EditSession; sel: string; bump: () => void }) {
  if (!sel) return <Empty description="从左侧选择一个对象 / 规则 / 事件进行结构化编辑" />
  const [kind, id] = splitSel(sel)
  if (kind === 'obj') return <ObjectEditForm session={session} id={id} bump={bump} />
  if (kind === 'rule') return <RuleEditForm session={session} id={id} bump={bump} />
  if (kind === 'evt') return <EventEditForm session={session} id={id} bump={bump} />
  return <Alert type="info" showIcon message="该节点暂不支持结构化编辑"
    description="行为(M2 bizSteps) / 场景(M4 flowSteps) 结构复杂，请切到「源码编辑」修改；对象、规则、事件可在此结构化编辑。" />
}

function locateObject(m1: any, id: string): { aggIdx: number; kind: 'root' | 'entity'; entIdx?: number; node: any; agg: any } | null {
  const aggs = m1?.aggregates || []
  for (let ai = 0; ai < aggs.length; ai++) {
    const root = aggs[ai].aggregateRoot || {}
    if (root.name === id) return { aggIdx: ai, kind: 'root', node: root, agg: aggs[ai] }
    const ents = aggs[ai].entities || []
    for (let ei = 0; ei < ents.length; ei++) if (ents[ei].name === id) return { aggIdx: ai, kind: 'entity', entIdx: ei, node: ents[ei], agg: aggs[ai] }
  }
  return null
}

/** 约束编辑：展示全部现有约束（与预览一致）+ 弹层编辑全集（唯一/min/max/exclusiveMin/exclusiveMax/pattern/patternMessage）。 */
function ConstraintCell({ session, path, a, bump }: { session: EditSession; path: Path; a: any; bump: () => void }) {
  const cons = a.constraints || {}
  const setC = (key: string, val: any) => { session.setConstraint('M1', path, key, val); bump() }
  // v ?? undefined 保留 0（min:0 / exclusiveMin:0 均为合法值），清空(null)才删除该约束键
  const num = (key: string) => (
    <span style={{ whiteSpace: 'nowrap' }}>
      <Text type="secondary" style={{ fontSize: 12, marginRight: 4 }}>{key}</Text>
      <InputNumber size="small" style={{ width: 84 }} value={cons[key] ?? null} onChange={(v) => setC(key, v ?? undefined)} />
    </span>
  )
  const content = (
    <div style={{ width: 320, display: 'flex', flexDirection: 'column', gap: 10 }}>
      <Checkbox checked={!!cons.unique} onChange={(e) => setC('unique', e.target.checked)}>唯一 unique</Checkbox>
      <Space wrap>{num('min')}{num('max')}</Space>
      <Space wrap>{num('exclusiveMin')}{num('exclusiveMax')}</Space>
      <div>
        <Text type="secondary" style={{ fontSize: 12 }}>正则 pattern</Text>
        <Input size="small" className="om-mono" value={cons.pattern || ''} onChange={(e) => setC('pattern', e.target.value)} placeholder="如 ^\d{11}$" />
      </div>
      <div>
        <Text type="secondary" style={{ fontSize: 12 }}>校验提示 patternMessage</Text>
        <Input size="small" value={cons.patternMessage || ''} onChange={(e) => setC('patternMessage', e.target.value)} placeholder="不满足正则时的提示语" />
      </div>
    </div>
  )
  const keys = Object.keys(cons)
  return (
    <Space size={4} wrap>
      {keys.length === 0 && <Text type="secondary">—</Text>}
      {keys.filter((k) => k !== 'patternMessage').map((k) => <Tag key={k} style={{ margin: 0 }} className="om-mono">{k}={String(cons[k])}</Tag>)}
      {a.refAggregate && <Tag color="purple" style={{ margin: 0 }}>→ {a.refRoot}.{a.refIdentifier}</Tag>}
      <Popover trigger="click" placement="leftTop" title="编辑约束" content={content}>
        <Button size="small" type="link" style={{ padding: '0 4px' }} icon={<EditOutlined />} />
      </Popover>
    </Space>
  )
}

function ObjectEditForm({ session, id, bump }: { session: EditSession; id: string; bump: () => void }) {
  const m1 = session.toJS('M1')
  const loc = locateObject(m1, id)
  if (!loc) return <Alert type="warning" message={`在 M1 中找不到对象 ${id}（可能刚被改名，请重新选择）`} />
  const basePath: Path = loc.kind === 'root' ? ['aggregates', loc.aggIdx, 'aggregateRoot'] : ['aggregates', loc.aggIdx, 'entities', loc.entIdx!]
  const attrsPath: Path = [...basePath, 'attributes']
  const idField = loc.kind === 'root' ? 'identifier' : 'localIdentifier'
  const attrs: any[] = loc.node.attributes || []
  const set = (p: Path, v: any) => { session.setIn('M1', p, v); bump() }

  return (
    <div>
      <Space style={{ marginBottom: 10 }} wrap>
        <b style={{ fontSize: 16 }}>{loc.node.name}</b>
        <Tag color={loc.kind === 'root' ? 'gold' : 'default'}>{loc.kind === 'root' ? '聚合根' : '子实体'}</Tag>
        <Tag color="blue">{loc.agg.aggregateId}</Tag>
        <Text type="secondary" style={{ fontSize: 12 }}>（对象改名请用源码编辑，避免跨层引用错乱）</Text>
      </Space>

      <Descriptions size="small" column={1} bordered style={{ marginBottom: 12 }}>
        <Descriptions.Item label={<Space>🔑 主键字段名</Space>}>
          <Input size="small" value={loc.node[idField] || ''} onChange={(e) => set([...basePath, idField], e.target.value)} style={{ maxWidth: 260 }} />
        </Descriptions.Item>
        {loc.kind === 'root' && (
          <Descriptions.Item label="聚合描述">
            <Input.TextArea size="small" autoSize value={loc.agg.description || ''} onChange={(e) => set(['aggregates', loc.aggIdx, 'description'], e.target.value)} />
          </Descriptions.Item>
        )}
      </Descriptions>

      <Space style={{ marginBottom: 6 }}>
        <Text strong>属性（{attrs.length}）</Text>
        <Button size="small" icon={<PlusOutlined />} onClick={() => { session.addIn('M1', attrsPath, { name: 'newField', type: 'string' }); bump() }}>添加属性</Button>
      </Space>
      <Table size="small" pagination={false} rowKey={(_, i) => String(i)} dataSource={attrs}
        columns={[
          {
            title: '字段名', width: 200, render: (_: any, a: any, j: number) => (
              <Space size={4}>
                {loc.node[idField] === a.name && <Tooltip title="主键"><span>🔑</span></Tooltip>}
                {a.refAggregate && <Tooltip title={`引用 ${a.refRoot}.${a.refIdentifier}`}><span>🔗</span></Tooltip>}
                <Input size="small" className="om-mono" value={a.name || ''} onChange={(e) => set([...attrsPath, j, 'name'], e.target.value)} />
              </Space>
            ),
          },
          {
            title: '类型', width: 130, render: (_: any, a: any, j: number) => (
              <Select size="small" style={{ width: 118 }} value={a.type || 'string'} options={ATTR_TYPES.map((t) => ({ value: t, label: t }))}
                onChange={(v) => set([...attrsPath, j, 'type'], v)} />
            ),
          },
          {
            title: '约束 / 引用', render: (_: any, a: any, j: number) => (
              <ConstraintCell session={session} path={[...attrsPath, j]} a={a} bump={bump} />
            ),
          },
          {
            title: '', width: 40, render: (_: any, __: any, j: number) => (
              <Popconfirm title="删除该属性？" onConfirm={() => { session.deleteIn('M1', [...attrsPath, j]); bump() }}>
                <Button size="small" type="text" danger icon={<DeleteOutlined />} />
              </Popconfirm>
            ),
          },
        ]} />
      <Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 8 }}>
        提示：改字段类型 / 增删字段后点上方「保存并热重载」，「数据结构图」「场景运行台」表单会同步变化。
      </Text>
    </div>
  )
}

function locateRule(mr: any, id: string): { gIdx: number; rIdx: number; node: any } | null {
  const groups = mr?.metaRuleModel?.ruleGroups || []
  for (let g = 0; g < groups.length; g++) {
    const rules = groups[g].rules || []
    for (let r = 0; r < rules.length; r++) if (rules[r].ruleId === id) return { gIdx: g, rIdx: r, node: rules[r] }
  }
  return null
}

function RuleEditForm({ session, id, bump }: { session: EditSession; id: string; bump: () => void }) {
  const mr = session.toJS('MetaRule')
  const loc = locateRule(mr, id)
  if (!loc) return <Alert type="warning" message={`在 MetaRule 中找不到规则 ${id}`} />
  const base: Path = ['metaRuleModel', 'ruleGroups', loc.gIdx, 'rules', loc.rIdx]
  const set = (field: string, v: any) => { session.setIn('MetaRule', [...base, field], v); bump() }
  return (
    <div>
      <Space style={{ marginBottom: 10 }}><b style={{ fontSize: 16 }}>{loc.node.ruleName}</b><Text code>{loc.node.ruleId}</Text></Space>
      <Descriptions size="small" column={1} bordered>
        <Descriptions.Item label="规则名"><Input size="small" value={loc.node.ruleName || ''} onChange={(e) => set('ruleName', e.target.value)} /></Descriptions.Item>
        <Descriptions.Item label="效果">
          <Select size="small" style={{ width: 180 }} value={loc.node.effect} options={EFFECTS.map((x) => ({ value: x, label: x }))} onChange={(v) => set('effect', v)} />
        </Descriptions.Item>
        <Descriptions.Item label="匹配条件 (Aviator)"><Input.TextArea size="small" autoSize className="om-mono" value={loc.node.matchCondition || ''} onChange={(e) => set('matchCondition', e.target.value)} /></Descriptions.Item>
        <Descriptions.Item label="提示信息"><Input.TextArea size="small" autoSize value={loc.node.message || ''} onChange={(e) => set('message', e.target.value)} /></Descriptions.Item>
      </Descriptions>
      <Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 8 }}>提示：改匹配条件/阈值后保存，前后端规则即时生效（如把大额订单阈值改小，普通用户下单更易被 REJECT）。</Text>
    </div>
  )
}

function locateEvent(me: any, id: string): { dIdx: number; name: string; node: any } | null {
  const defs = me?.eventModel?.aggregateEventDefinitions || []
  for (let d = 0; d < defs.length; d++) {
    const events = defs[d].events || {}
    if (id in events) return { dIdx: d, name: id, node: events[id] }
  }
  return null
}

function EventEditForm({ session, id, bump }: { session: EditSession; id: string; bump: () => void }) {
  const me = session.toJS('ME')
  const loc = locateEvent(me, id)
  if (!loc) return <Alert type="warning" message={`在 ME 中找不到事件 ${id}`} />
  const base: Path = ['eventModel', 'aggregateEventDefinitions', loc.dIdx, 'events', loc.name]
  const set = (field: string, v: any) => { session.setIn('ME', [...base, field], v); bump() }
  return (
    <div>
      <Space style={{ marginBottom: 10 }}><b style={{ fontSize: 16 }}>{loc.name}</b><Tag color="magenta">事件</Tag></Space>
      <Descriptions size="small" column={1} bordered>
        <Descriptions.Item label="topic"><Input size="small" className="om-mono" value={loc.node.topic || ''} onChange={(e) => set('topic', e.target.value)} /></Descriptions.Item>
        <Descriptions.Item label="载荷字段">
          <Select size="small" mode="tags" style={{ width: '100%' }} value={loc.node.payload || []} onChange={(v) => set('payload', v)} placeholder="回车添加字段" />
        </Descriptions.Item>
        <Descriptions.Item label="消费方">
          <Select size="small" mode="tags" style={{ width: '100%' }} value={loc.node.consumers || []} onChange={(v) => set('consumers', v)} placeholder="回车添加消费方服务" />
        </Descriptions.Item>
      </Descriptions>
    </div>
  )
}

// ============================================================
// 源码编辑：逐层编辑 YAML 原文，保存即热重载
// ============================================================
function SourceEditor({ principal, onReloaded }: { principal: string; onReloaded?: () => void }) {
  const [layers, setLayers] = useState<{ code: string; fileName: string }[]>([])
  const [code, setCode] = useState('M1')
  const [text, setText] = useState('')
  const [orig, setOrig] = useState('')
  const [dir, setDir] = useState('')
  const [loading, setLoading] = useState(false)
  const [saving, setSaving] = useState(false)

  useEffect(() => { api.models().then((d) => { setLayers(d.layers); setDir(d.modelsDir) }) }, [])

  const loadLayer = (c: string) => {
    setLoading(true)
    api.model(c).then((d) => { setText(d.raw ?? ''); setOrig(d.raw ?? '') }).finally(() => setLoading(false))
  }
  useEffect(() => { if (code) loadLayer(code) }, [code])

  const dirty = text !== orig

  const switchLayer = (c: string) => {
    if (dirty && !window.confirm('当前层有未保存的修改，切换将丢弃。确定切换？')) return
    setCode(c)
  }

  async function save() {
    setSaving(true)
    try {
      const r = await api.saveModel(code, text, principal)
      message.success(r.message || '已保存')
      setOrig(text)
      onReloaded?.()
    } catch (e: any) {
      message.error('保存失败：' + e.message)
    } finally { setSaving(false) }
  }

  return (
    <div>
      <Space style={{ marginBottom: 8 }} wrap>
        <Select value={code} style={{ width: 300 }} onChange={switchLayer}
          options={layers.map((l) => ({ value: l.code, label: `${l.code} · ${l.fileName}` }))} />
        <Button type="primary" icon={<SaveOutlined />} loading={saving} disabled={!dirty} onClick={save}>
          保存并热重载
        </Button>
        <Button icon={<ReloadOutlined />} disabled={!dirty} onClick={() => setText(orig)}>撤销修改</Button>
        {dirty && <Tag color="orange">未保存</Tag>}
      </Space>
      <Alert style={{ marginBottom: 8 }} type="info" showIcon
        message={<span>直接编辑 <Text code>{dir}/{layers.find((l) => l.code === code)?.fileName}</Text> 的 YAML 原文；保存即写盘并热重载（含 M1/M3 变更自动补表）。<b>仅管理员主体可保存</b>（当前：{principal}）。</span>} />
      <Spin spinning={loading}>
        <Input.TextArea
          value={text}
          onChange={(e) => setText(e.target.value)}
          spellCheck={false}
          style={{ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontSize: 12.5, lineHeight: 1.5, minHeight: 480, whiteSpace: 'pre', overflowX: 'auto' }}
          autoSize={{ minRows: 22, maxRows: 40 }}
        />
      </Spin>
    </div>
  )
}
