import { html, css, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import FontStyles from "../styles/font-styles.js";
import SchemaStyles from "../styles/schema-styles.js";
import CustomStyles from "../styles/custom-styles.js";
import SchemaBaseElement from "./schema-base-element";

@customElement("schema-table")
export default class SchemaTable extends SchemaBaseElement {
  static get styles() {
    return [
      FontStyles,
      SchemaStyles,
      css`
        .table {
          font-size: var(--font-size-small);
          text-align: left;
          line-height: calc(var(--font-size-small) + 6px);

          & .table-container {
            border: 1px solid var(--border);
            /* Card surface, same as .param-list */
            background: color-mix(in srgb, var(--card) 80%, transparent);
            overflow-x: auto;
            max-width: 100%;
          }
          & .table-header {
            display: flex;
            background: var(--card);
            padding: 8px 4px;
            border-bottom: 1px solid var(--border);

            & .td {
              font-family: var(--font-sans);
              font-weight: bold;
              color: var(--foreground);
            }
          }
          & .tr {
            width: calc(100% - 5px);
            padding: 0 0 0 5px;
            border-bottom: 1px dotted var(--border);

            &.expanded {
              & .obj-toggle::after {
                content: '-';
              }
              & + .object-body {
                display: block;
              }
            }
            &.collapsed {
              & .obj-toggle::after {
                content: '+';
              }
              & + .object-body {
                display: none;
              }
            }
          }
          & .td {
            padding: 4px 0;
          }
          & .key {
            width: var(--table-schema-key-width);
            text-overflow: var(--table-schema-key-text-overflow);
            white-space: var(--table-schema-key-whitespace);
            overflow: hidden;

            & .key-label {
              font-size: var(--font-size-mono);
            }
            &.deprecated .key-label {
              color: var(--destructive);
            }
          }
          & .key-type {
            white-space: normal;
            width: 150px;
          }
          &.collapsed-all-descr .tr:not(.expanded-descr) {
            overflow: hidden;
            max-height: calc(var(--font-size-small) + 16px);
          }
        }

        .obj-toggle {
          background: transparent;
          border: 1px solid transparent;
          padding: 0;
          border-radius: 2px;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          margin-left: -16px;
          margin-top: -2px;
          color: var(--primary);
          cursor: pointer;
          font-size: 14px;
          font-family: var(--font-mono);
          line-height: 12px;
          width: 14px;
          height: 14px;
          min-width: 14px;
          text-align: center;
          user-select: none;
          box-sizing: border-box;
          vertical-align: middle;

          &:hover {
            border-color: var(--primary);
          }
          &:focus-visible {
            outline: 2px solid var(--primary);
            border-color: var(--primary);
          }
        }

        @supports (grid-template-columns: subgrid) {
          .table {
            & .table-container {
              display: grid;
              grid-template-columns: minmax(var(--table-schema-key-width), auto) minmax(150px, auto) 1fr;
            }
            & .table-header,
            & .tr {
              display: grid;
              grid-column: 1 / -1;
              grid-template-columns: subgrid;
              width: 100%;
              padding: 0 4px;
              box-sizing: border-box;

              & .key,
              & .key-type {
                width: auto;
              }
              & .key-descr {
                min-width: 0;
              }
            }
            & .object-body {
              display: contents;
            }
            & .tr.collapsed + .object-body {
              display: none;
            }
            & .tr.expanded + .object-body {
              display: contents;
            }
          }
        }

        /* Narrow schemas: the description spans the row, under the field and type, with no column of its own (#1074) */
        @container (width < 500px) {
          .table {
            /* no column for descriptions, and field and type share the width instead of scrolling sideways */
            & .table-container {
              grid-template-columns: minmax(0, 1fr) minmax(0, max-content) 0;
            }
            & .tr > .key-descr {
              grid-column: 1 / -1;
            }
            & .table-header > .key-descr {
              display: none;
            }
            &.collapsed-all-descr .tr:not(.expanded-descr) {
              max-height: calc(2 * (var(--font-size-small) + 16px));
            }
          }
        }
      `,
      CustomStyles,
    ];
  }

  render() {
    if (!this.data) {
      return "";
    }

    const rootType = this.data.kind === "primitive" ? this.data.type : this.data.kind || "";
    const rootDescription = this.data.description || "";

    return html`
      <div
        class="table ${this.schemaDescriptionExpanded === "true" ? "expanded-all-descr" : "collapsed-all-descr"}"
        @click="${(e) => this.handleAllEvents(e)}"
        @keydown="${(e) => this.handleKeyDown(e)}"
      >
        ${this.renderToolbar(rootType)}
        <span part="schema-description" class="m-markdown"> ${this.renderDescriptionHtml(rootDescription)} </span>
        <div class="table-container">
          <div class="tr table-header">
            <div class="td key">Field</div>
            <div class="td key-type">Type</div>
            <div class="td key-descr">Description</div>
          </div>
          ${this.renderAST(this.data)}
        </div>
      </div>
    `;
  }

  renderAST(node, parentType = "", schemaLevel = 0, indentLevel = 0) {
    if (this.isFieldHidden(node)) {
      return "";
    }

    const leftPadding = 16 * (indentLevel + 1);
    const isExpanded = schemaLevel < this.schemaExpandLevel;
    const nullClass = node.nullable ? "nullable" : "";
    const deprecatedIcon = this.renderDeprecatedIcon(node);
    const readWriteBadge = this.getReadWriteBadge(node);
    const readWriteTip = this.getReadWriteTip(node);

    // 1. Array kind
    if (node.kind === "array") {
      const items = node.items;
      if (!items) {
        const typeLabel = node.dataTypeLabel || (node.nullable ? "array ┃ null" : "array");
        return html`
          <div class="tr array ${nullClass}" data-obj="${node.name || ""}">
            <div class="td key" style="padding-left:${leftPadding}px">
              <span class="key-label" title="${node.name || nothing}">${deprecatedIcon}${node.name || ""}</span>
              ${node.required ? html`<span style="color:var(--destructive);">*</span>` : ""}
            </div>
            <div class="td key-type" title="${readWriteTip || nothing}">${typeLabel}${readWriteBadge}</div>
            ${this.renderDescrCell(node.description)}
          </div>
        `;
      }

      if (items.kind === "primitive") {
        const itemType = items.format || items.contentMediaType || items.type || "string";
        const dataTypeCss = this.getDataTypeCss(itemType);

        return html`
          <div class="tr primitive ${nullClass}" title="${node.deprecated || items.deprecated ? "Deprecated" : nothing}">
            <div class="td key ${node.deprecated || items.deprecated ? "deprecated" : ""}" style="padding-left:${leftPadding}px">
              ${deprecatedIcon}${node.name ? html`<span class="key-label" title="${node.name}">${node.name}</span>` : ""}
              ${node.required ? html`<span style="color:var(--destructive);">*</span>` : ""}
            </div>
            <div class="td key-type ${dataTypeCss}" title="${readWriteTip || nothing}">
              [${itemType}]${node.nullable ? " ┃ null" : ""}${readWriteBadge}
            </div>
            ${this.renderDescrCell(node.description || items.description, items)}
          </div>
        `;
      }

      let arrLabel = "array";
      if (items.kind === "object") {
        arrLabel = "array of object";
      } else if (items.kind === "array") {
        arrLabel = `array of array ${node.arrayType ? `of ${node.arrayType}` : ""}`.trim();
      } else if (items.kind === "union") {
        arrLabel = "array of union";
      }
      if (node.nullable) {
        arrLabel += " ┃ null";
      }

      const hasChildren =
        items.kind === "object"
          ? !!(
              (items.properties && items.properties.length > 0) ||
              (items.patternProperties && items.patternProperties.length > 0) ||
              items.additionalProperties ||
              (items.unions && items.unions.length > 0)
            )
          : true;

      return html`
        <div
          class="tr ${isExpanded ? "expanded" : "collapsed"} array ${nullClass}"
          data-obj="${node.name || ""}"
          title="${node.deprecated ? `Deprecated ${node.name}` : node.name || nothing}"
        >
          <div class="td key ${node.deprecated ? "deprecated" : ""}" style="padding-left:${leftPadding}px">
            ${
              node.name && hasChildren
                ? html`<button
                    type="button"
                    class="obj-toggle"
                    data-obj="${node.name}"
                    aria-label="Toggle ${node.name}"
                    aria-expanded="${isExpanded ? "true" : "false"}"
                  ></button>`
                : ""
            }
            <span class="key-label" title="${node.name || nothing}" style="display:inline-block; ${hasChildren ? "margin-left:-6px;" : ""}">
              ${deprecatedIcon}${node.name || ""}
            </span>
            ${node.required ? html`<span style="color:var(--destructive);">*</span>` : ""}
          </div>
          <div class="td key-type" title="${readWriteTip || nothing}">${arrLabel}${readWriteBadge}</div>
          ${this.renderDescrCell(node.description || items.description)}
        </div>
        ${
          hasChildren
            ? html`
                <div class="object-body">
                  ${
                    items.kind === "object"
                      ? html`
                          ${items.properties?.map((p) => this.renderAST(p, "object", schemaLevel + 1, indentLevel + 1))}
                          ${items.patternProperties?.map((p) => this.renderAST(p, "object", schemaLevel + 1, indentLevel + 1))}
                          ${items.additionalProperties ? this.renderAST(items.additionalProperties, "object", schemaLevel + 1, indentLevel + 1) : ""}
                          ${items.unions?.map((u) => this.renderAST(u, "object", schemaLevel + 1, indentLevel + 1))}
                        `
                      : this.renderAST(items, "array", schemaLevel + 1, indentLevel + 1)
                  }
                </div>
              `
            : ""
        }
      `;
    }

    // 2. Object kind
    if (node.kind === "object") {
      const typeLabel = node.dataTypeLabel || (node.nullable ? "object ┃ null" : "object");

      const hasChildren = !!(
        (node.properties && node.properties.length > 0) ||
        (node.patternProperties && node.patternProperties.length > 0) ||
        node.additionalProperties ||
        (node.unions && node.unions.length > 0)
      );

      return html`
        ${
          node.name
            ? html`
                <div
                  class="tr ${isExpanded ? "expanded" : "collapsed"} object ${nullClass}"
                  data-obj="${node.name}"
                  title="${node.deprecated ? `Deprecated ${node.name}` : node.name}"
                >
                  <div class="td key ${node.deprecated ? "deprecated" : ""}" style="padding-left:${leftPadding}px">
                    ${
                      hasChildren
                        ? html`<button
                            type="button"
                            class="obj-toggle"
                            data-obj="${node.name}"
                            aria-label="Toggle ${node.name}"
                            aria-expanded="${isExpanded ? "true" : "false"}"
                          ></button>`
                        : ""
                    }
                    <span
                      class="key-label"
                      title="${node.name || nothing}"
                      style="display:inline-block; ${hasChildren ? "margin-left:-6px;" : ""}"
                    >
                      ${deprecatedIcon}${node.name}
                    </span>
                    ${node.required ? html`<span style="color:var(--destructive);">*</span>` : ""}
                  </div>
                  <div class="td key-type" title="${readWriteTip || nothing}">${typeLabel}${readWriteBadge}</div>
                  ${this.renderDescrCell(node.description)}
                </div>
              `
            : ""
        }
        ${
          hasChildren
            ? html`
                <div class="object-body">
                  ${node.properties?.map((p) => this.renderAST(p, "object", schemaLevel + 1, indentLevel + (node.name ? 1 : 0)))}
                  ${node.patternProperties?.map((p) => this.renderAST(p, "object", schemaLevel + 1, indentLevel + (node.name ? 1 : 0)))}
                  ${node.additionalProperties ? this.renderAST(node.additionalProperties, "object", schemaLevel + 1, indentLevel + (node.name ? 1 : 0)) : ""}
                  ${node.unions?.map((u) => this.renderAST(u, "object", schemaLevel + 1, indentLevel + (node.name ? 1 : 0)))}
                </div>
              `
            : ""
        }
      `;
    }

    // 3. Union kind (oneOf / anyOf)
    if (node.kind === "union") {
      const opLabel =
        (node.operator === "anyOf" ? "ANY OF" : "ONE OF") + (node.suffix ? ` ${node.suffix}` : "");
      // Options are already labelled by their OPT row, so only property names are shown
      const showName = node.name && parentType !== "xxx-of-option";

      return html`
        ${node.properties?.map((p) => this.renderAST(p, "object", schemaLevel, indentLevel))}
        <div class="tr expanded xxx-of" data-obj="${showName ? node.name : opLabel}">
          ${
            showName
              ? html`
                  <div class="td key" style="padding-left:${leftPadding}px">
                    <span class="key-label" title="${node.name}">${node.name}</span>
                    ${node.required ? html`<span style="color:var(--destructive);">*</span>` : ""}
                  </div>
                  <div class="td key-type"><span class="xxx-of-key">${opLabel}</span></div>
                `
              : html`
                  <div class="td key" style="padding-left:${leftPadding}px">
                    <span class="xxx-of-key" style="margin-left:-6px">${opLabel}</span>
                  </div>
                  <div class="td key-type"></div>
                `
          }
          ${this.renderDescrCell(node.description)}
        </div>
        <div class="object-body">
          ${node.options?.map((opt, i) =>
            opt.kind === "primitive"
              ? // A primitive option fits on a single row: the badge, then its type and description
                html`<div class="object-body">
                  ${this.renderAST(opt, "xxx-of-option", schemaLevel + 1, indentLevel + 1)}
                </div>`
              : html`
                  <div class="tr expanded xxx-of-option" data-obj="OPTION ${opt.optionIndex || i + 1}">
                    <div class="td key" style="padding-left:${leftPadding + 16}px">
                      <span class="xxx-of-key">OPT ${opt.optionIndex || i + 1}</span>
                      ${
                        opt.optionTitle
                          ? html`<span class="xxx-of-descr">${opt.optionTitle}</span>`
                          : ""
                      }
                    </div>
                    <div class="td key-type"></div>
                    <div class="td key-descr"></div>
                  </div>
                  <div class="object-body">
                    ${this.renderAST(opt, "xxx-of-option", schemaLevel + 1, indentLevel + 1)}
                  </div>
                `,
          )}
        </div>
      `;
    }

    // 4. Primitive kind
    if (node.kind === "primitive") {
      const dataTypeCss = this.getDataTypeCss(node.type);
      // Primitive option of a oneOf / anyOf: the OPT n badge (and the title) takes the place of the name
      const isOption = parentType === "xxx-of-option" && !!node.optionIndex;

      return html`
        <div class="tr primitive ${nullClass}" title="${node.deprecated ? "Deprecated" : nothing}">
          <div class="td key ${node.deprecated ? "deprecated" : ""}" style="padding-left:${leftPadding}px">
            ${
              isOption
                ? html`<span class="xxx-of-key">OPT ${node.optionIndex}</span
                  >${
                    node.optionTitle
                      ? html`<span class="xxx-of-descr">${node.optionTitle}</span>`
                      : ""
                  }`
                : ""
            }${deprecatedIcon}${
              node.name && !isOption
                ? html`<span class="key-label" title="${node.name}">${node.name}</span>`
                : ""
            }
            ${node.required ? html`<span style="color:var(--destructive);">*</span>` : ""}
          </div>
          <div class="td key-type ${dataTypeCss}" title="${readWriteTip || nothing}">
            ${parentType === "array" ? `[${node.type}]` : node.type}${readWriteBadge}
          </div>
          ${this.renderDescrCell(node.description, node, node.title)}
        </div>
      `;
    }

    return "";
  }
}
