// Touch Therapies CMS — admin behaviours: rich text editor, image uploads,
// and the structured editor that replaces raw-JSON textareas.

const BASE = window.__BASE || ''

const csrfOf = (form) =>
  form?.querySelector('input[name=_csrf]')?.value || window.__CSRF || ''

async function uploadImage(file, csrf) {
  const body = new FormData()
  body.append('file', file)
  body.append('_csrf', csrf)
  const res = await fetch(BASE + '/admin/media/upload', { method: 'POST', body })
  const json = await res.json()
  if (!json.data?.url) throw new Error(json.error || 'Upload failed')
  return json.data.url
}

// Rich text: sync contenteditable into its hidden textarea on submit.
document.querySelectorAll('.rte').forEach((rte) => {
  const textarea = document.getElementById(rte.id.replace('rte-', 'f-'))
  rte.closest('form')?.addEventListener('submit', () => {
    textarea.value = rte.innerHTML
  })
})

document.querySelectorAll('.rte-toolbar button').forEach((btn) => {
  btn.addEventListener('click', () => {
    const cmd = btn.dataset.cmd
    if (cmd === 'createLink') {
      const url = prompt('Link URL:')
      if (url) document.execCommand(cmd, false, url)
    } else {
      document.execCommand(cmd, false, btn.dataset.arg || undefined)
    }
  })
})

// Image upload widgets on `image` fields: POST the file, drop the URL in the input.
document.querySelectorAll('.upload-btn input[type=file]').forEach((input) => {
  input.addEventListener('change', async () => {
    const file = input.files?.[0]
    if (!file) return
    const target = document.getElementById(input.dataset.target)
    const label = input.closest('.upload-btn')
    label.style.opacity = '0.5'
    try {
      const url = await uploadImage(file, csrfOf(input.closest('form')))
      target.value = url
      let preview = target.closest('.field').querySelector('.preview')
      if (!preview) {
        preview = document.createElement('img')
        preview.className = 'preview'
        target.closest('.field').appendChild(preview)
      }
      preview.src = url
    } catch (e) {
      alert(e.message || 'Upload failed')
    } finally {
      label.style.opacity = ''
      input.value = ''
    }
  })
})

// ---- Structured JSON editor -------------------------------------------------
// `json` fields (and JSON settings) render as a raw textarea with class .mono.
// When the content parses, we hide the textarea and build labeled inputs from
// the data: nested objects become groups, arrays become editable item lists,
// image-like keys get an upload button. The textarea stays the submitted
// source of truth — it is re-serialised on save, and "Edit as raw JSON"
// toggles back at any time.

const IMAGE_KEY = /(^|_)(image|img|photo|src|background|logo)(_|$)/i

const labelFor = (key) =>
  key.replace(/[_-]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())

function el(tag, className, text) {
  const node = document.createElement(tag)
  if (className) node.className = className
  if (text !== undefined) node.textContent = text
  return node
}

function buildEditor(value, key, form) {
  if (Array.isArray(value)) return buildArray(value, key, form)
  if (value && typeof value === 'object') return buildObject(value, key, form)
  return buildScalar(value, key, form)
}

function buildObject(value, key, form) {
  const wrap = el('div', key ? 'je-group' : 'je')
  if (key) wrap.appendChild(el('div', 'je-legend', labelFor(key)))
  const fields = Object.entries(value).map(([k, v]) => {
    const child = buildEditor(v, k, form)
    wrap.appendChild(child.node)
    return [k, child]
  })
  return {
    node: wrap,
    read: () => Object.fromEntries(fields.map(([k, c]) => [k, c.read()])),
  }
}

function buildArray(value, key, form) {
  const wrap = el('div', key ? 'je-group' : 'je')
  if (key) wrap.appendChild(el('div', 'je-legend', labelFor(key)))

  // List of plain strings (paragraphs, package inclusions, tags): one per line.
  if (value.every((v) => typeof v === 'string')) {
    const input = document.createElement('textarea')
    input.rows = Math.min(10, Math.max(3, value.length + 1))
    input.value = value.join('\n')
    wrap.append(input, el('small', '', 'One entry per line.'))
    return {
      node: wrap,
      read: () => input.value.split('\n').map((s) => s.trim()).filter(Boolean),
    }
  }

  // List of uniform objects (e.g. feature cards): item groups with add/remove.
  if (value.length && value.every((v) => v && typeof v === 'object' && !Array.isArray(v))) {
    const template = Object.keys(value[0])
    const list = el('div', 'je-list')
    wrap.appendChild(list)
    let items = []
    const addItem = (obj) => {
      const child = buildObject(obj, null, form)
      const item = el('div', 'je-item')
      const remove = el('button', 'btn sm danger je-remove', 'Remove')
      remove.type = 'button'
      const entry = { child }
      remove.addEventListener('click', () => {
        items = items.filter((i) => i !== entry)
        item.remove()
      })
      item.append(child.node, remove)
      list.appendChild(item)
      items.push(entry)
    }
    value.forEach(addItem)
    const add = el('button', 'btn sm je-add', '+ Add item')
    add.type = 'button'
    add.addEventListener('click', () =>
      addItem(Object.fromEntries(template.map((k) => [k, '']))))
    wrap.appendChild(add)
    return { node: wrap, read: () => items.map((i) => i.child.read()) }
  }

  // Anything irregular stays raw JSON for just this part.
  const input = document.createElement('textarea')
  input.className = 'mono'
  input.rows = 5
  input.value = JSON.stringify(value, null, 2)
  wrap.appendChild(input)
  return { node: wrap, read: () => JSON.parse(input.value) }
}

function buildScalar(value, key, form) {
  const wrap = el('div', 'je-field')
  if (key) wrap.appendChild(el('label', '', labelFor(key)))

  if (typeof value === 'boolean') {
    const label = el('label', 'switch')
    const box = document.createElement('input')
    box.type = 'checkbox'
    box.checked = value
    label.append(box, el('span', '', 'Enabled'))
    wrap.appendChild(label)
    return { node: wrap, read: () => box.checked }
  }

  if (typeof value === 'number') {
    const input = document.createElement('input')
    input.type = 'number'
    input.step = 'any'
    input.value = String(value)
    wrap.appendChild(input)
    return {
      node: wrap,
      read: () => (input.value.trim() === '' ? null : Number(input.value)),
    }
  }

  const str = value === null ? '' : String(value)
  if (key && IMAGE_KEY.test(key)) return buildImageField(str, wrap, form)

  let input
  if (str.length > 90 || str.includes('\n')) {
    input = document.createElement('textarea')
    input.rows = 3
  } else {
    input = document.createElement('input')
    input.type = 'text'
  }
  input.value = str
  wrap.appendChild(input)
  return { node: wrap, read: () => input.value }
}

function buildImageField(value, wrap, form) {
  const row = el('div', 'image-field')
  const input = document.createElement('input')
  input.type = 'text'
  input.value = value
  input.placeholder = 'https://… or upload →'
  const label = el('label', 'btn sm upload-btn', 'Upload')
  const file = document.createElement('input')
  file.type = 'file'
  file.accept = 'image/*'
  file.hidden = true
  label.appendChild(file)
  row.append(input, label)

  const preview = el('img', 'preview')
  preview.alt = ''
  const syncPreview = () => {
    if (input.value) preview.src = input.value
    preview.style.display = input.value ? '' : 'none'
  }
  input.addEventListener('input', syncPreview)
  syncPreview()

  file.addEventListener('change', async () => {
    const f = file.files?.[0]
    if (!f) return
    label.style.opacity = '0.5'
    try {
      input.value = await uploadImage(f, csrfOf(form))
      syncPreview()
    } catch (e) {
      alert(e.message || 'Upload failed')
    } finally {
      label.style.opacity = ''
      file.value = ''
    }
  })

  wrap.append(row, preview)
  return { node: wrap, read: () => input.value }
}

document.querySelectorAll('form textarea.mono').forEach((source) => {
  const form = source.closest('form')
  const help = source.closest('.field')?.querySelector('small')
  const toggle = el('button', 'je-toggle', 'Edit as raw JSON')
  toggle.type = 'button'
  let root = null

  const openEditor = () => {
    let parsed
    if (source.value.trim() === '' && source.dataset.shape === 'list') {
      parsed = [] // empty list field: open the easy editor instead of raw JSON
    } else {
      try {
        parsed = JSON.parse(source.value)
      } catch {
        return false
      }
    }
    if (parsed === null || typeof parsed !== 'object') return false
    root = buildEditor(parsed, null, form)
    root.node.classList.add('je')
    toggle.before(root.node)
    toggle.textContent = 'Edit as raw JSON'
    source.hidden = true
    if (help) help.hidden = true
    return true
  }

  const closeEditor = () => {
    source.value = JSON.stringify(root.read())
    root.node.remove()
    root = null
    source.hidden = false
    if (help) help.hidden = false
    toggle.textContent = 'Back to the easy editor'
  }

  toggle.addEventListener('click', () => {
    if (root) {
      try {
        closeEditor()
      } catch {
        alert('A raw JSON sub-section has a syntax error — fix it first.')
      }
    } else if (!openEditor()) {
      alert('Fix the JSON syntax first, then switch back to the easy editor.')
    }
  })

  form?.addEventListener('submit', (e) => {
    if (!root) return
    try {
      source.value = JSON.stringify(root.read())
    } catch {
      e.preventDefault()
      alert('A raw JSON sub-section has a syntax error — fix it before saving.')
    }
  })

  source.after(toggle)
  if (!openEditor()) toggle.remove()
})
