import { create } from 'zustand'
import type { SoloTask } from '@/components/solo/solo-board'
import type { ClaudeJobStatusApi } from '@/lib/job-status'

type TaskStatus = SoloTask['status']

export type VerifyResultApi = 'aligned' | 'partial' | 'empty' | 'divergent'

export interface JobState {
  job_id: string
  task_id: string
  status: ClaudeJobStatusApi
  branch?: string
  pushed_at?: string | null
  pr_url?: string | null
  verify_result?: VerifyResultApi | null
  summary?: string
  error?: string
}

export type ClaudeJobEvent =
  | { type: 'claude_job_enqueued'; job_id: string; task_id: string; user_id: string; product_id: string; status: 'queued' }
  | { type: 'claude_job_status'; job_id: string; task_id: string; user_id: string; product_id: string; status: ClaudeJobStatusApi; branch?: string; pushed_at?: string; pr_url?: string; verify_result?: VerifyResultApi; summary?: string; error?: string }

// Payload-shape gepubliceerd door de Postgres-trigger via pg_notify (ST-801
// + ST-804 prereq). Komt het Solo Paneel binnen via de SSE-stream uit
// /api/realtime/solo (ST-802).
export interface RealtimeEvent {
  op: 'I' | 'U' | 'D'
  entity: 'task' | 'story'
  id: string
  story_id?: string
  product_id: string
  sprint_id: string | null
  assignee_id: string | null
  // Task-specifieke velden (alleen aanwezig als entity === 'task')
  task_status?: TaskStatus
  task_sort_order?: number
  task_title?: string
  // Story-specifieke velden (alleen aanwezig als entity === 'story')
  story_status?: 'OPEN' | 'IN_SPRINT' | 'DONE'
  story_sort_order?: number
  story_title?: string
  story_code?: string | null
  // Op UPDATE: lijst van kolommen die zijn veranderd
  changed_fields?: string[]
}

export type RealtimeStatus = 'connecting' | 'open' | 'disconnected'

interface SoloStore {
  tasks: Record<string, SoloTask>
  /** Task-ids die op dit moment een eigen optimistic write in de lucht hebben.
   *  Realtime echos voor deze ids worden onderdrukt zodat de eigen update niet
   *  twee keer toegepast wordt of door een latere echo overschreven. */
  pendingOps: Set<string>

  /** Realtime-connection state, beheerd door useSoloRealtime in de
   *  (app)-layout. Hier in de store omdat de UI-indicator in SoloBoard zit en
   *  de hook niet direct in dezelfde subtree draait. */
  realtimeStatus: RealtimeStatus
  showConnectingIndicator: boolean

  claudeJobsByTaskId: Record<string, JobState>
  connectedWorkers: number

  initTasks: (tasks: SoloTask[]) => void
  optimisticMove: (taskId: string, toStatus: TaskStatus) => TaskStatus | null
  rollback: (taskId: string, prevStatus: TaskStatus) => void
  updatePlan: (taskId: string, plan: string | null) => void
  updateVerifyOnly: (taskId: string, value: boolean) => void
  updateVerifyRequired: (taskId: string, value: 'ALIGNED' | 'ALIGNED_OR_PARTIAL' | 'ANY') => void

  markPending: (taskId: string) => void
  clearPending: (taskId: string) => void

  setRealtimeStatus: (status: RealtimeStatus, showConnectingIndicator: boolean) => void

  initJobs: (jobs: JobState[]) => void
  handleJobEvent: (event: ClaudeJobEvent) => void

  setWorkers: (count: number) => void
  incrementWorkers: () => void
  decrementWorkers: () => void

  handleRealtimeEvent: (event: RealtimeEvent) => void
}

export const useSoloStore = create<SoloStore>((set, get) => ({
  tasks: {},
  pendingOps: new Set<string>(),
  realtimeStatus: 'connecting',
  showConnectingIndicator: false,
  claudeJobsByTaskId: {},
  connectedWorkers: 0,

  initTasks: (tasks) =>
    set({ tasks: Object.fromEntries(tasks.map(t => [t.id, t])) }),

  optimisticMove: (taskId, toStatus) => {
    const prev = get().tasks[taskId]?.status ?? null
    if (!prev) return null
    set((s) => ({ tasks: { ...s.tasks, [taskId]: { ...s.tasks[taskId], status: toStatus } } }))
    return prev
  },

  rollback: (taskId, prevStatus) =>
    set((s) => ({ tasks: { ...s.tasks, [taskId]: { ...s.tasks[taskId], status: prevStatus } } })),

  updatePlan: (taskId, plan) =>
    set((s) => ({ tasks: { ...s.tasks, [taskId]: { ...s.tasks[taskId], implementation_plan: plan } } })),

  updateVerifyOnly: (taskId, value) =>
    set((s) => ({ tasks: { ...s.tasks, [taskId]: { ...s.tasks[taskId], verify_only: value } } })),

  updateVerifyRequired: (taskId, value) =>
    set((s) => ({ tasks: { ...s.tasks, [taskId]: { ...s.tasks[taskId], verify_required: value } } })),

  markPending: (taskId) =>
    set((s) => {
      if (s.pendingOps.has(taskId)) return s
      const next = new Set(s.pendingOps)
      next.add(taskId)
      return { pendingOps: next }
    }),

  clearPending: (taskId) =>
    set((s) => {
      if (!s.pendingOps.has(taskId)) return s
      const next = new Set(s.pendingOps)
      next.delete(taskId)
      return { pendingOps: next }
    }),

  setRealtimeStatus: (status, showConnectingIndicator) =>
    set((s) => {
      if (s.realtimeStatus === status && s.showConnectingIndicator === showConnectingIndicator) {
        return s
      }
      return { realtimeStatus: status, showConnectingIndicator }
    }),

  initJobs: (jobs) =>
    set({ claudeJobsByTaskId: Object.fromEntries(jobs.map(j => [j.task_id, j])) }),

  setWorkers: (count) => set({ connectedWorkers: Math.max(0, count) }),
  incrementWorkers: () => set(s => ({ connectedWorkers: s.connectedWorkers + 1 })),
  decrementWorkers: () => set(s => ({ connectedWorkers: Math.max(0, s.connectedWorkers - 1) })),

  handleJobEvent: (event) => {
    const { job_id, task_id } = event
    if (event.type === 'claude_job_enqueued') {
      set((s) => ({
        claudeJobsByTaskId: {
          ...s.claudeJobsByTaskId,
          [task_id]: { job_id, task_id, status: 'queued' },
        },
      }))
      return
    }
    if (event.type === 'claude_job_status') {
      const { status, branch, pushed_at, pr_url, verify_result, summary, error } = event
      if (status === 'cancelled') {
        set((s) => {
          const next = { ...s.claudeJobsByTaskId }
          delete next[task_id]
          return { claudeJobsByTaskId: next }
        })
        return
      }
      set((s) => ({
        claudeJobsByTaskId: {
          ...s.claudeJobsByTaskId,
          [task_id]: { job_id, task_id, status, branch, pushed_at, pr_url, verify_result, summary, error },
        },
      }))
    }
  },

  handleRealtimeEvent: (event) => {
    if (event.entity === 'task') {
      const { id, op } = event

      if (op === 'D') {
        set((s) => {
          if (!(id in s.tasks)) return s
          const next = { ...s.tasks }
          delete next[id]
          return { tasks: next }
        })
        return
      }

      // INSERT en UPDATE: alleen bestaande taken bijwerken. Nieuwe taken
      // zonder story-context (story_title, story_code) renderen we niet
      // — gebruiker ziet ze pas na een refresh. Acceptabel voor v1.
      const existing = get().tasks[id]
      if (!existing) return

      if (get().pendingOps.has(id)) {
        // Echo van een eigen optimistic move — laat de optimistic-state staan
        return
      }

      const updates: Partial<SoloTask> = {}
      if (event.task_status !== undefined && event.task_status !== existing.status) {
        updates.status = event.task_status
      }
      if (
        event.task_sort_order !== undefined &&
        event.task_sort_order !== existing.sort_order
      ) {
        updates.sort_order = event.task_sort_order
      }
      if (event.task_title !== undefined && event.task_title !== existing.title) {
        updates.title = event.task_title
      }

      if (Object.keys(updates).length === 0) return
      set((s) => ({ tasks: { ...s.tasks, [id]: { ...s.tasks[id], ...updates } } }))
      return
    }

    if (event.entity === 'story') {
      const { id, op } = event

      if (op === 'D') {
        // Story-cascade pakt tasks ook in de DB; verwijder de bijbehorende
        // SoloTask-records uit de store.
        set((s) => {
          const next: Record<string, SoloTask> = {}
          for (const [taskId, task] of Object.entries(s.tasks)) {
            if (task.story_id !== id) next[taskId] = task
          }
          return { tasks: next }
        })
        return
      }

      const tasks = get().tasks
      const affectedIds = Object.entries(tasks)
        .filter(([, t]) => t.story_id === id)
        .map(([taskId]) => taskId)

      if (affectedIds.length === 0) return

      const newTitle = event.story_title
      const newCode = event.story_code ?? null

      set((s) => {
        const next = { ...s.tasks }
        for (const taskId of affectedIds) {
          const t = next[taskId]
          const titleChanged = newTitle !== undefined && t.story_title !== newTitle
          const codeChanged = newCode !== t.story_code
          if (!titleChanged && !codeChanged) continue
          next[taskId] = {
            ...t,
            ...(titleChanged && newTitle !== undefined && { story_title: newTitle }),
            ...(codeChanged && { story_code: newCode }),
          }
        }
        return { tasks: next }
      })
    }
  },
}))
