import { __esDecorate, __runInitializers } from "tslib";
import { LitElement, html } from 'lit';
import { property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { RequestLog } from '@api-client/core/models/RequestLog.js';
import { Headers } from '@api-client/core/lib/headers/Headers.js';
import { Events as CoreEvents } from '@api-client/core/events/Events.js';
import { statusTemplate } from './HttpStatus.js';
import '../../md/icons/ui-icon.js';
import '../../md/divider/ui-divider.js';
let LogHeaders = (() => {
    let _classSuper = LitElement;
    let _instanceExtraInitializers = [];
    let _effectiveLog_decorators;
    let _effectiveLog_initializers = [];
    let _effectiveLog_extraInitializers = [];
    let _set_httpLog_decorators;
    let _activeItem_decorators;
    let _activeItem_initializers = [];
    let _activeItem_extraInitializers = [];
    let _focusedItem_decorators;
    let _focusedItem_initializers = [];
    let _focusedItem_extraInitializers = [];
    let _requestHeaders_decorators;
    let _requestHeaders_initializers = [];
    let _requestHeaders_extraInitializers = [];
    let _responseHeaders_decorators;
    let _responseHeaders_initializers = [];
    let _responseHeaders_extraInitializers = [];
    return class LogHeaders extends _classSuper {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _effectiveLog_decorators = [state()];
            _set_httpLog_decorators = [property({ type: Object })];
            _activeItem_decorators = [state()];
            _focusedItem_decorators = [state()];
            _requestHeaders_decorators = [state()];
            _responseHeaders_decorators = [state()];
            __esDecorate(this, null, _effectiveLog_decorators, { kind: "accessor", name: "effectiveLog", static: false, private: false, access: { has: obj => "effectiveLog" in obj, get: obj => obj.effectiveLog, set: (obj, value) => { obj.effectiveLog = value; } }, metadata: _metadata }, _effectiveLog_initializers, _effectiveLog_extraInitializers);
            __esDecorate(this, null, _set_httpLog_decorators, { kind: "setter", name: "httpLog", static: false, private: false, access: { has: obj => "httpLog" in obj, set: (obj, value) => { obj.httpLog = value; } }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _activeItem_decorators, { kind: "accessor", name: "activeItem", static: false, private: false, access: { has: obj => "activeItem" in obj, get: obj => obj.activeItem, set: (obj, value) => { obj.activeItem = value; } }, metadata: _metadata }, _activeItem_initializers, _activeItem_extraInitializers);
            __esDecorate(this, null, _focusedItem_decorators, { kind: "accessor", name: "focusedItem", static: false, private: false, access: { has: obj => "focusedItem" in obj, get: obj => obj.focusedItem, set: (obj, value) => { obj.focusedItem = value; } }, metadata: _metadata }, _focusedItem_initializers, _focusedItem_extraInitializers);
            __esDecorate(this, null, _requestHeaders_decorators, { kind: "accessor", name: "requestHeaders", static: false, private: false, access: { has: obj => "requestHeaders" in obj, get: obj => obj.requestHeaders, set: (obj, value) => { obj.requestHeaders = value; } }, metadata: _metadata }, _requestHeaders_initializers, _requestHeaders_extraInitializers);
            __esDecorate(this, null, _responseHeaders_decorators, { kind: "accessor", name: "responseHeaders", static: false, private: false, access: { has: obj => "responseHeaders" in obj, get: obj => obj.responseHeaders, set: (obj, value) => { obj.responseHeaders = value; } }, metadata: _metadata }, _responseHeaders_initializers, _responseHeaders_extraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        httpLogInternal = __runInitializers(this, _instanceExtraInitializers);
        #effectiveLog_accessor_storage = __runInitializers(this, _effectiveLog_initializers, void 0);
        /**
         * This property is set when the `httpLog` is set. It always represent an instance
         * of the `RequestLog` (or undefined) regardless of the input format.
         */
        get effectiveLog() { return this.#effectiveLog_accessor_storage; }
        set effectiveLog(value) { this.#effectiveLog_accessor_storage = value; }
        /**
         * The request log to render. When not set it renders nothing.
         */
        get httpLog() {
            return this.httpLogInternal;
        }
        set httpLog(value) {
            const old = this.httpLogInternal;
            if (old === value) {
                return;
            }
            this.httpLogInternal = value;
            if (value) {
                if (typeof value.toJSON === 'function') {
                    this.effectiveLog = value;
                }
                else {
                    this.effectiveLog = new RequestLog(value);
                }
            }
            else {
                this.effectiveLog = undefined;
            }
            this.parseHeaders();
        }
        #activeItem_accessor_storage = (__runInitializers(this, _effectiveLog_extraInitializers), __runInitializers(this, _activeItem_initializers, void 0));
        get activeItem() { return this.#activeItem_accessor_storage; }
        set activeItem(value) { this.#activeItem_accessor_storage = value; }
        #focusedItem_accessor_storage = (__runInitializers(this, _activeItem_extraInitializers), __runInitializers(this, _focusedItem_initializers, void 0));
        get focusedItem() { return this.#focusedItem_accessor_storage; }
        set focusedItem(value) { this.#focusedItem_accessor_storage = value; }
        /**
         * The currently focused node.
         */
        focusedElement = __runInitializers(this, _focusedItem_extraInitializers);
        #requestHeaders_accessor_storage = __runInitializers(this, _requestHeaders_initializers, void 0);
        /**
         * Parsed request headers, when the request is set.
         */
        get requestHeaders() { return this.#requestHeaders_accessor_storage; }
        set requestHeaders(value) { this.#requestHeaders_accessor_storage = value; }
        #responseHeaders_accessor_storage = (__runInitializers(this, _requestHeaders_extraInitializers), __runInitializers(this, _responseHeaders_initializers, void 0));
        /**
         * Parsed response headers, when the response is set.
         */
        get responseHeaders() { return this.#responseHeaders_accessor_storage; }
        set responseHeaders(value) { this.#responseHeaders_accessor_storage = value; }
        /**
         * The list of ids of tree parents that are opened.
         */
        openedParents = (__runInitializers(this, _responseHeaders_extraInitializers), ['general', 'response-headers', 'request-headers']);
        constructor() {
            super();
            this.addEventListener('keydown', this.handleKeydown.bind(this));
            this.addEventListener('click', this.handleClick.bind(this));
            this.addEventListener('dblclick', this.handleDblclick.bind(this));
            this.addEventListener('focus', this.handleFocus.bind(this));
        }
        connectedCallback() {
            super.connectedCallback();
            this.manageTabindex();
        }
        updated(cp) {
            super.updated(cp);
            if (cp.has('focusedItem')) {
                this.manageTabindex();
            }
        }
        /**
         * Manages the state of the tabindex attribute of the element.
         * When the user changes the focus in the tree then the tabindex is removed from the element
         * and moved to the focused element.
         * When there's no focused element, the tabindex is again set on the element.
         */
        manageTabindex() {
            const id = this.focusedItem;
            if (id && this.hasAttribute('tabindex')) {
                this.removeAttribute('tabindex');
            }
            else if (!id && !this.hasAttribute('tabindex')) {
                this.setAttribute('tabindex', '0');
            }
            if (id) {
                const node = this.shadowRoot?.querySelector(`[data-id="${id}"]`);
                if (node) {
                    node.focus();
                }
            }
        }
        /**
         * Processes the request and response headers when the setter is invoked.
         * This way we can cache results instead of generating the headers in the
         * template.
         */
        parseHeaders() {
            const { effectiveLog } = this;
            if (!effectiveLog) {
                this.requestHeaders = undefined;
                this.responseHeaders = undefined;
                return;
            }
            const { request, response } = effectiveLog;
            if (request && request.headers) {
                this.requestHeaders = new Headers(request.headers);
            }
            if (response && response.headers) {
                this.responseHeaders = new Headers(response.headers);
            }
        }
        handleFocus() {
            let node;
            if (this.focusedItem) {
                node = this.shadowRoot?.querySelector(`[data-id="${this.focusedItem}"]`);
            }
            else {
                node = this.shadowRoot?.querySelector(`[data-id="general"]`);
            }
            if (node) {
                node.focus();
            }
        }
        handleKeydown(e) {
            const node = this.findClosestListItem(e);
            if (!node) {
                return;
            }
            switch (e.key) {
                case 'ArrowRight':
                    this.handleItemRight(node);
                    break;
                case 'ArrowLeft':
                    this.handleItemLeft(node);
                    break;
                case 'ArrowDown':
                    this.handleItemDown(node);
                    break;
                case 'ArrowUp':
                    this.handleItemUp(node);
                    break;
                case 'Home':
                    this.handleHome();
                    break;
                case 'End':
                    this.handleEnd();
                    break;
                case 'Enter':
                    this.handleItemEnter(node);
                    break;
                case 'c':
                    this.handleItemC(e, node);
                    break;
                default:
            }
        }
        async handleItemC(e, node) {
            if (!e.ctrlKey || !navigator.clipboard) {
                return;
            }
            const txt = node.textContent?.trim();
            if (txt) {
                await navigator.clipboard.writeText(txt);
            }
            CoreEvents.Telemetry.event({
                category: 'Usage',
                action: 'Click',
                label: 'Response header clipboard copy',
            }, this);
        }
        handleClick(e) {
            const node = this.findClosestListItem(e);
            if (!node) {
                return;
            }
            this.focusListItem(node);
        }
        handleDblclick(e) {
            const node = this.findClosestListItem(e);
            if (!node) {
                return;
            }
            if (!node.classList.contains('tree-parent')) {
                return;
            }
            const key = node.dataset.id;
            this.toggleParent(key);
            this.requestUpdate();
        }
        toggleParent(key) {
            const index = this.openedParents.indexOf(key);
            if (index >= 0) {
                this.openedParents.splice(index, 1);
            }
            else {
                this.openedParents.push(key);
            }
        }
        findClosestListItem(e) {
            const path = e.composedPath();
            while (path.length) {
                const target = path.shift();
                if (!target) {
                    break;
                }
                if (target.nodeType !== Node.ELEMENT_NODE) {
                    continue;
                }
                const elm = target;
                if (elm.classList.contains('tree-item') || elm.classList.contains('tree-parent')) {
                    return elm;
                }
            }
            return undefined;
        }
        /**
         * 1. When focus is on a closed node, opens the node; focus does not move.
         * 2. When focus is on a open node, moves focus to the first child node.
         * 3. When focus is on an end node (a tree item with no children), does nothing.
         */
        handleItemRight(node) {
            // #3
            if (!node.classList.contains('tree-parent')) {
                return;
            }
            const key = node.dataset.id;
            const opened = this.openedParents.includes(key);
            if (!opened) {
                // #1
                this.openedParents.push(key);
                this.requestUpdate();
                return;
            }
            // #2
            const list = node.nextElementSibling;
            if (list) {
                this.focusFirstDescendant(list);
            }
        }
        /**
         * 1. When focus is on an open node, closes the node.
         * 2. When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node.
         * 3. When focus is on a closed `tree`, does nothing.
         */
        handleItemLeft(node) {
            const isParent = node.classList.contains('tree-parent');
            const key = node.dataset.id;
            if (isParent) {
                const opened = this.openedParents.includes(key);
                if (opened) {
                    // #1
                    const index = this.openedParents.indexOf(key);
                    this.openedParents.splice(index, 1);
                    this.requestUpdate();
                    return;
                }
                // #3
                return;
            }
            const list = node.parentElement;
            if (!list) {
                return;
            }
            // #2
            const item = list.previousElementSibling;
            if (item) {
                this.focusListItem(item);
            }
        }
        /**
         * Moves focus to the next node that is focusable without opening or closing a node.
         */
        handleItemDown(node) {
            const isParent = node.classList.contains('tree-parent');
            if (!isParent) {
                if (this.focusNextSibling(node)) {
                    return;
                }
                // focus on the next parent
                const myParent = this.findParentListItem(node);
                if (!myParent) {
                    return;
                }
                let firstParent;
                let current = myParent.nextElementSibling;
                while (current) {
                    const isList = current.localName === 'li';
                    if (isList) {
                        firstParent = current;
                        break;
                    }
                    current = current.nextElementSibling;
                }
                if (firstParent) {
                    this.focusListItem(firstParent);
                }
                return;
            }
            const key = node.dataset.id;
            // if we are opened then we focus on the first child.
            // otherwise we focus on the next parent
            const opened = this.openedParents.includes(key);
            if (opened && this.focusFirstDescendant(node.nextElementSibling)) {
                return;
            }
            this.focusNextSibling(node);
        }
        /**
         * Moves focus to the previous node that is focusable without opening or closing a node.
         */
        handleItemUp(node) {
            const isParent = node.classList.contains('tree-parent');
            if (isParent) {
                // focuses on a last item of the first opened previous parent
                // or when the parent is closed then just on the parent.
                let firstParent;
                let current = node.previousElementSibling;
                while (current) {
                    if (current.localName === 'li') {
                        firstParent = current;
                        break;
                    }
                    current = current.previousElementSibling;
                }
                if (firstParent) {
                    if (firstParent.classList.contains('opened') &&
                        this.focusLastDescendant(firstParent.nextElementSibling)) {
                        return;
                    }
                    this.focusListItem(firstParent);
                }
                return;
            }
            if (this.focusPreviousSibling(node)) {
                return;
            }
            this.focusFirstParent(node);
        }
        handleHome() {
            const node = this.shadowRoot?.querySelector(`[data-id="general"]`);
            if (!node) {
                return;
            }
            this.focusListItem(node);
        }
        handleEnd() {
            const first = this.shadowRoot?.querySelector('ol');
            if (!first) {
                return;
            }
            const children = Array.from(first.children).reverse();
            for (const node of children) {
                if (node.localName === 'li') {
                    if (this.focusLastDescendant(node.nextElementSibling)) {
                        return;
                    }
                    this.focusListItem(node);
                    return;
                }
            }
        }
        /**
         * Performs the default action of the currently focused node.
         * For parent nodes, it opens or closes the node.
         * In single-select trees, if the node has no children, selects the current node
         * if not already selected (which is the default action).
         */
        handleItemEnter(node) {
            const isParent = node.classList.contains('tree-parent');
            if (!isParent) {
                return;
            }
            const key = node.dataset.id;
            this.toggleParent(key);
            this.requestUpdate();
        }
        /**
         * Focuses on the first descendant of a folder item.
         *
         * @param node The list containing children to focus on.
         * @returns true when focused on any of the descendants
         */
        focusFirstDescendant(node) {
            if (!node) {
                return false;
            }
            const items = Array.from(node.children).filter((n) => n.localName === 'li');
            if (!items || !items.length) {
                return false;
            }
            for (const child of items) {
                if (child.hasAttribute('disabled')) {
                    continue;
                }
                this.focusListItem(child);
                return true;
            }
            return false;
        }
        /**
         * Focuses on the last descendant of a folder item.
         *
         * @param node The list containing children to focus on.
         * @returns true when focused on any of the descendants
         */
        focusLastDescendant(node) {
            if (!node) {
                return false;
            }
            const items = Array.from(node.children).filter((n) => n.localName === 'li');
            if (!items || !items.length) {
                return false;
            }
            items.reverse();
            for (const child of items) {
                if (child.hasAttribute('disabled')) {
                    continue;
                }
                this.focusListItem(child);
                return true;
            }
            return false;
        }
        focusListItem(node) {
            node.focus();
            this.activeItem = node.dataset.id;
            this.focusedItem = node.dataset.id;
            this.focusedElement = node;
            node.scrollIntoView({
                block: 'nearest',
                inline: 'nearest',
            });
        }
        /**
         * Focuses on the next node relative to the passed node.
         *
         * @param node The node to use as a mark. When not set it uses the currently focused node.
         * @returns true when focused on any of the next siblings
         */
        focusNextSibling(node) {
            if (!node) {
                return false;
            }
            let current = node.nextElementSibling;
            while (current) {
                if (current.localName !== 'li' || current.hasAttribute('disabled')) {
                    current = current.nextElementSibling;
                    continue;
                }
                this.focusListItem(current);
                return true;
            }
            return false;
        }
        focusPreviousSibling(node) {
            if (!node) {
                return false;
            }
            let current = node.previousElementSibling;
            while (current) {
                if (current.localName !== 'li' || current.hasAttribute('disabled')) {
                    current = current.previousElementSibling;
                    continue;
                }
                this.focusListItem(current);
                return true;
            }
            return false;
        }
        /**
         * Focuses on a first parent list item that accepts focus relative to the given node.
         *
         * @param node The node to use as a reference.
         * @returns true when focused on any of the parents
         */
        focusFirstParent(node) {
            if (!node) {
                return false;
            }
            const parent = this.findParentListItem(node);
            if (parent) {
                this.focusListItem(parent);
                return true;
            }
            return false;
        }
        findParentListItem(node) {
            let parent = node.parentElement;
            while (parent) {
                if (parent === this) {
                    return undefined;
                }
                if (parent.localName === 'ol') {
                    const item = parent.previousElementSibling;
                    return item || undefined;
                }
                parent = parent.parentElement;
            }
            return undefined;
        }
        render() {
            const { effectiveLog } = this;
            if (!effectiveLog) {
                return html ``;
            }
            const { request, response } = effectiveLog;
            return html `
      <ol role="tree" tabindex="-1" class="tree" aria-multiselectable="false">
        ${this.RenderRequestGeneral(request, response)} ${this.renderResponseHeaders()} ${this.renderRequestHeaders()}
      </ol>
    `;
        }
        renderListItem(name, value, id) {
            const active = this.activeItem === id;
            const focused = this.focusedItem === id;
            const classes = {
                active,
                'tree-item': true,
            };
            const label = typeof value === 'string' ? `${name}: ${value}` : [name, value];
            return html `
      <li
        role="treeitem"
        class="${classMap(classes)}"
        data-id="${id}"
        tabindex="${focused ? '0' : '-1'}"
        aria-selected="${active ? 'true' : 'false'}"
      >
        <span class="item-content">${label}</span>
      </li>
    `;
        }
        renderParentListItem(label, id) {
            const active = this.activeItem === id;
            const focused = this.focusedItem === id;
            const opened = this.openedParents.includes(id);
            const classes = {
                active,
                opened,
                'closed': !opened,
                focused,
                'tree-parent': true,
            };
            return html `
      <li
        title="${label}"
        class="${classMap(classes)}"
        data-id="${id}"
        role="treeitem"
        tabindex="${focused ? '0' : '-1'}"
        aria-expanded="${opened ? 'true' : 'false'}"
        aria-selected="${active ? 'true' : 'false'}"
      >
        <ui-icon icon="arrowDropDown" role="presentation" class="toggle-icon"></ui-icon>
        <span class="tree-element-title">${label}</span>
      </li>
    `;
        }
        renderChildrenItems(id, items) {
            const opened = this.openedParents.includes(id);
            const classes = {
                expanded: opened,
                collapsed: !opened,
                children: true,
            };
            return html `
      <ol class="${classMap(classes)}" role="group">
        ${items}
      </ol>
      <ui-divider type="inset"></ui-divider>
    `;
        }
        RenderRequestGeneral(sent, response) {
            if (!sent) {
                return html ``;
            }
            const id = 'general';
            const items = html `
      ${this.renderListItem('Request URL', sent.url, `${id}-request-url`)}
      ${this.renderListItem('Request Method', sent.method, `${id}-request-method`)}
      ${this.renderResponseStatus(id, response)}
    `;
            return html ` ${this.renderParentListItem('General', id)} ${this.renderChildrenItems(id, items)} `;
        }
        renderResponseStatus(id, response) {
            if (!response) {
                return '';
            }
            const { status = 0, statusText = '' } = response;
            const tpl = statusTemplate(status, statusText);
            return this.renderListItem('Status Code', tpl, `${id}-status-code`);
        }
        renderRequestHeaders() {
            const id = 'request-headers';
            return html `
      ${this.renderParentListItem('Request headers', id)}
      ${this.renderChildrenItems(id, this.renderHeadersList(id, this.requestHeaders))}
    `;
        }
        renderResponseHeaders() {
            const id = 'response-headers';
            return html `
      ${this.renderParentListItem('Response headers', id)}
      ${this.renderChildrenItems(id, this.renderHeadersList(id, this.responseHeaders))}
    `;
        }
        renderHeadersList(prefix, headers) {
            if (!headers) {
                return html `<p class="no-data">No headers</p>`;
            }
            let i = 0;
            const templates = headers.map((value, name) => {
                const id = `${prefix}-${i}`;
                i += 1;
                return this.renderListItem(name, value, id);
            });
            return html `${templates}`;
        }
    };
})();
/**
 * An element that renders summary headers view for an HTTP response.
 */
export default LogHeaders;
//# sourceMappingURL=LogHeaders.js.map