/**
 * public/prefs.ts — client for the cross-device Projects UI prefs (GET/PUT /prefs).
 *
 * Favourites + group collapse-state live server-side (`~/.web-terminal-prefs.json`)
 * so they follow the user from laptop to phone — the whole point of this app.
 * localStorage is kept as an OFFLINE MIRROR: writes go there synchronously so a
 * reload works while the server is briefly unreachable, and a device that had the
 * legacy per-device favourites (`web-terminal:proj-favs`) is migrated on first load.
 *
 * loadPrefs/savePrefs are the only surface projects.ts needs. Both are defensive:
 * a failed fetch degrades to the local mirror rather than throwing.
 */

import type { UiPrefs } from '../src/types.js'

const LS_KEY = 'web-terminal:proj-prefs'
const LEGACY_FAV_KEY = 'web-terminal:proj-favs' // pre-v0.6 per-device favourites

export function emptyPrefs(): UiPrefs {
  return { favourites: [], collapsed: {} }
}

/** Coerce untrusted data (localStorage or a /prefs response) into a safe UiPrefs. */
export function sanitizePrefs(input: unknown): UiPrefs {
  if (input === null || typeof input !== 'object') return emptyPrefs()
  const o = input as Record<string, unknown>

  const favourites: string[] = []
  const seen = new Set<string>()
  if (Array.isArray(o['favourites'])) {
    for (const v of o['favourites']) {
      if (typeof v !== 'string' || v.length === 0 || seen.has(v)) continue
      seen.add(v)
      favourites.push(v)
    }
  }

  const collapsed: Record<string, boolean> = {}
  const c = o['collapsed']
  if (c !== null && typeof c === 'object' && !Array.isArray(c)) {
    for (const [k, v] of Object.entries(c as Record<string, unknown>)) {
      if (k.length > 0 && v === true) collapsed[k] = true
    }
  }

  return { favourites, collapsed }
}

function isEmpty(p: UiPrefs): boolean {
  return p.favourites.length === 0 && Object.keys(p.collapsed).length === 0
}

/** Read the local mirror, migrating legacy per-device favourites when present. */
function readLocal(): UiPrefs {
  try {
    const raw = localStorage.getItem(LS_KEY)
    if (raw !== null) return sanitizePrefs(JSON.parse(raw))
  } catch {
    // fall through to legacy / empty
  }
  try {
    const legacy = localStorage.getItem(LEGACY_FAV_KEY)
    if (legacy !== null) {
      const arr: unknown = JSON.parse(legacy)
      if (Array.isArray(arr)) return sanitizePrefs({ favourites: arr, collapsed: {} })
    }
  } catch {
    // ignore — corrupt legacy data
  }
  return emptyPrefs()
}

function writeLocal(prefs: UiPrefs): void {
  try {
    localStorage.setItem(LS_KEY, JSON.stringify(prefs))
  } catch {
    // localStorage unavailable — run without an offline mirror
  }
}

/** Load prefs: server is source of truth; fall back to the local mirror offline.
 *  When the server is empty but this device has local/legacy prefs, seed the server. */
export async function loadPrefs(): Promise<UiPrefs> {
  const local = readLocal()
  try {
    const res = await fetch('/prefs')
    if (res.ok) {
      const server = sanitizePrefs(await res.json())
      if (isEmpty(server) && !isEmpty(local)) {
        void savePrefs(local) // migrate this device's prefs up to the server
        return local
      }
      writeLocal(server) // refresh the offline mirror
      return server
    }
  } catch {
    // offline — use the local mirror
  }
  return local
}

/** Persist prefs: write the local mirror synchronously, then PUT best-effort. */
export async function savePrefs(prefs: UiPrefs): Promise<void> {
  writeLocal(prefs)
  try {
    await fetch('/prefs', {
      method: 'PUT',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(prefs),
    })
  } catch {
    // offline — the local mirror keeps the change until the next successful PUT
  }
}
