import*as Platform from"../../platform/platform.js";import*as Coordinator from"../../render_coordinator/render_coordinator.js";import*as LitHtml from"../../third_party/lit-html/lit-html.js";import{findNextNodeForTreeOutlineKeyboardNavigation,isExpandableNode,trackDOMNodeToTreeNode}from"./TreeOutlineUtils.js";const coordinator=Coordinator.RenderCoordinator.RenderCoordinator.instance();export function defaultRenderer(e){return LitHtml.html`${e.treeNodeData}`}export class TreeOutline extends HTMLElement{constructor(){super(...arguments),this.shadow=this.attachShadow({mode:"open"}),this.treeData=[],this.nodeExpandedMap=new WeakMap,this.nodeChildrenCacheMap=new WeakMap,this.domNodeToTreeNodeMap=new WeakMap,this.hasRenderedAtLeastOnce=!1,this.focusableTreeNode=null,this.defaultRenderer=(e,t)=>("string"!==typeof e.treeNodeData&&console.warn(`The default TreeOutline renderer simply stringifies its given value. You passed in ${JSON.stringify(e.treeNodeData,null,2)}. Consider providing a different defaultRenderer that can handle nodes of this type.`),LitHtml.html`${String(e.treeNodeData)}`),this.scheduledRender=!1,this.enqueuedRender=!1}get data(){return{tree:this.treeData,defaultRenderer:this.defaultRenderer}}set data(e){this.defaultRenderer=e.defaultRenderer,this.treeData=e.tree,this.hasRenderedAtLeastOnce||(this.focusableTreeNode=this.treeData[0]),this.render()}async expandRecursively(e=2){await Promise.all(this.treeData.map((t=>this.expandAndRecurse(t,0,e)))),this.render()}async collapseChildrenOfNode(e){const t=this.domNodeToTreeNodeMap.get(e);t&&(await this.recursivelyCollapseTreeNodeChildren(t),this.render())}async recursivelyCollapseTreeNodeChildren(e){if(!isExpandableNode(e)||!this.nodeIsExpanded(e))return;const t=await this.fetchNodeChildren(e),r=Promise.all(t.map((e=>this.recursivelyCollapseTreeNodeChildren(e))));await r,this.setNodeExpandedState(e,!1)}getFocusableTreeNode(){if(!this.focusableTreeNode)throw new Error("getFocusableNode was called but focusableNode is null");return this.focusableTreeNode}async fetchNodeChildren(e){const t=this.nodeChildrenCacheMap.get(e);if(t)return t;const r=await e.children();return this.nodeChildrenCacheMap.set(e,r),r}setNodeExpandedState(e,t){this.nodeExpandedMap.set(e,t)}nodeIsExpanded(e){return this.nodeExpandedMap.get(e)||!1}async expandAndRecurse(e,t,r){if(!isExpandableNode(e))return;if(this.setNodeExpandedState(e,!0),t===r||!isExpandableNode(e))return;const o=await this.fetchNodeChildren(e);await Promise.all(o.map((e=>this.expandAndRecurse(e,t+1,r))))}onArrowClick(e){return t=>{t.stopPropagation(),isExpandableNode(e)&&(this.setNodeExpandedState(e,!this.nodeIsExpanded(e)),this.render())}}onNodeClick(e){e.stopPropagation(),this.focusTreeNode(e.target)}async focusTreeNode(e){const t=this.domNodeToTreeNodeMap.get(e);t&&(this.focusableTreeNode=t,await this.render(),coordinator.write((()=>{e.focus()})))}processHomeAndEndKeysNavigation(e){if("Home"===e){const e=this.shadow.querySelector('ul[role="tree"] > li[role="treeitem"]');e&&this.focusTreeNode(e)}else if("End"===e){const e=this.shadow.querySelectorAll('li[role="treeitem"]'),t=e[e.length-1];t&&this.focusTreeNode(t)}}async processArrowKeyNavigation(e,t){const r=this.domNodeToTreeNodeMap.get(t);if(!r)return;const o=findNextNodeForTreeOutlineKeyboardNavigation({currentDOMNode:t,currentTreeNode:r,direction:e,setNodeExpandedState:(e,t)=>this.setNodeExpandedState(e,t)});this.focusTreeNode(o)}processEnterOrSpaceNavigation(e){const t=this.domNodeToTreeNodeMap.get(e);if(t&&isExpandableNode(t)){const e=this.nodeIsExpanded(t);this.setNodeExpandedState(t,!e),this.render()}}async onTreeKeyDown(e){if(!(e.target instanceof HTMLLIElement))throw new Error("event.target was not an <li> element");"Home"===e.key||"End"===e.key?(e.preventDefault(),this.processHomeAndEndKeysNavigation(e.key)):Platform.KeyboardUtilities.keyIsArrowKey(e.key)?(e.preventDefault(),await this.processArrowKeyNavigation(e.key,e.target)):"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),this.processEnterOrSpaceNavigation(e.target))}async renderNode(e,{depth:t,setSize:r,positionInSet:o}){let a;const i=this.nodeIsExpanded(e);if(isExpandableNode(e)&&i){const r=await this.fetchNodeChildren(e),o=Promise.all(r.map(((e,o)=>this.renderNode(e,{depth:t+1,setSize:r.length,positionInSet:o}))));a=LitHtml.html`<ul role="group">${LitHtml.Directives.until(o)}</ul>`}else a=LitHtml.nothing;const n=this.getFocusableTreeNode()===e?0:-1,d=LitHtml.Directives.classMap({expanded:isExpandableNode(e)&&i,parent:isExpandableNode(e)}),s=LitHtml.Directives.ifDefined(isExpandableNode(e)?String(i):void 0);let l;return l=e.renderer?e.renderer(e,{isExpanded:i}):this.defaultRenderer(e,{isExpanded:i}),LitHtml.html`
      <li role="treeitem"
        tabindex=${n}
        aria-setsize=${r}
        aria-expanded=${s}
        aria-level=${t+1}
        aria-posinset=${o+1}
        class=${d}
        @click=${this.onNodeClick}
        track-dom-node-to-tree-node=${trackDOMNodeToTreeNode(this.domNodeToTreeNodeMap,e)}
      >
        <span class="arrow-and-key-wrapper">
          <span class="arrow-icon" @click=${this.onArrowClick(e)}>
          </span>
          <span class="tree-node-key" data-node-key=${e.treeNodeData}>${l}</span>
        </span>
        ${a}
      </li>
    `}async render(){this.scheduledRender?this.enqueuedRender=!0:(this.scheduledRender=!0,await coordinator.write((()=>{LitHtml.render(LitHtml.html`
      <style>
        li {
          list-style: none;
          text-overflow: ellipsis;
          white-space: nowrap;
          min-height: 12px;
        }

        .arrow-icon {
          display: inline-block;
          user-select: none;
          -webkit-mask-image: url(Images/treeoutlineTriangles.svg);
          -webkit-mask-size: 32px 24px;
          -webkit-mask-position: 0 0;
          background-color: var(--color-text-primary);
          content: "";
          text-shadow: none;
          margin-right: -2px;
          height: 12px;
          width: 13px;
          overflow: hidden;
        }

        ul {
          margin: 0;
          padding: 0;
        }

        ul[role="group"] {
          padding-left: 16px;
        }

        li:not(.parent) > .arrow-and-key-wrapper > .arrow-icon {
          -webkit-mask-size: 0;
        }

        li.parent.expanded > .arrow-and-key-wrapper > .arrow-icon {
          -webkit-mask-position: -16px 0;
        }

        .arrow-and-key-wrapper {
          border: 2px solid transparent;
        }

        [role="treeitem"]:focus {
          outline: 0;
        }

        [role="treeitem"]:focus > .arrow-and-key-wrapper {
          /* stylelint-disable-next-line color-named */
          border-color: black;
        }
      </style>
      <div class="wrapping-container">
      <ul role="tree" @keydown=${this.onTreeKeyDown}>
        ${this.treeData.map(((e,t)=>LitHtml.Directives.until(this.renderNode(e,{depth:0,setSize:this.treeData.length,positionInSet:t}))))}
      </ul>
      </div>
      `,this.shadow,{eventContext:this})})),this.hasRenderedAtLeastOnce=!0,this.scheduledRender=!1,this.enqueuedRender&&(this.enqueuedRender=!1,this.render()))}}customElements.define("devtools-tree-outline",TreeOutline);