/**
 * public/grid-presets.ts — saved split-grid layout presets (v3).
 *
 * A preset bundles a layout + its custom track fractions under a name, so you can
 * save a board arrangement you like (e.g. "2×2, left column wide") and re-apply it
 * in one click. Persistence + the tiny toolbar dropdown live here; TabApp exposes
 * the current arrangement and an apply() via hooks, keeping tabs.ts lean.
 */

import { type GridLayout, type TrackSplit, gridAllowed, GRID_MIN_WIDTH } from './grid-layout.js'

export interface GridPreset {
  name: string
  layout: GridLayout
  split: TrackSplit
}

const PRESETS_KEY = 'web-terminal:grid-presets'
const VALID_LAYOUTS: readonly string[] = ['single', 'split-2', 'row-3', 'grid-4', 'grid-6']

function isPreset(v: unknown): v is GridPreset {
  if (v === null || typeof v !== 'object') return false
  const p = v as Record<string, unknown>
  return (
    typeof p['name'] === 'string' &&
    typeof p['layout'] === 'string' &&
    VALID_LAYOUTS.includes(p['layout']) &&
    typeof p['split'] === 'object' &&
    p['split'] !== null
  )
}

/** Load saved presets (best-effort; drops malformed entries). */
export function loadPresets(): GridPreset[] {
  try {
    const raw = localStorage.getItem(PRESETS_KEY)
    if (raw === null) return []
    const parsed: unknown = JSON.parse(raw)
    if (Array.isArray(parsed)) return parsed.filter(isPreset)
  } catch {
    // localStorage / JSON unavailable
  }
  return []
}

/** Persist the presets list. */
export function savePresets(list: readonly GridPreset[]): void {
  try {
    localStorage.setItem(PRESETS_KEY, JSON.stringify(list))
  } catch {
    // localStorage unavailable — run without persistence
  }
}

export interface PresetHooks {
  /** The current layout + its split, to snapshot into a new preset. */
  current: () => { layout: GridLayout; split: TrackSplit }
  /** Apply a chosen preset (set layout + splits + re-render). */
  apply: (preset: GridPreset) => void
}

export interface PresetMenu {
  /** Re-sync visibility (desktop gate) — call on layout/resize changes. */
  refresh: () => void
  dispose: () => void
}

/** Short human label for a layout in a preset row. */
const LAYOUT_LABEL: Readonly<Record<GridLayout, string>> = {
  single: 'single',
  'split-2': '1×2',
  'row-3': '1×3',
  'grid-4': '2×2',
  'grid-6': '2×3',
}

/** Mount the presets dropdown into the toolbar. Desktop-only (hidden below the
 *  grid width gate, like the layout toggle). */
export function mountGridPresets(toolbar: HTMLElement, hooks: PresetHooks): PresetMenu {
  let presets = loadPresets()

  const root = document.createElement('div')
  root.className = 'grid-presets'

  const btn = document.createElement('button')
  btn.type = 'button'
  btn.className = 'grid-presets-btn'
  btn.textContent = '☰'
  btn.title = 'Layout presets'
  btn.setAttribute('aria-label', 'Layout presets')

  const menu = document.createElement('div')
  menu.className = 'grid-presets-menu'
  menu.hidden = true

  const list = document.createElement('div')
  list.className = 'gp-list'

  const saveRow = document.createElement('div')
  saveRow.className = 'gp-save'
  const nameInput = document.createElement('input')
  nameInput.className = 'gp-name'
  nameInput.type = 'text'
  nameInput.placeholder = 'Save current as…'
  nameInput.maxLength = 40
  const saveBtn = document.createElement('button')
  saveBtn.type = 'button'
  saveBtn.className = 'gp-save-btn'
  saveBtn.textContent = 'Save'
  saveRow.append(nameInput, saveBtn)

  menu.append(list, saveRow)
  root.append(btn, menu)
  toolbar.appendChild(root)

  const renderList = (): void => {
    list.replaceChildren()
    if (presets.length === 0) {
      list.appendChild(el('div', 'gp-empty', 'No saved presets'))
      return
    }
    presets.forEach((preset, i) => {
      const row = el('div', 'gp-row')
      const apply = el('button', 'gp-apply')
      apply.append(
        el('span', 'gp-apply-name', preset.name),
        el('span', 'gp-apply-layout', LAYOUT_LABEL[preset.layout]),
      )
      apply.addEventListener('click', () => {
        hooks.apply(preset)
        close()
      })
      const del = el('button', 'gp-del', '×')
      del.title = 'Delete preset'
      del.setAttribute('aria-label', `Delete preset ${preset.name}`)
      del.addEventListener('click', (e) => {
        e.stopPropagation()
        presets = presets.filter((_, j) => j !== i)
        savePresets(presets)
        renderList()
      })
      row.append(apply, del)
      list.appendChild(row)
    })
  }

  const save = (): void => {
    const name = nameInput.value.trim()
    if (name === '') return
    const { layout, split } = hooks.current()
    // Replace a same-named preset rather than duplicating.
    presets = [...presets.filter((p) => p.name !== name), { name, layout, split }]
    savePresets(presets)
    nameInput.value = ''
    renderList()
  }
  saveBtn.addEventListener('click', save)
  nameInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') save()
    e.stopPropagation()
  })

  const open = (): void => {
    renderList()
    menu.hidden = false
    document.addEventListener('pointerdown', onOutside, true)
    document.addEventListener('keydown', onKey, true)
  }
  const close = (): void => {
    menu.hidden = true
    document.removeEventListener('pointerdown', onOutside, true)
    document.removeEventListener('keydown', onKey, true)
  }
  const onOutside = (e: Event): void => {
    if (!root.contains(e.target as Node)) close()
  }
  const onKey = (e: KeyboardEvent): void => {
    if (e.key === 'Escape') close()
  }
  btn.addEventListener('click', () => (menu.hidden ? open() : close()))

  const refresh = (): void => {
    root.style.display = gridAllowed() ? 'inline-flex' : 'none'
    if (!gridAllowed()) close()
  }

  const mql =
    typeof window !== 'undefined' && typeof window.matchMedia === 'function'
      ? window.matchMedia(`(min-width: ${GRID_MIN_WIDTH}px)`)
      : null
  mql?.addEventListener('change', refresh)
  refresh()

  return {
    refresh,
    dispose: (): void => {
      mql?.removeEventListener('change', refresh)
      close()
      root.remove()
    },
  }
}

/** Local element helper (kept tiny to avoid a cross-module import cycle). */
function el<K extends keyof HTMLElementTagNameMap>(
  tag: K,
  cls: string,
  text?: string,
): HTMLElementTagNameMap[K] {
  const node = document.createElement(tag)
  node.className = cls
  if (text !== undefined) node.textContent = text
  return node
}
