import { __decorate } from "tslib";
import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { ApiSchemaGenerator, ApiSchemaValues, AmfNamespace } from '@api-client/core/build/browser.js';
import { classMap } from 'lit/directives/class-map.js';
import { QueryParameterProcessor } from '../../amf/lib/QueryParameterProcessor.js';
import { ApiDocumentationBase } from './ApiDocumentationBase.js';
import { Events } from '../../events/amf/Events.js';
import { joinTraitNames } from '../../amf/lib/Utils.js';
import * as UrlLib from '../../amf/lib/UrlUtils.js';
import { tablePropertyTemplate } from './SchemaCommonTemplates.js';
import '../../define/amf/api-request-document.js';
import '../../define/amf/api-response-document.js';
import '../../define/amf/api-security-requirement-document.js';
import '../../define/ui/ui-icon.js';
import '../../define/ui/ui-button.js';
import '../../define/ui/ui-tabs.js';
import '../../define/ui/ui-tab.js';
import '../../define/ui/ui-divider.js';
import '../../define/http/http-snippets.js';
/**
 * A web component that renders the documentation page for an API operation built from
 * the AMF graph model.
 *
 * @fires tryit
 */
export default class ApiOperationDocument extends ApiDocumentationBase {
    /**
     * 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
     */
    get serverId() {
        return this.serverIdValue;
    }
    set serverId(value) {
        const old = this.serverIdValue;
        if (old === value) {
            return;
        }
        this.serverIdValue = value;
        this.computeUrlValue();
        this.computeParametersValue();
        this.requestUpdate();
    }
    /**
     * The computed list of servers.
     */
    get servers() {
        return this.serversValue;
    }
    /**
     * The current server in use.
     */
    get server() {
        const servers = this.serversValue;
        const serverId = this.serverIdValue;
        let server;
        if (Array.isArray(servers) && servers.length) {
            if (serverId) {
                server = servers.find((item) => item.id === serverId);
            }
            else {
                [server] = servers;
            }
        }
        return server;
    }
    get operation() {
        return this.operationValue;
    }
    set operation(value) {
        const old = this.operationValue;
        if (old === value) {
            return;
        }
        this.operationValue = value;
        this.processGraph();
    }
    get endpoint() {
        return this.endpointValue;
    }
    set endpoint(value) {
        const old = this.endpointValue;
        if (old === value) {
            return;
        }
        this.endpointValue = value;
        this.computeUrlValue();
        this.computeParametersValue();
        this.requestUpdate();
    }
    /**
     * 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.computeParametersValue();
        this.requestUpdate();
    }
    /**
     * The computed URI for the endpoint.
     */
    get endpointUri() {
        return this.urlValue;
    }
    get snippetsUri() {
        const base = this.urlValue || '';
        const query = this.snippetsParametersValue || '';
        return `${base}${query}`;
    }
    /**
     * The computed list of responses for this operation.
     */
    get responses() {
        return this.responsesValue;
    }
    /**
     * The API's protocols.
     */
    get protocols() {
        return this.protocolsValue;
    }
    get version() {
        return this.versionValue;
    }
    async processGraph() {
        await this.queryEndpoint();
        await this.queryOperation();
        await this.queryServers();
        await this.queryProtocols();
        await this.queryResponses();
        this.preselectResponse();
        this.preselectSecurity();
        this.computeUrlValue();
        this.computeParametersValue();
        this.computeSnippetsPayload();
        this.computeSnippetsHeaders();
        this.requestUpdate();
        await this.updateComplete;
        this.dispatchEvent(new Event('graphload'));
    }
    /**
     * Queries the store for the operation data, when needed.
     */
    async queryOperation() {
        const { domainId } = this;
        if (!domainId) {
            // this.operationValue = undefined;
            return;
        }
        if (this.operationValue && this.operationValue.id === domainId) {
            // in case the operation model was provided via the property setter.
            return;
        }
        try {
            const endpointId = this.endpointValue && this.endpointValue.id;
            const info = await Events.Operation.get(this, domainId, endpointId);
            this.operationValue = info;
        }
        catch (e) {
            const ex = e;
            Events.Telemetry.exception(this, ex.message, false);
            Events.Reporting.error(this, ex, `Unable to query for API operation data: ${ex.message}`, this.localName);
        }
    }
    /**
     * Queries the store for the endpoint data.
     */
    async queryEndpoint() {
        const { domainId, endpointId } = this;
        if (!domainId) {
            // this.endpointValue = undefined;
            return;
        }
        if (this.endpointValue && this.endpointValue.id === endpointId) {
            // in case the endpoint model was provided via the property setter.
            return;
        }
        this.endpointValue = undefined;
        try {
            const info = await (endpointId ? Events.Endpoint.get(this, endpointId) : Events.Operation.getParent(this, domainId));
            this.endpointValue = info;
        }
        catch (e) {
            const ex = e;
            Events.Telemetry.exception(this, ex.message, false);
            Events.Reporting.error(this, ex, `Unable to query for API endpoint data: ${ex.message}`, this.localName);
        }
    }
    /**
     * Queries for the current servers value.
     */
    async queryServers() {
        const { domainId } = this;
        const endpointId = this.endpointValue && this.endpointValue.id;
        try {
            const info = await Events.Server.query(this, {
                endpointId,
                methodId: domainId,
            });
            this.serversValue = info;
        }
        catch (e) {
            const ex = e;
            this.serversValue = undefined;
            Events.Telemetry.exception(this, ex.message, false);
            Events.Reporting.error(this, ex, `Unable to query for API servers: ${ex.message}`, this.localName);
        }
    }
    /**
     * Queries the API store for the API protocols list.
     */
    async queryProtocols() {
        this.protocolsValue = undefined;
        try {
            const info = await Events.Api.protocols(this);
            this.protocolsValue = info;
        }
        catch (e) {
            const ex = e;
            Events.Telemetry.exception(this, ex.message, false);
            Events.Reporting.error(this, ex, `Unable to query for API protocols list: ${ex.message}`, this.localName);
        }
    }
    /**
     * Queries the API store for the API version value.
     */
    async queryVersion() {
        this.versionValue = undefined;
        try {
            const info = await Events.Api.version(this);
            this.versionValue = info;
        }
        catch (e) {
            const ex = e;
            Events.Telemetry.exception(this, ex.message, false);
            Events.Reporting.error(this, ex, `Unable to query for API version value: ${ex.message}`, this.localName);
        }
    }
    /**
     * Queries for the responses data of the current operation.
     */
    async queryResponses() {
        this.responsesValue = undefined;
        const { operation } = this;
        if (!operation) {
            return;
        }
        const { responses = [] } = operation;
        if (!responses.length) {
            return;
        }
        this.responsesValue = responses;
    }
    /**
     * Updates the `selectedStatus` if not selected or the current selection doesn't
     * exists in the current list of responses.
     */
    preselectResponse() {
        const responses = this.responsesValue;
        if (!Array.isArray(responses) || !responses.length) {
            return;
        }
        responses.sort((a, b) => {
            if (a.statusCode === b.statusCode) {
                return 0;
            }
            return Number(a.statusCode) > Number(b.statusCode) ? 1 : -1;
        });
        const { selectedStatus } = this;
        if (!selectedStatus) {
            this.selectedStatus = responses[0].statusCode;
            return;
        }
        const selected = responses.find((item) => item.statusCode === selectedStatus);
        if (selected) {
            return;
        }
        this.selectedStatus = responses[0].statusCode;
    }
    /**
     * Updates the `securityId` if not selected or the current selection doesn't
     * exists in the current list of security.
     */
    preselectSecurity() {
        const { operation, showSecurity: renderSecurity, securityId } = this;
        if (!renderSecurity || !operation || !Array.isArray(operation.security) || !operation.security.length) {
            return;
        }
        if (!securityId) {
            this.securityId = operation.security[0].id;
            return;
        }
        const selected = operation.security.find((item) => item.id === securityId);
        if (selected) {
            return;
        }
        this.securityId = operation.security[0].id;
    }
    /**
     * Computes the URL value for the current serves, selected server, and endpoint's path.
     */
    computeUrlValue() {
        if (this.asyncApi) {
            return;
        }
        const { protocols, version, server, baseUri } = this;
        const endpoint = this.endpointValue;
        const url = UrlLib.computeEndpointUri({ baseUri, server, endpoint, protocols, version, });
        this.urlValue = url;
    }
    /**
     * Computes query parameters for the code snippets.
     */
    computeParametersValue() {
        this.snippetsParametersValue = undefined;
        const { operation } = this;
        if (!operation) {
            return;
        }
        const { request } = operation;
        if (!request) {
            return;
        }
        const { queryParameters, queryString } = request;
        let params;
        if (Array.isArray(queryParameters) && queryParameters.length) {
            params = queryParameters;
        }
        else if (queryString) {
            const factory = new QueryParameterProcessor();
            const items = factory.collectOperationParameters(request.queryString, 'query');
            if (Array.isArray(items) && items.length) {
                params = items.map(i => i.parameter);
            }
        }
        if (!params || !params.length) {
            return;
        }
        const qp = {};
        params.forEach((param) => {
            const { required, schema, paramName, name } = param;
            if (!required) {
                return;
            }
            const parameterName = paramName || name;
            if (!parameterName) {
                return;
            }
            const anySchema = schema;
            const { defaultValueStr, examples = [] } = anySchema;
            if (defaultValueStr) {
                qp[parameterName] = defaultValueStr;
            }
            else if (examples.length) {
                const exp = examples.find(e => e.value);
                if (exp && exp.value) {
                    qp[parameterName] = exp.value;
                }
            }
            else {
                const value = ApiSchemaValues.generateDefaultValue(schema);
                if (value || value === false || value === 0 || value === null) {
                    qp[parameterName] = value;
                }
                // if (typeof value === 'undefined') {
                //   qp[parameterName] = '';
                // } else {
                //   qp[parameterName] = value;
                // }
            }
        });
        const value = UrlLib.applyUrlParameters('', qp, true);
        this.snippetsParametersValue = value;
    }
    /**
     * Computes payload value for the code snippets.
     */
    computeSnippetsPayload() {
        this.snippetsPayloadValue = undefined;
        if (this.asyncApi) {
            return;
        }
        const { operation, requestMimeType } = this;
        if (!operation || !requestMimeType) {
            return;
        }
        const { request } = operation;
        if (!request) {
            return;
        }
        const { payloads = [] } = request;
        if (!payloads.length) {
            return;
        }
        const payload = payloads.find(p => p.mediaType === requestMimeType);
        if (!payload) {
            return;
        }
        const { examples = [], schema } = payload;
        let examplesCopy = [...examples];
        const anySchema = schema;
        if (Array.isArray(anySchema.examples) && anySchema.examples.length) {
            examplesCopy = examplesCopy.concat(anySchema.examples);
        }
        examplesCopy = examplesCopy.filter((i) => !!i.value || !!i.structuredValue);
        let payloadValue;
        if (examplesCopy.length) {
            const example = examplesCopy.find(e => !!e.value);
            if (example) {
                payloadValue = this.evaluateExample(example, requestMimeType);
            }
        }
        if (!payloadValue) {
            payloadValue = ApiSchemaGenerator.asExample(schema, requestMimeType, {
                renderExamples: true,
                renderOptional: true,
            });
        }
        if (payloadValue && payloadValue.renderValue) {
            this.snippetsPayloadValue = payloadValue.renderValue;
        }
    }
    /**
     * Computes headers value for the code snippets.
     */
    computeSnippetsHeaders() {
        this.snippetsHeadersValue = undefined;
        if (this.asyncApi) {
            return;
        }
        const { operation, requestMimeType } = this;
        if (!operation) {
            return;
        }
        const { request, method } = operation;
        if (!request) {
            return;
        }
        const { headers = [] } = request;
        const parts = [];
        let hasMime = false;
        headers.forEach((param) => {
            const { paramName, name, schema } = param;
            if (!schema || !schema.types.includes(AmfNamespace.aml.vocabularies.shapes.ScalarShape)) {
                return;
            }
            const typedScalar = schema;
            let value = ApiSchemaValues.readInputValue(param, typedScalar, { fromExamples: true });
            if (Array.isArray(value)) {
                value = value.join(',');
            }
            if (typeof value !== 'undefined') {
                const headerName = paramName || name || '';
                if (headerName.toLowerCase() === 'content-type') {
                    hasMime = true;
                }
                const header = `${paramName || name}: ${value}`;
                parts.push(header);
            }
        });
        if (!hasMime && requestMimeType && method !== 'get') {
            parts.push(`content-type: ${requestMimeType}`);
        }
        this.snippetsHeadersValue = parts.join('\n');
    }
    /**
     * A handler for the status code tab selection.
     */
    handleStatusCode(e) {
        const { item } = e.detail;
        const { status } = item.dataset;
        if (!status) {
            return;
        }
        this.selectedStatus = status;
    }
    /**
     * A handler for the status code tab selection.
     */
    handleSecuritySelection(e) {
        const { item } = e.detail;
        const { id } = item.dataset;
        if (!id) {
            return;
        }
        this.securityId = id;
    }
    /**
     * A handler for the try it button click.
     * It dispatches the `tryit` custom event.
     */
    handleTryIt() {
        const { operation, asyncApi } = this;
        if (!operation || asyncApi) {
            return;
        }
        const { id } = operation;
        const detail = { id };
        const config = {
            bubbles: true,
            composed: true,
            detail,
        };
        this.dispatchEvent(new CustomEvent('tryit', config));
    }
    /**
     * A handler for the request panel mime type change.
     */
    handleRequestMimeChange(e) {
        const panel = e.target;
        this.requestMimeType = panel.mimeType;
        this.computeSnippetsPayload();
        this.computeSnippetsHeaders();
        this.requestUpdate();
    }
    render() {
        const op = this.operationValue;
        if (!op) {
            return html ``;
        }
        return html `
    ${this.renderTitle(op)}
    ${this.renderSummary(op)}
    ${this.renderUrl(op)}
    ${this.renderTraits(op)}
    ${this.renderDeprecated(op)}
    ${this.descriptionTemplate(op.description)}
    ${this.renderMetaData(op)}
    ${this.customDomainPropertiesTemplate(op.customDomainProperties)}
    ${this.renderRequestTemplate()}
    ${this.renderCallbacks(op)}
    ${this.renderSnippets(op)}
    ${this.renderResponse()}
    ${this.renderSecuritySection(op)}
    `;
    }
    /**
     * @returns The template for the Operation title.
     */
    renderTitle(operation) {
        const { name, method, deprecated } = operation;
        const label = name || method;
        const labelClasses = {
            label: true,
            'text-selectable': true,
            deprecated,
        };
        const subTitle = this.asyncApi ? 'Async operation' : 'API operation';
        return html `
    <div class="operation-header">
      <div class="operation-title">
        <span class="${classMap(labelClasses)}">${label}</span>
        ${this.renderTryIt()}
      </div>
      <p class="sub-header">${subTitle}</p>
    </div>
    `;
    }
    /**
     * @returns The template for the Operation traits.
     */
    renderTraits(operation) {
        const { extends: traits } = operation;
        if (!traits || !traits.length) {
            return '';
        }
        const value = joinTraitNames(traits);
        return html `
    <section class="extensions">
      <p>Mixes in <span class="trait-name text-selectable">${value}</span>.</p>
    </section>`;
    }
    /**
     * @returns The template for the operation summary filed.
     */
    renderSummary(operation) {
        const { summary } = operation;
        if (!summary) {
            return '';
        }
        return html `
    <p class="summary text-selectable">${summary}</p>
    `;
    }
    /**
     * @returns The template for the Operation meta information.
     */
    renderMetaData(operation) {
        const { operationId } = operation;
        const result = [];
        if (operationId) {
            result.push(tablePropertyTemplate('Operation ID', operationId, 'operation-id'));
        }
        if (result.length) {
            return result;
        }
        return '';
    }
    /**
     * @returns The template for the deprecated message.
     */
    renderDeprecated(operation) {
        const { deprecated } = operation;
        if (!deprecated) {
            return '';
        }
        return html `
    <div class="deprecated-message">
      <ui-icon icon="warning"></ui-icon>
      <span class="message text-selectable">
      This operation is marked as deprecated.
      </span>
    </div>
    `;
    }
    /**
     * @returns The template for the operation's URL.
     */
    renderUrl(operation) {
        if (this.asyncApi) {
            return '';
        }
        const url = this.urlValue;
        const { method } = operation;
        return html `
    <div class="endpoint-url">
      <div class="http-label" data-method="${method}">${method}</div>
      <div class="url-value text-selectable">${url}</div>
    </div>
    `;
    }
    /**
     * @returns The template for the operation's request documentation element.
     */
    renderRequestTemplate() {
        const { operation } = this;
        if (!operation) {
            return '';
        }
        const { server, endpoint } = this;
        return html `
    <api-request-document 
      .domainId="${operation.request && operation.request.id}" 
      .request="${operation.request}" 
      .server=${server} 
      .endpoint=${endpoint}
      @mimechange="${this.handleRequestMimeChange}"
    ></api-request-document>
    `;
    }
    renderCallbacks(operation) {
        const { callbacks } = operation;
        if (!Array.isArray(callbacks) || !callbacks.length) {
            return '';
        }
        const content = callbacks.map(callback => this.renderCallback(callback));
        return this.paramsSectionTemplate('Callbacks', 'callbacksOpened', content);
    }
    /**
     * @returns The template for the operation's request documentation element.
     */
    renderCallback(callback) {
        const { name, endpoint } = callback;
        if (!endpoint) {
            return html ``;
        }
        const { operations = [] } = endpoint;
        const [operation] = operations;
        if (!operation) {
            return html ``;
        }
        return html `
    <div class="callback-section">
      <div class="heading4 table-title">${name}</div>
      <api-operation-document 
        .domainId="${operation.id}"
        .operation="${operation}"
        .serverId="${this.serverId}" 
        .endpoint="${endpoint}"
        data-domain-id="${operation.id}"
        class="operation"
      ></api-operation-document>
    </div>
    `;
    }
    renderResponse() {
        const responses = this.responsesValue;
        if (!Array.isArray(responses) || !responses.length) {
            return '';
        }
        const content = html `
    ${this.renderResponseTabs(responses)}
    ${this.renderResponseContent(responses)}
    `;
        return this.paramsSectionTemplate('Responses', 'responsesOpened', content);
    }
    /**
     * @param responses The responses to render.
     * @returns The template for the responses selector.
     */
    renderResponseTabs(responses) {
        const { selectedStatus } = this;
        const filtered = responses.filter((item) => !!item.statusCode);
        if (!filtered.length) {
            return '';
        }
        return html `
    <div class="status-codes-selector">
      <ui-tabs .selected="${selectedStatus}" @select="${this.handleStatusCode}" priority="secondary" scrollable>
      ${filtered.map((item) => html `<ui-tab aria-controls="status${item.statusCode || 0}" data-status="${item.statusCode || 0}">${item.statusCode}</ui-tab>`)}
      </ui-tabs>
    </div>
    `;
    }
    /**
     * @param responses The responses to render.
     * @returns The template for the currently selected response.
     */
    renderResponseContent(responses) {
        const { selectedStatus } = this;
        const response = responses.find((item) => item.statusCode === selectedStatus);
        if (!response) {
            return html `<div class="empty-info" id="status${selectedStatus}">Select a response to render the documentation.</div>`;
        }
        return html `
    <api-response-document 
      id="status${selectedStatus}"
      .domainId="${response.id}" 
      .response="${response}" 
      class="method-response"
    ></api-response-document>
    `;
    }
    /**
     * @returns The template for the security list section.
     */
    renderSecuritySection(operation) {
        const { showSecurity: renderSecurity, securityId } = this;
        if (!renderSecurity || !operation || !Array.isArray(operation.security) || !operation.security.length) {
            return '';
        }
        const { security } = operation;
        const content = [];
        if (security.length === 1) {
            content.push(this.renderSecurity(security[0]));
        }
        else if (securityId) {
            content.push(this.renderSecuritySelector(operation));
            const item = security.find(i => i.id === securityId);
            if (item) {
                content.push(this.renderSecurity(item));
            }
        }
        return this.paramsSectionTemplate('Security', 'securityOpened', content);
    }
    renderSecurity(security) {
        return html `<api-security-requirement-document
      .domainId="${security.id}" 
      .securityRequirement="${security}"
      id="security${security.id}"

    ></api-security-requirement-document>`;
    }
    renderSecuritySelector(operation) {
        const { securityId } = this;
        const { security } = operation;
        return html `
    <div class="security-selector">
      <ui-tabs .selected="${securityId}" @select="${this.handleSecuritySelection}" scrollable selectedAttribute="data-id">
      ${security.map((item) => this.renderSecurityTab(item))}
      </ui-tabs>
    </div>
    `;
    }
    renderSecurityTab(security) {
        const { name, schemes = [], id } = security;
        let label = 'unknown';
        if (name) {
            label = name;
        }
        else if (schemes.length) {
            const parts = schemes.map(i => i.name).filter(i => !!i);
            if (parts.length) {
                label = parts.join('/');
            }
        }
        return html `<ui-tab data-id="${id}" aria-controls="security${id}" >${label}</ui-tab>`;
    }
    /**
     * @returns The template for the "try it" button.
     */
    renderTryIt() {
        if (!this.tryItButton) {
            return nothing;
        }
        return html `
    <ui-button class="action-button"
      @click="${this.handleTryIt}"
      type="filled"
    >Try it</ui-button>
    `;
    }
    /**
     * @returns The template for the code snippets.
     */
    renderSnippets(operation) {
        if (!this.renderCodeSnippets || this.asyncApi) {
            return '';
        }
        const request = {
            url: this.snippetsUri || '',
            headers: this.snippetsHeadersValue,
            method: (operation.method || '').toUpperCase(),
            payload: this.snippetsPayloadValue,
        };
        const content = html `<http-snippets .request="${request}"></http-snippets>`;
        return this.paramsSectionTemplate('Code snippets', 'snippetsOpened', content);
    }
}
__decorate([
    property({ type: String, reflect: true })
], ApiOperationDocument.prototype, "serverId", null);
__decorate([
    property({ type: String })
], ApiOperationDocument.prototype, "baseUri", null);
__decorate([
    property({ type: String, reflect: true })
], ApiOperationDocument.prototype, "securityId", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiOperationDocument.prototype, "responsesOpened", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiOperationDocument.prototype, "securityOpened", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiOperationDocument.prototype, "snippetsOpened", void 0);
__decorate([
    property({ type: String })
], ApiOperationDocument.prototype, "selectedStatus", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiOperationDocument.prototype, "callbacksOpened", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiOperationDocument.prototype, "tryItButton", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiOperationDocument.prototype, "asyncApi", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiOperationDocument.prototype, "renderCodeSnippets", void 0);
__decorate([
    property({ type: Boolean, reflect: true })
], ApiOperationDocument.prototype, "showSecurity", void 0);
__decorate([
    property({ type: String })
], ApiOperationDocument.prototype, "requestMimeType", void 0);
__decorate([
    property({ type: String })
], ApiOperationDocument.prototype, "endpointId", void 0);
//# sourceMappingURL=ApiOperationDocument.element.js.map