import type { TableViewId, TableViewViewModel } from '@types'

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { z } from 'zod'

import { useTableViews } from './use-table-views'

/** Creates a Zod decoder that parses a JSON string and validates the result against the given schema. */
const jsonDecoder = <T,>(schema: z.ZodType<T>) =>
  z
    .string()
    .transform((s) => JSON.parse(s) as unknown)
    .pipe(schema)

const filtersDecoder = jsonDecoder(z.record(z.string(), z.unknown()))
const sortingDecoder = jsonDecoder(z.array(z.object({ field: z.string(), direction: z.enum(['asc', 'desc']) })))
const columnVisibilityDecoder = jsonDecoder(z.record(z.string(), z.boolean()))
const columnOrderDecoder = jsonDecoder(z.array(z.string()))
const columnPinningDecoder = jsonDecoder(
  z.object({ left: z.array(z.string()).optional(), right: z.array(z.string()).optional() }),
)
const columnSizingDecoder = jsonDecoder(z.record(z.string(), z.number()))

/**
 * Parsed table state stored in a TableView.
 * All JSON string fields are deserialized into their typed representations.
 */
export type TableViewState = {
  filters?: z.infer<typeof filtersDecoder>
  sorting?: z.infer<typeof sortingDecoder>
  columnVisibility?: z.infer<typeof columnVisibilityDecoder>
  columnOrder?: z.infer<typeof columnOrderDecoder>
  columnPinning?: z.infer<typeof columnPinningDecoder>
  columnSizing?: z.infer<typeof columnSizingDecoder>
}

export type UseTableViewConfigOptions = {
  /** The model/table name to scope views to (e.g. 'user', 'publication'). */
  model: string
  /** The current user's ID — used to filter views (own + global). */
  currentUserId?: string
}

export type UseTableViewConfigResult = {
  /** Views for this model visible to the current user (own + global). */
  views: TableViewViewModel[]
  /** Views mapped to the SavedView shape expected by DataGridViewMenu. */
  savedViews: { id: string; name: string; isGlobal: boolean; userId: string }[]
  /** Whether the initial data has been loaded. */
  isLoaded: boolean
  /** The currently active/applied view ID (local state, persisted to localStorage). */
  activeViewId: string | null
  /** Set the active view ID (also persists to localStorage). */
  setActiveViewId: (id: string | null) => void
  /** Clear the active view. */
  clearActiveView: () => void
  /** Parse a view's JSON string fields into typed TableViewState. */
  parseViewState: (view: TableViewViewModel) => TableViewState
  /** Get the parsed state of the currently active view, or null. */
  getActiveViewState: () => TableViewState | null
  /** Save a new view with the given state. Sets it as active on success. */
  saveView: (args: { name: string; isGlobal?: boolean; state: TableViewState }) => Promise<TableViewViewModel>
  /** Update an existing view's state. */
  updateView: (viewId: string, state: TableViewState) => Promise<TableViewViewModel>
  /** Rename an existing view. */
  renameView: (viewId: string, name: string) => Promise<TableViewViewModel>
  /** Delete a view. Clears active view if it was the deleted one. */
  deleteView: (viewId: string) => Promise<void>
}

const STORAGE_PREFIX = 'pxl.tableView:'

function getStorageKey(model: string): string {
  return `${STORAGE_PREFIX}${model}`
}

function readPersistedViewId(model: string): string | null {
  try {
    return localStorage.getItem(getStorageKey(model))
  } catch {
    return null
  }
}

function persistViewId(model: string, viewId: string | null): void {
  try {
    const key = getStorageKey(model)
    if (viewId) {
      localStorage.setItem(key, viewId)
    } else {
      localStorage.removeItem(key)
    }
  } catch {
    // ignore storage errors
  }
}

/**
 * Hook that wraps the generated `useTableViews` CRUD hook and adds:
 * - Scoping by model name and current user
 * - JSON parsing/serialization for table state fields
 * - Active view tracking (persisted to localStorage)
 * - Auto-restore of previously active view on mount
 * - Simplified save/update/rename/delete API
 */
export function useTableViewConfig(options: UseTableViewConfigOptions): UseTableViewConfigResult {
  const { model, currentUserId } = options
  const tableViews = useTableViews()
  const [activeViewId, setActiveViewIdState] = useState<string | null>(() => readPersistedViewId(model))
  const hasRestoredRef = useRef(false)

  // Persist active view ID to localStorage whenever it changes
  const setActiveViewId = useCallback(
    (id: string | null) => {
      setActiveViewIdState(id)
      persistViewId(model, id)
    },
    [model],
  )

  // Filter views: only this model, and only the user's own or global views
  const views = useMemo(
    (): TableViewViewModel[] =>
      tableViews.list.filter((v) => v.model === model && (v.isGlobal || v.userId === currentUserId)),
    [tableViews.list, model, currentUserId],
  )

  // Map to the shape expected by DataGridViewMenu
  const savedViews = useMemo(
    () => views.map((v) => ({ id: v.id, name: v.name, isGlobal: v.isGlobal, userId: v.userId })),
    [views],
  )

  // Once views are loaded, validate the persisted view ID still exists.
  // If the persisted view was deleted, clear it.
  useEffect(() => {
    if (!tableViews.isLoaded || hasRestoredRef.current) return
    hasRestoredRef.current = true

    if (activeViewId && !views.find((v) => v.id === activeViewId)) {
      setActiveViewId(null)
    }
  }, [tableViews.isLoaded, views, activeViewId, setActiveViewId])

  const parseViewState = useCallback((view: TableViewViewModel): TableViewState => {
    const decode = <T,>(json: string | null | undefined, decoder: z.ZodType<T>): T | undefined => {
      if (!json) {
        return undefined
      }
      const result = decoder.safeParse(json)
      return result.success ? result.data : undefined
    }
    return {
      filters: decode(view.filters, filtersDecoder),
      sorting: decode(view.sorting, sortingDecoder),
      columnVisibility: decode(view.columnVisibility, columnVisibilityDecoder),
      columnOrder: decode(view.columnOrder, columnOrderDecoder),
      columnPinning: decode(view.columnPinning, columnPinningDecoder),
      columnSizing: decode(view.columnSizing, columnSizingDecoder),
    }
  }, [])

  const getActiveViewState = useCallback((): TableViewState | null => {
    if (!activeViewId) return null
    const view = views.find((v) => v.id === activeViewId)
    if (!view) return null
    return parseViewState(view)
  }, [activeViewId, views, parseViewState])

  const serializeState = useCallback(
    (state: TableViewState) => ({
      filters: state.filters ? JSON.stringify(state.filters) : null,
      sorting: state.sorting ? JSON.stringify(state.sorting) : null,
      columnVisibility: state.columnVisibility ? JSON.stringify(state.columnVisibility) : null,
      columnOrder: state.columnOrder ? JSON.stringify(state.columnOrder) : null,
      columnPinning: state.columnPinning ? JSON.stringify(state.columnPinning) : null,
      columnSizing: state.columnSizing ? JSON.stringify(state.columnSizing) : null,
    }),
    [],
  )

  const saveView = useCallback(
    async (args: { name: string; isGlobal?: boolean; state: TableViewState }) => {
      if (!currentUserId) {
        throw new Error('Cannot save a view without a currentUserId')
      }
      const result = await tableViews.create({
        name: args.name,
        model,
        userId: currentUserId as TableViewViewModel['userId'],
        isGlobal: args.isGlobal ?? false,
        ...serializeState(args.state),
      })
      setActiveViewId(result.id)
      return result
    },
    [tableViews, model, currentUserId, serializeState, setActiveViewId],
  )

  const updateView = useCallback(
    async (viewId: string, state: TableViewState) => {
      const existing = views.find((v) => v.id === viewId)
      if (!existing) throw new Error(`View ${viewId} not found`)
      return tableViews.update({
        id: viewId as TableViewId,
        name: existing.name,
        model: existing.model,
        userId: existing.userId,
        isGlobal: existing.isGlobal,
        ...serializeState(state),
      })
    },
    [tableViews, views, serializeState],
  )

  const renameView = useCallback(
    async (viewId: string, name: string) => {
      return tableViews.updateField({ id: viewId as TableViewId, field: 'name', value: name })
    },
    [tableViews],
  )

  const deleteView = useCallback(
    async (viewId: string) => {
      await tableViews.delete(viewId as TableViewId)
      if (activeViewId === viewId) {
        setActiveViewId(null)
      }
    },
    [tableViews, activeViewId, setActiveViewId],
  )

  const clearActiveView = useCallback(() => setActiveViewId(null), [setActiveViewId])

  return {
    views,
    savedViews,
    isLoaded: tableViews.isLoaded,
    activeViewId,
    setActiveViewId,
    clearActiveView,
    parseViewState,
    getActiveViewState,
    saveView,
    updateView,
    renameView,
    deleteView,
  }
}
