import type { PiniaPluginContext } from 'pinia'
import localforage from 'localforage'
import { touchProjectEdited } from '@/lib/projectRegistry'
import { QUICK_PROJECT_ID, readCurrentProjectId } from '@/lib/workspace'

export type PersistOption = boolean | {
  key?: string
  debounce?: number
}

type PersistStoreExt = {
  $persistNow?: () => Promise<void>
  $clearPersisted?: () => Promise<void>
}

type PiniaStorageConfig = LocalForageOptions & {
  mode?: 'single' | 'multiple'
  debounce?: number
}

const baseConfig: PiniaStorageConfig = {
  driver: localforage.INDEXEDDB,
  name: 'DB',
  version: 1,
  description: 'pinia persisted storage',
  storeName: 'pinia-storage',
  debounce: 300
}

let singleStore: LocalForage | null = null
const multipleStores = new Map<string, LocalForage>()

const getStore = (mode: 'single' | 'multiple', storeName: string, config: LocalForageOptions) => {
  if (mode === 'single') {
    if (!singleStore) {
      singleStore = localforage.createInstance({ ...baseConfig, ...config })
    }
    return singleStore
  }
  if (!multipleStores.has(storeName)) {
    multipleStores.set(
      storeName,
      localforage.createInstance({ ...baseConfig, ...config, storeName })
    )
  }
  return multipleStores.get(storeName) as LocalForage
}

const resolvePersistOptions = (persist: PersistOption | undefined) => {
  if (!persist) return null
  if (persist === true) return {}
  return persist
}

const trimStringValuesDeep = (value: unknown): unknown => {
  if (typeof value === 'string') return value.trim()
  if (Array.isArray(value)) return value.map(item => trimStringValuesDeep(item))
  if (value && typeof value === 'object') {
    const normalized: Record<string, unknown> = {}
    for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
      normalized[key] = trimStringValuesDeep(child)
    }
    return normalized
  }
  return value
}

const hydratePromises = new Map<string, Promise<void>>()

export const waitForHydration = (storeId: string): Promise<void> => {
  const existing = hydratePromises.get(storeId)
  if (existing) return existing
  // store 尚未注册，返回一个会在注册后 resolve 的 Promise
  let resolve!: () => void
  const p = new Promise<void>(r => { resolve = r })
  hydratePromises.set(storeId, p)
  return p
}

export default (config?: PiniaStorageConfig) => {
  const finalConfig = { ...baseConfig, ...(config || {}) }
  const { mode = 'single', debounce = 300, ...forageConfig } = finalConfig

  return (context: PiniaPluginContext) => {
    const persistOptions = resolvePersistOptions(context.options.persist as PersistOption | undefined)
    if (!persistOptions) return

    const storeId = context.store.$id
    const shouldTouchProjectEditedAt = storeId !== 'tabs'
    const baseStoreName = forageConfig.storeName || baseConfig.storeName || 'pinia-storage'
    const resolvedStoreName = mode === 'multiple' ? `${baseStoreName}-${storeId}` : baseStoreName
    const key = persistOptions.key || (mode === 'single' ? `${baseStoreName}-${storeId}` : resolvedStoreName)
    const lf = getStore(mode, resolvedStoreName, forageConfig)
    const persistDebounce = persistOptions.debounce ?? debounce
    let timer: ReturnType<typeof setTimeout> | null = null
    let hydrating = false
    let userMutatedBeforeHydrate = false
    const writeState = (state: unknown) => {
      const clonedState = JSON.parse(JSON.stringify(state))
      return lf.setItem(key, trimStringValuesDeep(clonedState))
    }
    const markProjectEdited = () => {
      if (!shouldTouchProjectEditedAt) return
      const currentProjectId = readCurrentProjectId()
      if (!currentProjectId || currentProjectId === QUICK_PROJECT_ID) return
      touchProjectEdited(currentProjectId)
    }
    const storeExt = context.store as typeof context.store & PersistStoreExt

    storeExt.$persistNow = async () => {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      try {
        await writeState(context.store.$state)
        markProjectEdited()
      } catch (error) {
        console.error('pinia persist failed:', error)
      }
    }

    storeExt.$clearPersisted = async () => {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      try {
        await lf.removeItem(key)
      } catch (error) {
        console.error('pinia clear persisted failed:', error)
      }
    }

    context.store.$subscribe(
      (_mutation, state) => {
        if (hydrating) return
        userMutatedBeforeHydrate = true
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
          void writeState(state)
            .then(() => {
              markProjectEdited()
            })
            .catch(error => {
              console.error('pinia persist failed:', error)
            })
        }, Math.max(0, persistDebounce))
      },
      { detached: true }
    )

    // 注册或获取当前 store 的水合 Promise
    let resolveHydration!: () => void
    const hydrateP = new Promise<void>(r => { resolveHydration = r })
    hydratePromises.set(storeId, hydrateP)

    hydrating = true
    void lf.getItem<Record<string, unknown>>(key)
      .then(state => {
        if (!state || typeof state !== 'object') return
        // 若在异步hydrate返回前，store已被用户修改（如removeTab），不再回填旧缓存覆盖当前状态。
        if (userMutatedBeforeHydrate) return
        context.store.$patch(state as any)
      })
      .catch(error => {
        console.error('pinia hydrate failed:', error)
      })
      .finally(() => {
        hydrating = false
        resolveHydration()
      })
  }
}
