import { css, CSSResult, html, PropertyValues, TemplateResult } from "lit";
import { property, query, state } from "lit/decorators.js";
import { DataEntityKind, DataNamespace } from "@api-client/core/build/browser.js";
import ApiElement from "../ApiElement.js";
import theme from '../theme.js';
import schemaCommon from './schemaCommon.js';
import EditorCommon from './CommonStyles.js';
import MarkdownStyles from '../highlight/MarkdownStyles.js';
import typography from "../../styles/m3/typography.module.js";
import type DataSchemaDocument from "./DataSchemaDocument.js";
import type RadioElement from "../../ui/input/RadioElement.js";
import type CheckboxElement from "../../ui/input/CheckboxElement.js";
import '../../define/ui/ui-icon.js';
import '../../define/ui/ui-icon-button.js';
import '../../define/ui/ui-radio.js';
import '../../define/ui/ui-checkbox.js';
import '../../define/highlight/marked-highlight.js';
import '../../define/data/data-association-form.js';
import '../../define/data/data-property-form.js';
import '../../define/data/data-entity-form.js';
import '../../define/data/data-schema-document.js';

/**
 * The DataEntity editor UI and logic.
 * 
 * @fires change - When the entity has changed and the parent element / app should update the value in the store.
 * @fires namechange - When a name of the data entity has changed. This triggers a render in the navigation. This is fired after the `change` event.
 */
export default class DataEntityEditorElement extends ApiElement {
  static override get styles(): CSSResult[] {
    return [
      theme,
      schemaCommon,
      MarkdownStyles,
      EditorCommon,
      typography,
      css`
      :host {
        display: block;
        height: 100%;
      }

      .container {
        display: flex;
        flex-direction: row;
        height: inherit;
        flex: 1;
      }

      .structure {
        flex: 8;
        overflow: hidden;
      }

      .editor {
        flex: 4;
        background-color: var(--md-sys-color-surface);
        border-radius: var(--md-sys-shape-corner-small);
        box-shadow: var(--md-sys-elevation-1);
        overflow: hidden;
        display: flex;
        flex-direction: column;
        /* for the box shadow */
        margin: 4px; 
      }

      .editor-form {
        overflow: auto;
      }

      .mime-options {
        box-sizing: border-box;
        display: flex;
        align-items: center;
      }

      .schema-card {
        margin: 20px 20px 20px 0px;
      }
      `,
    ];
  }

  /**
   * The read data namespace.
   */
  @property({ type: Object }) root?: DataNamespace;

  /**
   * The selected entity.
   */
  @property({ type: String }) selected?: string;

  /**
   * The key of the selected property being edited.
   */
  @property({ type: String }) selectedProperty?: string;

  /**
   * The key of the selected association being edited.
   */
  @property({ type: String }) selectedAssociation?: string;

  @state() protected _highlightEntityDropZone?: boolean;

  @state() protected _schemaExample?: string | number | boolean | null;

  @state() protected _schemaMime: string;

  @state() protected _schemaMock: boolean;

  protected _schemaOpened?: boolean;

  @query('data-schema-document')
  protected _schemaDoc?: DataSchemaDocument;

  constructor() {
    super();
    this._schemaMime = 'application/json';
    this._schemaMock = true;
    this.addEventListener('dragenter', this._dragEnterHandler.bind(this));
    this.addEventListener('dragleave', this._dragLeaveHandler.bind(this));
  }

  protected override willUpdate(cp: PropertyValues<this>): void {
    super.willUpdate(cp);
    if (cp.has('selected')) {
      this._computeExampleSchemaIfNeeded();
    }
  }

  override notifyChange(): void {
    super.notifyChange();
    if (this._highlightEntityDropZone) {
      this._highlightEntityDropZone = false;
    }
  }

  protected _dragEnterHandler(e: DragEvent): void {
    const { dataTransfer: dt } = e;
    if (!dt) {
      return;
    }
    if (![...dt.types].includes(DataEntityKind.toLowerCase())) {
      return;
    }
    this._highlightEntityDropZone = true;
    this.selectedProperty = undefined;
    this.selectedAssociation = undefined;
  }

  protected _dragLeaveHandler(e: DragEvent): void {
    const { dataTransfer: dt } = e;
    if (!dt) {
      return;
    }
    if (![...dt.types].includes(DataEntityKind.toLowerCase())) {
      return;
    }
    this._highlightEntityDropZone = false;
  }

  protected _propertyChangeHandler(): void {
    this.notifyChange();
    const { _schemaDoc } = this;
    if (_schemaDoc) {
      _schemaDoc.requestUpdate();
    }
    this._computeExampleSchemaIfNeeded();
  }

  protected _entityChangeHandler(): void {
    this.notifyChange();
    const { _schemaDoc } = this;
    if (_schemaDoc) {
      _schemaDoc.requestUpdate();
    }
    this._computeExampleSchemaIfNeeded();
  }

  protected _schemaEntityChangeHandler(): void {
    this.notifyChange();
    this.requestUpdate();
    this._computeExampleSchemaIfNeeded();
  }

  protected _schemaEntityEditHandler(e: CustomEvent): void {
    const { type, key } = e.detail;
    if (type === 'property') {
      this.selectedProperty = key;
      this.selectedAssociation = undefined;
    } else if (type === 'association') {
      this.selectedProperty = undefined;
      this.selectedAssociation = key;
    }
    this._computeExampleSchemaIfNeeded();
  }

  protected _entityNameHandler(): void {
    // just retarget
    this.dispatchEvent(new Event('namechange'));
    this._computeExampleSchemaIfNeeded();
  }

  protected _cancelEditor(): void {
    this.selectedProperty = undefined;
    this.selectedAssociation = undefined;
  }

  protected _computeExampleSchema(): void {
    const { selected, root } = this;
    const entity = root?.definitions.entities.find(i => i.key === selected);
    if (!entity) {
      this._schemaExample = undefined;
      return
    }
    this._schemaExample = entity.toExample(this._schemaMime, {
      renderMocked: this._schemaMock,
      renderExamples: true,
      renderOptional: true,
    });
  }

  protected _computeExampleSchemaIfNeeded(): void {
    if (this._schemaOpened) {
      this._computeExampleSchema();
    }
  }

  protected _schemaOpenHandler(e: Event): void {
    const details = e.target as HTMLDetailsElement;
    this._schemaOpened = details.open;
    if (details.open) {
      this._computeExampleSchema();
    }
  }

  protected _schemaMimeHandler(e: Event): void {
    const button = e.target as RadioElement;
    if (!button.checked) {
      return;
    }
    this._schemaMime = button.value as string;
    this._computeExampleSchemaIfNeeded();
  }

  protected _mockSchemaValuesHandler(e: Event): void {
    const input = e.target as CheckboxElement;
    const { checked } = input;
    this._schemaMock = checked;
    this._computeExampleSchema();
  }

  protected override render(): TemplateResult {
    return html`
    <div class="container">
      ${this._structureTemplate()}
      ${this._editorTemplate()}
    </div>
    `;
  }

  protected _structureTemplate(): TemplateResult {
    return html`
    <div class="structure">
      <data-schema-document
        .root="${this.root}"
        .key="${this.selected}"
        .selectedProperty="${this.selectedProperty || this.selectedAssociation}"
        ?associationDropZone="${this._highlightEntityDropZone}"
        editable
        @change="${this._schemaEntityChangeHandler}"
        @edit="${this._schemaEntityEditHandler}"
      ></data-schema-document>
      ${this.renderSchema()}
    </div>
    `;
  }

  protected _editorTemplate(): TemplateResult {
    const { selectedProperty, selectedAssociation } = this;
    let content: TemplateResult;
    if (selectedProperty) {
      content = this._propertyEditorTemplate(selectedProperty);
    } else if (selectedAssociation) {
      content = this._associationEditorTemplate(selectedAssociation);
    } else {
      content = this.renderEntityEditor();
    }
    return html`
    <div class="editor">${content}</div>
    `;
  }

  protected _propertyEditorTemplate(id: string): TemplateResult {
    return html`
    <div class="editor-title title-large">
      Data Property
      <ui-icon-button class="title-icon" title="Close this editor" @click="${this._cancelEditor}">
        <ui-icon icon="close"></ui-icon>
      </ui-icon-button>
    </div>
    <data-property-form 
      .root="${this.root}" 
      .key="${id}" 
      @change="${this._propertyChangeHandler}"
      class="editor-form"
    ></data-property-form>
    `;
  }

  protected _associationEditorTemplate(id: string): TemplateResult {
    return html`
    <div class="editor-title title-large">
      Data Association
      <ui-icon-button class="title-icon" title="Close this editor" @click="${this._cancelEditor}">
        <ui-icon icon="close"></ui-icon>
      </ui-icon-button>
    </div>
    <data-association-form
      .root="${this.root}" 
      .key="${id}" 
      @change="${this._propertyChangeHandler}"
      class="editor-form"
    ></data-association-form>
    `;
  }

  protected renderEntityEditor(): TemplateResult {
    return html`
    <div class="editor-title title-large">Data Entity</div>
    <data-entity-form 
      .root="${this.root}" 
      .key="${this.selected}"
      ?parentDropZone="${this._highlightEntityDropZone}"
      @change="${this._entityChangeHandler}"
      @namechange="${this._entityNameHandler}"
      class="editor-form"
    ></data-entity-form>
    `;
  }

  protected renderSchema(): TemplateResult {
    return html`
    <details class="schema-card" @toggle="${this._schemaOpenHandler}">
      <summary class="title-medium">Example</summary>
      <div class="example-content">
        <div class="mime-options">
          <span id="mimeLabel" class="label-large">Mime type:</span>
          <label class="label-large">
            <ui-radio 
              name="mimeValue" 
              value="application/json" 
              .checked="${this._schemaMime === 'application/json'}" 
              @change="${this._schemaMimeHandler}"
              aria-describedby="mimeLabel"
            ></ui-radio>
            application/json
          </label>
          <label class="label-large">
            <ui-radio 
              name="mimeValue" 
              value="application/xml" 
              .checked="${this._schemaMime === 'application/xml'}" 
              @change="${this._schemaMimeHandler}"
              aria-describedby="mimeLabel"
            ></ui-radio>
            application/xml
          </label>
          <label class="label-large">
            <ui-checkbox
              .checked="${this._schemaMock}" 
              @change="${this._mockSchemaValuesHandler}" 
              title="Generates random values when examples are not defined."
            ></ui-checkbox>
            Mock values
          </label>
        </div>
        <pre class="code-value text-selectable"><code>${this._schemaExample}</code></pre>
      </div>
    </details>`;
  }
}
