// PBI-76 Phase 1: one-shot migratie van legacy localStorage-prefs → user.settings.
//
// `UserSettingsBridge` roept dit eenmaal na hydratie aan. Bestaande users
// behouden zo hun saved filters; nieuwe users hebben simpelweg geen legacy keys
// en de helper is een no-op. De marker (`scrum4me:settings_migrated`) zorgt
// dat de migratie idempotent is — tweede call na succes returnt `null`.

import type { UserSettings } from './user-settings'

const MIGRATION_MARKER = 'scrum4me:settings_migrated'
const CURRENT_VERSION = 'v2'

export interface MigrationResult {
  patch: Partial<UserSettings>
  legacyKeys: string[]
  legacyCookies: string[]
  hasData: boolean
}

function readCookies(): Record<string, string> {
  if (typeof document === 'undefined') return {}
  const out: Record<string, string> = {}
  for (const part of document.cookie.split(';')) {
    const eq = part.indexOf('=')
    if (eq < 0) continue
    const key = part.slice(0, eq).trim()
    const val = part.slice(eq + 1).trim()
    if (key) out[key] = val
  }
  return out
}

function readJsonArray(key: string): string[] | null {
  const raw = localStorage.getItem(key)
  if (!raw) return null
  try {
    const arr = JSON.parse(raw)
    return Array.isArray(arr) ? arr.filter((x): x is string => typeof x === 'string') : null
  } catch {
    return null
  }
}

function readPriority(key: string): number | 'all' | null {
  const raw = localStorage.getItem(key)
  if (raw === null) return null
  if (raw === 'all') return 'all'
  const n = parseInt(raw, 10)
  return Number.isInteger(n) && n >= 1 && n <= 4 ? n : null
}

function readEnum<T extends string>(key: string, allowed: readonly T[]): T | null {
  const raw = localStorage.getItem(key)
  return raw && (allowed as readonly string[]).includes(raw) ? (raw as T) : null
}

function readBoolean(key: string): boolean | null {
  const raw = localStorage.getItem(key)
  if (raw === 'true') return true
  if (raw === 'false') return false
  return null
}

function setIfNotNull<T>(target: Record<string, unknown>, key: string, value: T | null): boolean {
  if (value === null) return false
  target[key] = value
  return true
}

export function buildMigrationPatch(): MigrationResult {
  const empty: MigrationResult = {
    patch: {},
    legacyKeys: [],
    legacyCookies: [],
    hasData: false,
  }
  if (typeof window === 'undefined') return empty
  if (localStorage.getItem(MIGRATION_MARKER) === CURRENT_VERSION) return empty

  const patch: Partial<UserSettings> = {}
  const views: NonNullable<UserSettings['views']> = {}
  const layout: NonNullable<UserSettings['layout']> = {}
  const legacyKeys: string[] = []
  const legacyCookies: string[] = []
  let hasData = false

  // sprint_pb_*
  const sprintBacklog: Record<string, unknown> = {}
  const SPRINT_KEYS = {
    filterPriority: 'scrum4me:sprint_pb_filter_priority',
    filterStatus: 'scrum4me:sprint_pb_filter_status',
    sort: 'scrum4me:sprint_pb_sort',
    sortDir: 'scrum4me:sprint_pb_sort_dir',
    collapsed: 'scrum4me:sprint_pb_collapsed',
    popover: 'scrum4me:sprint_pb_filter_popover_open',
  }
  if (
    setIfNotNull(sprintBacklog, 'filterPriority', readPriority(SPRINT_KEYS.filterPriority)) ||
    [SPRINT_KEYS.filterPriority].some((k) => localStorage.getItem(k) !== null)
  ) {
    legacyKeys.push(SPRINT_KEYS.filterPriority)
  }
  if (
    setIfNotNull(
      sprintBacklog,
      'filterStatus',
      readEnum(SPRINT_KEYS.filterStatus, ['OPEN', 'IN_SPRINT', 'DONE', 'all'] as const),
    )
  ) {
    legacyKeys.push(SPRINT_KEYS.filterStatus)
  }
  if (
    setIfNotNull(
      sprintBacklog,
      'sort',
      readEnum(SPRINT_KEYS.sort, ['priority', 'status', 'code'] as const),
    )
  ) {
    legacyKeys.push(SPRINT_KEYS.sort)
  }
  if (
    setIfNotNull(
      sprintBacklog,
      'sortDir',
      readEnum(SPRINT_KEYS.sortDir, ['asc', 'desc'] as const),
    )
  ) {
    legacyKeys.push(SPRINT_KEYS.sortDir)
  }
  const collapsed = readJsonArray(SPRINT_KEYS.collapsed)
  if (collapsed !== null) {
    sprintBacklog.collapsedPbis = collapsed
    legacyKeys.push(SPRINT_KEYS.collapsed)
  }
  if (setIfNotNull(sprintBacklog, 'filterPopoverOpen', readBoolean(SPRINT_KEYS.popover))) {
    legacyKeys.push(SPRINT_KEYS.popover)
  }
  if (Object.keys(sprintBacklog).length > 0) {
    views.sprintBacklog = sprintBacklog as NonNullable<typeof views.sprintBacklog>
    hasData = true
  }

  // pbi_*
  const pbiList: Record<string, unknown> = {}
  const PBI_KEYS = {
    sort: 'scrum4me:pbi_sort',
    filterPriority: 'scrum4me:pbi_filter_priority',
    filterStatus: 'scrum4me:pbi_filter_status',
    sortDir: 'scrum4me:pbi_sort_dir',
  }
  if (
    setIfNotNull(pbiList, 'sort', readEnum(PBI_KEYS.sort, ['priority', 'code', 'date'] as const))
  ) {
    legacyKeys.push(PBI_KEYS.sort)
  }
  if (setIfNotNull(pbiList, 'filterPriority', readPriority(PBI_KEYS.filterPriority))) {
    legacyKeys.push(PBI_KEYS.filterPriority)
  }
  if (
    setIfNotNull(
      pbiList,
      'filterStatus',
      readEnum(PBI_KEYS.filterStatus, ['ready', 'blocked', 'done', 'all'] as const),
    )
  ) {
    legacyKeys.push(PBI_KEYS.filterStatus)
  }
  if (setIfNotNull(pbiList, 'sortDir', readEnum(PBI_KEYS.sortDir, ['asc', 'desc'] as const))) {
    legacyKeys.push(PBI_KEYS.sortDir)
  }
  if (Object.keys(pbiList).length > 0) {
    views.pbiList = pbiList as NonNullable<typeof views.pbiList>
    hasData = true
  }

  // story_sort
  const storyPanel: Record<string, unknown> = {}
  const STORY_KEY = 'scrum4me:story_sort'
  if (
    setIfNotNull(storyPanel, 'sort', readEnum(STORY_KEY, ['priority', 'code', 'date'] as const))
  ) {
    legacyKeys.push(STORY_KEY)
  }
  if (Object.keys(storyPanel).length > 0) {
    views.storyPanel = storyPanel as NonNullable<typeof views.storyPanel>
    hasData = true
  }

  // jobs-column dynamic prefixes: <prefix>_filter_kind, <prefix>_filter_status
  const jobsColumns: Record<string, { kinds: string[]; statuses: string[] }> = {}
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i)
    if (!key) continue
    const kindMatch = key.match(/^(.+)_filter_kind$/)
    const statusMatch = key.match(/^(.+)_filter_status$/)
    // Skip the legacy sprint/pbi keys we already handled above
    if (key.startsWith('scrum4me:')) continue
    if (kindMatch) {
      const prefix = kindMatch[1]
      const csv = localStorage.getItem(key) ?? ''
      const kinds = csv.split(',').map((s) => s.trim()).filter(Boolean)
      jobsColumns[prefix] = { ...(jobsColumns[prefix] ?? { kinds: [], statuses: [] }), kinds }
      legacyKeys.push(key)
    } else if (statusMatch) {
      const prefix = statusMatch[1]
      const csv = localStorage.getItem(key) ?? ''
      const statuses = csv.split(',').map((s) => s.trim()).filter(Boolean)
      jobsColumns[prefix] = { ...(jobsColumns[prefix] ?? { kinds: [], statuses: [] }), statuses }
      legacyKeys.push(key)
    }
  }
  if (Object.keys(jobsColumns).length > 0) {
    views.jobsColumns = jobsColumns
    hasData = true
  }

  if (Object.keys(views).length > 0) {
    patch.views = views
  }

  // devTools.debugMode
  const DEBUG_KEY = 'scrum4me:debug-mode'
  const debug = readBoolean(DEBUG_KEY)
  if (debug !== null) {
    patch.devTools = { debugMode: debug }
    legacyKeys.push(DEBUG_KEY)
    hasData = true
  }

  // layout from cookies (Phase 2)
  const cookies = readCookies()
  const splitPanePositions: Record<string, number[]> = {}
  const activeSprints: Record<string, string> = {}
  for (const [name, rawValue] of Object.entries(cookies)) {
    if (name.startsWith('sp:')) {
      const key = name.slice(3)
      try {
        const arr = JSON.parse(decodeURIComponent(rawValue))
        if (
          Array.isArray(arr) &&
          arr.every((n) => typeof n === 'number') &&
          Math.abs(arr.reduce((a, b) => a + b, 0) - 100) <= 1
        ) {
          splitPanePositions[key] = arr as number[]
          legacyCookies.push(name)
        }
      } catch {
        // ignore malformed cookie
      }
    } else if (name.startsWith('active_sprint_') && rawValue) {
      const productId = name.slice('active_sprint_'.length)
      activeSprints[productId] = decodeURIComponent(rawValue)
      legacyCookies.push(name)
    }
  }
  if (Object.keys(splitPanePositions).length > 0) {
    layout.splitPanePositions = splitPanePositions
    hasData = true
  }
  if (Object.keys(activeSprints).length > 0) {
    layout.activeSprints = activeSprints
    hasData = true
  }
  if (Object.keys(layout).length > 0) {
    patch.layout = layout
  }

  return { patch, legacyKeys, legacyCookies, hasData }
}

export function clearLegacyStorage(keys: string[], cookies: string[] = []): void {
  if (typeof window === 'undefined') return
  for (const k of keys) {
    try {
      localStorage.removeItem(k)
    } catch {
      // storage quota exceeded or disabled — ignore
    }
  }
  for (const c of cookies) {
    try {
      document.cookie = `${c}=; max-age=0; path=/; samesite=lax`
    } catch {
      // ignore
    }
  }
  try {
    localStorage.setItem(MIGRATION_MARKER, CURRENT_VERSION)
  } catch {
    // ignore
  }
}

/** @deprecated use clearLegacyStorage */
export const clearLegacyLocalStorage = (keys: string[]) =>
  clearLegacyStorage(keys, [])
