import { __decorate } from "tslib";
import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { AmfNamespace, SecurityProcessor } from '@api-client/core/build/browser.js';
import { ApiDocumentationBase } from './ApiDocumentationBase.js';
import { joinTraitNames } from '../../amf/lib/Utils.js';
import * as UrlLib from '../../amf/lib/UrlUtils.js';
import { ReportingEvents } from '../../events/amf/ReportingEvents.js';
import { TelemetryEvents } from '../../events/amf/TelemetryEvents.js';
import { EndpointEvents } from '../../events/amf/EndpointEvents.js';
import { ServerEvents } from '../../events/amf/ServerEvents.js';
import { ApiEvents } from '../../events/amf/ApiEvents.js';
import '../../define/amf/api-request.js';
import '../../define/amf/api-operation-document.js';
import '../../define/amf/api-parameter-document.js';
import '../../define/http/http-snippets.js';
/**
 * A web component that renders the resource documentation page for an API resource built from
 * the AMF graph model.
 *
 * @fires tryit
 */
export default class ApiResourceDocument extends ApiDocumentationBase {
    get snippets() {
        return { ...this.snippetsInternal };
    }
    get endpoint() {
        return this.endpointValue;
    }
    set endpoint(value) {
        const old = this.endpointValue;
        if (old === value) {
            return;
        }
        this.endpointValue = value;
        this.processGraph();
    }
    get operationId() {
        return this.operationIdValue;
    }
    /**
     * When set it scrolls to the operation with the given id, if exists.
     * The operation is performed after render.
     * @attribute
     */
    set operationId(value) {
        const old = this.operationIdValue;
        if (old === value) {
            return;
        }
        this.operationIdValue = value;
        this.requestUpdate('operationId', old);
        this.operationIdChanged();
    }
    get serverId() {
        return this.serverIdValue;
    }
    /**
     * The id of the currently selected server to use to construct the URL.
     * If not set a first server in the API servers array is used.
     * @attribute
     */
    set serverId(value) {
        const old = this.serverIdValue;
        if (old === value) {
            return;
        }
        this.serverIdValue = value;
        this.selectServer();
        this.processServerSelection();
        this.requestUpdate();
    }
    get server() {
        if (this.serverInternal) {
            return this.serverInternal;
        }
        const servers = this.serversValue;
        if (Array.isArray(servers) && servers.length) {
            const [server] = servers;
            if (server) {
                this.serverInternal = server;
            }
        }
        return this.serverInternal;
    }
    set server(value) {
        const old = this.serverInternal;
        if (old === value) {
            return;
        }
        this.serverInternal = value;
        this.processServerSelection();
        this.requestUpdate('server', old);
    }
    /**
     * The list of the servers read from the API and the endpoint.
     */
    get servers() {
        return this.serversValue;
    }
    /**
     * The list of protocols to render.
     */
    get protocol() {
        const { server } = this;
        if (!server) {
            return undefined;
        }
        const { protocol } = server;
        return protocol;
    }
    /**
     * A property to set to override AMF's model base URI information.
     * When this property is set, the `endpointUri` property is recalculated.
     * @attribute
     */
    get baseUri() {
        return this.baseUriValue;
    }
    set baseUri(value) {
        const old = this.baseUriValue;
        if (old === value) {
            return;
        }
        this.baseUriValue = value;
        this.computeUrlValue();
        this.requestUpdate();
    }
    /**
     * The computed URI for the endpoint.
     */
    get endpointUri() {
        return this.urlValue;
    }
    /**
     * The API's protocols.
     */
    get protocols() {
        return this.protocolsValue;
    }
    constructor() {
        super();
        this.snippetsInternal = {};
    }
    disconnectedCallback() {
        if (this.processSelectionTimeout) {
            clearTimeout(this.processSelectionTimeout);
            this.processSelectionTimeout = undefined;
        }
        super.disconnectedCallback();
    }
    /**
     * Scrolls the view to the operation, when present in the DOM.
     * @param id The operation domain id to scroll into.
     */
    scrollToOperation(id) {
        const { shadowRoot } = this;
        if (!shadowRoot) {
            return;
        }
        const elm = shadowRoot.querySelector(`api-operation-document[data-domain-id="${id}"]`);
        if (!elm) {
            return;
        }
        elm.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth' });
    }
    async processGraph() {
        await this.queryEndpoint();
        await this.queryServers();
        await this.queryProtocols();
        this.computeUrlValue();
        this.requestUpdate();
        await this.updateComplete;
        if (this.processSelectionTimeout) {
            clearTimeout(this.processSelectionTimeout);
            this.processSelectionTimeout = undefined;
        }
        // this timeout gives few milliseconds for operations to render.
        this.processSelectionTimeout = setTimeout(() => {
            this.processSelectionTimeout = undefined;
            this.collectCodeSnippets();
            if (this.operationId) {
                this.scrollToOperation(this.operationId);
            }
        }, 200);
    }
    /**
     * Queries the API store for the API summary object.
     */
    async queryProtocols() {
        this.protocolsValue = undefined;
        try {
            const info = await ApiEvents.protocols(this);
            this.protocolsValue = info;
        }
        catch (e) {
            const ex = e;
            TelemetryEvents.exception(this, ex.message, false);
            ReportingEvents.error(this, ex, `Unable to query for API protocols list: ${ex.message}`, this.localName);
        }
    }
    /**
     * Queries the store for the endpoint data.
     * @returns {Promise<void>}
     */
    async queryEndpoint() {
        const { domainId } = this;
        if (!domainId) {
            // this.endpointValue = undefined;
            return;
        }
        if (this.endpointValue && this.endpointValue.id === domainId) {
            // in case the endpoint model was provided via property setter.
            return;
        }
        try {
            const info = await EndpointEvents.get(this, domainId);
            this.endpointValue = info;
        }
        catch (e) {
            const ex = e;
            TelemetryEvents.exception(this, ex.message, false);
            ReportingEvents.error(this, ex, `Unable to query for API endpoint data: ${ex.message}`, this.localName);
        }
    }
    /**
     * Scrolls to the selected operation after view update.
     */
    async operationIdChanged() {
        await this.updateComplete;
        const { operationId } = this;
        if (operationId) {
            this.scrollToOperation(operationId);
        }
        else {
            this.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth' });
        }
    }
    /**
     * Queries the store for the current servers value.
     */
    async queryServers() {
        this.serversValue = undefined;
        const { domainId } = this;
        const endpointId = this.endpointValue && this.endpointValue.id;
        try {
            const info = await ServerEvents.query(this, {
                endpointId,
                methodId: domainId,
            });
            this.serversValue = info;
        }
        catch (e) {
            const ex = e;
            TelemetryEvents.exception(this, ex.message, false);
            ReportingEvents.error(this, ex, `Unable to query for API servers: ${ex.message}`, this.localName);
        }
    }
    /**
     * Sets the private server value for the current server defined by `serverId`.
     * Calls the `.processServerSelection()` function to set server related values.
     */
    selectServer() {
        this.serverInternal = undefined;
        const { serverId } = this;
        const servers = this.serversValue;
        if (!serverId || !Array.isArray(servers)) {
            return;
        }
        this.serverInternal = servers.find(s => s.id === serverId);
    }
    /**
     * Performs actions after a server is selected.
     */
    processServerSelection() {
        this.computeUrlValue();
    }
    /**
     * Computes the URL value for the current serves, selected server, and endpoint's path.
     */
    computeUrlValue() {
        const endpoint = this.endpointValue;
        const { baseUri, server, protocols } = this;
        const url = UrlLib.computeEndpointUri({ baseUri, server, endpoint, protocols, });
        this.urlValue = url;
    }
    /**
     * Runs over each request editor and collects request values for code snippets generators.
     */
    collectCodeSnippets() {
        const { shadowRoot } = this;
        if (!shadowRoot) {
            return;
        }
        const panels = shadowRoot.querySelectorAll('api-request');
        if (!panels.length) {
            return;
        }
        Array.from(panels).forEach((panel) => this.readCodeSnippets(panel));
        this.requestUpdate();
    }
    requestChangeHandler(e) {
        const panel = e.target;
        this.readCodeSnippets(panel);
        this.requestUpdate();
    }
    /**
     * Reads the request data from the request panel for the code snippets.
     */
    async readCodeSnippets(panel) {
        const { requestId } = panel.dataset;
        if (!requestId) {
            return;
        }
        if (!this.snippetsInternal[requestId]) {
            this.snippetsInternal[requestId] = { reading: false };
        }
        const info = this.snippetsInternal[requestId];
        if (info.reading) {
            return;
        }
        try {
            info.reading = true;
            const request = await panel.serialize();
            if (request.authorization && request.authorization.length) {
                SecurityProcessor.applyAuthorization(request.expects, request.authorization);
            }
            info.value = request;
            this.requestUpdate();
        }
        catch (e) {
            // what can go wrong it that the request is not yet set on the editor
            // due to the debouncer and we are trying to serialize a request
            // that is not yet ready. This will redo the operation when the request panel 
            // render the HTTP editor.
            // ...
        }
        finally {
            info.reading = false;
        }
    }
    render() {
        const { endpoint } = this;
        if (!endpoint) {
            return html ``;
        }
        return html `
    ${this.renderTitle(endpoint)}
    ${this.renderUrl()}
    ${this.renderExtensions(endpoint)}
    ${this.descriptionTemplate(endpoint.description)}
    ${this.customDomainPropertiesTemplate(endpoint.customDomainProperties)}
    ${this.renderOperations(endpoint)}
    `;
    }
    /**
     * @returns The template for the Operation title.
     */
    renderTitle(endpoint) {
        const { name, path } = endpoint;
        const label = name || path;
        if (!label) {
            return '';
        }
        const subLabel = this.asyncApi ? 'API channel' : 'API endpoint';
        return html `
    <div class="endpoint-header">
      <div class="endpoint-title">
        <span class="label text-selectable">${label}</span>
      </div>
      <p class="sub-header">${subLabel}</p>
    </div>
    `;
    }
    /**
     * @returns The template for the operation's URL.
     */
    renderUrl() {
        const url = this.urlValue;
        return html `
    <div class="endpoint-url">
      <div class="url-value text-selectable">${url}</div>
    </div>
    `;
    }
    /**
     * @returns The template for the list of operations.
     */
    renderOperations(endpoint) {
        const { operations } = endpoint;
        if (!operations.length) {
            return '';
        }
        return html `
    <div class="operations">
      ${operations.map((operation) => this.renderOperation(endpoint, operation))}
    </div>
    `;
    }
    /**
     * @param operation The operation to render.
     * @returns The template for the API operation.
     */
    renderOperation(endpoint, operation) {
        const { serverId, baseUri, tryItPanel, tryItButton, asyncApi } = this;
        const renderTryIt = !tryItPanel && !asyncApi && !!tryItButton;
        const classes = {
            'operation-container': true,
            surface1: true,
            tryit: !!tryItPanel,
        };
        return html `
    <div class="${classMap(classes)}">
      <api-operation-document
        .domainId="${operation.id}"
        .operation="${operation}"
        .endpoint="${endpoint}"
        .endpointId="${endpoint.id}"
        .serverId="${serverId}" 
        .baseUri="${baseUri}" 
        data-domain-id="${operation.id}"
        ?tryItButton="${renderTryIt}"
        showSecurity
        ?renderCodeSnippets="${!tryItPanel}"
        ?asyncApi="${this.asyncApi}"
        class="operation"
      ></api-operation-document>
      ${tryItPanel ? this.renderTryItColumn(operation) : ''}
    </div>
    `;
    }
    /**
     * @param operation The operation to render.
     * @returns The template for the try it column panel rendered next to the operation documentation/
     */
    renderTryItColumn(operation) {
        if (this.asyncApi) {
            return '';
        }
        return html `
    <div class="try-it-column">
      <!-- <div class="sticky-content"> -->
        ${this.renderHttpRequest(operation)}
        ${this.renderCodeSnippetsPanel(operation)}
      <!-- </div> -->
    </div>
    `;
    }
    /**
     * @param operation The operation to render.
     * @returns The template for the request editor.
     */
    renderHttpRequest(operation) {
        const content = html `
    <api-request
      .domainId="${operation.id}"
      .serverValue="${this.serverValue}"
      .serverType="${this.serverType}"
      .baseUri="${this.baseUri}"
      .redirectUri="${this.redirectUri}"
      .credentialsSource="${this.httpCredentialsSource}"
      ?urlEditor="${this.httpUrlEditor}"
      ?urlLabel="${!this.httpUrlEditor}"
      ?noServerSelector="${this.httpNoServerSelector}"
      ?allowCustomBaseUri="${this.httpAllowCustomBaseUri}"
      ?allowCustom="${this.httpAllowCustom}"
      allowHideOptional
      data-request-id="${operation.id}"
      @change="${this.requestChangeHandler}"
    ></api-request>
    `;
        return content;
    }
    /**
     * @param operation The operation to render.
     * @returns The template for the request's code snippets.
     */
    renderCodeSnippetsPanel(operation) {
        const values = this.snippetsInternal[operation.id];
        if (!values || !values.value) {
            return nothing;
        }
        const { expects } = values.value;
        let { payload } = expects;
        if (payload && typeof payload !== 'string') {
            payload = '';
        }
        const request = {
            url: expects.url,
            headers: expects.headers,
            method: expects.method,
            payload: payload,
        };
        return html `
    <section class="snippets text-selectable">
      <http-snippets .request="${request}"></http-snippets>
    </section>
    `;
    }
    /**
     * @return The template for the endpoint's extensions.
     */
    renderExtensions(endpoint) {
        const { extends: extensions } = endpoint;
        if (!extensions || !extensions.length) {
            return nothing;
        }
        const type = extensions.find(e => e.types.includes(AmfNamespace.aml.vocabularies.apiContract.ParametrizedResourceType));
        const traits = extensions.filter(e => e.types.includes(AmfNamespace.aml.vocabularies.apiContract.ParametrizedTrait));
        const traitsLabel = joinTraitNames(traits);
        const typeLabel = type && type.name;
        if (!traitsLabel && !typeLabel) {
            return nothing;
        }
        return html `
    <section class="extensions">
      ${this.renderExtends(typeLabel)} ${this.renderTraits(traitsLabel)}
    </section>
    `;
    }
    /**
     * @returns The template for the parent resource type.
     */
    renderExtends(label) {
        if (!label) {
            return nothing;
        }
        return html `<span>Implements </span><span class="resource-type-name text-selectable" title="Resource type applied to this endpoint">${label}</span>.`;
    }
    /**
     * @returns The template for the traits applied to the resource.
     */
    renderTraits(label) {
        if (!label) {
            return nothing;
        }
        return html `<span>Mixes in </span><span class="trait-name text-selectable">${label}</span>.`;
    }
}
__decorate([
    property({ type: String, reflect: true })
], ApiResourceDocument.prototype, "operationId", null);
__decorate([
    property({ type: String, reflect: true })
], ApiResourceDocument.prototype, "serverId", null);
__decorate([
    property({ type: String })
], ApiResourceDocument.prototype, "baseUri", null);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiResourceDocument.prototype, "tryItButton", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiResourceDocument.prototype, "tryItPanel", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiResourceDocument.prototype, "httpUrlEditor", void 0);
__decorate([
    property({ type: Array })
], ApiResourceDocument.prototype, "httpCredentialsSource", void 0);
__decorate([
    property({ type: String })
], ApiResourceDocument.prototype, "redirectUri", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiResourceDocument.prototype, "httpNoServerSelector", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiResourceDocument.prototype, "httpAllowCustom", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiResourceDocument.prototype, "httpAllowCustomBaseUri", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiResourceDocument.prototype, "asyncApi", void 0);
__decorate([
    property({ type: String })
], ApiResourceDocument.prototype, "serverValue", void 0);
__decorate([
    property({ type: String })
], ApiResourceDocument.prototype, "serverType", void 0);
//# sourceMappingURL=ApiResourceDocument.element.js.map