// Pure render helpers for DashboardCards.vue - the fleet dashboard's generic
// renderers. Same shape as pluginAssetPanels.js: the mapping logic that turns a
// plugin's JSON card declaration plus its endpoint data into rendered rows
// lives here so it can be unit tested without mounting a component.
//
// WHY GENERIC RENDERERS AT ALL: the older widget contract named a Vue component
// per widget. That cannot survive a lean build, where a plugin's component may
// never be staged into the frontend bundle - which is exactly why five plugins
// declared widgets pointing at components nobody ever wrote. A plugin declares
// data and shape; core owns the drawing.

export function toApiPath(endpoint) {
  // Cards declare absolute endpoints (/api/...); the api instance already
  // carries the /api base, so strip it before fetching through the instance.
  return String(endpoint).replace(/^\/api(?=\/)/, '')
}

export function rows(card) {
  const data = card._data
  if (Array.isArray(data)) return data
  if (data && Array.isArray(data.rows)) return data.rows
  if (data && Array.isArray(data.items)) return data.items
  return []
}

export function formatValue(value, spec = {}) {
  if (value === null || value === undefined || value === '') return ''
  if (spec.format === 'date') {
    const raw = String(value)
    return new Date(raw + (raw.length === 10 ? 'T00:00:00' : '')).toLocaleDateString()
  }
  if (typeof value === 'boolean') return value ? 'Yes' : 'No'
  return value
}

export function mapTitle(card, item) {
  const key = card.map && card.map.title
  return key ? item[key] : ''
}

export function mapDetail(card, item) {
  const key = card.map && card.map.detail
  return key ? item[key] : ''
}

// The untruncated text behind a shortened detail. Same rule as elsewhere: the
// tooltip explains, it never carries the only copy of something essential.
export function mapDetailTip(card, item) {
  const key = card.map && card.map.detailtooltip
  const full = key ? (item[key] || '') : ''
  return full && full !== mapDetail(card, item) ? full : ''
}

export function mapMeta(card, item) {
  const meta = (card.map && card.map.meta) || []
  return meta
    .map((spec) => {
      const value = formatValue(item[spec.key], spec)
      if (value === '') return null
      // label prefixes, suffix trails: 'quiet for 3 days' reads as a sentence,
      // where 'quiet for 3' reads as a truncation.
      const text = `${spec.label ? spec.label + ' ' : ''}${value}${spec.suffix || ''}`
      return { text, mono: !!spec.mono }
    })
    .filter(Boolean)
}

// A link template is '/pcs/{computerid}'. A row whose substitution value is
// missing gets NO link rather than a link to '/pcs/undefined' - the row still
// shows, because the finding is real even when the inventory is behind.
export function mapLink(card, item) {
  const template = card.map && card.map.link
  if (!template) return null
  let missing = false
  const href = String(template).replace(/\{(\w+)\}/g, (_m, key) => {
    const value = item[key]
    if (value === null || value === undefined || value === '') {
      missing = true
      return ''
    }
    return value
  })
  return missing ? null : href
}

// How many rows a card shows before collapsing the rest behind a count. A card
// listing forty PCs is a report someone has to read, not a board someone can
// scan - and it pushes every card below it off the screen. Five is enough to
// see the shape of the problem; the link goes to the full list.
export const MAXROWS = 5

export function visibleRows(card) {
  return cardRows(card).slice(0, MAXROWS)
}

export function overflowCount(card) {
  return Math.max(0, rows(card).length - MAXROWS)
}

// Chips are the row's own small facts, each carrying a tooltip: a depleted
// cartridge shows "Black 4%" and reveals the part number to order on hover.
// The percentage says something is wrong; the tooltip says what to do about
// it, which otherwise means opening the printer's page to find out.
export function mapChips(card, item) {
  const key = card.map && card.map.chips
  const list = key ? item[key] : null
  if (!Array.isArray(list)) return []
  return list
    .filter((chip) => chip && chip.text)
    .map((chip) => ({ text: chip.text, title: chip.title || '', level: chip.level || '' }))
}

// A tooltip on the row title, for context that would clutter the line - a
// printer's location, say. Never load-bearing: hover is not discoverable and
// does not exist on touch, so nothing essential goes here.
export function mapTitleTip(card, item) {
  const key = card.map && card.map.titletooltip
  return key ? (item[key] || '') : ''
}

// Floor-plan preview on hover, for rows that carry coordinates. Returns null
// when the card declares none or the row has not been placed on the map - an
// unplaced printer still belongs on the card, it just has nothing to preview.
export function mapHover(card, item) {
  const spec = card.map && card.map.maphover
  if (!spec) return null
  const x = item[spec.x]
  const y = item[spec.y]
  if (x === null || x === undefined || y === null || y === undefined) return null
  // The level travels with the coordinates (ADR-017) - they are pixels of ONE
  // drawing. Read `levelid` unless the card names another field, so a card that
  // predates levels still previews on the right floor instead of none.
  const levelid = item[spec.level || 'levelid']
  return {
    x, y,
    levelid: levelid === undefined ? null : levelid,
    label: spec.label ? (item[spec.label] || '') : '',
  }
}

export function cardRows(card) {
  return rows(card).map((item) => ({
    title: mapTitle(card, item),
    titletip: mapTitleTip(card, item),
    maphover: mapHover(card, item),
    detail: mapDetail(card, item),
    detailtip: mapDetailTip(card, item),
    meta: mapMeta(card, item),
    chips: mapChips(card, item),
    link: mapLink(card, item),
    timestamp: card.map && card.map.timestamp ? item[card.map.timestamp] : null,
  }))
}

// A metric card is the count, for the cases where the count IS the story.
export function metricValue(card) {
  const data = card._data
  if (typeof data === 'number') return data
  if (data && typeof data.value === 'number') return data.value
  return rows(card).length
}

// EMPTY HANDLING is the part that keeps the board readable. A card reporting
// "nothing wrong" every day teaches people to stop reading the page - the same
// way a fleet log reached 3,234 lines of which 3,217 were one repeated line.
// Default is to disappear; 'line' is for the few where absence is itself news.
export function cardVisible(card) {
  if (card._data === null || card._data === undefined) return false   // fetch failed
  const populated = card.render === 'metric'
    ? metricValue(card) > 0
    : rows(card).length > 0
  if (populated) return true
  return card.empty === 'line'
}

// Sorted by how much it wants a person: critical, then warning, then info, and
// by declared position within a severity. Ordering by position alone would let
// an info card sit above a failure.
const SEVERITY_ORDER = { critical: 0, warning: 1, info: 2 }

export function sortCards(cards) {
  return [...cards].sort((a, b) => {
    const sa = SEVERITY_ORDER[a.severity] ?? 3
    const sb = SEVERITY_ORDER[b.severity] ?? 3
    if (sa !== sb) return sa - sb
    return (a.position ?? 99) - (b.position ?? 99)
  })
}

// A card declares the permission its endpoint requires. Filtering here keeps
// the dashboard from becoming a way around RBAC, and avoids firing a request
// that would only 403. `hasPermission` comes from the auth store.
export function permittedCards(cards, hasPermission) {
  return cards.filter((card) => !card.permission || hasPermission(card.permission))
}

// Only cards using a renderer core actually has. An unknown render mode is
// skipped rather than drawn blank, so a plugin built against a newer core
// degrades instead of leaving a hole on the page.
export const RENDERERS = ['exceptions', 'metric', 'list']

export function renderableCards(cards) {
  return cards.filter((card) => RENDERERS.includes(card.render))
}
