import { html, TemplateResult, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { AmfNamespace, AmfShapes, IApiCustomDomainProperty, IApiDomainProperty } from '@api-client/core/build/browser.js';
import ApiElement from '../ApiElement.js';
import '../../define/ui/ui-icon.js';

/**
 * An element to render annotations (also known as custom properties)
 * from AMF model.
 *
 * Annotations are part of RAML language and API console supports it.
 * The element looks for annotations in model and renders them.
 */
export default class ApiAnnotationDocument extends ApiElement {
  protected shapeValue: IApiDomainProperty | undefined;

  /**
   * Serialized with `ApiSerializer` API domain model.
   * This is to be used instead of `shape`.
   */
  @property({ type: Object })
  get domainModel(): IApiDomainProperty | undefined {
    return this.shapeValue;
  }

  set domainModel(value: IApiDomainProperty | undefined) {
    const oldValue = this.shapeValue;
    if (oldValue === value) {
      return;
    }
    this.shapeValue = value;
    this.processShape();
  }

  protected propertiesValue: IApiCustomDomainProperty[] | undefined;

  /**
   * @returns `true` if any custom property has been found.
   */
  get hasCustomProperties(): boolean {
    const properties = this.propertiesValue;
    return Array.isArray(properties) && !!properties.length;
  }

  /**
   * List of custom properties in the shape.
   */
  @property({ type: Array })
  get customProperties(): IApiCustomDomainProperty[] | undefined {
    return this.propertiesValue;
  }


  set customProperties(value: IApiCustomDomainProperty[] | undefined) {
    const old = this.propertiesValue;
    if (old === value) {
      return;
    }
    this.propertiesValue = value;
    this.processVisibility();
    this.requestUpdate();
  }

  /**
   * Called when the shape property change.
   */
  protected processShape(): void {
    const shape = this.shapeValue;
    this.propertiesValue = undefined;
    if (!shape) {
      return;
    }
    const result = shape.customDomainProperties;
    if (Array.isArray(result) && result.length) {
      this.propertiesValue = result;
    }
    this.processVisibility();
    this.requestUpdate();
  }

  protected processVisibility(): void {
    const { hasCustomProperties } = this;
    if (hasCustomProperties) {
      this.setAttribute('aria-hidden', 'false');
      this.removeAttribute('hidden');
    } else {
      this.setAttribute('aria-hidden', 'true');
      this.setAttribute('hidden', 'true');
    }
  }

  protected scalarValue(scalar: AmfShapes.IApiScalarNode): string | number | boolean {
    let { value = '' } = scalar;
    if (value === 'nil') {
      value = '';
    }
    return value;
  }

  override render(): TemplateResult | string {
    const { hasCustomProperties, customProperties } = this;
    if (!hasCustomProperties || !customProperties) {
      return '';
    }
    const content = customProperties.map((prop) => this.renderProperty(prop));
    return html`
    ${content}
    `;
  }

  /**
   * @returns The template for the custom property.
   */
  protected renderProperty(prop: IApiCustomDomainProperty): TemplateResult | typeof nothing {
    const { name, extension } = prop;
    const { types } = extension;
    if (types.includes(AmfNamespace.aml.vocabularies.data.Scalar)) {
      return this.renderScalar(name, extension as AmfShapes.IApiScalarNode);
    }
    if (types.includes(AmfNamespace.aml.vocabularies.data.Object)) {
      return this.renderObject(name, extension as AmfShapes.IApiObjectNode);
    }
    return nothing;
  }

  /**
   * @param name The annotation name
   * @param content The content tp render.
   * @returns The template for the custom property.
   */
  protected renderAnnotationWrapper(name: string, content: TemplateResult | (TemplateResult | string)[] | string): TemplateResult {
    return html`
    <div class="custom-property">
      <ui-icon class="info-icon" icon="info"></ui-icon>
      <div class="info-value">
        <span class="name text-selectable">${name}</span>
        ${content || ''}
      </div>
    </div>
    `;
  }

  /**
   * @returns The template for the custom property.
   */
  protected renderScalar(name: string, scalar: AmfShapes.IApiScalarNode): TemplateResult {
    const content = html`<span class="scalar-value text-selectable">${this.scalarValue(scalar)}</span>`;
    return this.renderAnnotationWrapper(name, content);
  }

  /**
   * @returns The template for the custom property.
   */
  protected renderObject(name: string, object: AmfShapes.IApiObjectNode): TemplateResult {
    const { properties = {} } = object;
    const content = Object.keys(properties).map((key) => {
      const value = properties[key];
      const { types } = value;
      if (types.includes(AmfNamespace.aml.vocabularies.data.Scalar)) {
        return this.renderObjectScalarProperty(key, value);
      }
      return key;
    });
    return this.renderAnnotationWrapper(name, content);
  }

  /**
   * @returns The template for the custom property.
   */
  protected renderObjectScalarProperty(name: string, scalar: AmfShapes.IApiScalarNode): TemplateResult {
    const value = this.scalarValue(scalar);
    return html`
    <div class="object-property">
      <span class="object-name text-selectable">${name}</span>
      <span class="object-value text-selectable">${value}</span>
    </div>
    `;
  }
}
