// Desktop icon positions — a simple column/row grid, persisted to
// localStorage so icons stay where the operator put them across reloads.
// Deliberately NOT wmkit: wmkit manages floating windows (pixel bounds,
// z-order, stage), icons are a flat, non-overlapping grid with a much
// simpler drag model (snap-to-cell, no resize/minimize/stack). Reinventing
// that inside wmkit would mean fighting its window-shaped abstractions for
// no benefit.
//
// Reactive to the apps store (Phase 3): when an installed app registers
// after init, it gets a free cell on the next emission. Uninstalled apps'
// positions are KEPT (so reinstall remembers where the icon was) but their
// icons simply don't render — the desktop's {#each $apps} is the source of
// truth for what's visible.
import { writable, get } from 'svelte/store'
import { apps } from '$lib/apps'

export interface IconPos {
  col: number
  row: number
}

export const GRID = { cell: 96, gap: 12, padding: 16 }

const STORAGE_KEY = 'oikos-desktop-icons'

function load(): Record<string, IconPos> {
  if (typeof localStorage === 'undefined') return {}
  try {
    const raw = localStorage.getItem(STORAGE_KEY)
    if (!raw) return {}
    return JSON.parse(raw) as Record<string, IconPos>
  } catch {
    return {}
  }
}

export const iconPositions = writable<Record<string, IconPos>>(load())

function persist(positions: Record<string, IconPos>): void {
  if (typeof localStorage === 'undefined') return
  localStorage.setItem(STORAGE_KEY, JSON.stringify(positions))
}

iconPositions.subscribe(persist)

function occupied(
  positions: Record<string, IconPos>,
  col: number,
  row: number,
  exceptId: string
): boolean {
  return Object.entries(positions).some(
    ([id, p]) => id !== exceptId && p.col === col && p.row === row
  )
}

// Finds the nearest free cell to (col, row) via an expanding ring search,
// so dropping an icon onto an occupied cell nudges it to the closest open
// spot instead of silently overlapping or refusing the drop.
function nearestFreeCell(
  positions: Record<string, IconPos>,
  col: number,
  row: number,
  exceptId: string
): IconPos {
  if (!occupied(positions, col, row, exceptId))
    return { col: Math.max(0, col), row: Math.max(0, row) }
  for (let radius = 1; radius < 64; radius++) {
    for (let dc = -radius; dc <= radius; dc++) {
      for (let dr = -radius; dr <= radius; dr++) {
        if (Math.max(Math.abs(dc), Math.abs(dr)) !== radius) continue
        const c = col + dc
        const r = row + dr
        if (c < 0 || r < 0) continue
        if (!occupied(positions, c, r, exceptId)) return { col: c, row: r }
      }
    }
  }
  return { col: Math.max(0, col), row: Math.max(0, row) }
}

export function placeIcon(appId: string, col: number, row: number): void {
  iconPositions.update((positions) => {
    const target = nearestFreeCell(positions, col, row, appId)
    return { ...positions, [appId]: target }
  })
}

export function iconPixelPos(pos: IconPos): { x: number; y: number } {
  return {
    x: GRID.padding + pos.col * (GRID.cell + GRID.gap),
    y: GRID.padding + pos.row * (GRID.cell + GRID.gap)
  }
}

export function maxCols(viewportWidth: number): number {
  return Math.max(1, Math.floor((viewportWidth - GRID.padding) / (GRID.cell + GRID.gap)))
}

export function getIconPositions(): Record<string, IconPos> {
  return get(iconPositions)
}

// Bails a messy manual layout back to the classic left-edge column,
// registry order — the desktop's right-click menu's "Reset icon layout".
// Clears all positions then re-seeds from the current app list, so it
// respects the live registry (including installed apps) rather than a
// static snapshot.
export function resetIconLayout(): void {
  iconPositions.set(seedMissing({}, get(apps)))
}

// Seeds positions for any app in `list` that doesn't have one yet —
// classic OS default: one left-edge column, registry order. Returns the
// same positions object if nothing needed seeding (so callers can skip a
// no-op set), otherwise a fresh merged object.
function seedMissing(
  positions: Record<string, IconPos>,
  list: { id: string }[]
): Record<string, IconPos> {
  let next: Record<string, IconPos> | null = null
  let row = 0
  for (const app of list) {
    if (positions[app.id]) continue
    if (!next) next = { ...positions }
    while (occupied(next, 0, row, app.id)) row++
    next![app.id] = { col: 0, row }
    row++
  }
  return next ?? positions
}

// Seed on every apps-store emission so a newly-installed app gets a cell
// immediately. Existing positions (including uninstalled apps' remembered
// spots) are preserved.
apps.subscribe((list) => {
  const current = get(iconPositions)
  const next = seedMissing(current, list)
  if (next !== current) iconPositions.set(next)
})
