import { FormlyFieldConfig } from '@ngx-formly/core';

import { LanguageCode, DateTimeLocale } from '../../core/locale.constants';
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
import { ProjectCfgFormKey } from '../project/project.model';
import { KeyboardConfig } from '@sp/keyboard-config';
import { TaskReminderOptionId } from '../tasks/task.model';

export type AppFeaturesConfig = Readonly<{
  isTimeTrackingEnabled: boolean;
  isFocusModeEnabled: boolean;
  isSchedulerEnabled: boolean;
  isPlannerEnabled: boolean;
  isBoardsEnabled: boolean;
  isScheduleDayPanelEnabled: boolean;
  isIssuesPanelEnabled: boolean;
  isProjectNotesEnabled: boolean;
  isSyncIconEnabled: boolean;
  isSearchEnabled: boolean;
  isDonatePageEnabled: boolean;
  isEnableUserProfiles: boolean;
  isHabitsEnabled: boolean;
  isFinishDayEnabled: boolean;
}>;

export type MiscConfig = Readonly<{
  isConfirmBeforeExit: boolean;
  isConfirmBeforeExitWithoutFinishDay: boolean;
  isMinimizeToTray: boolean;
  isLocalRestApiEnabled?: boolean;
  // Desktop-only daily check for a newer GitHub release (#5463). Optional
  // because it was added later; a missing key means ON (see UpdateCheckService).
  isCheckForUpdates?: boolean;
  /** @deprecated Legacy hour-only representation. Use `startOfNextDayTime` as canonical source of truth. */
  startOfNextDay: number;
  /** Canonical start-of-next-day value, including minute precision. */
  startOfNextDayTime?: string;
  isDisableAnimations: boolean;
  // Experimental: render the header action buttons as a vertical strip on
  // the right edge of the viewport instead of the horizontal top header.
  // Desktop only. Optional because it was added later.
  isVerticalActionBar?: boolean;
  // optional because it was added later
  isDisableCelebration?: boolean;
  isShowProductivityTipLonger?: boolean;
  isTrayShowCurrentCountdown?: boolean;
  isUseCustomWindowTitleBar?: boolean;
  customTheme?: string;
  // number: one of DefaultStartPage. string: project id.
  defaultStartPage?: number | string;
  unsplashApiKey?: string | null;
  // Global wallpaper: shown on pages that don't provide their own background
  // (Planner, Schedule, Boards, Config, and tags/projects without an image).
  backgroundImageDark?: string | null;
  backgroundImageLight?: string | null;
  backgroundOverlayOpacity?: number;
  backgroundImageBlur?: number;

  // @todo: remove deprecated items in future major releases, after giving users time to migrate
  isConfirmBeforeTaskDelete?: boolean; // Deprecated
  isAutoAddWorkedOnToToday?: boolean; // Deprecated
  isAutMarkParentAsDone?: boolean; // Deprecated
  isTrayShowCurrentTask?: boolean; // Deprecated
  isTurnOffMarkdown?: boolean; // Deprecated
  defaultProjectId?: string | null | false; // Deprecated
  taskNotesTpl?: string; // Deprecated
  isOverlayIndicatorEnabled?: boolean; // Deprecated – moved to taskWidget.isEnabled
  overlayIndicatorOpacity?: number; // Deprecated – moved to taskWidget.opacity
}>;

export type TasksConfig = Readonly<{
  isAutoMarkParentAsDone: boolean;
  isAutoAddWorkedOnToToday: boolean;
  isConfirmBeforeDelete?: boolean;
  isTrayShowCurrent: boolean;
  isMarkdownFormattingInNotesEnabled: boolean;
  defaultProjectId?: string | null | false; // allow 'false' because of #569
  notesTemplate: string;
}>;

export type ShortSyntaxConfig = Readonly<{
  isEnableProject: boolean;
  isEnableDue: boolean;
  isEnableTag: boolean;
  urlBehavior?: 'keep' | 'extract' | 'keep-and-attach';
}>;

export type TimeTrackingConfig = Readonly<{
  defaultEstimate?: number | null;
  defaultEstimateSubTasks?: number | null;
  isAutoStartNextTask: boolean;
  isNotifyWhenTimeEstimateExceeded: boolean;
  isTrackingReminderEnabled: boolean;
  isTrackingReminderShowOnMobile: boolean;
  trackingReminderMinTime: number;
  isTrackingReminderNotify?: boolean;
  isTrackingReminderFocusWindow?: boolean;
}>;

export type EvaluationConfig = Readonly<{
  isHideEvaluationSheet: boolean;
}>;

export type IdleConfig = Readonly<{
  isEnableIdleTimeTracking: boolean;
  minIdleTime: number;
  isOnlyOpenIdleWhenCurrentTask: boolean;
}>;

export type TakeABreakConfig = Readonly<{
  isTakeABreakEnabled: boolean;
  isLockScreen: boolean;
  isTimedFullScreenBlocker: boolean;
  timedFullScreenBlockerDuration: number;
  isFocusWindow: boolean;
  takeABreakMessage: string;
  takeABreakMinWorkingTime: number;
  takeABreakSnoozeTime: number;
  // due to ngx-formly inconsistency they also can be undefined or null even
  motivationalImgs: (string | undefined | null)[];
}>;

export type PomodoroConfig = Readonly<{
  // due to formly not being reliable here we need to be more lenient
  duration?: number | null;
  breakDuration?: number | null;
  longerBreakDuration?: number | null;
  cyclesBeforeLongerBreak?: number | null;
}>;

export type FlowtimeBreakRule = Readonly<{
  minDuration: number; // in milliseconds
  maxDuration: number | null; // in milliseconds, or null for no upper bound
  breakDuration: number; // in milliseconds
}>;

export type FlowtimeConfig = Readonly<{
  isBreakEnabled?: boolean | null;
  breakMode?: 'ratio' | 'rule' | null; // ratio-based or rule-based break calculation
  breakPercentage?: number | null; // percentage of work time (for ratio mode)
  breakRules?: FlowtimeBreakRule[] | null; // rules for rule-based breaks
}>;

// NOTE: needs to be writable due to how we use it
// export type DropboxSyncConfig = object;

export interface WebDavConfig {
  baseUrl?: string | null;
  userName?: string | null;
  password?: string | null;
  // TODO remove and migrate
  syncFilePath?: string | null;
  syncFolderPath?: string | null;
}

export interface SuperSyncConfig extends WebDavConfig {
  accessToken?: string | null;
  /** Whether E2E encryption is enabled (SuperSync-specific setting) */
  isEncryptionEnabled?: boolean;
  /** Encryption password (SuperSync-specific, stored in private config) */
  encryptKey?: string | null;
}

export interface NextcloudConfig {
  serverUrl?: string | null;
  loginName?: string | null;
  userName?: string | null;
  password?: string | null;
  syncFolderPath?: string | null;
}

export interface OneDriveConfig {
  /** View-model only: false = use built-in official app (if available), true = use custom app */
  useCustomApp?: boolean | null;
  clientId?: string | null;
  tenantId?: string | null;
  syncFolderPath?: string | null;
}

export interface LocalFileSyncConfig {
  // TODO remove and migrate
  syncFilePath?: string | null;
  syncFolderPath?: string | null;
}

export type LocalBackupConfig = Readonly<{
  isEnabled: boolean;
  /** Desktop only. Optional for persisted data created before this setting existed. */
  maxBackupFiles?: number | null;
}>;

/**
 * App localization section
 * If property value is:
 * - `undefined` - that indicates value not been setted manually yet
 * - `null` - that indicates value manually reseted to app/system default
 *
 */
export type LocalizationConfig = Readonly<{
  lng?: LanguageCode | null;
  firstDayOfWeek?: number | null;
  dateTimeLocale?: DateTimeLocale | null;
}>;

export type SoundConfig = Readonly<{
  isIncreaseDoneSoundPitch: boolean;
  doneSound: string | null;
  breakReminderSound: string | null;
  trackTimeSound?: string | null;
  volume: number;
}>;

export type SyncConfig = Readonly<{
  isEnabled: boolean;
  isEncryptionEnabled?: boolean;
  isCompressionEnabled?: boolean;
  /**
   * SPAP-11: opt-in "Surgical sync" — store file-based sync as a small always-read
   * ops file (`sync-ops.json`) plus a rarely-rewritten snapshot (`sync-state.json`)
   * for O(delta) syncs. Default OFF. One-way per sync folder: once a client with
   * this ON migrates the folder, other clients must also turn it on to continue.
   */
  isUseSplitSyncFiles?: boolean;
  syncProvider: SyncProviderId | null;
  syncInterval: number;
  isManualSyncOnly?: boolean;

  /* NOTE: view model for form only*/
  encryptKey?: string | null;
  /* NOTE: view model for form only*/
  webDav?: WebDavConfig;
  /* NOTE: view model for form only*/
  superSync?: SuperSyncConfig;
  /* NOTE: view model for form only*/
  localFileSync?: LocalFileSyncConfig;
  /* NOTE: view model for form only*/
  nextcloud?: NextcloudConfig;
  /* NOTE: view model for form only*/
  oneDrive?: OneDriveConfig;
}>;

export type ScheduleConfig = Readonly<{
  isWorkStartEndEnabled: boolean;
  workStart: string;
  workEnd: string;
  isLunchBreakEnabled: boolean;
  lunchBreakStart: string;
  lunchBreakEnd: string;
}>;

export type ReminderConfig = Readonly<{
  isCountdownBannerEnabled: boolean;
  countdownDuration: number;
  defaultTaskRemindOption?: TaskReminderOptionId;
  disableReminders?: boolean;
  isFocusWindow?: boolean;
  // Android only: use alarm-style notifications (louder, more intrusive)
  useAlarmStyleReminders?: boolean;
  notifyOnDueDate?: boolean;
  dueDateNotificationHour?: number;
}>;

export type TrackingReminderConfigOld = Readonly<{
  isEnabled: boolean;
  isShowOnMobile: boolean;
  minTime: number;
}>;

/**
 * @deprecated Exists only for migration to the voice-reminder plugin. Can be removed once migration is no longer needed.
 */
export type DominaModeConfig = Readonly<{
  isEnabled: boolean;
  text: string;
  interval: number;
  volume: number;
  voice?: string | null;
}>;

export type FocusModeConfig = Readonly<{
  /**
   * @deprecated Replaced by the opt-in `isShowPreparation`. The full-screen
   * preparation countdown is now off by default; pressing start plays a brief
   * inline rocket launch instead. Kept (still written with its default) so older
   * clients that require this field keep validating synced config — no longer read.
   */
  isSkipPreparation: boolean;
  /**
   * Opt-in: when true, starting a focus session first shows the full-screen
   * preparation countdown (the "Get ready" checklist + rocket). Off/absent by
   * default — the default start plays a quick inline rocket launch and begins.
   */
  isShowPreparation?: boolean;
  focusModeSound?: 'off' | 'tick' | 'whiteNoise';
  /** @deprecated Use focusModeSound instead. Kept for backward-compat validation of old data. */
  isPlayTick?: boolean;
  isPauseTrackingDuringBreak?: boolean;
  /**
   * When true, pressing the time-tracking play button on a task also starts a
   * focus session (using the persistent mode the user last chose). The session
   * runs quietly via the header indicator — no overlay, no preparation screen.
   * Off by default; opt-in for users who want play = tracking + focus.
   */
  autoStartFocusOnPlay?: boolean;
  /**
   * @deprecated The auto-spawned session is now indicator-only by design — the
   * overlay never opens automatically — so this flag has no behavioural effect.
   * Kept on the type so old persisted configs deserialize without errors.
   */
  isStartInBackground?: boolean;
  /** Note: Controls Pomodoro overtime only (keeps timer running until manually ended) */
  isManualBreakStart?: boolean;
}>;

export type TaskWidgetConfig = Readonly<{
  isEnabled?: boolean;
  isAlwaysShow?: boolean;
  opacity?: number;
}>;

export type FocusModeLocalConfig = Readonly<{
  isLoopBreakEndAlarm?: boolean;
}>;

export type ClipboardImagesConfig = Readonly<{
  imagePath?: string | null;
}>;

export type DailySummaryNote = Readonly<{
  txt?: string;
  lastUpdateDayStr?: string;
}>;

// NOTE: config properties being undefined always means that they should be overwritten with the default value
export type GlobalConfigState = Readonly<{
  appFeatures: AppFeaturesConfig;
  localization: LocalizationConfig;
  misc: MiscConfig;
  tasks: TasksConfig;
  shortSyntax: ShortSyntaxConfig;
  evaluation: EvaluationConfig;
  idle: IdleConfig;
  takeABreak: TakeABreakConfig;
  pomodoro: PomodoroConfig;
  flowtime: FlowtimeConfig;
  keyboard: KeyboardConfig;
  localBackup: LocalBackupConfig;
  sound: SoundConfig;
  timeTracking: TimeTrackingConfig;
  reminder: ReminderConfig;
  schedule: ScheduleConfig;
  dominaMode: DominaModeConfig;
  focusMode: FocusModeConfig;
  // NOTE: taskWidget is intentionally NOT part of GlobalConfigState — it is a
  // per-instance setting persisted in localStorage (see TaskWidgetSettingsService)
  // because OS behavior (window dragging/resizing, transparency) varies enough
  // between Mac/Linux/Windows that syncing it across devices is undesirable.
  clipboardImages?: ClipboardImagesConfig;

  sync: SyncConfig;
  dailySummaryNote?: DailySummaryNote;
}>;

export type GlobalConfigSectionKey = keyof GlobalConfigState | 'EMPTY';

// 'taskWidget' isn't on GlobalConfigState (it's per-instance, see above), but
// the form layer still uses this key in form configs and the config-page save
// handler. Kept separate from `GlobalConfigSectionKey` so it cannot leak into
// `updateGlobalConfigSection` action payloads (which would create phantom ops
// in the sync log).
export type GlobalConfigFormSectionKey =
  | GlobalConfigSectionKey
  | 'taskWidget'
  | 'focusModeLocal';

export type GlobalSectionConfig =
  | MiscConfig
  | TasksConfig
  | PomodoroConfig
  | FlowtimeConfig
  | KeyboardConfig
  | ScheduleConfig
  | ReminderConfig
  | DailySummaryNote
  | SyncConfig
  | ClipboardImagesConfig
  | TaskWidgetConfig
  | FocusModeLocalConfig;
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

export interface LimitedFormlyFieldConfig<FormModel> extends Omit<
  FormlyFieldConfig,
  'key'
> {
  key?: keyof FormModel & (string | number);
}

export type CustomCfgSection =
  | 'FILE_IMPORT_EXPORT'
  | 'JIRA_CFG'
  | 'OPENPROJECT_CFG'
  | 'CLIPBOARD_IMAGES_CFG';

export interface ConfigSectionAction {
  label: string;
  icon?: string;
  onClick: () => void | Promise<void>;
}

// Intermediate model
export interface ConfigFormSection<FormModel> {
  title: string;
  key: GlobalConfigFormSectionKey | ProjectCfgFormKey;
  help?: string;
  helpArr?: { h?: string; p: string; p2?: string; p3?: string; p4?: string }[];
  customSection?: CustomCfgSection;
  items?: LimitedFormlyFieldConfig<FormModel>[];
  actions?: ConfigSectionAction[];
  isElectronOnly?: boolean;
  isHideForAndroidApp?: boolean;
}

export interface GenericConfigFormSection extends Omit<
  ConfigFormSection<unknown>,
  'items'
> {
  items?: FormlyFieldConfig[];
}

export type ConfigFormConfig = Readonly<GenericConfigFormSection[]>;
