{{define "config_friendly"}}
{{template "device_nav" .}}
<div class="card">
  <h2>识别方案配置</h2>
  <div class="muted small">节点：<a class="mono" href="/ui/devices/{{.Device.DeviceID}}">{{.Device.DeviceID}}</a>（{{.Device.DeviceName}}）。</div>
</div>

<div class="card config-wizard">
  <div class="wizard-steps">
    <button type="button" class="wizard-step active" data-step="node">1 选择节点</button>
    <button type="button" class="wizard-step" data-step="channels">2 编辑视频通道</button>
    <button type="button" class="wizard-step" data-step="preview">3 预览变更</button>
    <button type="button" class="wizard-step" data-step="deploy">4 部署结果</button>
  </div>
  <div class="wizard-panel" data-panel="node">
    <h2>选择节点</h2>
    <div class="drawer-grid" style="margin-top:12px">
      <div><div class="muted small">节点名称</div><div>{{.Device.DeviceName}}</div></div>
      <div><div class="muted small">节点标识</div><div class="mono">{{.Device.DeviceID}}</div></div>
      <div><div class="muted small">管理地址</div><div class="mono">{{.Device.IP}}:{{.Device.AgentPort}}</div></div>
      <div><div class="muted small">状态</div><div>{{if .Device.Online}}<span class="pill ok">在线</span>{{else}}<span class="pill bad">离线</span>{{end}}</div></div>
    </div>
    <div class="actions" style="margin-top:12px"><button type="button" onclick="activateWizardStep('channels')">下一步：编辑视频通道</button></div>
  </div>
  <div class="wizard-panel" data-panel="channels">
    <h2>编辑视频通道</h2>
    <div class="actions" style="margin-top:12px"><button type="button" onclick="document.getElementById('cfg-editor').scrollIntoView({block:'start'}); activateWizardStep('channels')">前往通道编辑器</button></div>
  </div>
  <div class="wizard-panel" data-panel="preview">
    <h2>预览变更</h2>
    <div class="actions" style="margin-top:12px"><button type="button" onclick="document.getElementById('btn-plan').click()">前往预览变更</button></div>
  </div>
  <div class="wizard-panel" data-panel="deploy">
    <h2>部署结果</h2>
    <div class="actions" style="margin-top:12px"><button type="button" onclick="document.getElementById('cfg-result').scrollIntoView({block:'start'})">查看部署结果</button></div>
  </div>
</div>

<div class="card" id="cfg-status">
  <h2 id="cfg-status-title">加载中</h2>
  <div class="muted small" id="cfg-status-msg">正在读取配置结构和当前状态...</div>
  <div class="actions" id="cfg-help" style="display:none;margin-top:10px">
    <a href="/ui/devices">去设备列表重新扫描</a>
    <a href="/ui/devices/{{.Device.DeviceID}}/config-ui">打开高级 JSON</a>
  </div>
</div>

<div class="card" id="cfg-editor" data-wizard-section="channels" style="display:none">
  <h2>视频通道</h2>
  <div class="row" style="margin-top:10px; gap:16px">
    <div style="flex:1">
      <div class="muted small">新增或编辑视频通道</div>
      <div class="row" style="margin-top:8px">
        <div>
          <div class="muted small">通道名称</div>
          <input id="inst-name" placeholder="cam1" />
        </div>
        <div>
          <div class="muted small">识别模板</div>
          <select id="inst-template"></select>
        </div>
      </div>

      <div style="margin-top:10px" id="inst-fields"></div>

      <div class="actions" style="margin-top:10px">
        <button id="btn-save">保存到列表</button>
        <button id="btn-reset" type="button">清空表单</button>
      </div>
      <div class="muted small" style="margin-top:8px">提示：这里只编辑视频通道列表，系统级参数会沿用设备当前配置。</div>
    </div>

    <div style="flex:1">
      <div class="muted small">当前视频通道（点击编辑）</div>
      <div class="table-wrap" style="margin-top:8px">
        <table id="inst-table" style="min-width:640px">
          <thead>
          <tr><th>通道</th><th>识别模板</th><th>操作</th></tr>
          </thead>
          <tbody></tbody>
        </table>
      </div>

      <div class="actions" style="margin-top:10px">
        <button id="btn-plan" type="button">预览变更</button>
        <button id="btn-apply" type="button">部署到设备</button>
      </div>
    </div>
  </div>
</div>

<div class="card" id="cfg-result" data-wizard-section="deploy" style="display:none">
  <h2>执行结果</h2>
  <pre id="result-pre"></pre>
</div>

<div class="card" id="cfg-json" data-wizard-section="preview" style="display:none">
  <h2>高级 JSON 预览</h2>
  <pre id="payload-pre"></pre>
</div>

<script>
(() => {
  const deviceId = {{printf "%q" .Device.DeviceID}};
  const statusEl = document.getElementById('cfg-status');
  const statusTitleEl = document.getElementById('cfg-status-title');
  const statusMsgEl = document.getElementById('cfg-status-msg');
  const helpEl = document.getElementById('cfg-help');
  const editorEl = document.getElementById('cfg-editor');
  const resultEl = document.getElementById('cfg-result');
  const resultPre = document.getElementById('result-pre');
  const payloadEl = document.getElementById('cfg-json');
  const payloadPre = document.getElementById('payload-pre');

  const nameEl = document.getElementById('inst-name');
  const tplEl = document.getElementById('inst-template');
  const fieldsEl = document.getElementById('inst-fields');
  const tableBody = document.querySelector('#inst-table tbody');
  const btnSave = document.getElementById('btn-save');
  const btnReset = document.getElementById('btn-reset');
  const btnPlan = document.getElementById('btn-plan');
  const btnApply = document.getElementById('btn-apply');

  const wizardSteps = Array.from(document.querySelectorAll('.wizard-step'));
  const wizardPanels = Array.from(document.querySelectorAll('.wizard-panel'));
  function activateWizardStep(step){
    for(const el of wizardSteps) el.classList.toggle('active', el.getAttribute('data-step') === step);
    for(const panel of wizardPanels) panel.classList.toggle('active', panel.getAttribute('data-panel') === step);
  }
  for(const el of wizardSteps){
    el.addEventListener('click', () => activateWizardStep(el.getAttribute('data-step')));
  }
  activateWizardStep('node');

  const idFromPath = (() => {
    try{
      const parts = String(location.pathname || '').split('/');
      // /ui/devices/{id}/config-friendly
      if(parts.length > 3 && parts[1] === 'ui' && parts[2] === 'devices') return decodeURIComponent(parts[3] || '');
    }catch(e){}
    return '';
  })();
  const effectiveId = idFromPath || deviceId;
  const apiBase = `/api/devices/${encodeURIComponent(effectiveId)}/v1`;

  const PRD_FALLBACK = {
    transcode_rtsp_hls: {
      fields: [
        {k:'url', t:'string', req:true, label:'RTSP URL'},
        {k:'fps', t:'integer', def:30},
        {k:'src_w', t:'integer', def:1280},
        {k:'src_h', t:'integer', def:720},
        {k:'gop', t:'integer', def:60},
        {k:'bitrate_kbps', t:'integer', def:2000},
        {k:'rtsp_port', t:'integer', def:8555},
        {k:'hls_path', t:'string'}
      ]
    },
    yolo_rtsp_hls: {
      fields: [
        {k:'url', t:'string', req:true, label:'RTSP URL'},
        {k:'model_path', t:'string', req:true},
        {k:'fps', t:'integer', def:30},
        {k:'src_w', t:'integer', def:1280},
        {k:'src_h', t:'integer', def:720},
        {k:'gop', t:'integer', def:60},
        {k:'bitrate_kbps', t:'integer', def:2000},
        {k:'rtsp_port', t:'integer', def:8555},
        {k:'hls_path', t:'string'}
      ]
    },
    yolo_alarm_minio: {
      fields: [
        {k:'url', t:'string', req:true, label:'RTSP URL'},
        {k:'model_path', t:'string', req:true},
        {k:'minio_endpoint', t:'string', req:true},
        {k:'minio_bucket', t:'string', req:true},
        {k:'minio_ak', t:'string', req:true},
        {k:'minio_sk', t:'string', req:true},
        {k:'cooldown_ms', t:'integer', def:3000}
      ]
    },
    face_det_rtsp_hls: {
      fields: [
        {k:'url', t:'string', req:true, label:'RTSP URL'},
        {k:'det_model_path', t:'string', req:true},
        {k:'fps', t:'integer', def:30},
        {k:'src_w', t:'integer', def:1280},
        {k:'src_h', t:'integer', def:720},
        {k:'gop', t:'integer', def:60},
        {k:'bitrate_kbps', t:'integer', def:2000},
        {k:'rtsp_port', t:'integer', def:8555},
        {k:'hls_path', t:'string'}
      ]
    },
    face_det_recog_rtsp_hls: {
      fields: [
        {k:'url', t:'string', req:true, label:'RTSP URL'},
        {k:'det_model_path', t:'string', req:true},
        {k:'recog_model_path', t:'string', req:true},
        {k:'gallery_path', t:'string', def:'./models/face_gallery.db'},
        {k:'thr_accept', t:'number', def:0.45},
        {k:'thr_margin', t:'number', def:0.05},
        {k:'fps', t:'integer', def:30},
        {k:'src_w', t:'integer', def:1280},
        {k:'src_h', t:'integer', def:720},
        {k:'gop', t:'integer', def:60},
        {k:'bitrate_kbps', t:'integer', def:2000},
        {k:'rtsp_port', t:'integer', def:8555},
        {k:'hls_path', t:'string'}
      ]
    }
  };

  let baseState = { global: undefined, queue: undefined, instances: [] };
  let templateSchemas = {}; // name -> {properties, required}

  const esc = (s) => String(s ?? '');
  const pretty = (obj) => {
    try { return JSON.stringify(obj, null, 2); } catch(e) { return String(obj); }
  };

  function setStatus(title, msg, showHelp){
    statusEl.style.display = '';
    statusTitleEl.textContent = esc(title);
    statusMsgEl.textContent = esc(msg);
    helpEl.style.display = showHelp ? '' : 'none';
  }

  function normalizeSchema(schema){
    // Expect something like: {templates:[{name, schema:{type:'object', properties:{...}, required:[...]}}]}
    // Fallback: {templates:["..."]}
    const out = { templates: [], schemas: {} };
    if(!schema || typeof schema !== 'object') return out;

    const tpls = schema.templates;
    if(Array.isArray(tpls)){
      for(const t of tpls){
        if(typeof t === 'string'){
          out.templates.push(t);
          continue;
        }
        if(t && typeof t === 'object'){
          const name = t.name || t.template || t.id;
          if(typeof name === 'string' && name){
            out.templates.push(name);
            const sch = t.schema || t.params_schema || t.paramsSchema;
            if(sch && typeof sch === 'object') out.schemas[name] = sch;
          }
        }
      }
    }

    // Sometimes schema might be a map: {template_schemas:{...}}
    const m = schema.template_schemas || schema.templateSchemas || schema.schemas;
    if(m && typeof m === 'object'){
      for(const [k,v] of Object.entries(m)){
        if(!out.templates.includes(k)) out.templates.push(k);
        if(v && typeof v === 'object') out.schemas[k] = v;
      }
    }

    if(!out.templates.length){
      out.templates = Object.keys(PRD_FALLBACK);
    }

    return out;
  }

  function buildFieldList(tplName){
    const sch = templateSchemas[tplName];
    if(sch && sch.type === 'object' && sch.properties && typeof sch.properties === 'object'){
      const req = Array.isArray(sch.required) ? new Set(sch.required) : new Set();
      const fields = [];
      for(const [k,prop] of Object.entries(sch.properties)){
        const t = (prop && typeof prop.type === 'string') ? prop.type : 'string';
        fields.push({k, t, req: req.has(k), def: prop.default, enum: prop.enum, desc: prop.description || prop.title});
      }
      return fields;
    }

    const fb = PRD_FALLBACK[tplName];
    return fb ? fb.fields : [];
  }

  function inputForField(f, value){
    const id = `f_${f.k}`;
    const label = f.label || f.k;
    const req = f.req ? '<span class="muted small">（必填）</span>' : '';
    const desc = f.desc ? `<div class="muted small" style="margin-top:6px">${esc(f.desc)}</div>` : '';

    if(Array.isArray(f.enum) && f.enum.length){
      const opts = f.enum.map(v => `<option value=${JSON.stringify(String(v))}>${esc(v)}</option>`).join('');
      return `<div class="card" style="margin:10px 0;padding:10px 12px">
        <div class="muted small">${esc(label)} ${req}</div>
        <select id="${id}">${opts}</select>
        ${desc}
      </div>`;
    }

    const type = (f.t === 'integer' || f.t === 'number') ? 'number' : 'text';
    const step = (f.t === 'number') ? 'any' : '1';
    const v = (value !== undefined && value !== null) ? value : (f.def !== undefined ? f.def : '');
    return `<div class="card" style="margin:10px 0;padding:10px 12px">
      <div class="muted small">${esc(label)} ${req}</div>
      <input id="${id}" type="${type}" step="${step}" value=${JSON.stringify(String(v))} />
      ${desc}
    </div>`;
  }

  function collectParams(tplName){
    const fields = buildFieldList(tplName);
    const params = {};
    for(const f of fields){
      const el = document.getElementById(`f_${f.k}`);
      if(!el) continue;
      let v = el.value;
      if(v === '' || v === null || v === undefined){
        if(f.req) throw new Error(`缺少必填字段: ${f.k}`);
        continue;
      }
      if(f.t === 'integer'){
        const n = Number(v);
        if(!Number.isFinite(n)) throw new Error(`字段 ${f.k} 不是有效整数`);
        params[f.k] = Math.trunc(n);
      }else if(f.t === 'number'){
        const n = Number(v);
        if(!Number.isFinite(n)) throw new Error(`字段 ${f.k} 不是有效数字`);
        params[f.k] = n;
      }else{
        params[f.k] = String(v);
      }
    }
    return params;
  }

  function renderFields(tplName, params){
    const fields = buildFieldList(tplName);
    if(!fields.length){
        fieldsEl.innerHTML = '<div class="muted small">（该模板没有可渲染字段；如需支持，请补充配置结构或使用高级 JSON。）</div>';
      return;
    }
    fieldsEl.innerHTML = fields.map(f => inputForField(f, params ? params[f.k] : undefined)).join('');

    // Auto fill hls_path if present
    const hls = document.getElementById('f_hls_path');
    if(hls && !hls.value){
      const nm = (nameEl.value || '').trim();
      if(nm) hls.value = `./web/hls/${nm}/index.m3u8`;
    }
  }

  function renderTable(){
    tableBody.innerHTML = '';
    for(const inst of baseState.instances){
      const tr = document.createElement('tr');
      tr.innerHTML = `<td class="mono">${esc(inst.name)}</td><td class="mono">${esc(inst.template)}</td><td></td>`;
      const td = tr.querySelector('td:last-child');
      const btnEdit = document.createElement('button');
      btnEdit.textContent = '编辑';
      btnEdit.type = 'button';
      btnEdit.addEventListener('click', () => {
        nameEl.value = inst.name || '';
        tplEl.value = inst.template || '';
        renderFields(tplEl.value, inst.params || {});
      });
      const btnDel = document.createElement('button');
      btnDel.textContent = '删除';
      btnDel.type = 'button';
      btnDel.addEventListener('click', () => {
        if(!confirm(`确认删除通道 ${inst.name} ?`)) return;
        baseState.instances = baseState.instances.filter(x => x.name !== inst.name);
        renderTable();
        renderPayload();
      });
      td.appendChild(btnEdit);
      td.appendChild(document.createTextNode(' '));
      td.appendChild(btnDel);
      tableBody.appendChild(tr);
    }
  }

  function buildPayload(){
    const p = { instances: baseState.instances };
    if(baseState.global !== undefined) p.global = baseState.global;
    if(baseState.queue !== undefined) p.queue = baseState.queue;
    return p;
  }

  function renderPayload(){
    payloadEl.style.display = '';
    payloadPre.textContent = pretty(buildPayload());
  }

  async function postJson(path, obj){
    const res = await fetch(path, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(obj)
    });
    const txt = await res.text();
    let j;
    try { j = JSON.parse(txt); } catch(e) {}
    if(!res.ok){
      const msg = j && j.error ? j.error : txt;
      throw new Error(`HTTP ${res.status}: ${msg}`);
    }
    return j || txt;
  }

  async function load(){
    setStatus('加载中', '正在读取配置结构和当前状态...');
    let schema, state;
    try{
      const schemaUrl = `${apiBase}/config/ui/schema`;
      const stateUrl = `${apiBase}/config/ui/state`;
      const [s1, s2] = await Promise.all([fetch(schemaUrl), fetch(stateUrl)]);
      const t1 = await s1.text();
      const t2 = await s2.text();
      schema = (() => { try { return JSON.parse(t1); } catch(e) { return null; } })();
      state = (() => { try { return JSON.parse(t2); } catch(e) { return null; } })();

      if(!s1.ok){
        const extra = `\n\n请求：GET ${schemaUrl}\n节点标识(页面): ${deviceId}\n节点标识(URL): ${idFromPath || '-'}`;
        const hint = (String(t1).includes('device not found') || String(t1).includes('DEVICE_NOT_FOUND'))
          ? `\n\n提示：设备列表保存在当前服务内存里；如果刚重启服务，需要先到“设备列表”页点一次“开始扫描”。`
          : '';
        throw new Error(`配置结构 HTTP ${s1.status}: ${t1}${extra}${hint}`);
      }
      if(!s2.ok){
        const extra = `\n\n请求：GET ${stateUrl}\n节点标识(页面): ${deviceId}\n节点标识(URL): ${idFromPath || '-'}`;
        throw new Error(`当前状态 HTTP ${s2.status}: ${t2}${extra}`);
      }
    }catch(e){
      setStatus('加载失败', String(e), true);
      return;
    }

    if(schema && state){
      const norm = normalizeSchema(schema);
      templateSchemas = norm.schemas || {};

      tplEl.innerHTML = '';
      for(const n of norm.templates){
        const opt = document.createElement('option');
        opt.value = n;
        opt.textContent = n;
        tplEl.appendChild(opt);
      }

      baseState = {
        global: state && typeof state.global === 'object' ? state.global : undefined,
        queue: state && typeof state.queue === 'object' ? state.queue : undefined,
        instances: Array.isArray(state && state.instances) ? state.instances.map(x => ({
          name: x.name,
          template: x.template,
          params: (x.params && typeof x.params === 'object') ? x.params : {}
        })) : []
      };

      statusEl.style.display = 'none';
      editorEl.style.display = '';
      renderTable();
      tplEl.value = norm.templates[0] || '';
      renderFields(tplEl.value, {});
      renderPayload();
      return;
    }
  }

  tplEl.addEventListener('change', () => renderFields(tplEl.value, {}));
  nameEl.addEventListener('input', () => {
    const hls = document.getElementById('f_hls_path');
    if(hls && (!hls.value || hls.value.includes('/hls/'))) {
      const nm = (nameEl.value || '').trim();
      if(nm) hls.value = `./web/hls/${nm}/index.m3u8`;
    }
  });

  btnReset.addEventListener('click', () => {
    nameEl.value = '';
    renderFields(tplEl.value, {});
  });

  btnSave.addEventListener('click', (ev) => {
    ev.preventDefault();
    resultEl.style.display = 'none';
    try{
      const name = (nameEl.value || '').trim();
      if(!name) throw new Error('name 不能为空');
      const tplName = tplEl.value;
      if(!tplName) throw new Error('template 不能为空');
      const params = collectParams(tplName);

      const next = { name, template: tplName, params };
      const idx = baseState.instances.findIndex(x => x.name === name);
      if(idx >= 0) baseState.instances[idx] = next;
      else baseState.instances.push(next);
      baseState.instances.sort((a,b) => String(a.name).localeCompare(String(b.name)));
      renderTable();
      renderPayload();
      setStatus('已更新', `已保存到列表：${name}`);
      statusEl.style.display = '';
      setTimeout(() => { statusEl.style.display = 'none'; }, 1200);
    }catch(e){
      setStatus('校验失败', String(e));
      statusEl.style.display = '';
    }
  });

  btnPlan.addEventListener('click', async () => {
    resultEl.style.display = 'none';
    activateWizardStep('preview');
    try{
      const payload = buildPayload();
      renderPayload();
      const out = await postJson(`${apiBase}/config/ui/plan`, payload);
      resultEl.style.display = '';
      resultPre.textContent = pretty(out);
    }catch(e){
      resultEl.style.display = '';
      resultPre.textContent = String(e);
    }
  });

  btnApply.addEventListener('click', async () => {
    resultEl.style.display = 'none';
    if(!confirm('确认部署到设备？系统会写入配置并重载视频分析服务，失败时会尝试回滚。')) return;
    activateWizardStep('deploy');
    try{
      const payload = buildPayload();
      renderPayload();
      const out = await postJson(`${apiBase}/config/ui/apply`, payload);
      resultEl.style.display = '';
      resultPre.textContent = pretty(out);
    }catch(e){
      resultEl.style.display = '';
      resultPre.textContent = String(e);
    }
  });

  load();
})();
</script>
{{end}}
