import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
  mdiDotsVertical,
  mdiDownload,
  mdiInformationOutline,
  mdiPencil,
  mdiRayEndArrow,
  mdiRayStartArrow,
  mdiRefresh,
} from "@mdi/js";
import type { CSSResultGroup, TemplateResult, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-tab-group";
import "../../../components/ha-tab-group-tab";
import "../../../components/trace/ha-trace-blueprint-config";
import "../../../components/trace/ha-trace-config";
import "../../../components/trace/ha-trace-logbook";
import "../../../components/trace/ha-trace-path-details";
import "../../../components/trace/ha-trace-timeline";
import "../../../components/trace/hat-script-graph";
import type {
  HatScriptGraph,
  NodeInfo,
} from "../../../components/trace/hat-script-graph";
import type { AutomationEntity } from "../../../data/automation";
import { fireRelatedContext, fullEntitiesContext } from "../../../data/context";
import type { EntityRegistryEntry } from "../../../data/entity/entity_registry";
import type { LogbookEntry } from "../../../data/logbook";
import { getLogbookDataForContext } from "../../../data/logbook";
import type {
  AutomationTrace,
  AutomationTraceExtended,
} from "../../../data/trace";
import { loadTrace, loadTraces } from "../../../data/trace";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import "../../../components/ha-trace-picker";

const TABS = ["details", "timeline", "logbook", "automation_config"] as const;

@customElement("ha-automation-trace")
export class HaAutomationTrace extends LitElement {
  @property({ attribute: false }) public hass!: HomeAssistant;

  @property({ attribute: false }) public automationId!: string;

  @property({ attribute: false }) public automations!: AutomationEntity[];

  @property({ attribute: "is-wide", type: Boolean }) public isWide = false;

  @property({ type: Boolean, reflect: true }) public narrow = false;

  @property({ attribute: false }) public route!: Route;

  @state()
  @consume({ context: fullEntitiesContext, subscribe: true })
  _entityRegistry?: EntityRegistryEntry[];

  @state() private _entityId?: string;

  @state() private _traces?: AutomationTrace[];

  @state() private _runId?: string;

  @state() private _selected?: NodeInfo;

  @state() private _trace?: AutomationTraceExtended;

  @state() private _logbookEntries?: LogbookEntry[];

  @state() private _view: (typeof TABS)[number] | "blueprint" = "details";

  @query("hat-script-graph") private _graph?: HatScriptGraph;

  protected render(): TemplateResult {
    const stateObj = this._entityId
      ? this.hass.states[this._entityId]
      : undefined;

    const graph = this._graph;
    const trackedNodes = graph?.trackedNodes;
    const renderedNodes = graph?.renderedNodes;

    const title = stateObj?.attributes.friendly_name || this._entityId;

    let devButtons: TemplateResult | string = "";
    if (__DEV__) {
      devButtons = html`<div style="position: absolute; right: 0; z-index: 1;">
        <button @click=${this._importTrace}>Import trace</button>
        <button @click=${this._loadLocalStorageTrace}>Load stored trace</button>
      </div>`;
    }

    return html`
      ${devButtons}
      <hass-subpage
        .hass=${this.hass}
        .narrow=${this.narrow}
        .header=${title}
        .scrollable=${this.narrow}
      >
        ${!this.narrow && stateObj?.attributes.id
          ? html`
              <ha-button
                appearance="plain"
                size="s"
                class="trace-link"
                @click=${this._navigateToAutomation}
                slot="toolbar-icon"
              >
                ${this.hass.localize(
                  "ui.panel.config.automation.trace.edit_automation"
                )}
              </ha-button>
            `
          : nothing}
        <ha-dropdown
          slot="toolbar-icon"
          @wa-select=${this._handleDropdownSelect}
        >
          <ha-icon-button
            slot="trigger"
            .label=${this.hass.localize("ui.common.menu")}
            .path=${mdiDotsVertical}
          ></ha-icon-button>

          <ha-dropdown-item .disabled=${!stateObj} value="show_info">
            ${this.hass.localize("ui.panel.config.automation.editor.show_info")}
            <ha-svg-icon
              slot="icon"
              .path=${mdiInformationOutline}
            ></ha-svg-icon>
          </ha-dropdown-item>

          ${stateObj?.attributes.id && this.narrow
            ? html`
                <ha-dropdown-item value="edit_automation">
                  ${this.hass.localize(
                    "ui.panel.config.automation.trace.edit_automation"
                  )}
                  <ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
                </ha-dropdown-item>
              `
            : nothing}

          <wa-divider></wa-divider>

          <ha-dropdown-item value="refresh">
            ${this.hass.localize("ui.panel.config.automation.trace.refresh")}
            <ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
          </ha-dropdown-item>

          <ha-dropdown-item .disabled=${!this._trace} value="download_trace">
            ${this.hass.localize(
              "ui.panel.config.automation.trace.download_trace"
            )}
            <ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
          </ha-dropdown-item>
        </ha-dropdown>

        <div class="toolbar">
          ${this._traces && this._traces.length > 0
            ? html`
                <ha-icon-button
                  .label=${this.hass!.localize(
                    "ui.panel.config.automation.trace.older_trace"
                  )}
                  .path=${computeRTL(
                    this.hass!.language,
                    this.hass!.translationMetadata.translations
                  )
                    ? mdiRayStartArrow
                    : mdiRayEndArrow}
                  .disabled=${this._traces[this._traces.length - 1].run_id ===
                  this._runId}
                  @click=${this._pickOlderTrace}
                ></ha-icon-button>
                <ha-trace-picker
                  .hass=${this.hass}
                  .traces=${this._traces}
                  .value=${this._runId}
                  @value-changed=${this._pickTrace}
                ></ha-trace-picker>
                <ha-icon-button
                  .label=${this.hass!.localize(
                    "ui.panel.config.automation.trace.newer_trace"
                  )}
                  .path=${computeRTL(
                    this.hass!.language,
                    this.hass!.translationMetadata.translations
                  )
                    ? mdiRayEndArrow
                    : mdiRayStartArrow}
                  .disabled=${this._traces[0].run_id === this._runId}
                  @click=${this._pickNewerTrace}
                ></ha-icon-button>
              `
            : ""}
        </div>

        ${this._traces === undefined
          ? html`<div class="container">
              ${this.hass!.localize("ui.common.loading")}
            </div>`
          : this._traces.length === 0
            ? html`<div class="container">
                ${this.hass!.localize(
                  "ui.panel.config.automation.trace.no_traces_found"
                )}
              </div>`
            : this._trace === undefined
              ? ""
              : html`
                  <div class="main">
                    <div class="graph">
                      <hat-script-graph
                        .trace=${this._trace}
                        .selected=${this._selected?.path}
                        @graph-node-selected=${this._pickNode}
                      ></hat-script-graph>
                    </div>

                    <div class="info">
                      <ha-tab-group @wa-tab-show=${this._handleTabChanged}>
                        ${TABS.map(
                          (view) => html`
                            <ha-tab-group-tab
                              slot="nav"
                              .active=${this._view === view}
                              .panel=${view}
                            >
                              ${this.hass!.localize(
                                `ui.panel.config.automation.trace.tabs.${view}`
                              )}
                            </ha-tab-group-tab>
                          `
                        )}
                        ${this._trace.blueprint_inputs
                          ? html`
                              <ha-tab-group-tab
                                slot="nav"
                                .active=${this._view === "blueprint"}
                                panel="blueprint"
                              >
                                ${this.hass!.localize(
                                  `ui.panel.config.automation.trace.tabs.blueprint_config`
                                )}
                              </ha-tab-group-tab>
                            `
                          : ""}
                      </ha-tab-group>
                      ${this._selected === undefined ||
                      this._logbookEntries === undefined ||
                      trackedNodes === undefined
                        ? nothing
                        : this._view === "details"
                          ? html`
                              <ha-trace-path-details
                                .hass=${this.hass}
                                .narrow=${this.narrow}
                                .trace=${this._trace}
                                .selected=${this._selected}
                                .logbookEntries=${this._logbookEntries}
                                .trackedNodes=${trackedNodes}
                                .renderedNodes=${renderedNodes!}
                              ></ha-trace-path-details>
                            `
                          : this._view === "automation_config"
                            ? html`
                                <ha-trace-config
                                  .trace=${this._trace}
                                ></ha-trace-config>
                              `
                            : this._view === "logbook"
                              ? html`
                                  <ha-trace-logbook
                                    .hass=${this.hass}
                                    .narrow=${this.narrow}
                                    .trace=${this._trace}
                                    .logbookEntries=${this._logbookEntries}
                                  ></ha-trace-logbook>
                                `
                              : this._view === "blueprint"
                                ? html`
                                    <ha-trace-blueprint-config
                                      .trace=${this._trace}
                                    ></ha-trace-blueprint-config>
                                  `
                                : html`
                                    <ha-trace-timeline
                                      .hass=${this.hass}
                                      .trace=${this._trace}
                                      .logbookEntries=${this._logbookEntries}
                                      .selected=${this._selected}
                                      @value-changed=${this._timelinePathPicked}
                                    ></ha-trace-timeline>
                                  `}
                    </div>
                  </div>
                `}
      </hass-subpage>
    `;
  }

  protected firstUpdated(changedProps: PropertyValues<this>) {
    super.firstUpdated(changedProps);

    this.hass.loadBackendTranslation("triggers");
    this.hass.loadBackendTranslation("conditions");

    if (!this.automationId) {
      return;
    }

    const params = new URLSearchParams(location.search);
    this._loadTraces(params.get("run_id") || undefined);
  }

  protected updated(changedProps: PropertyValues) {
    super.updated(changedProps);

    // Only reset if automationId has changed and we had one before.
    if (changedProps.get("automationId")) {
      this._traces = undefined;
      this._entityId = undefined;
      this._runId = undefined;
      this._trace = undefined;
      this._logbookEntries = undefined;
      if (this.automationId) {
        this._loadTraces();
      }
    }

    if (changedProps.has("_runId") && this._runId) {
      this._trace = undefined;
      this._logbookEntries = undefined;
      this._loadTrace();
    }

    if (
      (changedProps.has("automationId") || changedProps.has("automations")) &&
      this.automationId &&
      !this._entityId
    ) {
      const automation = this.automations.find(
        (entity: AutomationEntity) => entity.attributes.id === this.automationId
      );
      this._entityId = automation?.entity_id;
    }

    if (
      changedProps.has("automationId") ||
      changedProps.has("_entityId") ||
      changedProps.has("_entityRegistry")
    ) {
      this._setRelatedContext();
    }
  }

  private _setRelatedContext() {
    const areaId = this._entityId
      ? this._entityRegistry?.find(
          (entry) => entry.entity_id === this._entityId
        )?.area_id
      : undefined;
    fireRelatedContext(
      this,
      areaId
        ? {
            itemType: "area",
            itemId: areaId,
          }
        : undefined
    );
  }

  private _pickOlderTrace() {
    const curIndex = this._traces!.findIndex((tr) => tr.run_id === this._runId);
    this._runId = this._traces![curIndex + 1].run_id;
    this._selected = undefined;
  }

  private _pickNewerTrace() {
    const curIndex = this._traces!.findIndex((tr) => tr.run_id === this._runId);
    this._runId = this._traces![curIndex - 1].run_id;
    this._selected = undefined;
  }

  private _pickTrace(ev) {
    this._runId = ev.detail.value;
    this._selected = undefined;
  }

  private _pickNode(ev) {
    this._selected = ev.detail;
  }

  private _refreshTraces() {
    this._loadTraces();
  }

  private async _loadTraces(runId?: string) {
    this._traces = await loadTraces(this.hass, "automation", this.automationId);
    // Newest will be on top.
    this._traces.reverse();

    if (runId) {
      this._runId = runId;
    }

    // Check if current run ID still exists
    if (
      this._runId &&
      !this._traces.some((trace) => trace.run_id === this._runId)
    ) {
      this._runId = undefined;
      this._selected = undefined;

      // If we came here from a trace passed into the url, clear it.
      if (runId) {
        const params = new URLSearchParams(location.search);
        params.delete("run_id");
        history.replaceState(
          null,
          "",
          `${location.pathname}?${params.toString()}`
        );
      }

      await showAlertDialog(this, {
        text: this.hass!.localize(
          "ui.panel.config.automation.trace.trace_no_longer_available"
        ),
      });
    }

    // See if we can set a default runID
    if (!this._runId && this._traces.length > 0) {
      this._runId = this._traces[0].run_id;
    }
  }

  private async _loadTrace() {
    const trace = await loadTrace(
      this.hass,
      "automation",
      this.automationId,
      this._runId!
    );
    this._logbookEntries = isComponentLoaded(this.hass.config, "logbook")
      ? await getLogbookDataForContext(
          this.hass,
          trace.timestamp.start,
          trace.context.id
        )
      : [];

    this._trace = trace;
  }

  private _downloadTrace() {
    const json = JSON.stringify(
      {
        trace: this._trace,
        logbookEntries: this._logbookEntries,
      },
      undefined,
      2
    );
    const blob = new Blob([json], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    fileDownload(
      url,
      `trace ${this._entityId} ${this._trace!.timestamp.start}.json`
    );
  }

  private _importTrace() {
    const traceText = prompt(
      this.hass.localize(
        "ui.panel.config.automation.trace.enter_downloaded_trace"
      )
    );
    if (!traceText) {
      return;
    }
    window.localStorage.setItem("devTrace", traceText);
    this._loadLocalTrace(traceText);
  }

  private _loadLocalStorageTrace() {
    const devTrace = window.localStorage.getItem("devTrace");
    if (devTrace) {
      this._loadLocalTrace(devTrace);
    }
  }

  private _loadLocalTrace(traceText: string) {
    const traceInfo = JSON.parse(traceText);
    this._trace = traceInfo.trace;
    this._logbookEntries = traceInfo.logbookEntries;
  }

  private _handleTabChanged(ev: CustomEvent) {
    this._view = ev.detail.name as typeof this._view;
  }

  private _timelinePathPicked(ev: CustomEvent) {
    const path = ev.detail.value;
    const nodes = this._graph!.trackedNodes;
    if (nodes[path]) {
      this._selected = nodes[path];
    }
  }

  private _showInfo() {
    if (!this.hass || !this._entityId) {
      return;
    }
    fireEvent(this, "hass-more-info", { entityId: this._entityId });
  }

  private _navigateToAutomation() {
    if (this._entityId && this.hass.states[this._entityId]) {
      navigate(
        `/config/automation/edit/${encodeURIComponent(this.hass.states[this._entityId].attributes.id)}`
      );
    }
  }

  private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
    const action = ev.detail?.item?.value;

    if (!action) {
      return;
    }

    switch (action) {
      case "show_info":
        this._showInfo();
        break;
      case "refresh":
        this._refreshTraces();
        break;
      case "download_trace":
        this._downloadTrace();
        break;
      case "edit_automation":
        this._navigateToAutomation();
        break;
    }
  }

  static get styles(): CSSResultGroup {
    return [
      haStyle,
      css`
        .toolbar {
          display: flex;
          align-items: center;
          justify-content: center;
          font-size: var(--ha-font-size-xl);
          height: var(--header-height);
          padding: 4px;
          background-color: var(--primary-background-color);
          font-weight: var(--ha-font-weight-normal);
          color: var(--app-header-text-color, white);
          border-bottom: var(--app-header-border-bottom, none);
          box-sizing: border-box;
        }

        .main {
          flex: 1;
          min-height: 0;
          display: flex;
          overflow: hidden;
          background-color: var(--card-background-color);
          direction: ltr;
        }

        :host([narrow]) .main {
          flex: none;
          height: auto;
          flex-direction: column;
          overflow: visible;
        }

        .container {
          padding: 16px;
        }

        .graph {
          border-right: 1px solid var(--divider-color);
          max-width: 50%;
          box-sizing: border-box;
          display: flex;
          flex-direction: column;
          overflow: hidden;
        }
        hat-script-graph {
          flex: 1;
          min-width: 0;
          min-height: 0;
        }
        :host([narrow]) .graph {
          max-width: 100%;
          overflow: visible;
          height: auto;
        }
        :host([narrow]) hat-script-graph {
          overflow: visible;
          flex: none;
        }
        .info {
          flex: 1;
          overflow-y: auto;
          background-color: var(--card-background-color);
        }
        ha-tab-group {
          background-color: var(--primary-background-color);
          border-bottom: 1px solid var(--divider-color);
          direction: var(--direction);
        }
        ha-tab-group-tab::part(base) {
          padding: 2px 16px;
        }
        :host([narrow]) .info {
          overflow: visible;
        }
        .trace-link {
          text-decoration: none;
        }

        ha-trace-logbook {
          direction: var(--direction);
        }
        ha-trace-picker {
          flex-grow: 1;
          max-width: 500px;
        }
      `,
    ];
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "ha-automation-trace": HaAutomationTrace;
  }
}
