import { get, set, del } from 'idb-keyval'
import {
  ConfigurationBindings,
  type LocalConfiguration,
  type SessionConfiguration,
  type TelemetryConfiguration,
  TelemetryKey,
} from '../base/ConfigurationBindings.js'
import { ConfigBroadcast, ConfigBroadcastChannel } from '../../lib/config/Config.js'
import type { TelemetryConfig } from '@api-client/core/config/index.js'

/**
 * The background worker supporting application configuration.
 * It stores user local and session configuration, telemetry configuration,
 * and, in the future, any other data stored by the application locally.
 *
 * Data that are stored on the server (through the API) should not be stored here.
 *
 * The logic uses a `BroadcastChannel` to populate the changes across opened tabs and contexts.
 */
export class WebConfigurationBindings extends ConfigurationBindings {
  broadcast = new BroadcastChannel(ConfigBroadcastChannel)

  protected override sendIoCommand(): void {
    //
  }

  local: LocalConfiguration = {
    /**
     * Reads a previously stored local value.
     *
     * @param key The key under which the property was stored.
     * @returns The configuration value or undefined when not found.
     */
    get: async (key: string): Promise<unknown | undefined> => {
      const value = await get(key)
      if (value) {
        return value.value
      }
      return undefined
    },

    /**
     * Sets a config property that is permanently stored.
     *
     * @param key The key under to store the value.
     * @param value The value to store. If this is not a primitive it will be serialized with `JSON.stringify()`.
     */
    set: async (key: string, value: unknown): Promise<void> => {
      const storedValue = {
        type: typeof value,
        value,
      }
      await set(key, storedValue)
      this.broadcast.postMessage({
        path: 'local.set',
        key,
        value,
      } as ConfigBroadcast)
    },

    /**
     * Deletes previously stored local value.
     *
     * @param key The key under which the property was stored.
     */
    delete: async (key: string): Promise<void> => {
      await del(key)
      this.broadcast.postMessage({
        path: 'local.delete',
        key,
      } as ConfigBroadcast)
    },
  }

  session: SessionConfiguration = {
    get: async (key: string): Promise<unknown> => {
      const data = sessionStorage.getItem(key)
      if (data === null) {
        return undefined
      }
      let restored: unknown | undefined
      try {
        const parsed = JSON.parse(data)
        restored = parsed.value
      } catch {
        // ...
      }
      return restored
    },

    set: async (key: string, value: unknown): Promise<void> => {
      const storedValue = {
        type: typeof value,
        value,
      }
      sessionStorage.setItem(key, JSON.stringify(storedValue))
      this.broadcast.postMessage({
        path: 'session.set',
        key,
        value,
      } as ConfigBroadcast)
    },

    delete: async (key: string): Promise<void> => {
      sessionStorage.removeItem(key)
      this.broadcast.postMessage({
        path: 'session.delete',
        key,
      } as ConfigBroadcast)
    },
  }

  telemetry: TelemetryConfiguration = {
    read: async (): Promise<TelemetryConfig> => {
      let data = (await get(TelemetryKey)) as TelemetryConfig
      if (!data) {
        data = {
          level: 'noting',
        }
      }
      return data
    },

    set: async (config: TelemetryConfig): Promise<void> => {
      await set(TelemetryKey, config)
      this.broadcast.postMessage({
        path: 'telemetry.set',
        value: config,
      } as ConfigBroadcast)
    },
  }
}
