import type { KbxWorkspaceTab } from '@kbx/contracts'

const PREFIX = 'kbx.workspace.session.v1'
const MAX_SCOPE_LENGTH = 160
const MAX_TABS = 12
const MAX_PATH_LENGTH = 2048
const MAX_ID_LENGTH = 128
const MAX_TIME_LENGTH = 64
const MAX_STORED_BYTES = 48_000

export interface KbxWorkspaceSessionSnapshot {
  tabs: Array<Pick<KbxWorkspaceTab, 'screenId' | 'path' | 'pinned' | 'openedAt' | 'lastActivatedAt'>>
  dirtyDiscardedCount: number
  savedAt: string
}

function bounded(value: unknown, max: number) {
  if (typeof value !== 'string') return ''
  const clean = value.trim()
  return clean.length > 0 && clean.length <= max ? clean : ''
}
function scopeKey(scopeValue: unknown) {
  const scope = bounded(scopeValue, MAX_SCOPE_LENGTH)
  return scope ? `${PREFIX}:${encodeURIComponent(scope)}` : ''
}
function safeTime(value: unknown) {
  const time = bounded(value, MAX_TIME_LENGTH)
  return time && !Number.isNaN(Date.parse(time)) ? time : new Date().toISOString()
}
function safePath(value: unknown) {
  const raw = bounded(value, MAX_PATH_LENGTH)
  if (!raw || /[\u0000-\u001f\u007f\\]|%2f|%5c|%00|%0d|%0a|%09|%2e%2e/i.test(raw)) return ''
  try {
    const parsed = new URL(raw, 'https://kbx.local')
    if (parsed.origin !== 'https://kbx.local' || parsed.username || parsed.password) return ''
    // Workspace resume deliberately persists only route identity. Query/hash may contain transient
    // filters, tokens, customer/order text or other sensitive context and must not survive refresh.
    return parsed.pathname
  } catch { return '' }
}
function normalizeSnapshot(value: unknown): KbxWorkspaceSessionSnapshot | null {
  if (!value || typeof value !== 'object') return null
  const raw = value as Partial<KbxWorkspaceSessionSnapshot>
  if (!Array.isArray(raw.tabs)) return null
  const tabs: KbxWorkspaceSessionSnapshot['tabs'] = []
  const seen = new Set<string>()
  for (const candidate of raw.tabs.slice(0, MAX_TABS)) {
    if (!candidate || typeof candidate !== 'object') continue
    const item = candidate as Partial<KbxWorkspaceTab>
    const screenId = bounded(item.screenId, MAX_ID_LENGTH)
    const path = safePath(item.path)
    if (!screenId || !path) continue
    const identity = `${screenId}:${path}`
    if (seen.has(identity)) continue
    seen.add(identity)
    tabs.push({screenId, path, pinned:Boolean(item.pinned), openedAt:safeTime(item.openedAt), lastActivatedAt:safeTime(item.lastActivatedAt)})
  }
  const dirtyDiscardedCount = Math.min(MAX_TABS, Math.max(0, Number.isFinite(Number(raw.dirtyDiscardedCount)) ? Number(raw.dirtyDiscardedCount) : 0))
  return { tabs, dirtyDiscardedCount, savedAt:safeTime(raw.savedAt) }
}

export function loadWorkspaceSession(scopeValue: string): KbxWorkspaceSessionSnapshot | null {
  const key = scopeKey(scopeValue)
  if (!key || typeof sessionStorage === 'undefined') return null
  let raw = ''
  try { raw = sessionStorage.getItem(key) ?? '' } catch { return null }
  if (!raw || raw.length > MAX_STORED_BYTES) return null
  try { return normalizeSnapshot(JSON.parse(raw)) } catch { return null }
}

export function saveWorkspaceSession(scopeValue: string, tabs: readonly KbxWorkspaceTab[]) {
  const key = scopeKey(scopeValue)
  if (!key || typeof sessionStorage === 'undefined') return false
  const cleanTabs = tabs.filter(tab => !tab.dirty).slice(0, MAX_TABS).flatMap(tab => {
    const screenId = bounded(tab.screenId, MAX_ID_LENGTH)
    const path = safePath(tab.path)
    if (!screenId || !path) return []
    return [{screenId, path, pinned:Boolean(tab.pinned), openedAt:safeTime(tab.openedAt), lastActivatedAt:safeTime(tab.lastActivatedAt)}]
  })
  const snapshot:KbxWorkspaceSessionSnapshot = {
    tabs: cleanTabs,
    dirtyDiscardedCount: Math.min(MAX_TABS, tabs.filter(tab => tab.dirty).length),
    savedAt: new Date().toISOString(),
  }
  const payload = JSON.stringify(snapshot)
  if (payload.length > MAX_STORED_BYTES) return false
  try { sessionStorage.setItem(key, payload); return true } catch { return false }
}

export function clearWorkspaceSession(scopeValue: string) {
  const key = scopeKey(scopeValue)
  if (!key || typeof sessionStorage === 'undefined') return false
  try { sessionStorage.removeItem(key); return true } catch { return false }
}
