/**
 * Enhanced PlatformService Mixin with ultra-concise database operations and caching
 *
 * Provides cached platform service access and utility methods for Vue components.
 * Eliminates repetitive PlatformServiceFactory.getInstance() calls across components.
 *
 * Features:
 * - Cached platform service instance (created once per component)
 * - Enhanced database utility methods with comprehensive error handling
 * - Ultra-concise database interaction methods ($db, $exec, $one, etc.)
 * - Automatic query result mapping and JSON field parsing
 * - Specialized shortcuts for common queries (contacts, settings)
 * - Transaction support with automatic rollback on errors
 * - Mixin pattern for easy integration with existing class components
 * - Enhanced utility methods for common patterns
 * - Robust error handling and logging
 * - Settings shortcuts for ultra-frequent update patterns
 * - High-level entity operations (insertContact, updateContact, etc.)
 * - Result mapping helpers to eliminate verbose row processing
 *
 * @author Matthew Raymer
 * @version 4.1.0
 * @since 2025-07-02
 * @updated 2025-06-25 - Added high-level entity operations for code reduction
 */

import * as R from "ramda";

import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import type {
  PlatformService,
  PlatformCapabilities,
} from "@/services/PlatformService";
import {
  type Settings,
  type SettingsWithJsonStrings,
} from "@/db/tables/settings";
import { logger } from "@/utils/logger";
import {
  Contact,
  ContactMaybeWithJsonStrings,
  ContactWithLabels,
} from "@/db/tables/contacts";
import { Account } from "@/db/tables/accounts";
import { Temp } from "@/db/tables/temp";
import {
  QueryExecResult,
  DatabaseExecResult,
  SqlValue,
} from "@/interfaces/database";
import {
  generateInsertStatement,
  generateUpdateStatement,
} from "@/utils/sqlHelpers";
import { AppString } from "../constants/app";

// =================================================
// TYPESCRIPT INTERFACES
// =================================================

// /**
//  * Cache entry interface for storing data with TTL
//  */
// interface CacheEntry<T> {
//   data: T;
//   timestamp: number;
//   ttl: number; // milliseconds
// }

/**
 * Vue component interface that uses the PlatformServiceMixin
 * This provides proper typing for the cache system
 */
interface VueComponentWithMixin {
  _platformService: PlatformService | null;
  $options: { name?: string };
  activeDid?: string;
  platformService(): PlatformService;
}

/**
 * Interface for the JSON export format of database tables
 */
export interface TableExportData {
  tableName: string;
  rows: Array<Record<string, unknown>>;
}

/**
 * Interface for the complete database export format
 */
export interface DatabaseExport {
  data: {
    data: Array<TableExportData>;
  };
}

// /**
//  * Global cache store for mixin instances
//  * Uses WeakMap to avoid memory leaks when components are destroyed
//  */
// const componentCaches = new WeakMap<
//   VueComponentWithMixin,
//   Map<string, CacheEntry<unknown>>
// >();
//
// /**
//  * Cache configuration constants
//  */
// const CACHE_DEFAULTS = {
//   default: 15000, // 15 seconds default TTL
// } as const;

const _memoryLogs: string[] = [];

/**
 * Enhanced mixin that provides cached platform service access and utility methods
 * with smart caching layer for ultimate performance optimization
 */
export const PlatformServiceMixin = {
  data() {
    return {
      // Cache the platform service instance at component level
      _platformService: null as PlatformService | null,
      // Track the current activeDid for change detection
      _currentActiveDid: null as string | null,
    };
  },

  computed: {
    /**
     * Cached platform service instance
     * Created once per component lifecycle
     */
    platformService(): PlatformService {
      if (!(this as unknown as VueComponentWithMixin)._platformService) {
        (this as unknown as VueComponentWithMixin)._platformService =
          PlatformServiceFactory.getInstance();
      }
      return (this as unknown as VueComponentWithMixin)._platformService!;
    },

    /**
     * Current active DID from settings
     * Used for change detection and component updates
     */
    currentActiveDid(): string | null {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      return (this as any)._currentActiveDid;
    },

    /**
     * Access to in-memory logs array
     * Provides direct access to memoryLogs without requiring databaseUtil import
     */
    $memoryLogs(): string[] {
      return _memoryLogs;
    },

    /**
     * Platform detection utilities
     */
    isCapacitor(): boolean {
      // @ts-expect-error Accessing computed property value from Vue instance
      return this["platformService"].isCapacitor();
    },

    isWeb(): boolean {
      // @ts-expect-error Accessing computed property value from Vue instance
      return this["platformService"].isWeb();
    },

    isElectron(): boolean {
      // @ts-expect-error Accessing computed property value from Vue instance
      return this["platformService"].isElectron();
    },

    /**
     * Platform capabilities
     */
    capabilities() {
      // @ts-expect-error Accessing computed property value from Vue instance
      return this["platformService"].getCapabilities();
    },
  },

  watch: {
    /**
     * Watch for changes in the current activeDid
     * Triggers component updates when user switches identities
     */
    currentActiveDid: {
      handler(newDid: string | null, oldDid: string | null) {
        if (newDid !== oldDid) {
          logger.debug(
            `[PlatformServiceMixin] ActiveDid changed from ${oldDid} to ${newDid}`,
          );
          // // Clear caches that might be affected by the change
          // (this as any).$clearAllCaches();
        }
      },
      immediate: true,
    },
  },

  methods: {
    // =================================================
    // SELF-CONTAINED UTILITY METHODS (no databaseUtil dependency)
    // =================================================

    /**
     * Update the current activeDid and trigger change detection
     * This method should be called when the user switches identities
     */
    async $updateActiveDid(newDid: string | null): Promise<void> {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const oldDid = (this as any)._currentActiveDid;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (this as any)._currentActiveDid = newDid;

      if (newDid !== oldDid) {
        logger.debug(
          `[PlatformServiceMixin] ActiveDid updated from ${oldDid} to ${newDid}`,
        );

        // Write only to active_identity table (single source of truth)
        try {
          await this.$dbExec(
            "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
            [newDid || ""],
          );
          logger.debug(
            `[PlatformServiceMixin] ActiveDid updated in active_identity table: ${newDid}`,
          );
        } catch (error) {
          logger.error(
            `[PlatformServiceMixin] Error updating activeDid in active_identity table ${newDid}:`,
            error,
          );
          // Continue with in-memory update even if database write fails
        }

        // // Clear caches that might be affected by the change
        // this.$clearAllCaches();
      }
    },

    /**
     * Get available account DIDs for user selection
     * Returns array of DIDs that can be set as active identity
     */
    async $getAvailableAccountDids(): Promise<string[]> {
      try {
        const result = await this.$dbQuery(
          "SELECT did FROM accounts ORDER BY did",
        );

        if (!result?.values?.length) {
          return [];
        }

        return result.values.map((row: SqlValue[]) => row[0] as string);
      } catch (error) {
        logger.error(
          "[PlatformServiceMixin] Error getting available account DIDs:",
          error,
        );
        return [];
      }
    },

    /**
     * Map database columns to values with proper type conversion
     * Handles boolean conversion from SQLite integers (0/1) to boolean values
     */
    _mapColumnsToValues(
      columns: string[],
      values: unknown[][],
    ): Array<Record<string, unknown>> {
      return values.map((row) => {
        const obj: Record<string, unknown> = {};
        columns.forEach((column, index) => {
          let value = row[index];

          // Convert SQLite integer booleans to JavaScript booleans
          if (
            // settings
            column === "isRegistered" ||
            column === "finishedOnboarding" ||
            column === "filterFeedByVisible" ||
            column === "filterFeedByNearby" ||
            column === "hasBackedUpSeed" ||
            column === "hideRegisterPromptOnNewContact" ||
            column === "showContactGivesInline" ||
            column === "showGeneralAdvanced" ||
            column === "showShortcutBvc" ||
            column === "warnIfProdServer" ||
            column === "warnIfTestServer" ||
            // contacts
            column === "iViewContent" ||
            column === "registered" ||
            column === "seesMe"
          ) {
            if (value === 1) {
              value = true;
            } else if (value === 0) {
              value = false;
            }
            // Keep null values as null
          }

          // Convert SQLite JSON strings to objects/arrays
          if (
            column === "contactMethods" ||
            column === "searchBoxes" ||
            column === "starredPlanHandleIds"
          ) {
            value = this._parseJsonField(value, []);
          }

          obj[column] = value;
        });
        return obj;
      });
    },

    /**
     * Safely parses JSON strings with fallback to default value.
     * Handles different SQLite implementations:
     * - Web SQLite (wa-sqlite/absurd-sql): Auto-parses JSON strings to objects
     * - Capacitor SQLite: Returns raw strings that need manual parsing
     *
     * See also src/db/databaseUtil.ts parseJsonField
     * and maybe consolidate
     */
    _parseJsonField<T>(value: unknown, defaultValue: T): T {
      if (typeof value === "string") {
        try {
          return JSON.parse(value);
        } catch {
          return defaultValue;
        }
      }
      return (value as T) || defaultValue;
    },

    /**
     * Convert Settings object to SettingsWithJsonStrings for database storage
     * Handles conversion of complex objects like searchBoxes to JSON strings
     * @param settings Settings object to convert
     * @returns SettingsWithJsonStrings object ready for database storage
     */
    _convertSettingsForStorage(
      settings: Partial<Settings>,
    ): Partial<SettingsWithJsonStrings> {
      const converted = { ...settings } as Partial<SettingsWithJsonStrings>;

      // Convert searchBoxes array to JSON string if present
      if (settings.searchBoxes !== undefined) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (converted as any).searchBoxes = Array.isArray(settings.searchBoxes)
          ? JSON.stringify(settings.searchBoxes)
          : String(settings.searchBoxes);
      }
      if (settings.starredPlanHandleIds !== undefined) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (converted as any).starredPlanHandleIds = Array.isArray(
          settings.starredPlanHandleIds,
        )
          ? JSON.stringify(settings.starredPlanHandleIds)
          : String(settings.starredPlanHandleIds);
      }

      return converted;
    },

    // // =================================================
    // // CACHING UTILITY METHODS
    // // =================================================

    // /**
    //  * Get or initialize cache for this component instance
    //  */
    // _getCache(): Map<string, CacheEntry<unknown>> {
    //   let cache = componentCaches.get(this as unknown as VueComponentWithMixin);
    //   if (!cache) {
    //     cache = new Map();
    //     componentCaches.set(this as unknown as VueComponentWithMixin, cache);
    //   }
    //   return cache;
    // },

    // /**
    //  * Check if cache entry is valid (not expired)
    //  */
    // _isCacheValid(entry: CacheEntry<unknown>): boolean {
    //   return Date.now() - entry.timestamp < entry.ttl;
    // },

    // /**
    //  * Get data from cache if valid, otherwise return null
    //  */
    // _getCached<T>(key: string): T | null {
    //   const cache = this._getCache();
    //   const entry = cache.get(key);
    //   if (entry && this._isCacheValid(entry)) {
    //     return entry.data as T;
    //   }
    //   cache.delete(key); // Clean up expired entries
    //   return null;
    // },

    // /**
    //  * Store data in cache with TTL
    //  */
    // _setCached<T>(key: string, data: T, ttl?: number): T {
    //   const cache = this._getCache();
    //   const actualTtl = ttl || CACHE_DEFAULTS.default;
    //   cache.set(key, {
    //     data,
    //     timestamp: Date.now(),
    //     ttl: actualTtl,
    //   });
    //   return data;
    // },

    // /**
    //  * Invalidate specific cache entry
    //  */
    // _invalidateCache(key: string): void {
    //   const cache = this._getCache();
    //   cache.delete(key);
    // },

    // /**
    //  * Clear all cache entries for this component
    //  */
    // _clearCache(): void {
    //   const cache = this._getCache();
    //   cache.clear();
    // },

    // =================================================
    // ENHANCED DATABASE METHODS (with error handling)
    // =================================================

    /**
     * Enhanced database query method with error handling
     */
    async $dbQuery(sql: string, params?: unknown[]) {
      try {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return await (this as any).platformService.dbQuery(sql, params);
      } catch (error) {
        logger.error(
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          `[${(this as any).$options.name}] Database query failed:`,
          {
            sql,
            params,
            error,
          },
        );
        throw error;
      }
    },

    /**
     * Enhanced database execution method with error handling
     */
    async $dbExec(sql: string, params?: unknown[]) {
      try {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return await (this as any).platformService.dbExec(sql, params);
      } catch (error) {
        logger.error(
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          `[${(this as any).$options.name}] Database exec failed:`,
          {
            sql,
            params,
            error,
          },
        );
        throw error;
      }
    },

    /**
     * Enhanced database single row query method with error handling
     */
    async $dbGetOneRow(
      sql: string,
      params?: unknown[],
    ): Promise<SqlValue[] | undefined> {
      try {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return await (this as any).platformService.dbGetOneRow(sql, params);
      } catch (error) {
        logger.error(
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          `[${(this as any).$options.name}] Database single row query failed:`,
          {
            sql,
            params,
            error,
          },
        );
        throw error;
      }
    },

    /**
     * Database raw query method with error handling
     */
    async $dbRawQuery(sql: string, params?: unknown[]) {
      try {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return await (this as any).platformService.dbRawQuery(sql, params);
      } catch (error) {
        logger.error(
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          `[${(this as any).$options.name}] Database raw query failed:`,
          {
            sql,
            params,
            error,
          },
        );
        throw error;
      }
    },

    /**
     * Utility method for retrieving master settings
     * Common pattern used across many components
     */
    async _getMasterSettings(
      fallback: Settings | null = null,
    ): Promise<Settings | null> {
      try {
        // Get current active identity
        const activeIdentity = await this.$getActiveIdentity();
        const activeDid = activeIdentity.activeDid;

        if (!activeDid) {
          return fallback;
        }

        // Get identity-specific settings
        const result = await this.$dbQuery(
          "SELECT * FROM settings WHERE accountDid = ?",
          [activeDid],
        );

        if (!result?.values?.length) {
          return fallback;
        }

        const mappedResults = this._mapColumnsToValues(
          result.columns,
          result.values,
        );

        if (!mappedResults.length) {
          return fallback;
        }

        const settings = mappedResults[0] as Settings;

        // Handle JSON field parsing
        if (settings.searchBoxes) {
          settings.searchBoxes = this._parseJsonField(settings.searchBoxes, []);
        }
        if (settings.starredPlanHandleIds) {
          settings.starredPlanHandleIds = this._parseJsonField(
            settings.starredPlanHandleIds,
            [],
          );
        }

        return settings;
      } catch (error) {
        logger.error(`[Settings Trace] ❌ Failed to get master settings:`, {
          error,
        });
        return fallback;
      }
    },

    /**
     * Utility method for merging default and account-specific settings
     * Handles the common pattern of layered settings
     */
    async $getMergedSettings(
      accountDid?: string,
      defaultFallback: Settings = {},
    ): Promise<Settings> {
      try {
        // Get default settings
        const defaultSettings = await this._getMasterSettings(defaultFallback);

        // If no account DID, return defaults
        if (!accountDid) {
          return defaultSettings || defaultFallback;
        }

        // Get account-specific overrides
        const accountResult = await this.$dbQuery(
          "SELECT * FROM settings WHERE accountDid = ?",
          [accountDid],
        );

        if (!accountResult?.values?.length) {
          return defaultSettings || defaultFallback;
        }

        // Map and filter non-null overrides
        const mappedResults = this._mapColumnsToValues(
          accountResult.columns,
          accountResult.values,
        );

        if (!mappedResults.length) {
          return defaultSettings || defaultFallback;
        }

        const overrideSettings = mappedResults[0] as Settings;

        const filteredOverrides = Object.fromEntries(
          Object.entries(overrideSettings).filter(([_, v]) => v !== null),
        );

        // Merge settings with overrides taking precedence
        const mergedSettings = {
          ...defaultSettings,
          ...filteredOverrides,
        } as Settings;

        // Handle JSON field parsing
        if (mergedSettings.searchBoxes) {
          mergedSettings.searchBoxes = this._parseJsonField(
            mergedSettings.searchBoxes,
            [],
          );
        }
        if (mergedSettings.starredPlanHandleIds) {
          mergedSettings.starredPlanHandleIds = this._parseJsonField(
            mergedSettings.starredPlanHandleIds,
            [],
          );
        }

        return mergedSettings;
      } catch (error) {
        logger.error(`[Settings Trace] ❌ Failed to get merged settings:`, {
          accountDid,
          error,
        });
        return defaultFallback;
      }
    },

    /**
     * Get active identity from the new active_identity table
     * This replaces the activeDid field in settings for better architecture
     */
    async $getActiveIdentity(): Promise<{ activeDid: string }> {
      try {
        const result = await this.$dbQuery(
          "SELECT activeDid FROM active_identity WHERE id = 1",
        );

        if (!result?.values?.length) {
          logger.warn(
            "[PlatformServiceMixin] Active identity table is empty - this may indicate a migration issue",
          );
          return { activeDid: "" };
        }

        const activeDid = result.values[0][0] as string | null;

        // Handle null activeDid (initial state after migration) - auto-select first account
        if (activeDid === null) {
          const firstAccount = await this.$dbQuery(
            "SELECT did FROM accounts ORDER BY dateCreated, did LIMIT 1",
          );

          if (firstAccount?.values?.length) {
            const firstAccountDid = firstAccount.values[0][0] as string;
            await this.$dbExec(
              "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
              [firstAccountDid],
            );
            return { activeDid: firstAccountDid };
          }

          logger.warn(
            "[PlatformServiceMixin] No accounts available for auto-selection",
          );
          return { activeDid: "" };
        }

        // Validate activeDid exists in accounts
        const accountExists = await this.$dbQuery(
          "SELECT did FROM accounts WHERE did = ?",
          [activeDid],
        );

        if (accountExists?.values?.length) {
          return { activeDid };
        }

        // Clear corrupted activeDid and return empty
        logger.warn(
          "[PlatformServiceMixin] Active identity not found in accounts, clearing",
        );
        await this.$dbExec(
          "UPDATE active_identity SET activeDid = NULL, lastUpdated = datetime('now') WHERE id = 1",
        );
        return { activeDid: "" };
      } catch (error) {
        logger.error(
          "[PlatformServiceMixin] Error getting active identity:",
          error,
        );
        return { activeDid: "" };
      }
    },

    /**
     * Transaction wrapper with automatic rollback on error
     */
    async $withTransaction<T>(callback: () => Promise<T>): Promise<T> {
      try {
        await this.$dbExec("BEGIN TRANSACTION");
        const result = await callback();
        await this.$dbExec("COMMIT");
        return result;
      } catch (error) {
        await this.$dbExec("ROLLBACK");
        throw error;
      }
    },

    // =================================================
    // SMART DELETION PATTERN DAL METHODS
    // =================================================

    /**
     * Get account DID by ID
     * Required for smart deletion pattern
     */
    async $getAccountDidById(id: number): Promise<string> {
      const result = await this.$dbQuery(
        "SELECT did FROM accounts WHERE id = ?",
        [id],
      );
      return result?.values?.[0]?.[0] as string;
    },

    /**
     * Get active DID (returns null if none selected)
     * Required for smart deletion pattern
     */
    async $getActiveDid(): Promise<string | null> {
      const result = await this.$dbQuery(
        "SELECT activeDid FROM active_identity WHERE id = 1",
      );
      return (result?.values?.[0]?.[0] as string) || null;
    },

    /**
     * Set active DID (can be null for no selection)
     * Required for smart deletion pattern
     */
    async $setActiveDid(did: string | null): Promise<void> {
      await this.$dbExec(
        "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
        [did],
      );
    },

    /**
     * Count total accounts
     * Required for smart deletion pattern
     */
    async $countAccounts(): Promise<number> {
      const result = await this.$dbQuery("SELECT COUNT(*) FROM accounts");
      return (result?.values?.[0]?.[0] as number) || 0;
    },

    /**
     * Deterministic "next" picker for account selection
     * Required for smart deletion pattern
     */
    $pickNextAccountDid(all: string[], current?: string): string {
      const sorted = [...all].sort();
      if (!current) return sorted[0];
      const i = sorted.indexOf(current);
      return sorted[(i + 1) % sorted.length];
    },

    /**
     * Ensure an active account is selected (repair hook)
     * Required for smart deletion pattern bootstrapping
     */
    async $ensureActiveSelected(): Promise<void> {
      const active = await this.$getActiveDid();
      const all = await this.$getAllAccountDids();
      if (active === null && all.length > 0) {
        await this.$setActiveDid(this.$pickNextAccountDid(all));
      }
    },

    // =================================================
    // ULTRA-CONCISE DATABASE METHODS (shortest names)
    // =================================================

    /**
     * Ultra-short database query - just $db()
     * @param sql SQL query string
     * @param params Query parameters
     */
    async $db(
      sql: string,
      params: unknown[] = [],
    ): Promise<QueryExecResult | undefined> {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      return await (this as any).platformService.dbQuery(sql, params);
    },

    /**
     * Ultra-short database exec - just $exec()
     * @param sql SQL statement string
     * @param params Statement parameters
     */
    async $exec(
      sql: string,
      params: unknown[] = [],
    ): Promise<DatabaseExecResult> {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      return await (this as any).platformService.dbExec(sql, params);
    },

    /**
     * Ultra-short single row query - just $one()
     * @param sql SQL query string
     * @param params Query parameters
     */
    async $one(
      sql: string,
      params: unknown[] = [],
    ): Promise<SqlValue[] | undefined> {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      return await (this as any).platformService.dbGetOneRow(sql, params);
    },

    // =================================================
    // QUERY + MAPPING COMBO METHODS (ultimate conciseness)
    // =================================================

    /**
     * Query with automatic result mapping - $query()
     * Combines database query + mapping in one call
     * @param sql SQL query string
     * @param params Query parameters
     * @returns Mapped array of results
     */
    async $query<T = Record<string, unknown>>(
      sql: string,
      params: unknown[] = [],
    ): Promise<T[]> {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const result = await (this as any).platformService.dbQuery(sql, params);
      if (!result?.columns || !result?.values) {
        return [];
      }
      const mappedResults = this._mapColumnsToValues(
        result.columns,
        result.values,
      );
      return mappedResults as T[];
    },

    /**
     * Get first result with automatic mapping - $first()
     * @param sql SQL query string
     * @param params Query parameters
     * @returns First mapped result or null
     */
    async $first<T = Record<string, unknown>>(
      sql: string,
      params: unknown[] = [],
    ): Promise<T | null> {
      const results = await this.$query(sql, params);
      return results.length > 0 ? (results[0] as T) : null;
    },

    // =================================================
    // CACHED SPECIALIZED SHORTCUTS (massive performance boost)
    // =================================================

    /**
     * Normalize contact data by parsing JSON strings into proper objects
     * Handles the contactMethods field which can be either a JSON string or an array
     * @param rawContacts Raw contact data from database
     * @returns Normalized Contact[] array
     */
    $normalizeContacts(rawContacts: ContactMaybeWithJsonStrings[]): Contact[] {
      return rawContacts.map((contact) => {
        // Create a new contact object with proper typing
        const normalizedContact: Contact = {
          did: contact.did,
          iViewContent: contact.iViewContent,
          name: contact.name,
          nextPubKeyHashB64: contact.nextPubKeyHashB64,
          notes: contact.notes,
          profileImageUrl: contact.profileImageUrl,
          publicKeyBase64: contact.publicKeyBase64,
          seesMe: contact.seesMe,
          registered: contact.registered,
        };

        // Handle contactMethods field which can be a JSON string or an array
        if (contact.contactMethods !== undefined) {
          if (typeof contact.contactMethods === "string") {
            // Parse JSON string into array
            normalizedContact.contactMethods = this._parseJsonField(
              contact.contactMethods,
              [],
            );
          } else if (Array.isArray(contact.contactMethods)) {
            // Validate that each item in the array is a proper ContactMethod object
            normalizedContact.contactMethods = contact.contactMethods.filter(
              (method) => {
                const isValid =
                  method &&
                  typeof method === "object" &&
                  typeof method.label === "string" &&
                  typeof method.type === "string" &&
                  typeof method.value === "string";

                if (!isValid && method !== undefined) {
                  // eslint-disable-next-line no-console
                  console.warn(
                    "[ContactNormalization] Invalid contact method:",
                    method,
                  );
                }

                return isValid;
              },
            );
          } else {
            // Invalid data, use empty array
            normalizedContact.contactMethods = [];
          }
        } else {
          // No contactMethods, use empty array
          normalizedContact.contactMethods = [];
        }

        return normalizedContact;
      });
    },

    /**
     * Load all contacts (always fresh) - $contacts()
     * Always fetches fresh data from database for consistency
     * Handles JSON string/object duality for contactMethods field
     * @returns Promise<Contact[]> Array of normalized contact objects
     */
    async $contacts(): Promise<Contact[]> {
      const rawContacts = (await this.$query(
        "SELECT * FROM contacts ORDER BY name",
      )) as ContactMaybeWithJsonStrings[];

      return this.$normalizeContacts(rawContacts);
    },

    /**
     * Load all contacts sorted by when they were added (by ID)
     * Always fetches fresh data from database for consistency
     * Handles JSON string/object duality for contactMethods field
     * @returns Promise<Contact[]> Array of normalized contact objects sorted by addition date (newest first)
     */
    async $contactsByDateAdded(): Promise<Contact[]> {
      const rawContacts = (await this.$query(
        "SELECT * FROM contacts ORDER BY id DESC",
      )) as ContactMaybeWithJsonStrings[];

      return this.$normalizeContacts(rawContacts);
    },

    /**
     * Ultra-concise shortcut for getting number of contacts
     * @returns Promise<number> Total number of contacts
     */
    async $contactCount(): Promise<number> {
      const countRow = await this.$one("SELECT COUNT(*) FROM contacts");
      return (countRow?.[0] as number) || 0;
    },

    /**
     * Ultra-concise shortcut for getting all logs from database
     * @returns Promise<Array<Record<string, unknown>>> Array of log records
     */
    async $logs(): Promise<Array<Record<string, unknown>>> {
      return await this.$query("SELECT * FROM logs ORDER BY date DESC");
    },

    /**
     * Load settings with optional defaults WITHOUT caching - $settings()
     * Settings are loaded fresh every time for immediate consistency
     * @param defaults Optional default values
     * @returns Fresh settings object from database
     */
    async $settings(defaults: Settings = {}): Promise<Settings> {
      const settings = await this._getMasterSettings(defaults);

      if (!settings) {
        return defaults;
      }

      // FIXED: Set default apiServer for all platforms, not just Electron
      // Only set default if no user preference exists
      if (!settings.apiServer) {
        // Import constants dynamically to get platform-specific values
        const { DEFAULT_ENDORSER_API_SERVER } = await import(
          "../constants/app"
        );
        // Set default for all platforms when apiServer is empty
        settings.apiServer = DEFAULT_ENDORSER_API_SERVER;
      }

      return settings; // Return fresh data without caching
    },

    /**
     * Load account-specific settings WITHOUT caching - $accountSettings()
     * Settings are loaded fresh every time for immediate consistency
     * @param did DID identifier (optional, uses current active DID)
     * @param defaults Optional default values
     * @returns Fresh merged settings object from database
     */
    async $accountSettings(
      did?: string,
      defaults: Settings = {},
    ): Promise<Settings> {
      try {
        // Get default settings first
        const defaultSettings = await this._getMasterSettings(defaults);

        if (!defaultSettings) {
          return defaults;
        }

        // Get DID from active_identity table (single source of truth)
        const activeIdentity = await this.$getActiveIdentity();
        const targetDid = did || activeIdentity.activeDid;

        // If no target DID, return default settings
        if (!targetDid) {
          return defaultSettings;
        }

        // Get merged settings using existing method
        const mergedSettings = await this.$getMergedSettings(
          targetDid,
          defaultSettings,
        );

        // Set activeDid from active_identity table (single source of truth)
        mergedSettings.activeDid = activeIdentity.activeDid;
        logger.debug(
          "[PlatformServiceMixin] Using activeDid from active_identity table:",
          { activeDid: activeIdentity.activeDid },
        );
        logger.debug(
          "[PlatformServiceMixin] $accountSettings() returning activeDid:",
          { activeDid: mergedSettings.activeDid },
        );

        // FIXED: Set default apiServer for all platforms, not just Electron
        // Only set default if no user preference exists
        if (!mergedSettings.apiServer) {
          // Import constants dynamically to get platform-specific values
          const { DEFAULT_ENDORSER_API_SERVER } = await import(
            "../constants/app"
          );
          // Set default for all platforms when apiServer is empty
          mergedSettings.apiServer = DEFAULT_ENDORSER_API_SERVER;
        }

        // Merge with any provided defaults (these take highest precedence)
        // Filter out undefined and empty string values to prevent overriding real settings
        const filteredDefaults = Object.fromEntries(
          Object.entries(defaults).filter(
            ([_, value]) => value !== undefined && value !== "",
          ),
        );

        const finalSettings = { ...mergedSettings, ...filteredDefaults };
        return finalSettings;
      } catch (error) {
        logger.error("[Settings Trace] ❌ Error in $accountSettings:", error);

        // Fallback to defaults on error
        return defaults;
      }
    },

    // =================================================
    // SETTINGS UPDATE SHORTCUTS (eliminate 90% boilerplate)
    // =================================================

    /**
     * Save default settings - $saveSettings()
     * Ultra-concise shortcut for updateDefaultSettings
     *
     * ✅ KEEP: This method will be the primary settings save method after consolidation
     *
     * @param changes Settings changes to save
     * @returns Promise<boolean> Success status
     */
    async $saveSettings(changes: Partial<Settings>): Promise<boolean> {
      try {
        // Remove fields that shouldn't be updated
        const {
          accountDid,
          id,
          activeDid: activeDidField,
          ...safeChanges
        } = changes;
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        void accountDid;
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        void id;
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        void activeDidField;

        logger.debug(
          "[PlatformServiceMixin] $saveSettings - Original changes:",
          changes,
        );
        logger.debug(
          "[PlatformServiceMixin] $saveSettings - Safe changes:",
          safeChanges,
        );

        if (Object.keys(safeChanges).length === 0) return true;

        // Convert settings for database storage (handles searchBoxes conversion)
        const convertedChanges = this._convertSettingsForStorage(safeChanges);
        logger.debug(
          "[PlatformServiceMixin] $saveSettings - Converted changes:",
          convertedChanges,
        );

        const setParts: string[] = [];
        const params: unknown[] = [];

        Object.entries(convertedChanges).forEach(([key, value]) => {
          if (value !== undefined) {
            setParts.push(`${key} = ?`);
            params.push(value);
          }
        });

        logger.debug(
          "[PlatformServiceMixin] $saveSettings - Set parts:",
          setParts,
        );
        logger.debug("[PlatformServiceMixin] $saveSettings - Params:", params);

        if (setParts.length === 0) return true;

        // Get current active DID and update that identity's settings
        const activeIdentity = await this.$getActiveIdentity();
        const currentActiveDid = activeIdentity.activeDid;

        if (currentActiveDid) {
          params.push(currentActiveDid);
          await this.$dbExec(
            `UPDATE settings SET ${setParts.join(", ")} WHERE accountDid = ?`,
            params,
          );
        } else {
          logger.warn(
            "[PlatformServiceMixin] No active DID found, cannot save settings",
          );
        }

        // Update activeDid tracking if it changed
        if (activeDidField !== undefined) {
          await this.$updateActiveDid(activeDidField);
        }

        return true;
      } catch (error) {
        logger.error("[PlatformServiceMixin] Error saving settings:", error);
        return false;
      }
    },

    /**
     * Save user-specific settings - $saveUserSettings()
     * Ultra-concise shortcut for updateDidSpecificSettings
     * @param did DID identifier
     * @param changes Settings changes to save
     * @returns Promise<boolean> Success status
     */
    async $saveUserSettings(
      did: string,
      changes: Partial<Settings>,
    ): Promise<boolean> {
      try {
        // Remove fields that shouldn't be updated
        const { id, ...safeChanges } = changes;
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        void id;
        safeChanges.accountDid = did;

        if (Object.keys(safeChanges).length === 0) return true;

        // Convert settings for database storage (handles searchBoxes conversion)
        const convertedChanges = this._convertSettingsForStorage(safeChanges);

        const setParts: string[] = [];
        const params: unknown[] = [];

        Object.entries(convertedChanges).forEach(([key, value]) => {
          if (value !== undefined) {
            setParts.push(`${key} = ?`);
            params.push(value);
          }
        });

        if (setParts.length === 0) return true;

        params.push(did);
        const sql = `UPDATE settings SET ${setParts.join(", ")} WHERE accountDid = ?`;

        await this.$dbExec(sql, params);
        return true;
      } catch (error) {
        logger.error(
          "[PlatformServiceMixin] Error saving user settings:",
          error,
        );
        return false;
      }
    },

    /**
     * Save settings for current active user - $saveMySettings()
     * Ultra-concise shortcut using activeDid from component
     * @param changes Settings changes to save
     * @returns Promise<boolean> Success status
     */
    /**
     * Since this is unused, and since it relies on this.activeDid which isn't guaranteed to exist,
     * let's take this out for the sake of safety.
     * Totally remove after start of 2026 (since it would be obvious by then that it's not used).
     *
    async $saveMySettings(changes: Partial<Settings>): Promise<boolean> {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const currentDid = (this as any).activeDid;

      if (!currentDid) {
        return await this.$saveSettings(changes);
      }
      return await this.$saveUserSettings(currentDid, changes);
    },
    **/

    // =================================================
    // CACHE MANAGEMENT METHODS
    // =================================================

    /**
     * Refresh settings from database - $refreshSettings()
     * Since settings are no longer cached, this simply returns fresh settings
     */
    async $refreshSettings(): Promise<Settings> {
      return await this.$settings();
    },

    /**
     * Get fresh contacts from database - $refreshContacts()
     * Always returns fresh data (no caching)
     */
    async $refreshContacts(): Promise<Contact[]> {
      return await this.$contacts();
    },

    // /**
    //  * Clear all caches for this component - $clearAllCaches()
    //  * Useful for manual cache management
    //  */
    // $clearAllCaches(): void {
    //   this._clearCache();
    // },

    // =================================================
    // HIGH-LEVEL ENTITY OPERATIONS (eliminate verbose SQL patterns)
    // =================================================

    /**
     * Map SQL query results to typed objects - $mapResults()
     * Eliminates verbose row mapping patterns
     * @param results SQL query results
     * @param mapper Function to map each row to an object
     * @returns Array of mapped objects
     */
    $mapResults<T>(
      results: QueryExecResult | undefined,
      mapper: (row: unknown[]) => T,
    ): T[] {
      if (!results?.values) return [];
      return results.values.map(mapper);
    },

    /**
     * Maps a SQLite query result to an array of objects
     * @param record The query result from SQLite
     * @returns Array of objects where each object maps column names to their corresponding values
     */
    $mapQueryResultToValues(
      record: QueryExecResult | undefined,
    ): Array<Record<string, unknown>> {
      if (!record) {
        return [];
      }
      return this.$mapColumnsToValues(record.columns, record.values) as Array<
        Record<string, unknown>
      >;
    },

    /**
     * Public method for mapping database columns to values
     * Provides the same functionality as _mapColumnsToValues but as a public method
     */
    $mapColumnsToValues(
      columns: string[],
      values: unknown[][],
    ): Array<Record<string, unknown>> {
      return this._mapColumnsToValues(columns, values);
    },

    // =================================================
    // CONTACT METHODS
    // =================================================

    /**
     * Insert or replace contact - $insertContact()
     * Eliminates verbose INSERT OR REPLACE patterns
     * @param contact Contact object to insert
     * @returns Promise<boolean> Success status
     */
    async $insertContact(contact: Partial<Contact>): Promise<boolean> {
      try {
        // Convert undefined values to null for SQL.js compatibility
        const safeContact = {
          did: contact.did !== undefined ? contact.did : null,
          name: contact.name !== undefined ? contact.name : null,
          publicKeyBase64:
            contact.publicKeyBase64 !== undefined
              ? contact.publicKeyBase64
              : null,
          seesMe: contact.seesMe !== undefined ? contact.seesMe : null,
          registered:
            contact.registered !== undefined ? contact.registered : null,
          nextPubKeyHashB64:
            contact.nextPubKeyHashB64 !== undefined
              ? contact.nextPubKeyHashB64
              : null,
          profileImageUrl:
            contact.profileImageUrl !== undefined
              ? contact.profileImageUrl
              : null,
          notes: contact.notes !== undefined ? contact.notes : null,
          iViewContent:
            contact.iViewContent !== undefined ? contact.iViewContent : null,
          contactMethods:
            contact.contactMethods !== undefined
              ? Array.isArray(contact.contactMethods)
                ? JSON.stringify(contact.contactMethods)
                : contact.contactMethods
              : null,
        };

        await this.$dbExec(
          `INSERT OR REPLACE INTO contacts 
           (did, name, publicKeyBase64, seesMe, registered, nextPubKeyHashB64, profileImageUrl, notes, iViewContent, contactMethods) 
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
          [
            safeContact.did,
            safeContact.name,
            safeContact.publicKeyBase64,
            safeContact.seesMe,
            safeContact.registered,
            safeContact.nextPubKeyHashB64,
            safeContact.profileImageUrl,
            safeContact.notes,
            safeContact.iViewContent,
            safeContact.contactMethods,
          ],
        );
        return true;
      } catch (error) {
        logger.error("[PlatformServiceMixin] Error inserting contact:", error);
        return false;
      }
    },

    /**
     * Update contact - $updateContact()
     * Eliminates verbose UPDATE patterns
     * @param did Contact DID to update
     * @param changes Partial contact changes
     * @returns Promise<boolean> Success status
     */
    async $updateContact(
      did: string,
      changes: Partial<Contact>,
    ): Promise<boolean> {
      try {
        const setParts: string[] = [];
        const params: unknown[] = [];

        Object.entries(changes).forEach(([key, value]) => {
          if (value !== undefined) {
            setParts.push(`${key} = ?`);

            // Handle contactMethods field - convert array to JSON string
            if (key === "contactMethods" && Array.isArray(value)) {
              params.push(JSON.stringify(value));
            } else {
              params.push(value);
            }
          }
        });

        if (setParts.length === 0) return true;

        params.push(did);
        await this.$dbExec(
          `UPDATE contacts SET ${setParts.join(", ")} WHERE did = ?`,
          params,
        );

        return true;
      } catch (error) {
        logger.error("[PlatformServiceMixin] Error updating contact:", error);
        return false;
      }
    },

    /**
     * Get all contacts as typed objects - $getAllContacts()
     * Eliminates verbose query + mapping patterns
     * Handles JSON string/object duality for contactMethods field
     * @returns Promise<Contact[]> Array of normalized contact objects
     */
    async $getAllContacts(): Promise<Contact[]> {
      const rawContacts = (await this.$query(
        "SELECT * FROM contacts ORDER BY name",
      )) as ContactMaybeWithJsonStrings[];

      return this.$normalizeContacts(rawContacts);
    },

    /**
     * Get single contact by DID - $getContact()
     * Eliminates verbose single contact query patterns
     * Handles JSON string/object duality for contactMethods field
     * @param did Contact DID to retrieve
     * @returns Promise<Contact | null> Normalized contact object or null if not found
     */
    async $getContact(did: string): Promise<Contact | null> {
      const rawContacts = (await this.$query(
        "SELECT * FROM contacts WHERE did = ?",
        [did],
      )) as ContactMaybeWithJsonStrings[];

      if (rawContacts.length === 0) {
        return null;
      }

      const normalizedContacts = this.$normalizeContacts(rawContacts);
      return normalizedContacts[0];
    },

    /**
     * Delete contact by DID - $deleteContact()
     * Eliminates verbose contact deletion patterns
     * @param did Contact DID to delete
     * @returns Promise<boolean> Success status
     */
    async $deleteContact(did: string): Promise<boolean> {
      try {
        await this.$dbExec("DELETE FROM contacts WHERE did = ?", [did]);
        return true;
      } catch (error) {
        logger.error("[PlatformServiceMixin] Error deleting contact:", error);
        return false;
      }
    },

    // =================================================
    // CONTACT LABELS METHODS
    // =================================================

    /**
     * Get labels for a specific contact - $getContactLabels()
     * @param did Contact DID
     * @returns Promise<string[]> Array of labels
     */
    async $getContactLabelsForDid(did: string): Promise<string[]> {
      try {
        const results = (await this.$dbQuery(
          "SELECT label FROM contact_labels WHERE did = ? ORDER BY label",
          [did],
        )) as QueryExecResult;
        return results?.values?.map((r: SqlValue[]) => r[0] as string) || [];
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error getting labels for contact ${did}:`,
          error,
        );
        return [];
      }
    },

    /**
     * Get contact IDs with all labels
     * @param labels Array of labels
     * @returns Promise<string[]> Array of contacts with all labels
     */
    async $getContactIdsWithAllLabels(labels: string[]): Promise<string[]> {
      try {
        const numberOfLabels = labels.length;
        const questionsForInput = labels.map(() => `?`).join(", ");
        const results = (await this.$dbQuery(
          `SELECT did FROM contact_labels WHERE label IN (${questionsForInput})`,
          [...labels],
        )) as QueryExecResult;
        // count the occurrences of each did
        const didCounts: Record<string, number> =
          results?.values?.reduce(
            (acc: Record<string, number>, curr: SqlValue[]) => {
              acc[curr[0] as unknown as string] =
                (acc[curr[0] as unknown as string] || 0) + 1;
              return acc;
            },
            {},
          ) || {};
        // filter out the dids that do not occur for as many labels as there are labels
        const contactIdsWithAllLabels = Object.keys(didCounts).filter(
          (did: string) => didCounts[did] === numberOfLabels,
        );
        return contactIdsWithAllLabels;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error getting contact IDs with all labels ${labels}:`,
          error,
        );
        return [];
      }
    },

    /**
     * Add a label to a contact - $addContactLabel()
     * @param did Contact DID
     * @param label Label string
     * @returns Promise<boolean> Success status
     */
    async $addContactLabel(did: string, label: string): Promise<boolean> {
      try {
        await this.$dbExec(
          "INSERT OR IGNORE INTO contact_labels (did, label) VALUES (?, ?)",
          [did, label],
        );
        return true;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error adding label ${label} to contact ${did}:`,
          error,
        );
        return false;
      }
    },

    async $deleteContactLabel(did: string, label: string): Promise<boolean> {
      try {
        await this.$dbExec(
          "DELETE FROM contact_labels WHERE did = ? AND label = ?",
          [did, label],
        );
        return true;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error deleting label ${label} from contact ${did}:`,
          error,
        );
        return false;
      }
    },

    async $deleteContactLabelsForDid(did: string): Promise<boolean> {
      try {
        await this.$dbExec("DELETE FROM contact_labels WHERE did = ?", [did]);
        return true;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error deleting labels for contact ${did}:`,
          error,
        );
        return false;
      }
    },

    async $insertContactLabels(
      did: string,
      labels: string[],
    ): Promise<boolean> {
      try {
        for (const label of labels) {
          await this.$dbExec(
            "INSERT INTO contact_labels (did, label) VALUES (?, ?)",
            [did, label],
          );
        }
        return true;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error inserting labels for contact ${did}:`,
          error,
        );
        return false;
      }
    },

    async $updateContactLabels(
      did: string,
      labels: string[],
    ): Promise<boolean> {
      try {
        await this.$dbExec("DELETE FROM contact_labels WHERE did = ?", [did]);
        for (const label of labels) {
          await this.$dbExec(
            "INSERT INTO contact_labels (did, label) VALUES (?, ?)",
            [did, label],
          );
        }
        return true;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error updating labels for contact ${did}:`,
          error,
        );
        return false;
      }
    },

    /**
     * Get all unique labels available
     * @returns Promise<string[]> Array of unique labels
     */
    async $getUniqueContactLabels(): Promise<string[]> {
      try {
        const results = (await this.$dbQuery(
          "SELECT DISTINCT label FROM contact_labels ORDER BY label",
        )) as QueryExecResult;
        return results?.values?.map((r: SqlValue[]) => r[0] as string) || [];
      } catch (error) {
        logger.error(
          "[PlatformServiceMixin] Error getting unique labels:",
          error,
        );
        return [];
      }
    },

    /**
     * Get all accounts - $getAllAccounts()
     * Retrieves all account metadata from the accounts table
     * @returns Promise<Account[]> Array of account objects
     */
    async $getAllAccounts(): Promise<Account[]> {
      try {
        return await this.$query<Account>("SELECT * FROM accounts");
      } catch (error) {
        logger.error(
          "[PlatformServiceMixin] Error getting all accounts:",
          error,
        );
        return [];
      }
    },

    /**
     * Get all account DIDs - $getAllAccountDids()
     * Retrieves all account DIDs from the accounts table
     * @returns Promise<string[]> Array of account DIDs
     */
    async $getAllAccountDids(): Promise<string[]> {
      try {
        const result = await this.$dbQuery(
          "SELECT did FROM accounts ORDER BY did",
        );

        if (!result?.values?.length) {
          return [];
        }

        return result.values.map((row: SqlValue[]) => row[0] as string);
      } catch (error) {
        logger.error(
          "[PlatformServiceMixin] Error getting all account DIDs:",
          error,
        );
        return [];
      }
    },

    // =================================================
    // TEMP TABLE METHODS (for temporary storage)
    // =================================================

    /**
     * Get temporary data by ID - $getTemp()
     * Retrieves temporary data from the temp table
     * @param id Temporary storage ID
     * @returns Promise<Temp | null> Temporary data or null if not found
     */
    async $getTemp(id: string): Promise<Temp | null> {
      try {
        return await this.$first<Temp>("SELECT * FROM temp WHERE id = ?", [id]);
      } catch (error) {
        logger.error("[PlatformServiceMixin] Error getting temp data:", error);
        return null;
      }
    },

    /**
     * Delete temporary data by ID - $deleteTemp()
     * Removes temporary data from the temp table
     * @param id Temporary storage ID
     * @returns Promise<boolean> Success status
     */
    async $deleteTemp(id: string): Promise<boolean> {
      try {
        await this.$dbExec("DELETE FROM temp WHERE id = ?", [id]);
        return true;
      } catch (error) {
        logger.error("[PlatformServiceMixin] Error deleting temp data:", error);
        return false;
      }
    },

    /**
     * Generic entity insertion - $insertEntity()
     * Eliminates verbose INSERT patterns for any entity
     * @param tableName Database table name
     * @param entity Entity object to insert
     * @param fields Array of field names to insert
     * @returns Promise<boolean> Success status
     */
    async $insertEntity(
      tableName: string,
      entity: Record<string, unknown>,
      fields: string[],
    ): Promise<boolean> {
      try {
        const placeholders = fields.map(() => "?").join(", ");
        // Convert undefined values to null for SQL.js compatibility
        const values = fields.map((field) =>
          entity[field] !== undefined ? entity[field] : null,
        );

        await this.$dbExec(
          `INSERT OR REPLACE INTO ${tableName} (${fields.join(", ")}) VALUES (${placeholders})`,
          values,
        );
        return true;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error inserting entity into ${tableName}:`,
          error,
        );
        return false;
      }
    },

    /**
     * Update settings with direct SQL - $updateSettings()
     * Eliminates verbose settings update patterns
     * @param changes Settings changes to apply
     * @param did Optional DID for user-specific settings
     * @returns Promise<boolean> Success status
     */
    /**
     * Update settings - $updateSettings()
     * Ultra-concise shortcut for updating settings (default or user-specific)
     *
     * ⚠️ DEPRECATED: This method will be removed in favor of $saveSettings()
     * Use $saveSettings(changes, did?) instead for better consistency
     *
     * @param changes Settings changes to save
     * @param did Optional DID for user-specific settings
     * @returns Promise<boolean> Success status
     */
    async $updateSettings(
      changes: Partial<Settings>,
      did?: string,
    ): Promise<boolean> {
      try {
        // Use self-contained methods which handle the correct schema
        if (did) {
          return await this.$saveUserSettings(did, changes);
        } else {
          return await this.$saveSettings(changes);
        }
      } catch (error) {
        logger.error("[PlatformServiceMixin] Error updating settings:", error);
        return false;
      }
    },

    /**
     * Get settings row as array - $getSettingsRow()
     * Eliminates verbose settings retrieval patterns
     * @param fields Array of field names to retrieve
     * @param did Optional DID for user-specific settings
     * @returns Promise<unknown[]> Settings row as array
     */
    async $getSettingsRow(
      fields: string[],
      did?: string,
    ): Promise<unknown[] | undefined> {
      // Use current active DID if no specific DID provided
      const targetDid = did || (await this.$getActiveIdentity()).activeDid;

      if (!targetDid) {
        return undefined;
      }

      return await this.$one(
        `SELECT ${fields.join(", ")} FROM settings WHERE accountDid = ?`,
        [targetDid],
      );
    },

    /**
     * Update entity with direct SQL - $updateEntity()
     * Eliminates verbose UPDATE patterns for any table
     * @param tableName Name of the table to update
     * @param entity Object containing fields to update
     * @param whereClause WHERE clause for the update (e.g. "id = ?")
     * @param whereParams Parameters for the WHERE clause
     * @returns Promise<boolean> Success status
     */
    async $updateEntity(
      tableName: string,
      entity: Record<string, unknown>,
      whereClause: string,
      whereParams: unknown[],
    ): Promise<boolean> {
      try {
        const setParts: string[] = [];
        const params: unknown[] = [];

        Object.entries(entity).forEach(([key, value]) => {
          if (value !== undefined) {
            setParts.push(`${key} = ?`);
            // Convert values to SQLite-compatible types
            let convertedValue = value ?? null;
            if (convertedValue !== null) {
              if (typeof convertedValue === "object") {
                // Convert objects and arrays to JSON strings
                convertedValue = JSON.stringify(convertedValue);
              } else if (typeof convertedValue === "boolean") {
                // Convert boolean to integer (0 or 1)
                convertedValue = convertedValue ? 1 : 0;
              }
            }
            params.push(convertedValue);
          }
        });

        if (setParts.length === 0) return true;

        const sql = `UPDATE ${tableName} SET ${setParts.join(", ")} WHERE ${whereClause}`;
        await this.$dbExec(sql, [...params, ...whereParams]);
        return true;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error updating entity in ${tableName}:`,
          error,
        );
        return false;
      }
    },

    /**
     * Insert user-specific settings - $insertUserSettings()
     * Creates new settings record for a specific DID
     * @param did DID identifier for the user
     * @param settings Settings to insert (accountDid will be set automatically)
     * @returns Promise<boolean> Success status
     */
    async $insertUserSettings(
      did: string,
      settings: Partial<Settings>,
    ): Promise<boolean> {
      try {
        // Ensure accountDid is set and remove id to avoid conflicts
        const { id, ...safeSettings } = settings;
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        void id;
        const insertSettings = { ...safeSettings, accountDid: did };

        // Convert to SQL-compatible values
        const fields = Object.keys(insertSettings);
        const values = fields.map((field) => {
          const value = insertSettings[field as keyof typeof insertSettings];
          if (value === undefined) return null;
          if (typeof value === "object" && value !== null) {
            return JSON.stringify(value);
          }
          if (typeof value === "boolean") {
            return value ? 1 : 0;
          }
          return value;
        });

        const placeholders = fields.map(() => "?").join(", ");
        await this.$dbExec(
          `INSERT OR REPLACE INTO settings (${fields.join(", ")}) VALUES (${placeholders})`,
          values,
        );
        return true;
      } catch (error) {
        logger.error(
          "[PlatformServiceMixin] Error inserting user settings:",
          error,
        );
        return false;
      }
    },

    // =================================================
    // LOGGING METHODS (convenience methods for components)
    // =================================================

    /**
     * Log message to database - $log()
     * @param message Message to log
     * @param level Log level (info, warn, error)
     * @returns Promise<void>
     */
    async $log(message: string, level?: string): Promise<void> {
      return logger.toDb(message, level);
    },

    /**
     * Log error message to database - $logError()
     * @param message Error message to log
     * @returns Promise<void>
     */
    async $logError(message: string): Promise<void> {
      return logger.toDb(message, "error");
    },

    /**
     * Log message to console and database - $logAndConsole()
     * @param message Message to log
     * @param isError Whether this is an error message
     * @returns Promise<void>
     */
    async $logAndConsole(message: string, isError = false): Promise<void> {
      return logger.toConsoleAndDb(message, isError);
    },

    $appendToMemoryLogs(message: string): void {
      _memoryLogs.push(`${new Date().toISOString()}: ${message}`);
      if (_memoryLogs.length > 1000) {
        _memoryLogs.splice(0, _memoryLogs.length - 1000);
      }
    },

    /**
     * Public wrapper for generateInsertStatement
     */
    $generateInsertStatement(
      model: Record<string, unknown>,
      tableName: string,
    ): { sql: string; params: unknown[] } {
      return generateInsertStatement(model, tableName);
    },

    /**
     * Public wrapper for generateUpdateStatement
     */
    $generateUpdateStatement(
      model: Record<string, unknown>,
      tableName: string,
      whereClause: string,
      whereParams: unknown[] = [],
    ): { sql: string; params: unknown[] } {
      return generateUpdateStatement(
        model,
        tableName,
        whereClause,
        whereParams,
      );
    },

    /**
     * Converts an array of contacts to the export JSON format.
     * This format is used for data migration and backup purposes.
     *
     * @param contacts - Array of Contact objects to convert
     * @returns DatabaseExport object in the standardized format
     */
    $contactsToExportJson(contacts: ContactWithLabels[]): DatabaseExport {
      return {
        data: {
          data: [
            {
              tableName: "contacts",
              rows: contacts,
            },
          ],
        },
      };
    },

    /**
     * Prepares contact and label data for export to a JSON file.
     * Handles normalization of contact data and generates a timestamped filename.
     *
     * @param appName - Application name for filename (defaults to "TimeSafari")
     * @returns Object containing the JSON string and filename
     *
     * @example
     * ```typescript
     * const contacts = await $contacts();
     * const labelsResult = await $dbQuery("SELECT did, label FROM contact_labels");
     * const labels = mapQueryResultToValues(labelsResult);
     * const { jsonString, fileName } = saveContactExport(contacts, labels);
     * await platformService.writeAndShareFile(fileName, jsonString);
     * ```
     */
    async $saveContactExport(): Promise<string> {
      const contacts = await this.$contacts();

      // Fetch all contact labels from database
      const labelsResult = await this.$dbQuery(
        "SELECT did, label FROM contact_labels ORDER BY did, label",
      );
      // create a map of did to labels
      const contactToLabelsMap = new Map<string, string[]>();
      // iterate over the labelsResult and accumulate the labels for each did
      labelsResult?.values?.forEach((contactLabel: [string, string]) => {
        const did = contactLabel[0];
        const label = contactLabel[1];
        if (!contactToLabelsMap.has(did)) {
          contactToLabelsMap.set(did, []);
        }
        contactToLabelsMap.get(did)?.push(label);
      });

      // Process contacts to normalize contactMethods field
      // Handle both array format (from normalized contacts) and string format (legacy/database)
      const processedContacts: ContactWithLabels[] = contacts.map((contact) => {
        // Remove contactMethods field temporarily to get a clean type
        const exContact: ContactWithLabels = R.omit(
          ["contactMethods"],
          contact,
        );

        // Add contactMethods as a proper array of ContactMethod objects
        if (contact.contactMethods) {
          if (Array.isArray(contact.contactMethods)) {
            // Already an array, use it directly
            exContact.contactMethods = contact.contactMethods;
          } else {
            // Check if it's a string that needs parsing
            const contactMethodsValue = contact.contactMethods as unknown;
            if (
              typeof contactMethodsValue === "string" &&
              contactMethodsValue.trim() !== ""
            ) {
              try {
                // String that needs parsing
                exContact.contactMethods = JSON.parse(contactMethodsValue);
              } catch (error) {
                // Invalid JSON, use empty array
                logger.warn(
                  `Invalid contactMethods JSON for contact ${contact.did}:`,
                  error,
                );
                exContact.contactMethods = [];
              }
            } else {
              // Invalid data, use empty array
              exContact.contactMethods = [];
            }
          }
        } else {
          // No contactMethods, use empty array
          exContact.contactMethods = [];
        }

        // add the labels to the contact
        exContact.labels = contactToLabelsMap.get(contact.did) || [];

        return exContact;
      });

      // Build export data with contacts
      const exportData = this.$contactsToExportJson(processedContacts);

      // Generate JSON string
      const jsonString = JSON.stringify(exportData, null, 2);

      // Generate filename with current date
      const today = new Date();
      const dateString = today.toISOString().split("T")[0]; // YYYY-MM-DD format
      const appName = AppString.APP_NAME_NO_SPACES;
      const fileName = `${appName}-backup-contacts-${dateString}.json`;

      // Use platform service to handle export
      await (
        this as unknown as IPlatformServiceMixin
      ).platformService.writeAndShareFile(fileName, jsonString);

      return fileName;
    },

    // =================================================
    // DEBUGGING
    // =================================================

    /**
     * Debug method to verify settings for a specific DID
     * Useful for troubleshooting settings propagation issues
     * @param did DID to check settings for
     * @returns Promise<Settings | null> Settings object or null if not found
     */
    async $debugDidSettings(did: string): Promise<Settings | null> {
      try {
        const result = await this.$dbQuery(
          "SELECT * FROM settings WHERE accountDid = ?",
          [did],
        );

        if (!result?.values?.length) {
          logger.warn(
            `[PlatformServiceMixin] No settings found for DID: ${did}`,
          );
          return null;
        }

        const mappedResults = this._mapColumnsToValues(
          result.columns,
          result.values,
        );

        if (!mappedResults.length) {
          logger.warn(
            `[PlatformServiceMixin] Failed to map settings for DID: ${did}`,
          );
          return null;
        }

        const settings = mappedResults[0] as Settings;

        logger.debug(`[PlatformServiceMixin] Settings for DID ${did}:`, {
          firstName: settings.firstName,
          isRegistered: settings.isRegistered,
          activeDid: settings.activeDid,
          apiServer: settings.apiServer,
        });

        return settings;
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error debugging settings for DID ${did}:`,
          error,
        );
        return null;
      }
    },

    /**
     * Debug method to verify merged settings for a specific DID
     * Shows both default and DID-specific settings
     * @param did DID to check merged settings for
     * @returns Promise<void> Logs debug information
     */
    async $debugMergedSettings(did: string): Promise<void> {
      try {
        // Get default settings
        const defaultSettings = await this._getMasterSettings({});
        logger.debug(
          `[PlatformServiceMixin] Default settings:`,
          defaultSettings,
        );

        // Get DID-specific settings
        const didSettings = await this.$debugDidSettings(did);

        // Get merged settings
        const mergedSettings = await this.$getMergedSettings(
          did,
          defaultSettings || {},
        );

        logger.debug(`[PlatformServiceMixin] Merged settings for ${did}:`, {
          defaultSettings,
          didSettings,
          mergedSettings,
          isRegistered: mergedSettings.isRegistered,
        });
      } catch (error) {
        logger.error(
          `[PlatformServiceMixin] Error debugging merged settings for DID ${did}:`,
          error,
        );
      }
    },
  },
};

// =================================================
// TYPESCRIPT INTERFACES
// =================================================

/**
 * Enhanced interface with caching utility methods
 */
export interface IPlatformServiceMixin {
  platformService: PlatformService;
  $dbQuery(
    sql: string,
    params?: unknown[],
  ): Promise<QueryExecResult | undefined>;
  $dbExec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
  $dbGetOneRow(
    sql: string,
    params?: unknown[],
  ): Promise<SqlValue[] | undefined>;
  $dbRawQuery(sql: string, params?: unknown[]): Promise<unknown | undefined>;
  $getMergedSettings(
    defaultKey: string,
    accountDid?: string,
    defaultFallback?: Settings,
  ): Promise<Settings>;
  $getActiveIdentity(): Promise<{ activeDid: string }>;
  $withTransaction<T>(callback: () => Promise<T>): Promise<T>;
  $getAvailableAccountDids(): Promise<string[]>;
  isCapacitor: boolean;
  isWeb: boolean;
  isElectron: boolean;
  capabilities: PlatformCapabilities;

  // High-level entity operations
  $mapResults<T>(
    results: QueryExecResult | undefined,
    mapper: (row: unknown[]) => T,
  ): T[];
  $insertContact(contact: Partial<Contact>): Promise<boolean>;
  $updateContact(did: string, changes: Partial<Contact>): Promise<boolean>;
  $getAllContacts(): Promise<Contact[]>;
  $getContact(did: string): Promise<Contact | null>;
  $deleteContact(did: string): Promise<boolean>;
  $contactCount(): Promise<number>;
  $getContactLabelsForDid(did: string): Promise<string[]>;
  $getContactIdsWithAllLabels(labels: string[]): Promise<string[]>;
  $addContactLabel(did: string, label: string): Promise<boolean>;
  $deleteContactLabel(did: string, label: string): Promise<boolean>;
  $deleteContactLabelsForDid(did: string): Promise<boolean>;
  $insertContactLabels(did: string, labels: string[]): Promise<boolean>;
  $updateContactLabels(did: string, labels: string[]): Promise<boolean>;
  $getUniqueContactLabels(): Promise<string[]>;
  $getAllAccounts(): Promise<Account[]>;
  $getAllAccountDids(): Promise<string[]>;
  $insertEntity(
    tableName: string,
    entity: Record<string, unknown>,
    fields: string[],
  ): Promise<boolean>;
  $updateSettings(changes: Partial<Settings>, did?: string): Promise<boolean>;
  $getSettingsRow(
    fields: string[],
    did?: string,
  ): Promise<unknown[] | undefined>;
  $updateEntity(
    tableName: string,
    entity: Record<string, unknown>,
    whereClause: string,
    whereParams: unknown[],
  ): Promise<boolean>;
  $insertUserSettings(
    did: string,
    settings: Partial<Settings>,
  ): Promise<boolean>;
  $getTemp(id: string): Promise<Temp | null>;
  $deleteTemp(id: string): Promise<boolean>;

  // Logging methods
  $log(message: string, level?: string): Promise<void>;
  $logError(message: string): Promise<void>;
  $logAndConsole(message: string, isError?: boolean): Promise<void>;

  // Memory logs access
  $memoryLogs: string[];

  // New additions
  $logs(): Promise<Array<Record<string, unknown>>>;

  // New additions
  $generateInsertStatement(
    model: Record<string, unknown>,
    tableName: string,
  ): { sql: string; params: unknown[] };
  $generateUpdateStatement(
    model: Record<string, unknown>,
    tableName: string,
    whereClause: string,
    whereParams?: unknown[],
  ): { sql: string; params: unknown[] };
  $mapQueryResultToValues(
    record: QueryExecResult | undefined,
  ): Array<Record<string, unknown>>;
  $mapColumnsToValues(
    columns: string[],
    values: unknown[][],
  ): Array<Record<string, unknown>>;

  // Contact export methods
  $saveContactExport(): Promise<string>;

  // Debug methods
  $debugDidSettings(did: string): Promise<Settings | null>;
  $debugMergedSettings(did: string): Promise<void>;
}

// TypeScript declaration merging to eliminate (this as any) type assertions
declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    // Core platform service access
    platformService: PlatformService;
    isCapacitor: boolean;
    isWeb: boolean;
    isElectron: boolean;
    capabilities: PlatformCapabilities;

    // ActiveDid tracking
    currentActiveDid: string | null;
    $updateActiveDid(newDid: string | null): Promise<void>;

    // Ultra-concise database methods (shortest possible names)
    $db(sql: string, params?: unknown[]): Promise<QueryExecResult | undefined>;
    $exec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
    $one(sql: string, params?: unknown[]): Promise<SqlValue[] | undefined>;

    // Query + mapping combo methods
    $query<T = Record<string, unknown>>(
      sql: string,
      params?: unknown[],
    ): Promise<T[]>;
    $first<T = Record<string, unknown>>(
      sql: string,
      params?: unknown[],
    ): Promise<T | null>;

    // Enhanced utility methods
    $dbQuery(
      sql: string,
      params?: unknown[],
    ): Promise<QueryExecResult | undefined>;
    $dbExec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
    $dbGetOneRow(
      sql: string,
      params?: unknown[],
    ): Promise<unknown[] | undefined>;
    $dbRawQuery(sql: string, params?: unknown[]): Promise<unknown | undefined>;
    $getMergedSettings(
      key: string,
      did?: string,
      defaults?: Settings,
    ): Promise<Settings>;
    $getActiveIdentity(): Promise<{ activeDid: string }>;
    $withTransaction<T>(fn: () => Promise<T>): Promise<T>;
    $getAvailableAccountDids(): Promise<string[]>;

    // Specialized shortcuts - settings fresh
    $settings(defaults?: Settings): Promise<Settings>;
    $accountSettings(did?: string, defaults?: Settings): Promise<Settings>;
    $normalizeContacts(rawContacts: ContactMaybeWithJsonStrings[]): Contact[];

    // Settings update shortcuts (eliminate 90% boilerplate)
    $saveSettings(changes: Partial<Settings>): Promise<boolean>;
    $saveUserSettings(
      did: string,
      changes: Partial<Settings>,
    ): Promise<boolean>;
    // @deprecated; see implementation note above
    // $saveMySettings(changes: Partial<Settings>): Promise<boolean>;

    $contacts(): Promise<Contact[]>;
    $contactsByDateAdded(): Promise<Contact[]>;

    // Cache management methods
    $refreshSettings(): Promise<Settings>;
    $refreshContacts(): Promise<Contact[]>;
    // $clearAllCaches(): void;

    // High-level entity operations (eliminate verbose SQL patterns)
    $mapResults<T>(
      results: QueryExecResult | undefined,
      mapper: (row: unknown[]) => T,
    ): T[];
    $insertContact(contact: Partial<Contact>): Promise<boolean>;
    $updateContact(did: string, changes: Partial<Contact>): Promise<boolean>;
    $getAllContacts(): Promise<Contact[]>;
    $getContact(did: string): Promise<Contact | null>;
    $deleteContact(did: string): Promise<boolean>;
    $contactCount(): Promise<number>;
    $getContactLabelsForDid(did: string): Promise<string[]>;
    $getContactIdsWithAllLabels(labels: string[]): Promise<string[]>;
    $addContactLabel(did: string, label: string): Promise<boolean>;
    $deleteContactLabel(did: string, label: string): Promise<boolean>;
    $deleteContactLabelsForDid(did: string): Promise<boolean>;
    $insertContactLabels(did: string, labels: string[]): Promise<boolean>;
    $updateContactLabels(did: string, labels: string[]): Promise<boolean>;
    $getUniqueContactLabels(): Promise<string[]>;
    $getAllAccounts(): Promise<Account[]>;
    $getAllAccountDids(): Promise<string[]>;
    $insertEntity(
      tableName: string,
      entity: Record<string, unknown>,
      fields: string[],
    ): Promise<boolean>;
    $updateSettings(changes: Partial<Settings>, did?: string): Promise<boolean>;
    $getSettingsRow(
      fields: string[],
      did?: string,
    ): Promise<unknown[] | undefined>;
    $updateEntity(
      tableName: string,
      entity: Record<string, unknown>,
      whereClause: string,
      whereParams: unknown[],
    ): Promise<boolean>;
    $insertUserSettings(
      did: string,
      settings: Partial<Settings>,
    ): Promise<boolean>;
    $getTemp(id: string): Promise<Temp | null>;
    $deleteTemp(id: string): Promise<boolean>;

    // Logging methods
    $log(message: string, level?: string): Promise<void>;
    $logError(message: string): Promise<void>;
    $logAndConsole(message: string, isError?: boolean): Promise<void>;

    // Memory logs access
    $memoryLogs: string[];

    // New additions
    $logs(): Promise<Array<Record<string, unknown>>>;

    // New additions
    $generateInsertStatement(
      model: Record<string, unknown>,
      tableName: string,
    ): { sql: string; params: unknown[] };
    $generateUpdateStatement(
      model: Record<string, unknown>,
      tableName: string,
      whereClause: string,
      whereParams?: unknown[],
    ): { sql: string; params: unknown[] };
    $mapQueryResultToValues(
      record: QueryExecResult | undefined,
    ): Array<Record<string, unknown>>;
    $mapColumnsToValues(
      columns: string[],
      values: unknown[][],
    ): Array<Record<string, unknown>>;

    // Contact export methods
    $saveContactExport(): Promise<string>;

    // Debug methods
    $debugDidSettings(did: string): Promise<Settings | null>;
    $debugMergedSettings(did: string): Promise<void>;
  }
}
