import { type TemplateResult, noChange } from 'lit';
import type { DirectiveResult } from 'lit/directive.js';
import { AsyncDirective, directive } from 'lit/async-directive.js';
import { rxjs } from '@push.rocks/smartrx';

/**
 * Subscribes to an observable and applies a template function to each emission.
 * @param observable - the source Observable
 * @param templateFn - function mapping each emitted value to a TemplateResult or other renderable content
 */
class SubscribeWithTemplateDirective extends AsyncDirective {
  private observable?: rxjs.Observable<unknown>;
  private templateFn?: (value: unknown) => TemplateResult | unknown;
  private sub: rxjs.Subscription | null = null;

  render(
    observable: rxjs.Observable<unknown>,
    templateFn: (value: unknown) => TemplateResult | unknown
  ) {
    const changed = this.observable !== observable || this.templateFn !== templateFn;
    if (changed) {
      this.sub?.unsubscribe();
      this.observable = observable;
      this.templateFn = templateFn;
      if (this.isConnected) {
        this.startSubscription();
      }
    }
    return noChange;
  }

  private startSubscription() {
    this.sub = this.observable!.subscribe((v: unknown) => {
      const out = this.templateFn!(v);
      this.setValue(out);
    });
  }

  disconnected() {
    this.sub?.unsubscribe();
  }

  reconnected() {
    this.startSubscription();
  }
}

/**
 * Directive that renders templates for each emission of an Observable.
 * Usage: html`${subscribeWithTemplate(myObservable, v => html`<span>${v}</span>`)}`
 */
/**
 * Typed directive function signature: returns a Lit DirectiveResult.
 */
type SubscribeWithTemplateFn = <T>(
  observable: rxjs.Observable<T>,
  templateFn: (value: T) => TemplateResult | unknown
) => DirectiveResult;
export const subscribeWithTemplate = directive(
  SubscribeWithTemplateDirective
) as SubscribeWithTemplateFn;