let ws = null
// params is a map: { name: { v: currentValue, d: defaultValue, t: typeString }, ... }
let params = {}
let last_ts = 0
let _updateFadeTimer = null
let collapsedGroups = {}
let serverParamNotSaved = false
let hostnameUAV = ''

function updateSaveAllButton(){
  const b = document.getElementById('saveAllBtn')
  if(!b) return
  try{ b.disabled = !serverParamNotSaved }catch(e){}
}

function updateHostnameDisplay(){
  const el = document.getElementById('hostnameUAV')
  if(!el) return
  try{ el.textContent = hostnameUAV ? `(${hostnameUAV})` : '' }catch(e){}
}

const $ = id => document.getElementById(id)
const tbody = document.querySelector('#paramsTable tbody')
const onlyDiffCheckbox = () => document.getElementById('onlyDiff')
const filterInputEl = () => document.getElementById('filterInput')

function setStatus(connected){
  const s = $('status')
  s.className = connected ? 'connected' : 'disconnected'
  s.textContent = connected ? 'Connected' : 'Disconnected'
  const cb = $('connectBtn')
  if(cb) cb.textContent = connected ? 'Disconnect' : 'Connect'
  updateInputsEnabled(connected)
}

function isConnected(){
  return !!(ws && ws.readyState === WebSocket.OPEN)
}

function updateInputsEnabled(enabled){
  try{
    document.querySelectorAll('.paramVal').forEach(i=>{ i.disabled = !enabled })
    const rb = document.getElementById('rebootBtn')
    if(rb) rb.disabled = !enabled
  }catch(e){}
}

function connect(){
  const url = $('wsUrl').value.trim()
  if(ws){ ws.close() }
  try{ ws = new WebSocket(url) }catch(e){ alert('WebSocket error: '+e.message); return }

  ws.onopen = ()=>{
    setStatus(true)
    try{ ws.send('loginramytech') }catch(e){ console.error('failed to send login', e) }
    // requestList()
  }
  ws.onclose = ()=> setStatus(false)
  ws.onerror = e=> console.error('ws err',e)
  ws.onmessage = e=>{
    try{ const msg = JSON.parse(e.data); handleMessage(msg) }
    catch(err){ handleText(String(e.data)) }
  }
}

function send(msg){
  if(!ws || ws.readyState!==WebSocket.OPEN) return alert('WebSocket not connected')
  if(typeof msg === 'string') {
    console.log('sending text msg:', msg)
    ws.send(msg)
  }
  else ws.send(JSON.stringify(msg))
}

// disable a button for `ms` milliseconds to prevent double-clicks
function lockButton(btn, ms = 3000){
  if(!btn) return
  try{ btn.disabled = true }catch(e){}
  setTimeout(()=>{ try{ btn.disabled = false;
    updateSaveAllButton();
   }catch(e){} }, ms)
}

function requestList(){ send('param') }

function handleMessage(msg){
    console.log('received msg', msg)
    if (msg.ret !== 0) {
      console.error('Error from server:', msg.msg)
      // show server error message in UI if present; require manual close
      const errEl = $('errorMsg')
      if(errEl){
        // clear existing content
        errEl.innerHTML = ''
        const txt = document.createElement('span')
        txt.className = 'error-text'
        txt.textContent = msg.msg || 'Error'
        const btn = document.createElement('button')
        btn.className = 'error-close'
        btn.type = 'button'
        btn.textContent = 'Close'
        btn.addEventListener('click', ()=>{ try{ errEl.innerHTML = '' }catch(e){} })
        errEl.appendChild(txt)
        errEl.appendChild(btn)
      }
      return
    }
    if (msg.cmd === 68 && msg.ret === 0)
    {
      // param saved acknowledgement
      console.log('Parameters saved successfully')
      try{ document.querySelectorAll('.save-btn').forEach(b=>{ b.style.display = 'none' }) }catch(e){}
      serverParamNotSaved = true
      updateSaveAllButton()
      return
    }
    //login successfully
    if (msg.cmd === 42 && msg.ret === 0) 
      {
        requestList()
        return
      }
    //get param list
    if(msg.param && msg.ret === 0 && msg.cmd === 2)
    {
      hostnameUAV = msg.param.hostname || ''
      updateHostnameDisplay()
      params = msg.param.params;
      // remove license entry from params for security/clean display
      if(params && Object.prototype.hasOwnProperty.call(params, 'license')){
        delete params['license']
      }
      serverParamNotSaved = msg.param.changed
      updateSaveAllButton()
      // normalize types: map short codes and normalize string types
      Object.entries(params || {}).forEach(([k, v])=>{
        if(!v || v.t === undefined) return
        const tt = String(v.t).toLowerCase()
        const map = { b: 'bool', i: 'int', d: 'double', f: 'float' }
        if(map[tt]) v.t = map[tt]
        else if(tt.includes('string')) v.t = 'string'        
      })
      renderParams();
      last_ts = msg.ts
      showUpdateTime(last_ts)     
      return
    }
    if (msg.cmd === 86 && msg.ret === 0)
    {
      //save_param success acknowledgement
      console.log('Parameter saved successfully')
      serverParamNotSaved = false
      updateSaveAllButton()
      return
    }
}

function handleText(text){
  console.log('plain text message:', text)
}

function toDateFromTs(ts){
  if(!ts) return new Date()
  // `ts` is in microseconds (1e-6 seconds). Convert to milliseconds for Date: ms = ts / 1000
  const n = Number(ts)
  if(Number.isNaN(n)) return new Date()
  return new Date(n / 1000)
}

function showUpdateTime(ts){
  const el = $('lastUpdate')
  if(!el) return
  const d = toDateFromTs(ts)
  try{ el.textContent = 'Updated: ' + d.toLocaleString() }catch(e){ el.textContent = 'Updated' }
  // make visible
  el.classList.add('show')
  // clear any existing timer
  if(_updateFadeTimer) clearTimeout(_updateFadeTimer)
  _updateFadeTimer = setTimeout(()=>{
    el.classList.remove('show')
    _updateFadeTimer = null
  }, 5000)
}

function renderParams(){
  tbody.innerHTML = ''
  const onlyDiff = onlyDiffCheckbox() && onlyDiffCheckbox().checked
  const filter = filterInputEl() ? String(filterInputEl().value || '').trim().toLowerCase() : ''
  // group params by prefix before '/'
  const entries = Object.entries(params || {}).filter(([name, obj])=>{
    if(onlyDiff && obj && obj.v !== undefined && obj.d !== undefined && String(obj.v) === String(obj.d)) return false
    if(filter && !String(name).toLowerCase().includes(filter)) return false
    return true
  })
  const groups = {}
  entries.forEach(([name, obj])=>{
    // group by prefix before '/', put names without '/' into a single '(root)' group
    const parts = String(name).split('/')
    let prefix = '(root)'
    if(parts.length > 1 && parts[0] !== '') prefix = parts[0]
    if(!groups[prefix]) groups[prefix] = []
    groups[prefix].push([name, obj])
  })

  Object.keys(groups).sort().forEach(prefix=>{
    const members = groups[prefix]
    // ensure collapsed state exists (default: expanded)
    if(collapsedGroups[prefix] === undefined) collapsedGroups[prefix] = false
    // render group header (single td spanning columns)
    const gtr = document.createElement('tr')
    gtr.className = 'group-row'
    const gtd = document.createElement('td')
    gtd.colSpan = 5
    gtd.textContent = `${prefix} (${members.length})`
    gtr.appendChild(gtd)
    gtr.addEventListener('click', ()=>{ collapsedGroups[prefix] = !collapsedGroups[prefix]; renderParams() })
    tbody.appendChild(gtr)

    if(!collapsedGroups[prefix]){
      members.forEach(([name, obj])=>{
        const tr = document.createElement('tr')
        const nameTd = document.createElement('td'); nameTd.textContent = name
        const valTd = document.createElement('td');
        const input = document.createElement('input'); input.className='paramVal'
        input.value = obj && obj.v !== undefined ? obj.v : ''
        // disable input if not connected
        try{ input.disabled = !isConnected() }catch(e){}
        // show Save when value is edited
        input.addEventListener('input', ()=> {
          if(!params[name]) params[name] = { v: input.value, d: '', t: 'string' }
          else params[name].v = input.value
          try{ saveBtn.style.display = '' }catch(e){}
          try{
            // enable reset only when value differs from default and default exists
            if(params[name] && params[name].d !== undefined) resetBtn.disabled = String(params[name].v) === String(params[name].d)
            else resetBtn.disabled = true
          }catch(e){}
        })
        input.addEventListener('change', ()=> {
          if(!params[name]) params[name] = { v: input.value, d: '', t: 'string' }
          else params[name].v = input.value
        })
        valTd.appendChild(input)
        const defTd = document.createElement('td'); defTd.textContent = obj && obj.d !== undefined ? obj.d : ''
        // mark default value in red if current value differs from default
        if(obj && obj.d !== undefined && obj.v !== undefined && String(obj.v) !== String(obj.d)){
          defTd.classList.add('default-diff')
        }
        const typeTd = document.createElement('td'); typeTd.textContent = obj && obj.t !== undefined ? obj.t : 'string'
        const actTd = document.createElement('td');
        const saveBtn = document.createElement('button'); saveBtn.textContent='Save'; saveBtn.className='save-btn'; saveBtn.style.display = 'none'; saveBtn.onclick = ()=> { lockButton(saveBtn); updateParamByName(name); }
        const resetBtn = document.createElement('button'); resetBtn.textContent='Reset'; resetBtn.onclick = ()=> { lockButton(resetBtn); resetParamByName(name) }
        // initial disabled state: disabled if default not present or equal to current
        try{
          if(obj && obj.d !== undefined) resetBtn.disabled = String(obj.v) === String(obj.d)
          else resetBtn.disabled = true
        }catch(e){ resetBtn.disabled = true }
        actTd.appendChild(saveBtn);
        actTd.appendChild(resetBtn)
        tr.appendChild(nameTd); tr.appendChild(actTd); tr.appendChild(valTd); tr.appendChild(defTd); tr.appendChild(typeTd)
        tbody.appendChild(tr)
      })
    }
  })
}

function updateParam(name, p){ send(`set_param${name}:${p.v}`) }
function saveAll(){
  if(!confirm('Confirm: save all parameters to disk?')) return
  const b = $('saveAllBtn')
  lockButton(b)
  send('save_param')
}

function resetAll(){
  if(!confirm('Confirm: reset ALL parameters to their default values? This cannot be undone.') ) return
  const b = $('resetAllBtn')
  lockButton(b)
  send('reset_param')
}

function updateParamByName(name){
  const p = params[name]
  if(!p) return
  // send plain-text update command
  updateParam(name, p)
}
function resetParamByName(name){
  if(!params[name]) return
    params[name].v = params[name].d;
    renderParams();
    updateParam(name, params[name]);
}

document.getElementById('connectBtn').addEventListener('click', ()=>{
  if(ws && ws.readyState === WebSocket.OPEN) ws.close()
  else connect()
})
document.getElementById('refreshBtn').addEventListener('click', requestList)
document.getElementById('saveAllBtn').addEventListener('click', e=>{ const b=e.currentTarget; lockButton(b); saveAll() })
document.getElementById('resetAllBtn').addEventListener('click', e=>{ const b=e.currentTarget; lockButton(b); resetAll() })
const rebootBtn = document.getElementById('rebootBtn')
if(rebootBtn) rebootBtn.addEventListener('click', e=>{
  if(!confirm('Confirm: reboot node?')) return
  const b = e.currentTarget
  lockButton(b, 1000)
  send('reboot_node')
})
const onlyDiffEl = document.getElementById('onlyDiff')
if(onlyDiffEl) onlyDiffEl.addEventListener('change', ()=> renderParams())
const filterEl = document.getElementById('filterInput')
if(filterEl) filterEl.addEventListener('input', ()=> renderParams())

setStatus(false)
updateSaveAllButton()
