import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

import {
  isDetail,
  type ActiveProductRef,
  type OptimisticMutation,
  type PendingOptimisticMutation,
  type RealtimeStatus,
  type ResyncReason,
  type SprintWorkspaceSnapshot,
  type SprintWorkspaceSprint,
  type SprintWorkspaceStory,
  type SprintWorkspaceTask,
  type SprintWorkspaceTaskDetail,
} from './types'
import {
  readHints,
  writeProductHint,
  writeSprintHint,
  writeStoryHint,
  writeTaskHint,
} from './restore'
import {
  normalizeSprintTask,
  normalizeSprintWorkspaceSnapshot,
  normalizeStoryStatusForStore,
  normalizeTaskStatusForStore,
} from '@/stores/workspace-status-adapter'

interface ContextSlice {
  activeProduct: ActiveProductRef | null
  activeSprintId: string | null
  activeStoryId: string | null
  activeTaskId: string | null
}

interface EntitiesSlice {
  sprintsById: Record<string, SprintWorkspaceSprint>
  storiesById: Record<string, SprintWorkspaceStory>
  tasksById: Record<string, SprintWorkspaceTask | SprintWorkspaceTaskDetail>
}

interface RelationsSlice {
  sprintIdsByProduct: Record<string, string[]>
  storyIdsBySprint: Record<string, string[]>
  taskIdsByStory: Record<string, string[]>
}

interface LoadingSlice {
  loadedProductSprintsIds: Record<string, true>
  loadingProductId: string | null
  loadedSprintIds: Record<string, true>
  loadingSprintId: string | null
  loadedStoryIds: Record<string, true>
  loadedTaskIds: Record<string, true>
  activeRequestId: string | null
}

interface SyncSlice {
  realtimeStatus: RealtimeStatus
  lastEventAt: number | null
  lastResyncAt: number | null
  resyncReason: ResyncReason | null
}

interface State {
  context: ContextSlice
  entities: EntitiesSlice
  relations: RelationsSlice
  loading: LoadingSlice
  sync: SyncSlice
  pendingMutations: Record<string, PendingOptimisticMutation>
}

interface Actions {
  hydrateSnapshot(snapshot: SprintWorkspaceSnapshot): void
  hydrateProductSprints(productId: string, sprints: SprintWorkspaceSprint[]): void

  setActiveProduct(product: ActiveProductRef | null): void
  setActiveSprint(sprintId: string | null): void
  setActiveStory(storyId: string | null): void
  setActiveTask(taskId: string | null): void

  ensureProductSprintsLoaded(productId: string, requestId?: string): Promise<void>
  ensureSprintLoaded(sprintId: string, requestId?: string): Promise<void>
  ensureStoryLoaded(storyId: string, requestId?: string): Promise<void>
  ensureTaskLoaded(taskId: string, requestId?: string): Promise<void>

  applyRealtimeEvent(event: Record<string, unknown>): void
  resyncActiveScopes(reason: ResyncReason): Promise<void>
  resyncLoadedScopes(reason: ResyncReason): Promise<void>

  applyOptimisticMutation(mutation: OptimisticMutation): string
  rollbackMutation(mutationId: string): void
  settleMutation(mutationId: string): void

  setRealtimeStatus(status: RealtimeStatus): void
}

export type SprintWorkspaceStore = State & Actions

const initialState: State = {
  context: {
    activeProduct: null,
    activeSprintId: null,
    activeStoryId: null,
    activeTaskId: null,
  },
  entities: {
    sprintsById: {},
    storiesById: {},
    tasksById: {},
  },
  relations: {
    sprintIdsByProduct: {},
    storyIdsBySprint: {},
    taskIdsByStory: {},
  },
  loading: {
    loadedProductSprintsIds: {},
    loadingProductId: null,
    loadedSprintIds: {},
    loadingSprintId: null,
    loadedStoryIds: {},
    loadedTaskIds: {},
    activeRequestId: null,
  },
  sync: {
    realtimeStatus: 'connecting',
    lastEventAt: null,
    lastResyncAt: null,
    resyncReason: null,
  },
  pendingMutations: {},
}

function compareSprint(a: SprintWorkspaceSprint, b: SprintWorkspaceSprint): number {
  // OPEN sprints first, then CLOSED
  if (a.status !== b.status) return a.status === 'OPEN' ? -1 : 1
  // Newest start_date first within same status
  const aStart = a.start_date ? new Date(a.start_date).getTime() : 0
  const bStart = b.start_date ? new Date(b.start_date).getTime() : 0
  if (aStart !== bStart) return bStart - aStart
  return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
}

function compareStory(a: SprintWorkspaceStory, b: SprintWorkspaceStory): number {
  if (a.sort_order !== b.sort_order) return a.sort_order - b.sort_order
  return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
}

function compareTask(a: SprintWorkspaceTask, b: SprintWorkspaceTask): number {
  if (a.sort_order !== b.sort_order) return a.sort_order - b.sort_order
  return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
}

function newRequestId(): string {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID()
  }
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`
}

function isKnownEntity(entity: unknown): entity is 'sprint' | 'story' | 'task' {
  return entity === 'sprint' || entity === 'story' || entity === 'task'
}

function isUnknownEntityEvent(p: Record<string, unknown>): boolean {
  if (typeof p.entity !== 'string') return false
  if (isKnownEntity(p.entity)) return false
  if ('type' in p) return false
  return true
}

async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
  const response = await fetch(url, { cache: 'no-store', ...init })
  if (!response.ok) {
    throw new Error(`Fetch ${url} failed with ${response.status}`)
  }
  return (await response.json()) as T
}

export const useSprintWorkspaceStore = create<SprintWorkspaceStore>()(
  immer((set, get) => ({
    ...initialState,

    hydrateSnapshot(inputSnapshot) {
      const snapshot = normalizeSprintWorkspaceSnapshot(inputSnapshot)
      set((s) => {
        if (snapshot.product) s.context.activeProduct = snapshot.product

        const sprintId = snapshot.sprint?.id ?? null
        const productId = snapshot.product?.id ?? snapshot.sprint?.product_id ?? null

        if (snapshot.sprint) {
          s.entities.sprintsById[snapshot.sprint.id] = snapshot.sprint
          if (productId) {
            const list = s.relations.sprintIdsByProduct[productId] ?? []
            if (!list.includes(snapshot.sprint.id)) {
              list.push(snapshot.sprint.id)
              s.relations.sprintIdsByProduct[productId] = sortSprintIds(
                s.entities.sprintsById,
                list,
              )
            }
          }
        }

        for (const story of snapshot.stories) {
          s.entities.storiesById[story.id] = story
        }
        if (sprintId) {
          s.relations.storyIdsBySprint[sprintId] = [...snapshot.stories]
            .sort(compareStory)
            .map((st) => st.id)
        }

        for (const [storyId, tasks] of Object.entries(snapshot.tasksByStory)) {
          for (const task of tasks) {
            s.entities.tasksById[task.id] = task
          }
          s.relations.taskIdsByStory[storyId] = [...tasks]
            .sort(compareTask)
            .map((t) => t.id)
        }

        if (sprintId) {
          s.loading.loadedSprintIds[sprintId] = true
        }
      })
    },

    hydrateProductSprints(productId, sprints) {
      set((s) => {
        for (const sprint of sprints) {
          s.entities.sprintsById[sprint.id] = sprint
        }
        s.relations.sprintIdsByProduct[productId] = [...sprints]
          .sort(compareSprint)
          .map((sp) => sp.id)
        s.loading.loadedProductSprintsIds[productId] = true
      })
    },

    setActiveProduct(product) {
      const requestId = newRequestId()
      const productChanged = get().context.activeProduct?.id !== product?.id

      set((s) => {
        s.context.activeProduct = product
        s.context.activeSprintId = null
        s.context.activeStoryId = null
        s.context.activeTaskId = null
        s.loading.activeRequestId = requestId

        if (productChanged) {
          s.entities.sprintsById = {}
          s.entities.storiesById = {}
          s.entities.tasksById = {}
          s.relations.sprintIdsByProduct = {}
          s.relations.storyIdsBySprint = {}
          s.relations.taskIdsByStory = {}
          s.loading.loadedProductSprintsIds = {}
          s.loading.loadedSprintIds = {}
          s.loading.loadedStoryIds = {}
          s.loading.loadedTaskIds = {}
        }
      })

      writeProductHint(product?.id ?? null)

      if (product) {
        const productId = product.id
        void (async () => {
          await get().ensureProductSprintsLoaded(productId, requestId)
          if (get().loading.activeRequestId !== requestId) return
          const hint = readHints().perProduct[productId]?.lastActiveSprintId
          if (hint && get().entities.sprintsById[hint]) {
            get().setActiveSprint(hint)
          }
        })()
      }
    },

    setActiveSprint(sprintId) {
      const requestId = newRequestId()
      const productId = get().context.activeProduct?.id ?? null

      set((s) => {
        s.context.activeSprintId = sprintId
        s.context.activeStoryId = null
        s.context.activeTaskId = null
        s.loading.activeRequestId = requestId
      })

      if (productId) writeSprintHint(productId, sprintId)

      if (sprintId) {
        void (async () => {
          await get().ensureSprintLoaded(sprintId, requestId)
          if (get().loading.activeRequestId !== requestId) return
          const hint = readHints().perSprint[sprintId]?.lastActiveStoryId
          if (hint && get().entities.storiesById[hint]) {
            get().setActiveStory(hint)
          }
        })()
      }
    },

    setActiveStory(storyId) {
      const requestId = newRequestId()
      const sprintId = get().context.activeSprintId

      set((s) => {
        s.context.activeStoryId = storyId
        s.context.activeTaskId = null
        s.loading.activeRequestId = requestId
      })

      if (sprintId) writeStoryHint(sprintId, storyId)

      if (storyId) {
        void (async () => {
          await get().ensureStoryLoaded(storyId, requestId)
          if (get().loading.activeRequestId !== requestId) return
          if (!sprintId) return
          const hint = readHints().perSprint[sprintId]?.lastActiveTaskId
          if (hint && get().entities.tasksById[hint]) {
            get().setActiveTask(hint)
          }
        })()
      }
    },

    setActiveTask(taskId) {
      const sprintId = get().context.activeSprintId

      set((s) => {
        s.context.activeTaskId = taskId
      })

      if (sprintId) writeTaskHint(sprintId, taskId)

      if (taskId) {
        void get().ensureTaskLoaded(taskId)
      }
    },

    async ensureProductSprintsLoaded(productId, requestId) {
      set((s) => {
        s.loading.loadingProductId = productId
      })
      try {
        const sprints = await fetchJson<SprintWorkspaceSprint[] | null>(
          `/api/products/${encodeURIComponent(productId)}/sprints`,
        )
        if (requestId && get().loading.activeRequestId !== requestId) return
        if (!Array.isArray(sprints)) return
        get().hydrateProductSprints(productId, sprints)
      } finally {
        set((s) => {
          if (s.loading.loadingProductId === productId) {
            s.loading.loadingProductId = null
          }
        })
      }
    },

    async ensureSprintLoaded(sprintId, requestId) {
      set((s) => {
        s.loading.loadingSprintId = sprintId
      })
      try {
        const snapshot = await fetchJson<SprintWorkspaceSnapshot | null>(
          `/api/sprints/${encodeURIComponent(sprintId)}/workspace`,
        )
        if (requestId && get().loading.activeRequestId !== requestId) return
        if (!snapshot || !Array.isArray(snapshot.stories)) return
        get().hydrateSnapshot(snapshot)
      } finally {
        set((s) => {
          if (s.loading.loadingSprintId === sprintId) {
            s.loading.loadingSprintId = null
          }
        })
      }
    },

    async ensureStoryLoaded(storyId, requestId) {
      const tasks = await fetchJson<SprintWorkspaceTask[] | null>(
        `/api/stories/${encodeURIComponent(storyId)}/tasks`,
      )
      if (requestId && get().loading.activeRequestId !== requestId) return
      if (!Array.isArray(tasks)) return
      const normalizedTasks = tasks.map(normalizeSprintTask)
      set((s) => {
        for (const task of normalizedTasks) {
          const existing = s.entities.tasksById[task.id]
          if (existing && isDetail(existing)) {
            s.entities.tasksById[task.id] = { ...existing, ...task }
          } else {
            s.entities.tasksById[task.id] = task
          }
        }
        s.relations.taskIdsByStory[storyId] = [...normalizedTasks]
          .sort(compareTask)
          .map((t) => t.id)
        s.loading.loadedStoryIds[storyId] = true
      })
    },

    async ensureTaskLoaded(taskId, requestId) {
      const detail = await fetchJson<SprintWorkspaceTaskDetail | null>(
        `/api/tasks/${encodeURIComponent(taskId)}`,
      )
      if (requestId && get().loading.activeRequestId !== requestId) return
      if (!detail || typeof detail !== 'object') return
      const normalizedDetail = normalizeSprintTask(detail)
      set((s) => {
        s.entities.tasksById[taskId] = { ...normalizedDetail, _detail: true }
        s.loading.loadedTaskIds[taskId] = true
      })
    },

    applyRealtimeEvent(event) {
      const payload = event as Record<string, unknown>
      const activeProductId = get().context.activeProduct?.id ?? null

      set((s) => {
        s.sync.lastEventAt = Date.now()
      })

      if (
        typeof payload.product_id === 'string' &&
        activeProductId &&
        payload.product_id !== activeProductId
      ) {
        return
      }

      if (isUnknownEntityEvent(payload)) {
        if (payload.product_id === activeProductId) {
          void get().resyncActiveScopes('unknown-event')
        }
        return
      }

      const entity = payload.entity
      const op = payload.op
      if (!isKnownEntity(entity)) return
      if (op !== 'I' && op !== 'U' && op !== 'D') return

      const id = payload.id
      if (typeof id !== 'string') return

      if (entity === 'sprint') {
        applySprintEvent(id, op, payload, set, get)
      } else if (entity === 'story') {
        applyStoryEvent(id, op, payload, set, get)
      } else if (entity === 'task') {
        applyTaskEvent(id, op, payload, set, get)
      }
    },

    async resyncActiveScopes(reason) {
      const ctx = get().context
      const tasks: Promise<void>[] = []
      if (ctx.activeProduct?.id) {
        tasks.push(get().ensureProductSprintsLoaded(ctx.activeProduct.id))
      }
      if (ctx.activeSprintId) tasks.push(get().ensureSprintLoaded(ctx.activeSprintId))
      if (ctx.activeStoryId) tasks.push(get().ensureStoryLoaded(ctx.activeStoryId))
      if (ctx.activeTaskId) tasks.push(get().ensureTaskLoaded(ctx.activeTaskId))
      set((s) => {
        s.sync.lastResyncAt = Date.now()
        s.sync.resyncReason = reason
      })
      await Promise.allSettled(tasks)
    },

    async resyncLoadedScopes(reason) {
      const loading = get().loading
      const tasks: Promise<void>[] = []
      for (const productId of Object.keys(loading.loadedProductSprintsIds)) {
        tasks.push(get().ensureProductSprintsLoaded(productId))
      }
      for (const sprintId of Object.keys(loading.loadedSprintIds)) {
        tasks.push(get().ensureSprintLoaded(sprintId))
      }
      for (const storyId of Object.keys(loading.loadedStoryIds)) {
        tasks.push(get().ensureStoryLoaded(storyId))
      }
      for (const taskId of Object.keys(loading.loadedTaskIds)) {
        tasks.push(get().ensureTaskLoaded(taskId))
      }
      set((s) => {
        s.sync.lastResyncAt = Date.now()
        s.sync.resyncReason = reason
      })
      await Promise.allSettled(tasks)
    },

    applyOptimisticMutation(mutation) {
      const id = newRequestId()
      set((s) => {
        s.pendingMutations[id] = {
          id,
          mutation,
          createdAt: Date.now(),
        }
      })
      return id
    },

    rollbackMutation(mutationId) {
      const pending = get().pendingMutations[mutationId]
      if (!pending) return
      const { mutation } = pending
      set((s) => {
        switch (mutation.kind) {
          case 'entity-patch': {
            const { entity, id, prev } = mutation
            if (prev) {
              if (entity === 'sprint')
                s.entities.sprintsById[id] = prev as SprintWorkspaceSprint
              else if (entity === 'story')
                s.entities.storiesById[id] = prev as SprintWorkspaceStory
              else
                s.entities.tasksById[id] = prev as
                  | SprintWorkspaceTask
                  | SprintWorkspaceTaskDetail
            } else {
              if (entity === 'sprint') delete s.entities.sprintsById[id]
              else if (entity === 'story') delete s.entities.storiesById[id]
              else delete s.entities.tasksById[id]
            }
            break
          }
        }
        delete s.pendingMutations[mutationId]
      })
    },

    settleMutation(mutationId) {
      set((s) => {
        delete s.pendingMutations[mutationId]
      })
    },

    setRealtimeStatus(status) {
      set((s) => {
        s.sync.realtimeStatus = status
      })
    },
  })),
)

type ImmerSet = Parameters<Parameters<typeof immer<SprintWorkspaceStore>>[0]>[0]
type ImmerGet = () => SprintWorkspaceStore

function applySprintEvent(
  id: string,
  op: 'I' | 'U' | 'D',
  payload: Record<string, unknown>,
  set: ImmerSet,
  get: ImmerGet,
) {
  if (op === 'D') {
    set((s) => {
      const sprint = s.entities.sprintsById[id]
      const productId = sprint?.product_id
      // Cascade: stories binnen deze sprint, tasks binnen die stories
      const childStoryIds = s.relations.storyIdsBySprint[id] ?? []
      for (const sid of childStoryIds) {
        const childTaskIds = s.relations.taskIdsByStory[sid] ?? []
        for (const tid of childTaskIds) {
          delete s.entities.tasksById[tid]
        }
        delete s.relations.taskIdsByStory[sid]
        delete s.entities.storiesById[sid]
      }
      delete s.relations.storyIdsBySprint[id]
      delete s.entities.sprintsById[id]
      if (productId) {
        const list = s.relations.sprintIdsByProduct[productId]
        if (list) {
          s.relations.sprintIdsByProduct[productId] = list.filter((sid) => sid !== id)
        }
      } else {
        for (const pid of Object.keys(s.relations.sprintIdsByProduct)) {
          s.relations.sprintIdsByProduct[pid] = s.relations.sprintIdsByProduct[pid].filter(
            (sid) => sid !== id,
          )
        }
      }
      if (s.context.activeSprintId === id) {
        s.context.activeSprintId = null
        s.context.activeStoryId = null
        s.context.activeTaskId = null
      }
    })
    return
  }

  if (op === 'U') {
    if (!get().entities.sprintsById[id]) return
    set((s) => {
      const existing = s.entities.sprintsById[id]
      if (!existing) return
      Object.assign(existing, sanitizeSprintPayload(payload))
      const productId = existing.product_id
      if (productId && s.relations.sprintIdsByProduct[productId]) {
        s.relations.sprintIdsByProduct[productId] = sortSprintIds(
          s.entities.sprintsById,
          s.relations.sprintIdsByProduct[productId],
        )
      }
    })
    return
  }

  // I
  if (get().entities.sprintsById[id]) return
  set((s) => {
    const sprint = coerceSprintPayload(id, payload)
    s.entities.sprintsById[id] = sprint
    const productId = sprint.product_id
    const list = s.relations.sprintIdsByProduct[productId] ?? []
    list.push(id)
    s.relations.sprintIdsByProduct[productId] = sortSprintIds(s.entities.sprintsById, list)
  })
}

function applyStoryEvent(
  id: string,
  op: 'I' | 'U' | 'D',
  payload: Record<string, unknown>,
  set: ImmerSet,
  get: ImmerGet,
) {
  const activeSprintId = get().context.activeSprintId

  if (op === 'D') {
    set((s) => {
      const childTaskIds = s.relations.taskIdsByStory[id] ?? []
      for (const tid of childTaskIds) {
        delete s.entities.tasksById[tid]
      }
      delete s.relations.taskIdsByStory[id]
      const story = s.entities.storiesById[id]
      delete s.entities.storiesById[id]
      if (story?.sprint_id) {
        const ids = s.relations.storyIdsBySprint[story.sprint_id]
        if (ids) {
          s.relations.storyIdsBySprint[story.sprint_id] = ids.filter((sid) => sid !== id)
        }
      } else {
        for (const sprintId of Object.keys(s.relations.storyIdsBySprint)) {
          s.relations.storyIdsBySprint[sprintId] = s.relations.storyIdsBySprint[sprintId].filter(
            (sid) => sid !== id,
          )
        }
      }
      if (s.context.activeStoryId === id) {
        s.context.activeStoryId = null
        s.context.activeTaskId = null
      }
    })
    return
  }

  if (op === 'U') {
    const existing = get().entities.storiesById[id]
    if (!existing) {
      // Story moved into our active sprint? If sprint_id matches active, treat as I
      if (
        activeSprintId &&
        payload.sprint_id === activeSprintId &&
        get().context.activeProduct?.id === payload.product_id
      ) {
        set((s) => {
          const story = coerceStoryPayload(id, payload)
          s.entities.storiesById[id] = story
          if (story.sprint_id) {
            const list = s.relations.storyIdsBySprint[story.sprint_id] ?? []
            if (!list.includes(id)) list.push(id)
            s.relations.storyIdsBySprint[story.sprint_id] = sortStoryIds(
              s.entities.storiesById,
              list,
            )
          }
        })
      }
      return
    }
    set((s) => {
      const story = s.entities.storiesById[id]
      if (!story) return
      const oldSprintId = story.sprint_id
      Object.assign(story, sanitizeStoryPayload(payload))
      const newSprintId = story.sprint_id
      if (oldSprintId !== newSprintId) {
        if (oldSprintId) {
          const oldList = s.relations.storyIdsBySprint[oldSprintId]
          if (oldList) {
            s.relations.storyIdsBySprint[oldSprintId] = oldList.filter((sid) => sid !== id)
          }
        }
        if (newSprintId) {
          const targetList = s.relations.storyIdsBySprint[newSprintId] ?? []
          if (!targetList.includes(id)) targetList.push(id)
          s.relations.storyIdsBySprint[newSprintId] = sortStoryIds(
            s.entities.storiesById,
            targetList,
          )
        }
      } else if (oldSprintId && s.relations.storyIdsBySprint[oldSprintId]) {
        s.relations.storyIdsBySprint[oldSprintId] = sortStoryIds(
          s.entities.storiesById,
          s.relations.storyIdsBySprint[oldSprintId],
        )
      }
    })
    return
  }

  // I
  if (get().entities.storiesById[id]) return
  set((s) => {
    const story = coerceStoryPayload(id, payload)
    s.entities.storiesById[id] = story
    if (story.sprint_id) {
      const list = s.relations.storyIdsBySprint[story.sprint_id] ?? []
      list.push(id)
      s.relations.storyIdsBySprint[story.sprint_id] = sortStoryIds(s.entities.storiesById, list)
    }
  })
}

function applyTaskEvent(
  id: string,
  op: 'I' | 'U' | 'D',
  payload: Record<string, unknown>,
  set: ImmerSet,
  get: ImmerGet,
) {
  if (op === 'D') {
    set((s) => {
      const task = s.entities.tasksById[id]
      delete s.entities.tasksById[id]
      if (task) {
        const ids = s.relations.taskIdsByStory[task.story_id]
        if (ids) {
          s.relations.taskIdsByStory[task.story_id] = ids.filter((tid) => tid !== id)
        }
      } else {
        for (const storyId of Object.keys(s.relations.taskIdsByStory)) {
          s.relations.taskIdsByStory[storyId] = s.relations.taskIdsByStory[storyId].filter(
            (tid) => tid !== id,
          )
        }
      }
      if (s.context.activeTaskId === id) {
        s.context.activeTaskId = null
      }
    })
    return
  }

  if (op === 'U') {
    const existing = get().entities.tasksById[id]
    if (!existing) return
    set((s) => {
      const task = s.entities.tasksById[id]
      if (!task) return
      const oldStoryId = task.story_id
      Object.assign(task, sanitizeTaskPayload(payload))
      const newStoryId = task.story_id
      if (oldStoryId !== newStoryId) {
        const oldList = s.relations.taskIdsByStory[oldStoryId]
        if (oldList) {
          s.relations.taskIdsByStory[oldStoryId] = oldList.filter((tid) => tid !== id)
        }
        const targetList = s.relations.taskIdsByStory[newStoryId] ?? []
        if (!targetList.includes(id)) targetList.push(id)
        s.relations.taskIdsByStory[newStoryId] = sortTaskIds(s.entities.tasksById, targetList)
      } else if (s.relations.taskIdsByStory[oldStoryId]) {
        s.relations.taskIdsByStory[oldStoryId] = sortTaskIds(
          s.entities.tasksById,
          s.relations.taskIdsByStory[oldStoryId],
        )
      }
    })
    return
  }

  // I
  if (get().entities.tasksById[id]) return
  set((s) => {
    const task = coerceTaskPayload(id, payload)
    s.entities.tasksById[id] = task
    const list = s.relations.taskIdsByStory[task.story_id] ?? []
    list.push(id)
    s.relations.taskIdsByStory[task.story_id] = sortTaskIds(s.entities.tasksById, list)
  })
}

function sortSprintIds(
  byId: Record<string, SprintWorkspaceSprint>,
  ids: string[],
): string[] {
  return [...new Set(ids)]
    .filter((id) => byId[id] !== undefined)
    .sort((a, b) => compareSprint(byId[a], byId[b]))
}

function sortStoryIds(
  byId: Record<string, SprintWorkspaceStory>,
  ids: string[],
): string[] {
  return [...new Set(ids)]
    .filter((id) => byId[id] !== undefined)
    .sort((a, b) => compareStory(byId[a], byId[b]))
}

function sortTaskIds(
  byId: Record<string, SprintWorkspaceTask | SprintWorkspaceTaskDetail>,
  ids: string[],
): string[] {
  return [...new Set(ids)]
    .filter((id) => byId[id] !== undefined)
    .sort((a, b) => compareTask(byId[a], byId[b]))
}

function sanitizeSprintPayload(p: Record<string, unknown>): Partial<SprintWorkspaceSprint> {
  const { entity: _e, op: _o, ...rest } = p
  void _e
  void _o
  return rest as Partial<SprintWorkspaceSprint>
}

function sanitizeStoryPayload(p: Record<string, unknown>): Partial<SprintWorkspaceStory> {
  const {
    entity: _e,
    op: _o,
    story_status,
    story_sort_order,
    story_title,
    story_code,
    ...rest
  } = p
  void _e
  void _o
  if (rest.status === undefined && typeof story_status === 'string') {
    rest.status = story_status
  }
  if (rest.sort_order === undefined && typeof story_sort_order === 'number') {
    rest.sort_order = story_sort_order
  }
  if (rest.title === undefined && typeof story_title === 'string') {
    rest.title = story_title
  }
  if (rest.code === undefined && (typeof story_code === 'string' || story_code === null)) {
    rest.code = story_code
  }
  if (typeof rest.status === 'string') {
    rest.status = normalizeStoryStatusForStore(rest.status)
  }
  return rest as Partial<SprintWorkspaceStory>
}

function sanitizeTaskPayload(p: Record<string, unknown>): Partial<SprintWorkspaceTask> {
  const {
    entity: _e,
    op: _o,
    task_status,
    task_sort_order,
    task_title,
    ...rest
  } = p
  void _e
  void _o
  if (rest.status === undefined && typeof task_status === 'string') {
    rest.status = task_status
  }
  if (rest.sort_order === undefined && typeof task_sort_order === 'number') {
    rest.sort_order = task_sort_order
  }
  if (rest.title === undefined && typeof task_title === 'string') {
    rest.title = task_title
  }
  if (typeof rest.status === 'string') {
    rest.status = normalizeTaskStatusForStore(rest.status)
  }
  return rest as Partial<SprintWorkspaceTask>
}

function coerceSprintPayload(
  id: string,
  p: Record<string, unknown>,
): SprintWorkspaceSprint {
  return {
    id,
    product_id: String(p.product_id ?? ''),
    code: String(p.code ?? ''),
    sprint_goal: String(p.sprint_goal ?? ''),
    status: (p.status as SprintWorkspaceSprint['status']) ?? 'OPEN',
    start_date: (p.start_date as string | null | undefined) ?? null,
    end_date: (p.end_date as string | null | undefined) ?? null,
    created_at:
      p.created_at instanceof Date
        ? p.created_at
        : new Date(String(p.created_at ?? Date.now())),
    completed_at:
      p.completed_at instanceof Date
        ? p.completed_at
        : p.completed_at
          ? new Date(String(p.completed_at))
          : null,
  }
}

function coerceStoryPayload(
  id: string,
  p: Record<string, unknown>,
): SprintWorkspaceStory {
  const status = p.status ?? p.story_status ?? 'OPEN'
  const sortOrder = p.sort_order ?? p.story_sort_order ?? 0
  const title = p.title ?? p.story_title ?? ''
  const code = p.code ?? p.story_code ?? null
  return {
    id,
    code: (code as string | null) ?? null,
    title: String(title),
    description: (p.description as string | null | undefined) ?? null,
    acceptance_criteria: (p.acceptance_criteria as string | null | undefined) ?? null,
    priority: Number(p.priority ?? 4),
    sort_order: Number(sortOrder),
    status: normalizeStoryStatusForStore(String(status)),
    pbi_id: String(p.pbi_id ?? ''),
    sprint_id: (p.sprint_id as string | null | undefined) ?? null,
    created_at:
      p.created_at instanceof Date
        ? p.created_at
        : new Date(String(p.created_at ?? Date.now())),
  }
}

function coerceTaskPayload(id: string, p: Record<string, unknown>): SprintWorkspaceTask {
  const status = p.status ?? p.task_status ?? 'TO_DO'
  const sortOrder = p.sort_order ?? p.task_sort_order ?? 0
  const title = p.title ?? p.task_title ?? ''
  return {
    id,
    code: (p.code as string | null) ?? null,
    title: String(title),
    description: (p.description as string | null | undefined) ?? null,
    priority: Number(p.priority ?? 4),
    sort_order: Number(sortOrder),
    status: normalizeTaskStatusForStore(String(status)),
    story_id: String(p.story_id ?? ''),
    sprint_id: (p.sprint_id as string | null | undefined) ?? null,
    created_at:
      p.created_at instanceof Date
        ? p.created_at
        : new Date(String(p.created_at ?? Date.now())),
  }
}
