import { z } from 'zod'
import { JOBS_TIME_FILTER_VALUES } from '@/lib/jobs-time-filter'
import { IDEA_STATUS_API_VALUES, type IdeaStatusApi } from '@/lib/idea-status'

const PriorityFilter = z.union([
  z.number().int().min(1).max(4),
  z.literal('all'),
])

const SortDir = z.enum(['asc', 'desc'])

const SprintBacklogPrefs = z.object({
  filterPriority: PriorityFilter.optional(),
  filterStatus: z.enum(['OPEN', 'IN_SPRINT', 'DONE', 'all']).optional(),
  sort: z.enum(['priority', 'status', 'code']).optional(),
  sortDir: SortDir.optional(),
  collapsedPbis: z.array(z.string()).optional(),
  filterPopoverOpen: z.boolean().optional(),
}).strict()

const PbiListPrefs = z.object({
  sort: z.enum(['priority', 'code', 'date']).optional(),
  filterPriority: PriorityFilter.optional(),
  filterStatus: z.enum(['ready', 'blocked', 'done', 'all']).optional(),
  sortDir: SortDir.optional(),
}).strict()

const StoryPanelPrefs = z.object({
  sort: z.enum(['priority', 'code', 'date']).optional(),
}).strict()

const JobsColumnPrefs = z.object({
  kinds: z.array(z.string()),
  statuses: z.array(z.string()),
}).strict()

const JobsViewPrefs = z.object({
  timeFilter: z.enum(JOBS_TIME_FILTER_VALUES).optional(),
}).strict()

const IdeasListPrefs = z.object({
  filterStatuses: z.array(
    z.enum(IDEA_STATUS_API_VALUES as [IdeaStatusApi, ...IdeaStatusApi[]])
  ).optional(),
}).strict()

const ViewsPrefs = z.object({
  sprintBacklog: SprintBacklogPrefs.optional(),
  pbiList: PbiListPrefs.optional(),
  storyPanel: StoryPanelPrefs.optional(),
  jobsColumns: z.record(z.string(), JobsColumnPrefs).optional(),
  jobs: JobsViewPrefs.optional(),
  ideasList: IdeasListPrefs.optional(),
}).strict()

const DevToolsPrefs = z.object({
  debugMode: z.boolean().optional(),
}).strict()

const LayoutPrefs = z.object({
  splitPanePositions: z.record(z.string(), z.array(z.number())).optional(),
  activeSprints: z.record(z.string(), z.string().nullable()).optional(),
  activePbis: z.record(z.string(), z.string().nullable()).optional(),
  activeStories: z.record(z.string(), z.string().nullable()).optional(),
}).strict()

const PbiIntent = z.enum(['all', 'none'])

const StoryOverrides = z.object({
  add: z.array(z.string()),
  remove: z.array(z.string()),
}).strict()

const PendingSprintDraftSchema = z.object({
  goal: z.string().min(1),
  startAt: z.string().date().optional(),
  endAt: z.string().date().optional(),
  pbiIntent: z.record(z.string(), PbiIntent).default({}),
  storyOverrides: z.record(z.string(), StoryOverrides).default({}),
}).strict()

const WorkflowPrefs = z.object({
  pendingSprintDraft: z.record(z.string(), PendingSprintDraftSchema).optional(),
}).strict()

export const UserSettingsSchema = z.object({
  views: ViewsPrefs.optional(),
  devTools: DevToolsPrefs.optional(),
  layout: LayoutPrefs.optional(),
  workflow: WorkflowPrefs.optional(),
}).strict()

export type UserSettings = z.infer<typeof UserSettingsSchema>
export type PendingSprintDraft = z.infer<typeof PendingSprintDraftSchema>
export type PbiIntent = z.infer<typeof PbiIntent>
export type StoryOverrides = z.infer<typeof StoryOverrides>

export const DEFAULT_USER_SETTINGS: UserSettings = {}

function isPlainObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

export function mergeSettings(
  prev: UserSettings,
  patch: Partial<UserSettings>,
): UserSettings {
  const out: Record<string, unknown> = { ...prev }
  for (const [key, patchValue] of Object.entries(patch)) {
    if (patchValue === undefined) continue
    const prevValue = (prev as Record<string, unknown>)[key]
    if (isPlainObject(patchValue) && isPlainObject(prevValue)) {
      out[key] = mergeSettings(
        prevValue as UserSettings,
        patchValue as Partial<UserSettings>,
      )
    } else {
      out[key] = patchValue
    }
  }
  return out as UserSettings
}

export function parseUserSettings(raw: unknown): UserSettings {
  if (raw === null || raw === undefined) return DEFAULT_USER_SETTINGS
  const result = UserSettingsSchema.safeParse(raw)
  return result.success ? result.data : DEFAULT_USER_SETTINGS
}
