import { html, type TemplateResult, nothing } from 'lit'
import { ExposedEntity, type ApiModel } from '@api-client/core/browser.js'
import type { ApiAction } from '@api-client/core/modeling/types.js'
import { state } from 'lit/decorators.js'
import { ApiModelingScreen } from './ApiModelingScreen.js'
import type ApiModelingEntityCardElement from './ApiModelingEntityCard.js'

export interface EditActionEventDetail {
  /**
   * The id of the exposed entity.
   */
  exposedId: string
  /**
   * The kind of operation to edit (list, read, delete, etc)
   */
  kind: string
}

export interface EditExposedEntityEventDetail {
  /**
   * The id of the exposed entity.
   */
  exposedId: string
}

/**
 * A screen that allows the user to expose data entities as a root level actions
 * and configure their properties:
 * - Collection actions (create, list, search)
 * - Resource actions (read, update, delete)
 *
 * @fires exposed-new - When the user wants to expose an entity.
 * @fires action-edit - When the user wants to edit an action.
 * @fires exposed-edit - When the user wants to edit an exposed entity.
 * @fires exposed-remove - When the user wants to remove an exposed entity.
 */
export default class ApiModelingActionsScreen extends ApiModelingScreen {
  /**
   * A flag that determines whether the API has added a data domain.
   */
  @state() accessor hasDomain = false

  /**
   * The list of exposed entities **on the root level**.
   */
  @state() accessor exposed: ExposedEntity[] = []

  protected handleModelChange(model: ApiModel | undefined): void {
    this.hasDomain = false
    this.exposed = []
    if (!model) {
      return
    }

    if (!model.domain) {
      return
    }
    this.hasDomain = true
    this.exposed = model.exposes.filter((expose) => expose.isRoot)
  }

  protected override handleDomainChange(): void {
    // TODO: this is too much for a model change, but temporary we will re-render the screen on any model change
    // until we figure out how to expose a new entity to the API and capture only that change.
    this.handleModelChange(this.model)
    this.requestUpdate()
  }

  /**
   * A handler for the "Add data" button click.
   * It dispatches the `exposeentity` event to notify the parent component that the user wants to expose an entity.
   */
  protected handleAddDataClick(): void {
    this.dispatchEvent(new Event('exposed-new'))
  }

  /**
   * A handler for the "Remove action" button click.
   * It removes the action from the model and updates the exposed list.
   * @param event The event object.
   */
  protected handleRemoveAction(e: Event): void {
    const card = e.currentTarget as ApiModelingEntityCardElement
    const id = card.domainKey
    if (!id) {
      return
    }
    this.dispatchEvent(
      new CustomEvent<EditExposedEntityEventDetail>('exposed-remove', {
        detail: {
          exposedId: id,
        },
      })
    )
  }

  /**
   * Handles the action selection event.
   * It dispatches the `editaction` event to notify the parent component that the user wants to edit an action.
   * @param e The event object.
   */
  protected handleActionSelect(e: CustomEvent<{ kind: string }>): void {
    const card = e.currentTarget as ApiModelingEntityCardElement
    const id = card.domainKey
    if (!id) {
      return
    }
    this.dispatchEvent(
      new CustomEvent<EditActionEventDetail>('action-edit', {
        detail: {
          exposedId: id,
          kind: e.detail.kind,
        },
      })
    )
  }

  protected handleEditAction(e: Event): void {
    const card = e.currentTarget as ApiModelingEntityCardElement
    const id = card.domainKey
    if (!id) {
      return
    }
    this.dispatchEvent(
      new CustomEvent<EditExposedEntityEventDetail>('exposed-edit', {
        detail: {
          exposedId: id,
        },
      })
    )
  }

  override render(): TemplateResult | typeof nothing {
    const model = this.model
    if (!model) {
      return nothing
    }
    return html`
      <div class="config-section horizontal center">
        <div class="section-header">
          <h2 class="headline-medium">API Actions</h2>
          <p class="body-medium">
            You define your API logic by selecting data objects you want to use in your API and adding predefined
            actions to these data.
          </p>
        </div>
        <ui-button @click="${this.handleAddDataClick}">Add data</ui-button>
      </div>
      ${this.hasDomain ? this.renderContent(model) : html`<p>No data domain selected.</p>`}
    `
  }

  private renderContent(model: ApiModel): TemplateResult | typeof nothing {
    if (!this.exposed.length) {
      return this.renderEmptyView()
    }
    return html`<div class="exposed-canvas">
      ${this.exposed.map((expose) => this.renderExposedPath(model, expose))}
    </div>`
  }

  private renderEmptyView(): TemplateResult | typeof nothing {
    return html`
      <section class="empty-screen">
        <div class="icon-wrapper">
          <ui-icon class="icon" icon="api"></ui-icon>
        </div>
        <p class="empty-message body-large">Find your API logic here</p>
        <p class="empty-message body-medium">Add a data entity to define your API behavior.</p>
      </section>
    `
  }

  private renderExposedPath(model: ApiModel, rootExpose: ExposedEntity): TemplateResult | typeof nothing {
    // Render the root expose and all its nested children (by parent key) in a single row.
    // Use the model.exposes list as the source of truth for child relationships.
    const allExposes = model.exposes ?? []
    const row = this.renderExposeTree(model, rootExpose, allExposes)
    return html`<div class="exposure-row">${row}</div>`
  }

  /**
   * Recursively render an expose and its children. Shows entity name only.
   * The model guarantees no infinite cycles, so the UI does not need to track visited nodes.
   */
  private renderExposeTree(
    model: ApiModel,
    rootExpose: ExposedEntity,
    allExposes: ExposedEntity[]
  ): TemplateResult | typeof nothing {
    if (!rootExpose || !rootExpose.key) {
      return nothing
    }

    // Render current entity
    const current = this.renderEntity(model, rootExpose)

    // Find children that reference this expose as parent.
    const children = allExposes.filter((e) => e.parent?.key === rootExpose.key)

    // If no children, render just the entity
    if (!children.length) {
      return html`<div class="expose-node inline">${current}</div>`
    }

    // Render current entity and stack children vertically. Each child row shows
    // the child and its inline descendants (classic tree view).
    return html`${current}
      <div class="children-list">
        ${children.map(
          (child) => html`
            <div class="child-row">
              <span class="expose-sep">→</span>
              ${this.renderExposeTree(model, child, allExposes)}
            </div>
          `
        )}
      </div>`
    // <!-- ${this.renderExposeInline(model, child, allExposes)} -->
  }

  /**
   * Render a node and all its descendants inline (used for child rows).
   */
  private renderExposeInline(
    model: ApiModel,
    expose: ExposedEntity,
    allExposes: ExposedEntity[]
  ): TemplateResult | typeof nothing {
    if (!expose || !expose.key) {
      return nothing
    }
    const current = this.renderEntity(model, expose)

    const children = allExposes.filter((e) => {
      const parentRef = (e as unknown as { parent?: { key?: string } }).parent
      const parentKey = parentRef?.key
      return parentKey === expose.key
    })

    if (!children.length) {
      return html`${current}`
    }

    return html`${current}${children.map(
      (child) => html`
        <span class="expose-sep">→</span>
        ${this.renderExposeInline(model, child, allExposes)}
      `
    )}`
  }

  private renderEntity(model: ApiModel, expose: ExposedEntity): TemplateResult {
    const entity = model.domain?.findEntity(expose.entity.key, expose.entity.domain)
    if (!entity) {
      return this.renderMissingEntity(expose)
    }
    return html`<api-modeling-entity-card
      .model=${model}
      .domainKey=${expose.key}
      ?showCollectionActions="${expose.hasCollection}"
      @select="${this.handleActionSelect}"
      @edit="${this.handleEditAction}"
      @remove="${this.handleRemoveAction}"
    ></api-modeling-entity-card>`
  }

  private renderMissingEntity(expose: ExposedEntity): TemplateResult {
    return html`<div class="entity missing-entity">
      <span class="entity-missing-label">Missing entity:</span>
      <span class="api-action-entity">${expose.key}</span>
    </div>`
  }

  private renderEntityActions(expose: ExposedEntity): TemplateResult {
    const hasActions = !!expose.actions.length
    if (!hasActions) {
      return html`<div class="no-actions">
        <p class="body-medium">No API actions are defined for this object.</p>
      </div>`
    }
    return html`${this.renderCollectionActions(expose)}${this.renderResourceActions(expose)}`
  }

  private renderCollectionActions(expose: ExposedEntity): TemplateResult {
    const actions = expose.actions.filter((action) => ['create', 'list', 'search'].includes(action.kind))
    const hasActions = !!actions.length
    const templates: TemplateResult[] = []
    if (!hasActions) {
      templates.push(html`<p class="body-medium">No API actions are defined for this collection.</p>`)
    } else {
      templates.push(...actions.map((action) => this.renderEntityAction(action)))
    }
    return html`<div class="actions-group">
      <div class="label-small">Collection</div>
      ${templates}
    </div>`
  }

  private renderResourceActions(expose: ExposedEntity): TemplateResult {
    const actions = expose.actions.filter((action) => ['read', 'update', 'delete'].includes(action.kind))
    const hasActions = !!actions.length
    const templates: TemplateResult[] = []
    if (!hasActions) {
      templates.push(html`<p class="body-medium">No API actions are defined for this resource.</p>`)
    } else {
      templates.push(...actions.map((action) => this.renderEntityAction(action)))
    }
    return html`<div class="actions-group">
      <div class="label-small">Resource</div>
      ${templates}
    </div>`
  }

  private renderEntityAction(action: ApiAction): TemplateResult {
    return html`<ui-chip type="assist">${action.kind}</ui-chip>`
  }
}
