/**
 * @license
 * Copyright 2022 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import {css, html, LitElement, TemplateResult} from 'lit';
import {customElement, property, query} from 'lit/decorators.js';
import {DependencyToken, provide} from './dependency';

/**
 * Example usage:
 *
 *   const providerElement = await fixture<DIProviderElement>(
 *     wrapInProvider(
 *       html`<my-element-to-test></my-element-to-test>`,
 *       myModelToken,
 *       myModel,
 *     )
 *   );
 *   const element = providerElement.element as MyElementToTest;
 *
 * For injecting multiple tokens, make nested calls to `wrapInProvider` such as:
 *
 *   wrapInProvider(wrapInProvider(html`...`, token1, value1), token2, value2);
 */
export function wrapInProvider<T>(
  template: TemplateResult,
  token: DependencyToken<T>,
  value: T
) {
  return html`
    <di-provider-element .token=${token} .value=${value}
      >${template}</di-provider-element
    >
  `;
}

/**
 * Use `.element` to get the wrapped element for assertions.
 */
@customElement('di-provider-element')
export class DIProviderElement extends LitElement {
  @property({type: Object})
  token?: DependencyToken<unknown>;

  @property({type: Object})
  value?: unknown;

  get element() {
    return this.slotElement.assignedElements()[0];
  }

  private isProvided = false;

  @query('slot')
  private slotElement!: HTMLSlotElement;

  static override get styles() {
    return css`
      :host() {
        display: contents;
      }
    `;
  }

  /** Only calls `provide` even after reconnection. */
  override connectedCallback(): void {
    super.connectedCallback();
    if (!this.token || this.isProvided) return;
    this.isProvided = true;
    provide(this, this.token, () => this.value);
  }

  override render() {
    return html`<slot></slot>`;
  }
}
