import {
  FlowRequestDataEnum,
  FlowResponseDataEnum,
  FlowSourceEnum,
} from '@api-client/core/models/http-flows/HttpFlows.js'
import * as DataSteps from '@api-client/core/models/http-flows/steps/data/index.js'
import * as CoreAssertions from '@api-client/core/models/http-flows/steps/assertion/index.js'
import * as TransformationSteps from '@api-client/core/models/http-flows/steps/transformation/index.js'
import { AmfNamespace } from '@api-client/core/amf/definitions/Namespace.js'
import type { ToBeTypes } from '@api-client/core/models/http-flows/steps/assertion/index.js'
import { type IHttpStep } from '@api-client/core/models/http-flows/HttpStep.js'
import { live } from 'lit/directives/live.js'
import { html, nothing, TemplateResult } from 'lit'
import { ifDefined } from 'lit/directives/if-defined.js'
import ApiElement from '../ApiElement.js'
import type UiChip from '../../md/chip/internals/Chip.js'
import type Input from '../../md/input/Input.js'
import type { UiDialogClosingReason } from '../../md/dialog/internals/Dialog.js'
import '../../md/list/ui-list.js'
import '../../md/list/ui-list-item.js'
import '../../md/dropdown-list/ui-dropdown-list.js'
import '../../md/icons/ui-icon.js'
import '../../md/icon-button/ui-icon-button.js'
import '../../md/text-field/ui-filled-text-field.js'
import '../../md/chip/ui-chip.js'
import '../../md/dialog/ui-dialog.js'
import '../dialog/delete-cookie-action-dialog.js'
import '../dialog/set-cookie-action-dialog.js'

export interface ChipCreateInfo {
  source?: FlowSourceEnum
  data?: FlowRequestDataEnum | FlowResponseDataEnum
  variant?: string
  disabled?: boolean
  title?: string
}

export interface StepsTarget {
  steps: IHttpStep[]
}

/**
 * The base UI class for HTTP flows and assertions
 * since they are practically the same concept.
 */
export abstract class HttpFlowsUi extends ApiElement {
  /**
   * A chip that is currently being dragged in the UI.
   */
  protected draggedChip?: UiChip

  /**
   * Because of the differences in the model between assertions and flows we need to abstract a method
   * to find an object that contains the `steps` so the logic can operate on it.
   *
   * @param assertionOrActionIndex The index of the assertion or the action in the model.
   */
  protected abstract getStepsTarget(assertionOrActionIndex: number): StepsTarget | undefined

  /**
   * A method to read an HttpStep from an event.
   */
  protected abstract stepFromChangeEvent(e: Event): IHttpStep | undefined

  /**
   * Checks whether the dragged element has the required content in the data transfer.
   */
  protected isValidDropTarget(dataTransfer?: DataTransfer | null): boolean {
    if (!dataTransfer) {
      return false
    }
    const { types } = dataTransfer
    return types.includes('flow/group') && types.includes('flow/kind')
  }

  /**
   * Handles the suggestion chip drag start event.
   */
  protected handleChipDragStart(e: DragEvent): void {
    const { dataTransfer } = e
    if (!dataTransfer) {
      return
    }
    const chip = e.target as UiChip
    const { group, kind, source, data, variant } = chip.dataset
    if (!group || !kind) {
      return
    }
    this.illustrateDraggedChip(chip, dataTransfer)
    dataTransfer.setData('flow/group', group)
    dataTransfer.setData('flow/kind', kind)
    dataTransfer.setData(`flow/${group}`, '')
    dataTransfer.setData(`flow/${kind}`, '')
    if (source) {
      dataTransfer.setData('flow/source', source)
    }
    if (data) {
      dataTransfer.setData('flow/data', data)
    }
    if (variant) {
      dataTransfer.setData('flow/variant', variant)
    }
    chip.blur()
  }

  /**
   * Handles the suggestion chip drag end event.
   */
  protected handleChipDragEnd(e: DragEvent): void {
    const { draggedChip } = this
    if (!draggedChip) {
      return
    }
    e.preventDefault()
    draggedChip.parentElement?.removeChild(draggedChip)
  }

  /**
   * When interacting with a chip an MD ripple effect is generated.
   * This causes the default drag image to include the surrounding ripple effect and
   * everything around. This makes a copy of the chip and sets it as the drag image.
   *
   * Note, the copy of the chip has to be added to the DOM and be there for some time
   * so the browser render it and set as the image. Because of that the copy is removed
   * with the drop and not immediately.
   */
  protected illustrateDraggedChip(chip: UiChip, dataTransfer: DataTransfer): void {
    const copy = chip.cloneNode(true) as UiChip
    copy.style.position = 'absolute'
    copy.style.top = `-100px`
    this.draggedChip = copy
    document.body.appendChild(copy)
    dataTransfer.setDragImage(copy, 0, 0)
  }

  /**
   * Removes all `dragover` classes from elements on the shadow DOM.
   */
  protected resetDragIndices(): void {
    const nodes = (this.shadowRoot as ShadowRoot).querySelectorAll('.dragover')
    Array.from(nodes).forEach((n) => n.classList.remove('dragover'))
  }

  /**
   * A handler for the `dragover` event over a valid target.
   * Sets the `dragover` class on the target.
   */
  protected handleTargetDragOver(e: DragEvent): void {
    const { draggedChip } = this
    if (!draggedChip || e.defaultPrevented || !this.isValidDropTarget(e.dataTransfer)) {
      return
    }
    e.preventDefault()
    const dt = e.dataTransfer as DataTransfer
    dt.dropEffect = 'copy'
    const target = e.currentTarget as HTMLElement
    target.classList.add('dragover')
  }

  /**
   * A handler for the `dragleave` event over a valid target.
   * Removes the `dragover` class from the target.
   */
  protected handleTargetDragLeave(e: DragEvent): void {
    if (e.defaultPrevented || !this.isValidDropTarget(e.dataTransfer)) {
      return
    }
    const target = e.currentTarget as HTMLElement
    target.classList.remove('dragover')
  }

  /**
   * Reads the `data-index` and `data-step-index` from the target of the event
   * and calls the `processDrop()` with read values.
   */
  protected handleDrop(e: DragEvent): void {
    const { dataTransfer } = e
    if (!dataTransfer || e.defaultPrevented || !this.isValidDropTarget(dataTransfer)) {
      return
    }
    e.preventDefault()
    this.resetDragIndices()
    const { index, stepIndex } = (e.target as HTMLElement).dataset
    const assertionOrActionIndex = Number(index)
    if (Number.isNaN(assertionOrActionIndex)) {
      return
    }
    let typedStepIndex: number | undefined
    if (stepIndex) {
      const tmp = Number(stepIndex)
      if (!Number.isNaN(tmp)) {
        typedStepIndex = tmp
      }
    }
    this.processDrop(dataTransfer, assertionOrActionIndex, typedStepIndex)
  }

  /**
   * A logic executed when a suggestion chip drop happens on a valid target.
   *
   * @param dataTransfer The event's dataTransfer object
   * @param assertionOrActionIndex Action's or assertion's index where the drop happened.
   * @param stepIndex The detected index of the step. When not set it adds the step at the end.
   */
  protected processDrop(dataTransfer: DataTransfer, assertionOrActionIndex: number, stepIndex?: number): void {
    const group = dataTransfer.getData('flow/group')
    if (!group) {
      return
    }
    if (group === 'data-source') {
      this.processDataSourceDrop(dataTransfer, assertionOrActionIndex, stepIndex)
    } else if (group === 'transformation') {
      this.processTransformationDrop(dataTransfer, assertionOrActionIndex, stepIndex)
    } else if (group === 'assertion') {
      this.processAssertionDrop(dataTransfer, assertionOrActionIndex, stepIndex)
    } else if (group === 'action') {
      this.processActionDrop(dataTransfer, assertionOrActionIndex, stepIndex)
    }
  }

  /**
   * Processes dropping of a `data-source` suggestion chip.
   */
  protected processDataSourceDrop(
    dataTransfer: DataTransfer,
    assertionOrActionIndex: number,
    stepIndex?: number
  ): void {
    const target = this.getStepsTarget(assertionOrActionIndex)
    if (!target) {
      return
    }
    this.setupDataSource(target, dataTransfer, stepIndex)
  }

  /**
   * Processes dropping of a `transformation` suggestion chip.
   */
  protected processTransformationDrop(
    dataTransfer: DataTransfer,
    assertionOrActionIndex: number,
    stepIndex?: number
  ): void {
    const target = this.getStepsTarget(assertionOrActionIndex)
    if (!target) {
      return
    }
    this.setupTransformation(target, dataTransfer, stepIndex)
  }

  /**
   * Processes dropping of a `assertion` suggestion chip.
   */
  protected processAssertionDrop(dataTransfer: DataTransfer, assertionOrActionIndex: number, stepIndex?: number): void {
    const target = this.getStepsTarget(assertionOrActionIndex)
    if (!target) {
      return
    }
    this.setupAssertion(target, dataTransfer, stepIndex)
  }

  /**
   * Processes dropping of a `action` suggestion chip.
   */
  protected processActionDrop(dataTransfer: DataTransfer, assertionOrActionIndex: number, stepIndex?: number): void {
    const target = this.getStepsTarget(assertionOrActionIndex)
    if (!target) {
      return
    }
    this.setupAction(target, dataTransfer, stepIndex)
  }

  protected setupDataSource(target: StepsTarget, dataTransfer: DataTransfer, stepIndex?: number): void {
    const source = dataTransfer.getData('flow/source') as FlowSourceEnum
    const data = dataTransfer.getData('flow/data') as FlowRequestDataEnum | FlowResponseDataEnum
    const step = new DataSteps.ReadDataSourceStep(source, data).toJSON()
    this.appendStep(step, target, stepIndex)
  }

  protected setupTransformation(target: StepsTarget, dataTransfer: DataTransfer, stepIndex?: number): void {
    let step: IHttpStep | undefined
    const kind = dataTransfer.getData('flow/kind')
    if (kind === DataSteps.ReadValueStepKind) {
      step = new DataSteps.ReadValueStep().toJSON()
    } else if (kind === TransformationSteps.AsNumberStepKind) {
      step = new TransformationSteps.AsNumberStep().toJSON()
    } else if (kind === TransformationSteps.RoundStepKind) {
      step = new TransformationSteps.RoundStep().toJSON()
    } else if (kind === TransformationSteps.AsLowerCaseStepKind) {
      step = new TransformationSteps.AsLowerCaseStep().toJSON()
    } else if (kind === TransformationSteps.AsUpperCaseStepKind) {
      step = new TransformationSteps.AsUpperCaseStep().toJSON()
    } else if (kind === TransformationSteps.TrimStepKind) {
      step = new TransformationSteps.TrimStep().toJSON()
    } else if (kind === TransformationSteps.SubstringStepKind) {
      step = new TransformationSteps.SubstringStep().toJSON()
    }
    if (!step) {
      return
    }
    this.appendStep(step, target, stepIndex)
  }

  protected setupAssertion(target: StepsTarget, dataTransfer: DataTransfer, stepIndex?: number): void {
    let step: IHttpStep | undefined
    const kind = dataTransfer.getData('flow/kind')
    if (kind === CoreAssertions.EqualAssertionKind) {
      step = new CoreAssertions.EqualAssertion().toJSON()
    } else if (kind === CoreAssertions.NotEqualAssertionKind) {
      step = new CoreAssertions.NotEqualAssertion().toJSON()
    } else if (kind === CoreAssertions.GreaterThanAssertionKind) {
      const instance = new CoreAssertions.GreaterThanAssertion()
      if (dataTransfer.getData('flow/variant') === 'equal') {
        instance.equal = true
      }
      step = instance.toJSON()
    } else if (kind === CoreAssertions.LessThanAssertionKind) {
      const instance = new CoreAssertions.LessThanAssertion()
      if (dataTransfer.getData('flow/variant') === 'equal') {
        instance.equal = true
      }
      step = instance.toJSON()
    } else if (kind === CoreAssertions.IncludesAssertionKind) {
      step = new CoreAssertions.IncludesAssertion().toJSON()
    } else if (kind === CoreAssertions.NotIncludesAssertionKind) {
      step = new CoreAssertions.NotIncludesAssertion().toJSON()
    } else if (kind === CoreAssertions.MatchesAssertionKind) {
      step = new CoreAssertions.MatchesAssertion().toJSON()
    } else if (kind === CoreAssertions.OkAssertionKind) {
      step = new CoreAssertions.OkAssertion().toJSON()
    } else if (kind === CoreAssertions.NotOkAssertionKind) {
      step = new CoreAssertions.NotOkAssertion().toJSON()
    } else if (kind === CoreAssertions.ToBeAssertionKind) {
      step = new CoreAssertions.ToBeAssertion().toJSON()
    } else if (kind === CoreAssertions.NotToBeAssertionKind) {
      step = new CoreAssertions.NotToBeAssertion().toJSON()
    }
    if (!step) {
      return
    }
    this.appendStep(step, target, stepIndex)
  }

  protected setupAction(target: StepsTarget, dataTransfer: DataTransfer, stepIndex?: number): void {
    let step: IHttpStep | undefined
    const kind = dataTransfer.getData('flow/kind')
    if (kind === DataSteps.SetCookieStepKind) {
      step = new DataSteps.SetCookieStep().toJSON()
    } else if (kind === DataSteps.SetVariableStepKind) {
      step = new DataSteps.SetVariableStep().toJSON()
    } else if (kind === DataSteps.DeleteCookieStepKind) {
      step = new DataSteps.DeleteCookieStep().toJSON()
    } else if (kind === DataSteps.DeleteVariableStepKind) {
      step = new DataSteps.DeleteVariableStep().toJSON()
    }
    if (!step) {
      return
    }
    this.appendStep(step, target, stepIndex)
  }

  protected appendStep(step: IHttpStep, target: StepsTarget, stepIndex?: number): void {
    if (stepIndex === undefined) {
      target.steps.push(step)
    } else if (stepIndex === -1) {
      target.steps.unshift(step)
    } else {
      target.steps.splice(stepIndex, 0, step)
    }
    this.requestUpdate()
    this.notifyChange()
  }

  protected removeStep(target: StepsTarget, stepIndex: number): void {
    target.steps.splice(stepIndex, 1)
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleSubstringStartChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as TransformationSteps.ISubstringStep | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    input.checkValidity()
    step.start = input.valueAsNumber
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleSubstringEndChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as TransformationSteps.ISubstringStep | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    input.checkValidity()
    step.end = input.valueAsNumber
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleRoundPrecisionChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as TransformationSteps.IRoundStep | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    input.checkValidity()
    step.precision = input.valueAsNumber
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleReadValueStepChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as DataSteps.IReadValueStep | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    input.checkValidity()
    step.path = input.value
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleGreaterThanAssertionValueChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as CoreAssertions.IGreaterThanAssertion | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    input.checkValidity()
    step.value = Number(input.value)
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleLessThanAssertionValueChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as CoreAssertions.ILessThanAssertion | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    input.checkValidity()
    step.value = Number(input.value)
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleIncludesAssertionValueChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as CoreAssertions.IIncludesAssertion | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    input.checkValidity()
    step.value = input.value
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleToBeAssertionValueChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as
      | CoreAssertions.IToBeAssertion
      | CoreAssertions.INotToBeAssertion
      | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    input.checkValidity()
    step.value = input.value as ToBeTypes
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleMatchesAssertionValueChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as CoreAssertions.IMatchesAssertion | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    step.value = input.value
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleEqualAssertionValueChange(e: Event): void {
    const input = e.target as Input
    input.checkValidity()
    const assertionOrActionIndex = Number(input.dataset.assertionIndex)
    const stepIndex = Number(input.dataset.stepIndex)
    if (Number.isNaN(assertionOrActionIndex) || Number.isNaN(stepIndex)) {
      return
    }
    const stepsTarget = this.getStepsTarget(assertionOrActionIndex)
    if (!stepsTarget) {
      return
    }
    const step = stepsTarget.steps[stepIndex] as CoreAssertions.IEqualAssertion | undefined
    if (!step) {
      return
    }
    step.value = input.value
    step.dataType = this.detectInputDataType(stepsTarget.steps.slice(0, stepIndex))
    this.requestUpdate()
    this.notifyChange()
  }

  /**
   * Detects the correct data type for the step value based on previous steps.
   *
   * @param steps The steps before the current step
   * @returns The data type for the step in the AMF namespace.
   */
  protected detectInputDataType(steps: IHttpStep[]): string {
    // we reverse the list so we move from the last step.
    const reversed = [...steps].reverse()
    for (const step of reversed) {
      if (step.kind === TransformationSteps.AsNumberStepKind) {
        return AmfNamespace.aml.vocabularies.shapes.number
      }
      if (step.kind === DataSteps.ReadDataSourceStepKind) {
        const typed = step as DataSteps.IReadDataSourceStep
        if (typed.data === FlowResponseDataEnum.status) {
          return AmfNamespace.aml.vocabularies.shapes.number
        }
      }
    }
    return AmfNamespace.w3.xmlSchema.string
  }

  protected validateRegexPattern(e: Event): void {
    const input = e.target as Input
    try {
      RegExp(input.value)
      input.setCustomValidity('')
    } catch {
      input.setCustomValidity('Pattern is invalid')
    }
    input.reportValidity()
  }

  protected handleVariableNameChange(e: Event): void {
    const step = this.stepFromChangeEvent(e) as DataSteps.ISetVariableStep | DataSteps.IDeleteVariableStep | undefined
    if (!step) {
      return
    }
    const input = e.target as Input
    step.name = input.value
    this.requestUpdate()
    this.notifyChange()
  }

  protected handleRemoveStep(e: Event): void {
    const target = e.currentTarget as HTMLElement
    const assertionOrActionIndex = Number(target.dataset.index)
    const stepIndex = Number(target.dataset.stepIndex)
    if (Number.isNaN(assertionOrActionIndex) || Number.isNaN(stepIndex)) {
      return
    }
    const stepsTarget = this.getStepsTarget(assertionOrActionIndex)
    if (!stepsTarget) {
      return
    }
    this.removeStep(stepsTarget, stepIndex)
  }

  protected handleConfigureDeleteCookieStep(e: Event): void {
    const info = this.getEventStepLocation(e)
    if (!info) {
      return
    }
    const stepsTarget = this.getStepsTarget(info.actionIndex)
    if (!stepsTarget) {
      return
    }
    const step = stepsTarget.steps[info.stepIndex] as DataSteps.IDeleteCookieStep
    const dialog = document.createElement('delete-cookie-action-dialog')
    dialog.step = step
    dialog.modal = true
    dialog.open = true
    document.body.appendChild(dialog)
    dialog.addEventListener('close', (ev: Event): void => {
      document.parentElement?.removeChild(dialog)
      const typed = ev as CustomEvent<UiDialogClosingReason>
      if (typed.detail.cancelled) {
        return
      }
      const value = typed.detail.value as DataSteps.IDeleteCookieStep

      if (!stepsTarget) {
        return
      }
      stepsTarget.steps[info.stepIndex] = value
      this.notifyChange()
      this.requestUpdate()
    })
  }

  protected handleConfigureSetCookieStep(e: Event): void {
    const info = this.getEventStepLocation(e)
    if (!info) {
      return
    }
    const stepsTarget = this.getStepsTarget(info.actionIndex)
    if (!stepsTarget) {
      return
    }
    const step = stepsTarget.steps[info.stepIndex] as DataSteps.ISetCookieStep
    const dialog = document.createElement('set-cookie-action-dialog')
    dialog.step = step
    dialog.modal = true
    dialog.open = true
    document.body.appendChild(dialog)
    dialog.addEventListener('close', (ev: Event): void => {
      document.parentElement?.removeChild(dialog)
      const typed = ev as CustomEvent<UiDialogClosingReason>
      if (typed.detail.cancelled) {
        return
      }
      const value = typed.detail.value as DataSteps.ISetCookieStep
      if (!stepsTarget) {
        return
      }
      stepsTarget.steps[info.stepIndex] = value
      this.notifyChange()
      this.requestUpdate()
    })
  }

  protected getEventStepLocation(e: Event): { actionIndex: number; stepIndex: number } | undefined {
    const button = e.currentTarget as HTMLElement
    const { index: ai, stepIndex: si } = button.dataset
    const actionIndex = Number(ai)
    const stepIndex = Number(si)
    if (Number.isNaN(actionIndex) || Number.isNaN(stepIndex)) {
      return undefined
    }
    return {
      actionIndex,
      stepIndex,
    }
  }

  protected renderBlockChip(label: string, group: string, kind: string, opts: ChipCreateInfo = {}): TemplateResult {
    const { data, disabled, source, variant, title } = opts
    return html`
      <ui-chip
        draggable="true"
        data-group="${group}"
        data-kind="${kind}"
        data-source="${ifDefined(source)}"
        data-data="${ifDefined(data)}"
        data-variant="${ifDefined(variant)}"
        @dragstart="${this.handleChipDragStart}"
        @dragend="${this.handleChipDragEnd}"
        ?disabled="${disabled}"
        title="${ifDefined(title)}"
        >${label}</ui-chip
      >
    `
  }

  protected renderDropSpacer(assertionOrActionIndex: number, stepIndex: number): TemplateResult {
    return html`<div
      class="drop-target spacer"
      data-index="${assertionOrActionIndex}"
      data-step-index="${stepIndex}"
      @drop="${this.handleDrop}"
      @dragover="${this.handleTargetDragOver}"
      @dragleave="${this.handleTargetDragLeave}"
    ></div>`
  }

  protected renderStepPill(
    label: string,
    assertionOrActionIndex: number,
    stepIndex: number,
    suffix?: TemplateResult
  ): TemplateResult {
    return html`
      <span class="step-pill with-icon">
        ${label} ${suffix || nothing}
        <ui-icon-button
          title="Remove"
          aria-label="Deletes this step"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </span>
    `
  }

  protected renderSteps(item: StepsTarget, assertionOrActionIndex: number): TemplateResult {
    const content =
      Array.isArray(item.steps) && !!item.steps.length
        ? this.renderStepsList(item.steps, assertionOrActionIndex)
        : this.renderEmptyStep(assertionOrActionIndex)
    return html`
      <div
        class="steps"
        data-index="${assertionOrActionIndex}"
        @drop="${this.handleDrop}"
        @dragover="${this.handleTargetDragOver}"
        @dragleave="${this.handleTargetDragLeave}"
      >
        ${content}
      </div>
    `
  }

  protected renderEmptyStep(actionIndex: number): TemplateResult {
    return html`
      <span
        class="step-pill empty drop-target space-step-left"
        data-index="${actionIndex}"
        @drop="${this.handleDrop}"
        @dragover="${this.handleTargetDragOver}"
        >Empty slot</span
      >
      <span
        class="drop-info drop-target space-step-left"
        data-index="${actionIndex}"
        @drop="${this.handleDrop}"
        @dragover="${this.handleTargetDragOver}"
        >Drop an action here</span
      >
    `
  }

  protected renderStepsList(steps: IHttpStep[], assertionOrActionIndex: number): TemplateResult[] {
    const items: TemplateResult[] = []
    steps.forEach((item, stepIndex) => {
      const content = this.renderStep(item, assertionOrActionIndex, stepIndex)
      if (content) {
        items.push(html`
          <div class="step-unit">${this.renderDropSpacer(assertionOrActionIndex, stepIndex)} ${content}</div>
        `)
      }
    })
    return items
  }

  protected renderStep(item: IHttpStep, assertionOrActionIndex: number, stepIndex: number): TemplateResult | undefined {
    const { kind } = item
    switch (kind) {
      // actions
      case DataSteps.ReadDataSourceStepKind:
        return this.renderReadDataSourceStep(item, assertionOrActionIndex, stepIndex)
      case DataSteps.ReadValueStepKind:
        return this.renderReadValueStep(item as DataSteps.IReadValueStep, assertionOrActionIndex, stepIndex)
      case DataSteps.DeleteCookieStepKind:
        return this.renderDeleteCookieStep(item as DataSteps.IDeleteCookieStep, assertionOrActionIndex, stepIndex)
      case DataSteps.SetCookieStepKind:
        return this.renderSetCookieStep(item as DataSteps.ISetCookieStep, assertionOrActionIndex, stepIndex)
      case DataSteps.SetVariableStepKind:
        return this.renderSetVariableStep(item as DataSteps.ISetVariableStep, assertionOrActionIndex, stepIndex)
      case DataSteps.DeleteVariableStepKind:
        return this.renderDeleteVariableStep(item as DataSteps.IDeleteVariableStep, assertionOrActionIndex, stepIndex)

      // transformations
      case TransformationSteps.AsNumberStepKind:
        return this.renderStepPill('As number', assertionOrActionIndex, stepIndex)
      case TransformationSteps.AsLowerCaseStepKind:
        return this.renderStepPill('To lower case', assertionOrActionIndex, stepIndex)
      case TransformationSteps.AsUpperCaseStepKind:
        return this.renderStepPill('To upper case', assertionOrActionIndex, stepIndex)
      case TransformationSteps.TrimStepKind:
        return this.renderStepPill('Trim', assertionOrActionIndex, stepIndex)
      case TransformationSteps.RoundStepKind:
        return this.renderRoundStep(item as TransformationSteps.IRoundStep, assertionOrActionIndex, stepIndex)
      case TransformationSteps.SubstringStepKind:
        return this.renderSubstringStep(item as TransformationSteps.ISubstringStep, assertionOrActionIndex, stepIndex)

      // assertions
      case CoreAssertions.EqualAssertionKind:
        return this.renderEqualAssertion(item as CoreAssertions.IEqualAssertion, assertionOrActionIndex, stepIndex)
      case CoreAssertions.NotEqualAssertionKind:
        return this.renderNotEqualAssertion(
          item as CoreAssertions.INotEqualAssertion,
          assertionOrActionIndex,
          stepIndex
        )
      case CoreAssertions.GreaterThanAssertionKind:
        return this.renderGreaterThanAssertion(
          item as CoreAssertions.IGreaterThanAssertion,
          assertionOrActionIndex,
          stepIndex
        )
      case CoreAssertions.LessThanAssertionKind:
        return this.renderLessThanAssertion(
          item as CoreAssertions.ILessThanAssertion,
          assertionOrActionIndex,
          stepIndex
        )
      case CoreAssertions.IncludesAssertionKind:
        return this.renderIncludesAssertion(
          item as CoreAssertions.IIncludesAssertion,
          assertionOrActionIndex,
          stepIndex
        )
      case CoreAssertions.NotIncludesAssertionKind:
        return this.renderNotIncludesAssertion(
          item as CoreAssertions.INotIncludesAssertion,
          assertionOrActionIndex,
          stepIndex
        )
      case CoreAssertions.MatchesAssertionKind:
        return this.renderMatchesAssertion(item as CoreAssertions.IMatchesAssertion, assertionOrActionIndex, stepIndex)
      case CoreAssertions.OkAssertionKind:
        return this.renderStepPill('Is OK', assertionOrActionIndex, stepIndex)
      case CoreAssertions.NotOkAssertionKind:
        return this.renderStepPill('Is not OK', assertionOrActionIndex, stepIndex)
      case CoreAssertions.ToBeAssertionKind:
        return this.renderToBeAssertion(item as CoreAssertions.IToBeAssertion, assertionOrActionIndex, stepIndex)
      case CoreAssertions.NotToBeAssertionKind:
        return this.renderNotToBeAssertion(item as CoreAssertions.INotToBeAssertion, assertionOrActionIndex, stepIndex)

      default:
        // eslint-disable-next-line no-console
        console.warn(`Unsupported step`, kind)
        return undefined
    }
  }

  protected renderReadDataSourceStep(
    item: DataSteps.IReadDataSourceStep,
    actionIndex: number,
    stepIndex: number
  ): TemplateResult | undefined {
    const { data, source } = item
    if (!data || !source) {
      // invalid configuration
      return undefined
    }
    let label: string | undefined
    if (source === FlowSourceEnum.response) {
      if (data === FlowResponseDataEnum.url) {
        label = 'Response URL'
      } else if (data === FlowResponseDataEnum.headers) {
        label = 'Response headers'
      } else if (data === FlowResponseDataEnum.status) {
        label = 'Status code'
      } else if (data === FlowResponseDataEnum.statusText) {
        label = 'Status message'
      } else if (data === FlowResponseDataEnum.body) {
        label = 'Response body'
      }
    } else if (source === FlowSourceEnum.request) {
      if (data === FlowRequestDataEnum.url) {
        label = 'Request URL'
      } else if (data === FlowRequestDataEnum.headers) {
        label = 'Request headers'
      } else if (data === FlowRequestDataEnum.body) {
        label = 'Request body'
      } else if (data === FlowRequestDataEnum.method) {
        label = 'Request method'
      }
    }
    if (!label) {
      return undefined
    }
    return this.renderStepPill(label, actionIndex, stepIndex)
  }

  protected renderReadValueStep(
    item: DataSteps.IReadValueStep,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const pill = this.renderStepPill('Read value', assertionOrActionIndex, stepIndex)
    return html`
      ${pill}
      <ui-filled-text-field
        name="value.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        label="Path to value"
        required
        .value="${live(item.path || '')}"
        class="space-step-left"
        @change="${this.handleReadValueStepChange}"
      ></ui-filled-text-field>
    `
  }

  protected renderDeleteCookieStep(
    item: DataSteps.IDeleteCookieStep,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { name, url } = item
    let label: string
    if (name && url) {
      label = 'by name and URL'
    } else if (name) {
      label = 'by name'
    } else if (url) {
      label = 'by URL'
    } else {
      label = 'all cookies'
    }

    const suffix = html`
      <ui-icon-button
        title="Configure this step"
        aria-label="Configure this step"
        data-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        @click="${this.handleConfigureDeleteCookieStep}"
        ><ui-icon icon="tune" role="presentation"></ui-icon
      ></ui-icon-button>
    `
    const pill = this.renderStepPill(`Delete cookie (${label})`, assertionOrActionIndex, stepIndex, suffix)
    return pill
  }

  protected renderSetCookieStep(
    item: DataSteps.ISetCookieStep,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const suffix = html`
      <ui-icon-button
        title="Configure this step"
        aria-label="Configure this step"
        data-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        @click="${this.handleConfigureSetCookieStep}"
        ><ui-icon icon="tune" role="presentation"></ui-icon
      ></ui-icon-button>
    `
    const pill = this.renderStepPill('Set cookie', assertionOrActionIndex, stepIndex, suffix)
    return pill
  }

  protected renderSetVariableStep(
    item: DataSteps.ISetVariableStep,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { name = '' } = item
    const pill = this.renderStepPill('Set variable', assertionOrActionIndex, stepIndex)
    return html`
      ${pill}
      <ui-filled-text-field
        name="variable.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        label="Variable name"
        required
        .value="${live(name)}"
        @change="${this.handleVariableNameChange}"
        class="space-step-left"
        pattern="[a-zA-Z0-9_]+"
        aria-label="Variable name"
      ></ui-filled-text-field>
    `
  }

  protected renderDeleteVariableStep(
    item: DataSteps.IDeleteVariableStep,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { name = '' } = item
    const pill = this.renderStepPill('Delete variable', assertionOrActionIndex, stepIndex)
    return html`
      ${pill}
      <ui-filled-text-field
        name="variable.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        label="Variable name"
        required
        .value="${live(name)}"
        @change="${this.handleVariableNameChange}"
        class="space-step-left"
        pattern="[a-zA-Z0-9_]+"
        aria-label="Variable name"
      ></ui-filled-text-field>
    `
  }

  protected renderRoundStep(
    item: TransformationSteps.IRoundStep,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const pill = this.renderStepPill('Round', assertionOrActionIndex, stepIndex)
    const { precision = 0 } = item
    return html`
      ${pill}
      <ui-filled-text-field
        name="precision.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        label="Precision"
        required
        .value="${live(String(precision))}"
        @change="${this.handleRoundPrecisionChange}"
        class="space-step-left number-input"
        type="number"
        min="0"
        max="100"
        step="1"
        title="The number of digits to appear after the decimal point. Should be a value between 0 and 100, inclusive."
      ></ui-filled-text-field>
    `
  }

  protected renderSubstringStep(
    item: TransformationSteps.ISubstringStep,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const pill = this.renderStepPill('Substring', assertionOrActionIndex, stepIndex)
    const { start = 0, end } = item
    return html`
      ${pill}
      <ui-filled-text-field
        name="start.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        label="Start"
        required
        .value="${live(String(start))}"
        @change="${this.handleSubstringStartChange}"
        class="space-step-left number-input"
        type="number"
        step="1"
        title="The index of the first character to include in the returned substring."
      ></ui-filled-text-field>

      <ui-filled-text-field
        name="end.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        label="End"
        .value="${live(end ? String(end) : '')}"
        @change="${this.handleSubstringEndChange}"
        class="space-step-left number-input"
        type="number"
        step="1"
        title="The index of the first character to exclude from the returned substring."
      ></ui-filled-text-field>
    `
  }

  protected renderEqualAssertion(
    item: CoreAssertions.IEqualAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    return html`
      <ui-filled-text-field
        name="equal.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.EqualAssertionKind}"
        label="Equal to"
        required
        .value="${live(item.value || '')}"
        @change="${this.handleEqualAssertionValueChange}"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderNotEqualAssertion(
    item: CoreAssertions.INotEqualAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    return html`
      <ui-filled-text-field
        name="not-equal.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.NotEqualAssertionKind}"
        label="Not equal to"
        required
        .value="${live(item.value || '')}"
        @change="${this.handleEqualAssertionValueChange}"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderGreaterThanAssertion(
    item: CoreAssertions.IGreaterThanAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { equal, value } = item
    const label = equal ? 'Greater than equal' : 'Greater than'
    const effectiveValue = typeof value === 'number' ? String(value) : ''
    return html`
      <ui-filled-text-field
        name="value.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.GreaterThanAssertionKind}"
        data-variant="${equal ? 'equal' : ''}"
        label="${label}"
        required
        .value="${live(effectiveValue)}"
        @change="${this.handleGreaterThanAssertionValueChange}"
        inputMode="numeric"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderLessThanAssertion(
    item: CoreAssertions.ILessThanAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { equal, value } = item
    const label = equal ? 'Less than equal' : 'Less than'
    const effectiveValue = typeof value === 'number' ? String(value) : ''
    return html`
      <ui-filled-text-field
        name="value.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.LessThanAssertionKind}"
        data-variant="${equal ? 'equal' : ''}"
        label="${label}"
        required
        .value="${live(effectiveValue)}"
        @change="${this.handleLessThanAssertionValueChange}"
        inputMode="numeric"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderIncludesAssertion(
    item: CoreAssertions.IIncludesAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { value } = item
    return html`
      <ui-filled-text-field
        name="includes.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.IncludesAssertionKind}"
        label="Includes value"
        required
        .value="${live(value || '')}"
        @change="${this.handleIncludesAssertionValueChange}"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderNotIncludesAssertion(
    item: CoreAssertions.INotIncludesAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { value } = item
    return html`
      <ui-filled-text-field
        name="not-includes.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.NotIncludesAssertionKind}"
        label="Not includes value"
        required
        .value="${live(value || '')}"
        @change="${this.handleIncludesAssertionValueChange}"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderMatchesAssertion(
    item: CoreAssertions.IMatchesAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { value } = item
    return html`
      <ui-filled-text-field
        name="matches.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.MatchesAssertionKind}"
        label="Matches regex"
        required
        .value="${live(value || '')}"
        @change="${this.handleMatchesAssertionValueChange}"
        @input="${this.validateRegexPattern}"
        invalidText="Pattern is invalid"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderToBeAssertion(
    item: CoreAssertions.IToBeAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { value } = item
    const types = Object.values(CoreAssertions.ToBeTypes)
    return html`
      <ui-filled-text-field
        name="to-be.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.ToBeAssertionKind}"
        label="To be..."
        required
        .value="${live(value || '')}"
        @change="${this.handleToBeAssertionValueChange}"
        .list="${types}"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderNotToBeAssertion(
    item: CoreAssertions.INotToBeAssertion,
    assertionOrActionIndex: number,
    stepIndex: number
  ): TemplateResult {
    const { value } = item
    const types = Object.values(CoreAssertions.ToBeTypes)
    return html`
      <ui-filled-text-field
        name="not-to-be.${assertionOrActionIndex}.${stepIndex}"
        data-assertion-index="${assertionOrActionIndex}"
        data-step-index="${stepIndex}"
        data-kind="${CoreAssertions.NotToBeAssertionKind}"
        label="Not to be..."
        required
        .value="${live(value || '')}"
        @change="${this.handleToBeAssertionValueChange}"
        .list="${types}"
      >
        <ui-icon-button
          slot="suffix"
          title="Remove assertion"
          aria-label="Deletes this assertion"
          @click="${this.handleRemoveStep}"
          data-index="${assertionOrActionIndex}"
          data-step-index="${stepIndex}"
          ><ui-icon icon="close" role="presentation"></ui-icon
        ></ui-icon-button>
      </ui-filled-text-field>
    `
  }

  protected renderResponseDataSourceSuggestions(disabled = false): TemplateResult {
    return html`
      <details class="action-group" open>
        <summary>Data Source</summary>
        <div class="group-content">
          ${this.renderBlockChip('Status code', 'data-source', DataSteps.ReadDataSourceStepKind, {
            source: FlowSourceEnum.response,
            data: FlowResponseDataEnum.status,
            disabled,
            title: 'Reads the response status code and passes it to the next step.',
          })}
          ${this.renderBlockChip('Response headers', 'data-source', DataSteps.ReadDataSourceStepKind, {
            source: FlowSourceEnum.response,
            data: FlowResponseDataEnum.headers,
            disabled,
            title: 'Reads the response headers and passes them to the next step.',
          })}
          ${this.renderBlockChip('Response body', 'data-source', DataSteps.ReadDataSourceStepKind, {
            source: FlowSourceEnum.response,
            data: FlowResponseDataEnum.body,
            disabled,
            title: 'Reads the response body and passes it to the next step.',
          })}
          ${this.renderBlockChip('Status message', 'data-source', DataSteps.ReadDataSourceStepKind, {
            source: FlowSourceEnum.response,
            data: FlowResponseDataEnum.statusText,
            disabled,
            title: 'Reads the response status message and passes it to the next step.',
          })}
        </div>
      </details>
    `
  }

  protected renderRequestDataSourceSuggestions(disabled = false): TemplateResult {
    return html`
      <details class="action-group" open>
        <summary>Data Source</summary>
        <div class="group-content">
          ${this.renderBlockChip('Request headers', 'data-source', DataSteps.ReadDataSourceStepKind, {
            source: FlowSourceEnum.request,
            data: FlowRequestDataEnum.headers,
            disabled,
            title: 'Reads the request headers and passes them to the next step.',
          })}
          ${this.renderBlockChip('Request body', 'data-source', DataSteps.ReadDataSourceStepKind, {
            source: FlowSourceEnum.request,
            data: FlowRequestDataEnum.body,
            disabled,
            title: 'Reads the request body and passes it to the next step.',
          })}
          ${this.renderBlockChip('Request method', 'data-source', DataSteps.ReadDataSourceStepKind, {
            source: FlowSourceEnum.request,
            data: FlowRequestDataEnum.method,
            disabled,
            title: 'Reads the request method and passes it to the next step.',
          })}
        </div>
      </details>
    `
  }

  protected renderTransformationSuggestions(disabled = false): TemplateResult {
    return html`
      <details class="action-group" open>
        <summary>Transformation</summary>
        <div class="group-content">
          ${this.renderBlockChip('Read value', 'transformation', DataSteps.ReadValueStepKind, {
            disabled,
            title: 'Reads a value from the previous step. Configure the path to the value.',
          })}
          ${this.renderBlockChip('As number', 'transformation', TransformationSteps.AsNumberStepKind, {
            disabled,
            title: 'Transforms the value received from the previous step to a number.',
          })}
          ${this.renderBlockChip('Round', 'transformation', TransformationSteps.RoundStepKind, {
            disabled,
            title: 'Rounds the value received from the previous step with a precision',
          })}
          ${this.renderBlockChip('To lower case', 'transformation', TransformationSteps.AsLowerCaseStepKind, {
            disabled,
            title: 'Transforms the value received from the previous step to a lower case string',
          })}
          ${this.renderBlockChip('To upper case', 'transformation', TransformationSteps.AsUpperCaseStepKind, {
            disabled,
            title: 'Transforms the value received from the previous step to an upper case string',
          })}
          ${this.renderBlockChip('Trim', 'transformation', TransformationSteps.TrimStepKind, {
            disabled,
            title: 'Treats the value received from the previous step as string as trims the whitespace.',
          })}
          ${this.renderBlockChip('Substring', 'transformation', TransformationSteps.SubstringStepKind, {
            disabled,
            title: 'Passes a sub string of the current value to the next step.',
          })}
        </div>
      </details>
    `
  }

  protected renderActionSuggestions(disabled = false): TemplateResult {
    return html`
      <details class="action-group" open>
        <summary>Actions</summary>
        <div class="group-content">
          ${this.renderBlockChip('Set cookie', 'action', DataSteps.SetCookieStepKind, {
            disabled,
            title: 'Sets and stores a cookie',
          })}
          ${this.renderBlockChip('Delete cookie', 'action', DataSteps.DeleteCookieStepKind, {
            disabled,
            title: 'Deletes a cookie.',
          })}
          ${this.renderBlockChip('Set variable', 'action', DataSteps.SetVariableStepKind, {
            disabled,
            title: 'Sets a variable during the execution time. It does not store the value.',
          })}
          ${this.renderBlockChip('Unset variable', 'action', DataSteps.DeleteVariableStepKind, {
            disabled,
            title: 'Clears a variable during the execution time. It does not store the value.',
          })}
        </div>
      </details>
    `
  }

  protected renderAssertionSuggestions(disabled = false): TemplateResult {
    return html`
      <details class="action-group" open>
        <summary>Assertions</summary>
        <div class="group-content">
          ${this.renderBlockChip('Equal', 'assertion', CoreAssertions.EqualAssertionKind, { disabled })}
          ${this.renderBlockChip('Not equal', 'assertion', CoreAssertions.NotEqualAssertionKind, { disabled })}
          ${this.renderBlockChip('Greater than', 'assertion', CoreAssertions.GreaterThanAssertionKind, { disabled })}
          ${this.renderBlockChip('Greater than equal', 'assertion', CoreAssertions.GreaterThanAssertionKind, {
            disabled,
            variant: 'equal',
          })}
          ${this.renderBlockChip('Less than', 'assertion', CoreAssertions.LessThanAssertionKind, { disabled })}
          ${this.renderBlockChip('Less than equal', 'assertion', CoreAssertions.LessThanAssertionKind, {
            disabled,
            variant: 'equal',
          })}
          ${this.renderBlockChip('Includes', 'assertion', CoreAssertions.IncludesAssertionKind, { disabled })}
          ${this.renderBlockChip('Not includes', 'assertion', CoreAssertions.NotIncludesAssertionKind, { disabled })}
          ${this.renderBlockChip('Matches', 'assertion', CoreAssertions.MatchesAssertionKind, { disabled })}
          ${this.renderBlockChip('OK', 'assertion', CoreAssertions.OkAssertionKind, { disabled })}
          ${this.renderBlockChip('Not OK', 'assertion', CoreAssertions.NotOkAssertionKind, { disabled })}
          ${this.renderBlockChip('To be...', 'assertion', CoreAssertions.ToBeAssertionKind, { disabled })}
          ${this.renderBlockChip('Not to be...', 'assertion', CoreAssertions.NotToBeAssertionKind, { disabled })}
        </div>
      </details>
    `
  }
}
