import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import { APP_SETTINGS_DB_KEY } from '@shared/types/settings'
import {
  DEFAULT_DOCKER_FUNASR_HOST_PORT,
  DEFAULT_DOCKER_FUNASR_WS_URL,
  DEFAULT_FUNASR_IMAGE,
  DEFAULT_VOICE_INPUT_SETTINGS,
  type VoiceInputSettings
} from '@shared/types/voice'
import { DEFAULT_LOCALE, type AppLocale } from '@/i18n/messages'
import { applyFontScale } from '@/lib/font-size'

// ==========================================
// Migration helpers
// ==========================================

/**
 * Migrate legacy settings shape to current shape.
 * Converts `defaultAgentSdk` → `defaultRuntimeId` and removes the old field.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function migrateSettingsShape(raw: Record<string, any>): Record<string, any> {
  const result = { ...raw }
  // Migrate defaultAgentSdk → defaultRuntimeId (prefer new field if both present)
  if ('defaultAgentSdk' in result && !('defaultRuntimeId' in result)) {
    result.defaultRuntimeId = result.defaultAgentSdk
  }
  delete result.defaultAgentSdk
  return result
}

// Re-export for convenience
export { applyFontScale } from '@/lib/font-size'

// ==========================================
// Types
// ==========================================

export type EditorOption =
  | 'vscode'
  | 'cursor'
  | 'trae'
  | 'windsurf'
  | 'antigravity'
  | 'idea'
  | 'webstorm'
  | 'pycharm'
  | 'goland'
  | 'sublime'
  | 'zed'
  | 'custom'
export type TerminalOption =
  | 'terminal'
  | 'iterm'
  | 'warp'
  | 'alacritty'
  | 'kitty'
  | 'ghostty'
  | 'powershell'
  | 'cmd'
  | 'custom'
export type EmbeddedTerminalBackend = 'xterm' | 'ghostty'

export interface SelectedModel {
  providerID: string
  modelID: string
  variant?: string
}

export interface ModeDefaultModels {
  build: SelectedModel | null
  plan: SelectedModel | null
  ask: SelectedModel | null
}

export type QuickActionType = 'cursor' | 'terminal' | 'copy-path' | 'finder'

export interface CommandFilterSettings {
  allowlist: string[]
  blocklist: string[]
  defaultBehavior: 'ask' | 'allow' | 'block'
  enabled: boolean
}

export interface AppSettings {
  // General
  locale: AppLocale
  autoStartSession: boolean
  breedType: 'dogs' | 'cats'
  vimModeEnabled: boolean

  // Editor
  defaultEditor: EditorOption
  customEditorCommand: string

  // Terminal
  defaultTerminal: TerminalOption
  customTerminalCommand: string
  embeddedTerminalBackend: EmbeddedTerminalBackend
  ghosttyFontSize: number
  terminalFontFamily: string
  ghosttyPromotionDismissed: boolean

  // Model
  selectedModel: SelectedModel | null
  selectedModelByProvider: Record<string, SelectedModel>
  defaultModels: ModeDefaultModels | null

  // Quick Actions
  lastOpenAction: QuickActionType | null

  // Favorites
  favoriteModels: string[] // Array of "providerID::modelID" keys

  // Chrome
  customChromeCommand: string // Custom chrome launch command, e.g. "open -a Chrome {url}"

  // Variant defaults per model
  modelVariantDefaults: Record<string, string> // "providerID::modelID" → variant

  // Model icons
  showModelIcons: boolean

  // Model provider
  showModelProvider: boolean

  // Usage indicator
  showUsageIndicator: boolean

  // Agent SDK
  defaultAgentSdk: 'opencode' | 'claude-code' | 'codex' | 'terminal'

  // Setup
  initialSetupComplete: boolean

  // Chat
  stripAtMentions: boolean
  skipForkFromMessageConfirm: boolean
  codexFastMode: boolean
  codexFastModeAccepted: boolean

  // Updates
  updateChannel: 'stable' | 'canary'
  skippedUpdateVersion: string | null

  // Command Filter
  commandFilter: CommandFilterSettings

  // Privacy
  telemetryEnabled: boolean
  fdaOnboardingDismissed: boolean

  // Git
  autoPullBeforeWorktree: boolean
  keepAwakeEnabled: boolean

  // Appearance
  uiZoomLevel: number
  uiFontScale: number

  // Voice input
  voiceInput: VoiceInputSettings

  // Experimental — Phase 7 feature flag
  sessionUiV2Enabled: boolean
}

const DEFAULT_COMMAND_FILTER_ALLOWLIST = [
  'edit: **',
  'write: **',
  'read: **',
  'grep: * in *',
  'glob: *'
]

const LEGACY_COMMAND_FILTER_ALLOWLIST = ['edit: **', 'write: **']

const DEFAULT_SETTINGS: AppSettings = {
  locale: DEFAULT_LOCALE,
  autoStartSession: true,
  breedType: 'dogs',
  vimModeEnabled: false,
  defaultEditor: 'vscode',
  customEditorCommand: '',
  defaultTerminal: 'terminal',
  customTerminalCommand: '',
  embeddedTerminalBackend: 'xterm',
  ghosttyFontSize: 14,
  terminalFontFamily: '',
  ghosttyPromotionDismissed: false,
  selectedModel: null,
  selectedModelByProvider: {},
  defaultModels: null,
  lastOpenAction: null,
  favoriteModels: [],
  customChromeCommand: '',
  modelVariantDefaults: {},
  showModelIcons: false,
  showModelProvider: false,
  showUsageIndicator: true,
  defaultAgentSdk: 'opencode',
  stripAtMentions: true,
  skipForkFromMessageConfirm: false,
  codexFastMode: false,
  codexFastModeAccepted: false,
  updateChannel: 'stable',
  skippedUpdateVersion: null,
  initialSetupComplete: false,
  commandFilter: {
    allowlist: DEFAULT_COMMAND_FILTER_ALLOWLIST,
    blocklist: [
      'bash: rm -rf *',
      'bash: sudo rm *',
      'bash: sudo *',
      'edit: **/.env',
      'edit: **/*.key',
      'edit: **/credentials*',
      'write: **/.env',
      'write: **/*.key',
      'write: **/credentials*'
    ],
    defaultBehavior: 'ask',
    enabled: false
  },
  telemetryEnabled: true,
  fdaOnboardingDismissed: false,
  autoPullBeforeWorktree: true,
  keepAwakeEnabled: false,
  uiZoomLevel: 0,
  uiFontScale: 1,
  voiceInput: DEFAULT_VOICE_INPUT_SETTINGS,
  sessionUiV2Enabled: true
}

function samePatternSet(left: string[], right: string[]): boolean {
  if (left.length !== right.length) return false
  const rightSet = new Set(right)
  return left.every((pattern) => rightSet.has(pattern))
}

function resolveCommandFilterAllowlist(persistedPatterns?: string[]): string[] {
  if (!persistedPatterns) {
    return [...DEFAULT_SETTINGS.commandFilter.allowlist]
  }

  if (
    samePatternSet(persistedPatterns, LEGACY_COMMAND_FILTER_ALLOWLIST) ||
    samePatternSet(persistedPatterns, DEFAULT_SETTINGS.commandFilter.allowlist)
  ) {
    return [...DEFAULT_SETTINGS.commandFilter.allowlist]
  }

  return [...persistedPatterns]
}

export function mergeCommandFilterSettings(
  persisted?: Partial<CommandFilterSettings> | null
): CommandFilterSettings {
  return {
    ...DEFAULT_SETTINGS.commandFilter,
    ...(persisted || {}),
    allowlist: resolveCommandFilterAllowlist(persisted?.allowlist),
    blocklist: persisted?.blocklist ?? DEFAULT_SETTINGS.commandFilter.blocklist
  }
}

export function mergeVoiceInputSettings(
  persisted?: Partial<VoiceInputSettings> | null
): VoiceInputSettings {
  const persistedFunasr = persisted?.funasr
  const shouldMigrateLegacyDockerDefault =
    persisted?.runtimeProvider === 'docker' &&
    (!persistedFunasr?.wsUrl || persistedFunasr.wsUrl === DEFAULT_DOCKER_FUNASR_WS_URL) &&
    (!persistedFunasr?.hostPort || persistedFunasr.hostPort === DEFAULT_DOCKER_FUNASR_HOST_PORT) &&
    (!persistedFunasr?.image || persistedFunasr.image === DEFAULT_FUNASR_IMAGE)

  if (shouldMigrateLegacyDockerDefault) {
    return {
      ...DEFAULT_VOICE_INPUT_SETTINGS,
      ...(persisted || {}),
      runtimeProvider: 'managed',
      funasr: {
        ...DEFAULT_VOICE_INPUT_SETTINGS.funasr,
        ...(persistedFunasr || {}),
        wsUrl: DEFAULT_VOICE_INPUT_SETTINGS.funasr.wsUrl,
        hostPort: DEFAULT_VOICE_INPUT_SETTINGS.funasr.hostPort,
        hotwords: persistedFunasr?.hotwords ?? DEFAULT_VOICE_INPUT_SETTINGS.funasr.hotwords
      }
    }
  }

  return {
    ...DEFAULT_VOICE_INPUT_SETTINGS,
    ...(persisted || {}),
    funasr: {
      ...DEFAULT_VOICE_INPUT_SETTINGS.funasr,
      ...(persisted?.funasr || {}),
      hotwords: persisted?.funasr?.hotwords ?? DEFAULT_VOICE_INPUT_SETTINGS.funasr.hotwords
    }
  }
}

interface SettingsState extends AppSettings {
  isOpen: boolean
  activeSection: string
  isLoading: boolean

  // Cached SDK availability (non-persisted, re-detected each launch)
  availableAgentSdks: { opencode: boolean; claude: boolean; codex: boolean } | null

  // Actions
  openSettings: (section?: string) => void
  closeSettings: () => void
  setActiveSection: (section: string) => void
  updateSetting: <K extends keyof AppSettings>(key: K, value: AppSettings[K]) => void
  setSelectedModel: (
    model: SelectedModel | null,
    agentSdk?: AppSettings['defaultAgentSdk']
  ) => Promise<void>
  setSelectedModelForSdk: (
    agentSdk: AppSettings['defaultAgentSdk'],
    model: SelectedModel | null,
    options?: { skipBackendPush?: boolean }
  ) => Promise<void>
  setModeDefaultModel: (
    mode: 'build' | 'plan' | 'ask',
    model: SelectedModel | null
  ) => Promise<void>
  getModelForMode: (mode: 'build' | 'plan' | 'ask') => SelectedModel | null
  toggleFavoriteModel: (providerID: string, modelID: string) => void
  setModelVariantDefault: (providerID: string, modelID: string, variant: string) => void
  getModelVariantDefault: (providerID: string, modelID: string) => string | undefined
  resetToDefaults: () => void
  loadFromDatabase: () => Promise<void>
  detectAvailableAgentSdks: () => Promise<void>
}

async function saveToDatabase(settings: AppSettings): Promise<void> {
  try {
    if (typeof window !== 'undefined' && window.db?.setting) {
      await window.db.setting.set(APP_SETTINGS_DB_KEY, JSON.stringify(settings))
    }
  } catch (error) {
    console.error('Failed to save settings to database:', error)
  }
}

async function loadSettingsFromDatabase(): Promise<AppSettings | null> {
  try {
    if (typeof window !== 'undefined' && window.db?.setting) {
      const value = await window.db.setting.get(APP_SETTINGS_DB_KEY)
      if (value) {
        const parsed = JSON.parse(value)
        return {
          ...DEFAULT_SETTINGS,
          ...parsed,
          // Deep-merge commandFilter so new fields and new default allowlist
          // patterns are present for users whose saved settings pre-date them.
          commandFilter: mergeCommandFilterSettings(parsed.commandFilter),
          voiceInput: mergeVoiceInputSettings(parsed.voiceInput)
        }
      }
    }
  } catch (error) {
    console.error('Failed to load settings from database:', error)
  }
  return null
}

function extractSettings(state: SettingsState): AppSettings {
  return {
    locale: state.locale,
    autoStartSession: state.autoStartSession,
    breedType: state.breedType,
    vimModeEnabled: state.vimModeEnabled,
    defaultEditor: state.defaultEditor,
    customEditorCommand: state.customEditorCommand,
    defaultTerminal: state.defaultTerminal,
    customTerminalCommand: state.customTerminalCommand,
    embeddedTerminalBackend: state.embeddedTerminalBackend,
    ghosttyFontSize: state.ghosttyFontSize,
    terminalFontFamily: state.terminalFontFamily,
    ghosttyPromotionDismissed: state.ghosttyPromotionDismissed,
    selectedModel: state.selectedModel,
    selectedModelByProvider: state.selectedModelByProvider,
    defaultModels: state.defaultModels,
    lastOpenAction: state.lastOpenAction,
    favoriteModels: state.favoriteModels,
    customChromeCommand: state.customChromeCommand,
    modelVariantDefaults: state.modelVariantDefaults,
    showModelIcons: state.showModelIcons,
    showModelProvider: state.showModelProvider,
    showUsageIndicator: state.showUsageIndicator,
    defaultAgentSdk: state.defaultAgentSdk,
    stripAtMentions: state.stripAtMentions,
    skipForkFromMessageConfirm: state.skipForkFromMessageConfirm,
    codexFastMode: state.codexFastMode,
    codexFastModeAccepted: state.codexFastModeAccepted,
    updateChannel: state.updateChannel,
    skippedUpdateVersion: state.skippedUpdateVersion,
    initialSetupComplete: state.initialSetupComplete,
    commandFilter: state.commandFilter,
    telemetryEnabled: state.telemetryEnabled,
    fdaOnboardingDismissed: state.fdaOnboardingDismissed,
    autoPullBeforeWorktree: state.autoPullBeforeWorktree,
    keepAwakeEnabled: state.keepAwakeEnabled,
    uiZoomLevel: state.uiZoomLevel,
    uiFontScale: state.uiFontScale,
    voiceInput: state.voiceInput,
    sessionUiV2Enabled: state.sessionUiV2Enabled
  }
}

/**
 * Resolve the default model for a given agent SDK using the per-provider priority chain.
 * Priority: per-provider default → (legacy only) global selectedModel.
 * Returns null when per-provider defaults exist but none matches the requested SDK.
 *
 * Accepts an optional state snapshot so it can be used inside Zustand selectors
 * (where getState() must not be called). Falls back to store.getState() when omitted.
 */
export function resolveModelForSdk(
  agentSdk: string,
  state?: Pick<AppSettings, 'selectedModelByProvider' | 'selectedModel'>
): SelectedModel | null {
  const s = state ?? useSettingsStore.getState()
  const perProvider = s.selectedModelByProvider[agentSdk]
  if (perProvider) return perProvider
  // Legacy fallback only when per-provider feature not yet active (migration)
  if (Object.keys(s.selectedModelByProvider).length > 0) return null
  return s.selectedModel
}

export const useSettingsStore = create<SettingsState>()(
  persist(
    (set, get) => ({
      // Default values
      ...DEFAULT_SETTINGS,
      isOpen: false,
      activeSection: 'appearance',
      isLoading: true,
      availableAgentSdks: null,

      openSettings: (section?: string) => {
        set({ isOpen: true, activeSection: section || get().activeSection })
      },

      closeSettings: () => {
        set({ isOpen: false })
      },

      setActiveSection: (section: string) => {
        set({ activeSection: section })
      },

      updateSetting: <K extends keyof AppSettings>(key: K, value: AppSettings[K]) => {
        set({ [key]: value } as Partial<SettingsState>)
        // Persist to database
        const settings = extractSettings({ ...get(), [key]: value } as SettingsState)
        saveToDatabase(settings)
        // Notify main process of channel change
        if (key === 'updateChannel' && window.updaterOps?.setChannel) {
          window.updaterOps.setChannel(value as string)
        }
      },

      setSelectedModel: async (
        model: SelectedModel | null,
        agentSdk?: AppSettings['defaultAgentSdk']
      ) => {
        if (agentSdk) {
          return get().setSelectedModelForSdk(agentSdk, model)
        }
        set({ selectedModel: model })
        // Persist to backend (settings DB + agent runtime service)
        try {
          const defaultSdk = get().defaultAgentSdk ?? 'opencode'
          const runtimeId = defaultSdk === 'terminal' ? 'opencode' : defaultSdk
          await window.agentOps.setModel(model ? { ...model, runtimeId } : null)
        } catch (error) {
          console.error('Failed to persist model selection:', error)
        }
        // Always save to app settings (including null to clear)
        const settings = extractSettings({ ...get(), selectedModel: model } as SettingsState)
        saveToDatabase(settings)
      },

      setSelectedModelForSdk: async (
        agentSdk: AppSettings['defaultAgentSdk'],
        model: SelectedModel | null,
        options?: { skipBackendPush?: boolean }
      ) => {
        // null clears the per-SDK entry
        const current = { ...get().selectedModelByProvider }
        if (model) {
          current[agentSdk] = model
        } else {
          delete current[agentSdk]
        }
        set({ selectedModelByProvider: current })
        // Push to backend (skip for terminal — no backend service, or when caller already pushed)
        if (agentSdk !== 'terminal' && !options?.skipBackendPush) {
          try {
            await window.agentOps.setModel(model ? { ...model, runtimeId: agentSdk } : null)
          } catch (error) {
            console.error('Failed to persist model selection for SDK:', error)
          }
        }
        // Persist to app settings DB
        const settings = extractSettings({
          ...get(),
          selectedModelByProvider: current
        } as SettingsState)
        saveToDatabase(settings)
      },

      setModeDefaultModel: async (mode: 'build' | 'plan' | 'ask', model: SelectedModel | null) => {
        const currentDefaults = get().defaultModels || { build: null, plan: null, ask: null }
        const updated = { ...currentDefaults, [mode]: model }
        set({ defaultModels: updated })

        // Save to database (preference only — don't mutate the live service model)
        const settings = extractSettings({ ...get(), defaultModels: updated } as SettingsState)
        await saveToDatabase(settings)
      },

      getModelForMode: (mode: 'build' | 'plan' | 'ask') => {
        // Return only the mode-specific default (no global fallback).
        // Callers that need a fallback chain should check selectedModel separately.
        return get().defaultModels?.[mode] ?? null
      },

      setModelVariantDefault: (providerID: string, modelID: string, variant: string) => {
        const key = `${providerID}::${modelID}`
        const updated = { ...get().modelVariantDefaults, [key]: variant }
        set({ modelVariantDefaults: updated })
        const settings = extractSettings({
          ...get(),
          modelVariantDefaults: updated
        } as SettingsState)
        saveToDatabase(settings)
      },

      getModelVariantDefault: (providerID: string, modelID: string) => {
        const key = `${providerID}::${modelID}`
        return get().modelVariantDefaults[key]
      },

      toggleFavoriteModel: (providerID: string, modelID: string) => {
        const key = `${providerID}::${modelID}`
        const current = get().favoriteModels
        const updated = current.includes(key) ? current.filter((k) => k !== key) : [...current, key]
        set({ favoriteModels: updated })
        const settings = extractSettings({ ...get(), favoriteModels: updated } as SettingsState)
        saveToDatabase(settings)
      },

      resetToDefaults: () => {
        set({ ...DEFAULT_SETTINGS })
        saveToDatabase(DEFAULT_SETTINGS)
      },

      loadFromDatabase: async () => {
        const dbSettings = await loadSettingsFromDatabase()
        if (dbSettings) {
          set({
            ...dbSettings,
            // Existing users upgrading: if field missing, they've already set up
            initialSetupComplete: dbSettings.initialSetupComplete ?? true,
            isLoading: false
          })
        } else {
          set({ isLoading: false })
          await saveToDatabase(extractSettings(get()))
        }
      },

      detectAvailableAgentSdks: async () => {
        try {
          const result = await window.systemOps.detectAgentRuntimes()
          set({ availableAgentSdks: result })
        } catch {
          // Fail gracefully — context menu just won't show
          set({ availableAgentSdks: null })
        }
      }
    }),
    {
      name: 'hive-settings',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        autoStartSession: state.autoStartSession,
        locale: state.locale,
        breedType: state.breedType,
        vimModeEnabled: state.vimModeEnabled,
        defaultEditor: state.defaultEditor,
        customEditorCommand: state.customEditorCommand,
        defaultTerminal: state.defaultTerminal,
        customTerminalCommand: state.customTerminalCommand,
        embeddedTerminalBackend: state.embeddedTerminalBackend,
        ghosttyFontSize: state.ghosttyFontSize,
        terminalFontFamily: state.terminalFontFamily,
        ghosttyPromotionDismissed: state.ghosttyPromotionDismissed,
        selectedModel: state.selectedModel,
        selectedModelByProvider: state.selectedModelByProvider,
        defaultModels: state.defaultModels,
        lastOpenAction: state.lastOpenAction,
        favoriteModels: state.favoriteModels,
        customChromeCommand: state.customChromeCommand,
        modelVariantDefaults: state.modelVariantDefaults,
        showModelIcons: state.showModelIcons,
        showModelProvider: state.showModelProvider,
        showUsageIndicator: state.showUsageIndicator,
        defaultAgentSdk: state.defaultAgentSdk,
        activeSection: state.activeSection,
        stripAtMentions: state.stripAtMentions,
        skipForkFromMessageConfirm: state.skipForkFromMessageConfirm,
        codexFastMode: state.codexFastMode,
        codexFastModeAccepted: state.codexFastModeAccepted,
        updateChannel: state.updateChannel,
        skippedUpdateVersion: state.skippedUpdateVersion,
        initialSetupComplete: state.initialSetupComplete,
        commandFilter: state.commandFilter,
        telemetryEnabled: state.telemetryEnabled,
        fdaOnboardingDismissed: state.fdaOnboardingDismissed,
        keepAwakeEnabled: state.keepAwakeEnabled,
        uiZoomLevel: state.uiZoomLevel,
        uiFontScale: state.uiFontScale,
        voiceInput: state.voiceInput,
        sessionUiV2Enabled: state.sessionUiV2Enabled
      }),
      merge: (persistedState, currentState) => {
        const persisted = persistedState as Partial<SettingsState> | undefined
        return {
          ...currentState,
          ...(persisted || {}),
          commandFilter: mergeCommandFilterSettings(persisted?.commandFilter),
          voiceInput: mergeVoiceInputSettings(persisted?.voiceInput)
        }
      }
    }
  )
)

// Load from database on startup, then detect available agent SDKs
if (typeof window !== 'undefined') {
  setTimeout(() => {
    useSettingsStore
      .getState()
      .loadFromDatabase()
      .then(() => {
        useSettingsStore.getState().detectAvailableAgentSdks()

        // Apply saved zoom level (Electron webContents zoom)
        const zoomLevel = useSettingsStore.getState().uiZoomLevel
        if (zoomLevel !== 0 && window.systemOps?.setZoomLevel) {
          window.systemOps.setZoomLevel(zoomLevel)
        }

        // Apply saved font scale (Tailwind CSS variable override)
        const fontScale = useSettingsStore.getState().uiFontScale
        if (fontScale !== 1) {
          applyFontScale(fontScale)
        }
      })
  }, 200)

  // Sync zoom changes from keyboard shortcuts / menu back into settings
  if (window.systemOps?.onZoomChanged) {
    window.systemOps.onZoomChanged((level: number) => {
      useSettingsStore.getState().updateSetting('uiZoomLevel', level)
    })
  }

  // Listen for settings updates from main process (e.g., when "Allow always" adds to allowlist)
  window.settingsOps?.onSettingsUpdated((data) => {
    const typedData = data as { commandFilter?: CommandFilterSettings }
    if (typedData.commandFilter) {
      useSettingsStore.setState({ commandFilter: typedData.commandFilter })
    }
  })
}
