import { BehaviorSubject, PartialObserver, Unsubscribable } from 'rxjs';
import * as cdecode from 'cdecode/client';
import { ClientThreadClientConfigurationShape, WebExtHostClientConfigurationShape } from '@workbench-stack/extension-api';
import { IConfiguredSettingsCascade } from '@workbench-stack/core';

/**
 * @internal
 * @template C - The configuration schema.
 */
class WebExtHostConfigurationSection<C extends object> implements cdecode.Configuration<C> {
    constructor(private proxy: ClientThreadClientConfigurationShape, private data: C) {}

    public get<K extends keyof C>(key: K): C[K] | undefined {
        return this.data[key];
    }

    public async update<K extends keyof C>(key: K, value: C[K] | undefined): Promise<void> {
        // Cast `key` to `string | number` (i.e., eliminate `symbol`). We could use `Extract<keyof
        // C, string | number` in the cdecode.d.ts type signature, but that would add useless
        // complexity.
        await this.proxy.$acceptConfigurationUpdate({ path: [key as string | number], value});
    }

    public get value(): Readonly<C> {
        return this.data;
    }

    public toJSON(): any {
        return this.data;
    }
}


/**
 * @internal
 * @template C - The configuration schema.
 */
export class WebExtHostConfiguration<C extends object> implements WebExtHostClientConfigurationShape<C> {

    /**
     * The settings data observable, assigned when the initial data is received from the client. Extensions should
     * never be able to call {@link WebExtHostClientConfigurationShape}'s methods before the initial data is received.
     */
    private data?: BehaviorSubject<Readonly<IConfiguredSettingsCascade<C>>>;

    constructor(private proxy: ClientThreadClientConfigurationShape) {}

    public async $acceptConfigurationData(data: Readonly<IConfiguredSettingsCascade<C>>): Promise<void> {
        if (!this.data) {
            this.data = new BehaviorSubject(data);
        } else {
            this.data.next(Object.freeze(data));
        }
    }

    private getData(): BehaviorSubject<Readonly<IConfiguredSettingsCascade<C>>> {
        if (!this.data) {
            throw new Error('unexpected internal error: settings data is not yet available');
        }
        return this.data;
    }

    public get(): cdecode.Configuration<C> {
        return Object.freeze(new WebExtHostConfigurationSection<C>(this.proxy, this.getData().value.final));
    }

    public subscribe(observer?: PartialObserver<C>): Unsubscribable;
    public subscribe(next?: (value: C) => void, error?: (error: any) => void, complete?: () => void): Unsubscribable;
    public subscribe(...args: any[]): cdecode.Unsubscribable {
        return this.getData().subscribe(...args);
    }
}
