import { property, query } from "lit/decorators.js";
import { LitElementWw } from "@webwriter/lit";
import {
  CSSResult, html, LitElement, TemplateResult,
} from "lit";
import HelpCircleIcon from "@tabler/icons/outline/help-circle.svg";

import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js";
import SlCheckbox from "@shoelace-style/shoelace/dist/components/checkbox/checkbox.component.js";
import SlOption from "@shoelace-style/shoelace/dist/components/option/option.component.js";
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
import SlTooltip from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.component.js";
import SlTree from "@shoelace-style/shoelace/dist/components/tree/tree.component.js";
import SlTreeItem from "@shoelace-style/shoelace/dist/components/tree-item/tree-item.component.js";

import { styles } from "./options.styles";
import { msg } from "../../locales";
import { OptionsChangeEvent, StageType } from "../../types";
import { BlockTypes, SelectedBlocks } from "../../lib/blockly";

enum TreeItemState {
  UNSELECTED,
  INDETERMINATE,
  SELECTED,
}

/**
 * The options component.
 */
export class Options extends LitElementWw {
  /**
   * Whether the widget are readonly.
   */
  @property({ type: Number })
  public accessor readonly: 0 | 1;

  /**
   * The selected stage type.
   */
  @property({ type: String })
  public accessor stageType: StageType;

  /**
   * The selected blocks.
   */
  @property({ type: Array })
  public accessor selectedBlocks: SelectedBlocks;

  /**
   * The available blocks.
   */
  @property({ type: Array })
  public accessor availableBlocks: BlockTypes[];

  /**
   * @inheritDoc
   */
  public static get scopedElements(): Record<string, typeof LitElement> {
    return {
      "sl-checkbox": SlCheckbox,
      "sl-select": SlSelect,
      "sl-option": SlOption,
      "sl-tree": SlTree,
      "sl-tree-item": SlTreeItem,
      "sl-tooltip": SlTooltip,
      "sl-icon": SlIcon,
    };
  }

  /**
   * @inheritDoc
   */
  public static get styles(): CSSResult[] {
    return [
      styles,
    ];
  }

  /**
   * @inheritDoc
   */
  public connectedCallback(): void {
    super.connectedCallback();
  }

  /**
   * Determines in which state a tree item folder should be.
   * @param category The category of the tree item.
   * @param blocks The blocks under the category.
   * @param selectedBlocksSet The set of selected blocks.
   * @returns True if the tree item should be indeterminate, false otherwise.
   * @private
   */
  private getTreeItemFolderState(category: string, blocks: string[], selectedBlocksSet: Set<BlockTypes>): TreeItemState {
    const totalBlocks = blocks.length;
    const selectedBlocks = blocks.filter((name) => selectedBlocksSet.has(`${category}:${name}` as BlockTypes)).length;

    if (selectedBlocks === 0) {
      return TreeItemState.UNSELECTED;
    }
    if (selectedBlocks === totalBlocks) {
      return TreeItemState.SELECTED;
    }
    return TreeItemState.INDETERMINATE;
  }

  /**
   * @inheritDoc
   */
  public render(): TemplateResult {
    const selectedBlocksSet = new Set(this.selectedBlocks);
    const availableBlocksMap = new Map<string, string[]>();

    this.availableBlocks.forEach((block: BlockTypes) => {
      const [category, name] = block.split(":") as [string, string];
      if (!availableBlocksMap.has(category)) {
        availableBlocksMap.set(category, []);
      }
      if (name) {
        availableBlocksMap.get(category)!.push(name);
      }
    });

    return html`
        <div class="group">
            <sl-checkbox .checked=${this.readonly === 1} @sl-change=${this.handleReadonlyChange}>
              <span class="label">
                ${msg("OPTIONS.READONLY")}
                <sl-tooltip content=${msg("OPTIONS.READONLY_TOOLTIP")}>
                    <sl-icon src="${HelpCircleIcon}"></sl-icon>
                </sl-tooltip>
              </span>
            </sl-checkbox>
        </div>
        <div class="group">
            <span class="label">
              ${msg("OPTIONS.STAGE")}
              <sl-tooltip content=${msg("OPTIONS.STAGE_TOOLTIP")}>
                  <sl-icon src="${HelpCircleIcon}"></sl-icon>
              </sl-tooltip>
            </span>
            <sl-select value=${this.stageType} @sl-change=${this.handleStageTypeChange} hoist>
                ${Object.values(StageType).map((type) => html`
                    <sl-option value=${type}>
                        ${msg(`OPTIONS.STAGE_TYPES.${type.toUpperCase() as Uppercase<StageType>}`)}
                    </sl-option>
                `)}
            </sl-select>
        </div>
        <div class="group">
            <span class="label">
              ${msg("OPTIONS.AVAILABLE_BLOCKS")}
              <sl-tooltip content=${msg("OPTIONS.AVAILABLE_BLOCKS_TOOLTIP")}>
                  <sl-icon src="${HelpCircleIcon}"></sl-icon>
              </sl-tooltip>
            </span>
            <sl-tree selection="multiple" @sl-selection-change=${this.handleSelectedBlocksChange}>
                <sl-tree-item expanded>
                    all
                    ${Array.from(availableBlocksMap.entries()).map(([category, blocks]) => {
    if (blocks.length === 0) {
      return html`
                  <sl-tree-item ?selected=${selectedBlocksSet.has(category as BlockTypes)}
                                data-block-key=${`${category}`}>
                      ${category}
                  </sl-tree-item>
      `;
    }
    const folderState = this.getTreeItemFolderState(category, blocks, selectedBlocksSet);
    return html`
                  <sl-tree-item 
                    .indeterminate=${folderState === TreeItemState.INDETERMINATE}
                    ?selected=${folderState === TreeItemState.SELECTED}>
                      ${category}
                      ${blocks.map((name) => html`
                      <sl-tree-item ?selected=${selectedBlocksSet.has(`${category}:${name}` as BlockTypes)}
                                    data-block-key=${`${category}:${name}`}>
                          ${name}
                      </sl-tree-item>
                      `)}
                  </sl-tree-item>
                `;
  })}
                </sl-tree-item>
            </sl-tree>
        </div>
    `;
  }

  /**
   * Handles the readonly change event.
   * @param event The change event.
   * @private
   */
  private handleReadonlyChange(event): void {
    const changeEvent = new OptionsChangeEvent({
      readonly: event.target.checked ? 1 : 0,
    });
    this.dispatchEvent(changeEvent);
  }

  /**
   * Handles the stage type change event.
   * @param event The change event.
   * @private
   */
  private handleStageTypeChange(event): void {
    const changeEvent = new OptionsChangeEvent({
      stageType: event.target.value,
    });
    this.dispatchEvent(changeEvent);
  }

  /**
   * Handles the selected blocks change event.
   * @param event The change event.
   * @private
   */
  private handleSelectedBlocksChange(event: CustomEvent<{ selection: SlTreeItem[] }>): void {
    const selectedBlocks = event.detail.selection
      .filter((item) => item.getAttribute("data-block-key"))
      .map((item) => item.getAttribute("data-block-key") as BlockTypes);

    const changeEvent = new OptionsChangeEvent({
      selectedBlocks,
    });
    this.dispatchEvent(changeEvent);
  }
}
