import { consume } from "@lit/context";
import { mdiEye, mdiGauge, mdiWaterPercent, mdiWeatherWindy } from "@mdi/js";
import type { HassConfig } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { DragScrollController } from "../../../common/controllers/drag-scroll-controller";
import { formatDateWeekdayShort } from "../../../common/datetime/format_date";
import { formatTime } from "../../../common/datetime/format_time";
import { transform } from "../../../common/decorators/transform";
import { formatNumber } from "../../../common/number/format_number";
import "../../../components/ha-alert";
import "../../../components/ha-relative-time";
import "../../../components/ha-spinner";
import "../../../components/ha-state-icon";
import "../../../components/ha-svg-icon";
import "../../../components/ha-tab-group";
import "../../../components/ha-tab-group-tab";
import "../../../components/ha-tooltip";
import {
  configContext,
  connectionContext,
  formattersContext,
  internationalizationContext,
} from "../../../data/context";
import type {
  ForecastAttribute,
  ForecastEvent,
  ModernForecastType,
  WeatherEntity,
} from "../../../data/weather";
import {
  getDefaultForecastType,
  getForecast,
  getSecondaryWeatherAttribute,
  getSupportedForecastTypes,
  getWeatherStateIcon,
  getWeatherUnit,
  getWind,
  subscribeForecast,
  weatherSVGStyles,
} from "../../../data/weather";
import type {
  HomeAssistantConfig,
  HomeAssistantConnection,
  HomeAssistantFormatters,
  HomeAssistantInternationalization,
} from "../../../types";

@customElement("more-info-weather")
class MoreInfoWeather extends LitElement {
  @property({ attribute: false }) public stateObj?: WeatherEntity;

  @state()
  @consume({ context: internationalizationContext, subscribe: true })
  private _i18n!: HomeAssistantInternationalization;

  @state()
  @consume({ context: formattersContext, subscribe: true })
  private _formatters!: HomeAssistantFormatters;

  @state()
  @consume({ context: configContext, subscribe: true })
  @transform<HomeAssistantConfig, HassConfig>({
    transformer: ({ config }) => config,
  })
  private _config!: HassConfig;

  @state()
  @consume({ context: connectionContext, subscribe: true })
  private _connection!: HomeAssistantConnection;

  @state() private _forecastEvent?: ForecastEvent;

  @state() private _forecastType?: ModernForecastType;

  @state() private _subscribed?: Promise<() => void>;

  private _dragScrollController = new DragScrollController(this, {
    selector: ".forecast",
    enabled: false,
  });

  private _unsubscribeForecastEvents() {
    if (this._subscribed) {
      this._subscribed.then((unsub) => unsub());
      this._subscribed = undefined;
    }
    this._forecastEvent = undefined;
  }

  private async _subscribeForecastEvents() {
    this._unsubscribeForecastEvents();
    if (
      !this.isConnected ||
      !this._connection ||
      !this.stateObj ||
      !this._forecastType
    ) {
      return;
    }

    this._subscribed = subscribeForecast(
      this._connection.connection,
      this.stateObj.entity_id,
      this._forecastType,
      (event) => {
        this._forecastEvent = event;
      }
    );
  }

  public connectedCallback() {
    super.connectedCallback();
    if (this.hasUpdated) {
      this._subscribeForecastEvents();
    }
  }

  public disconnectedCallback(): void {
    super.disconnectedCallback();
    this._unsubscribeForecastEvents();
  }

  protected willUpdate(changedProps: PropertyValues): void {
    super.willUpdate(changedProps);

    if ((changedProps.has("stateObj") || !this._subscribed) && this.stateObj) {
      const oldState = changedProps.get("stateObj") as
        WeatherEntity | undefined;
      if (
        oldState?.entity_id !== this.stateObj?.entity_id ||
        !this._subscribed
      ) {
        this._forecastType = getDefaultForecastType(this.stateObj);
        this._subscribeForecastEvents();
      }
    } else if (changedProps.has("_forecastType")) {
      this._subscribeForecastEvents();
    }
  }

  protected updated(_changedProps: PropertyValues<this>): void {
    super.updated(_changedProps);

    if (!this.stateObj) {
      this._dragScrollController.enabled = false;
      return;
    }

    this._dragScrollController.enabled = Boolean(
      getForecast(this.stateObj.attributes, this._forecastEvent)?.forecast
        ?.length
    );
  }

  private _supportedForecasts = memoizeOne((stateObj: WeatherEntity) =>
    getSupportedForecastTypes(stateObj)
  );

  private _groupForecastByDay = memoizeOne((forecast: ForecastAttribute[]) => {
    if (!forecast) return [];

    const grouped = new Map<string, NonNullable<typeof forecast>>();

    forecast.forEach((item) => {
      const date = new Date(item.datetime);
      const dateKey = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;

      if (!grouped.has(dateKey)) {
        grouped.set(dateKey, []);
      }
      grouped.get(dateKey)!.push(item);
    });

    return Array.from(grouped.values());
  });

  protected render() {
    if (!this._i18n || !this._formatters || !this._config || !this.stateObj) {
      return nothing;
    }

    const supportedForecasts = this._supportedForecasts(this.stateObj);

    const forecastData = getForecast(
      this.stateObj.attributes,
      this._forecastEvent
    );
    const forecast = forecastData?.forecast;
    const hourly = forecastData?.type === "hourly";
    const dayNight = forecastData?.type === "twice_daily";

    const weatherStateIcon = getWeatherStateIcon(this.stateObj.state, this);

    return html`
      <div class="content">
        <div class="icon-image">
          ${
            weatherStateIcon ||
            html`
              <ha-state-icon
                class="weather-icon"
                .stateObj=${this.stateObj}
              ></ha-state-icon>
            `
          }
        </div>
        <div class="info">
          <div class="name-state">
            <div class="state">
              ${this._formatters.formatEntityState(this.stateObj)}
            </div>
            <div class="time-ago">
              <ha-relative-time
                id="relative-time"
                .datetime=${this.stateObj.last_changed}
                capitalize
              ></ha-relative-time>
              <ha-tooltip for="relative-time">
                <div class="row">
                  <span class="column-name">
                    ${this._i18n.localize(
                      "ui.dialogs.more_info_control.last_changed"
                    )}:
                  </span>
                  <ha-relative-time
                    .datetime=${this.stateObj.last_changed}
                    capitalize
                  ></ha-relative-time>
                </div>
                <div class="row">
                  <span>
                    ${this._i18n.localize(
                      "ui.dialogs.more_info_control.last_updated"
                    )}:
                  </span>
                  <ha-relative-time
                    .datetime=${this.stateObj.last_updated}
                    capitalize
                  ></ha-relative-time>
                </div>
              </ha-tooltip>
            </div>
          </div>
          <div class="temp-attribute">
            <div class="temp">
              ${
                this.stateObj.attributes.temperature !== undefined &&
                this.stateObj.attributes.temperature !== null
                  ? html`
                      ${formatNumber(
                        this.stateObj.attributes.temperature,
                        this._i18n.locale
                      )}&nbsp;<span
                        >${getWeatherUnit(
                          this._config,
                          this.stateObj,
                          "temperature"
                        )}</span
                      >
                    `
                  : nothing
              }
            </div>
            <div class="attribute">
              ${getSecondaryWeatherAttribute(
                {
                  formatEntityAttributeValue:
                    this._formatters.formatEntityAttributeValue,
                  localize: this._i18n.localize,
                },
                this.stateObj,
                forecast!
              )}
            </div>
          </div>
        </div>
      </div>
      ${
        this._showValue(this.stateObj.attributes.pressure)
          ? html`
              <div class="flex">
                <ha-svg-icon .path=${mdiGauge}></ha-svg-icon>
                <div class="main">
                  ${this._i18n.localize(
                    "ui.card.weather.attributes.air_pressure"
                  )}
                </div>
                <div>
                  ${this._formatters.formatEntityAttributeValue(
                    this.stateObj,
                    "pressure"
                  )}
                </div>
              </div>
            `
          : nothing
      }
      ${
        this._showValue(this.stateObj.attributes.humidity)
          ? html`
              <div class="flex">
                <ha-svg-icon .path=${mdiWaterPercent}></ha-svg-icon>
                <div class="main">
                  ${this._i18n.localize("ui.card.weather.attributes.humidity")}
                </div>
                <div>
                  ${this._formatters.formatEntityAttributeValue(
                    this.stateObj,
                    "humidity"
                  )}
                </div>
              </div>
            `
          : nothing
      }
      ${
        this._showValue(this.stateObj.attributes.wind_speed)
          ? html`
              <div class="flex">
                <ha-svg-icon .path=${mdiWeatherWindy}></ha-svg-icon>
                <div class="main">
                  ${this._i18n.localize("ui.card.weather.attributes.wind_speed")}
                </div>
                <div>
                  ${getWind(
                    this._formatters.formatEntityAttributeValue,
                    this._i18n.localize,
                    this.stateObj,
                    this.stateObj.attributes.wind_speed!,
                    this.stateObj.attributes.wind_bearing
                  )}
                </div>
              </div>
            `
          : nothing
      }
      ${
        this._showValue(this.stateObj.attributes.visibility)
          ? html`
              <div class="flex">
                <ha-svg-icon .path=${mdiEye}></ha-svg-icon>
                <div class="main">
                  ${this._i18n.localize("ui.card.weather.attributes.visibility")}
                </div>
                <div>
                  ${this._formatters.formatEntityAttributeValue(
                    this.stateObj,
                    "visibility"
                  )}
                </div>
              </div>
            `
          : nothing
      }
      ${
        supportedForecasts?.length
          ? html`
              <div class="section">
                ${this._i18n.localize("ui.card.weather.forecast")}:
              </div>
              ${
                supportedForecasts?.length > 1
                  ? html`<ha-tab-group
                      @wa-tab-show=${this._handleForecastTypeChanged}
                    >
                      ${supportedForecasts.map(
                        (forecastType) =>
                          html`<ha-tab-group-tab
                            slot="nav"
                            .panel=${forecastType}
                            .active=${this._forecastType === forecastType}
                          >
                            ${this._i18n.localize(
                              `ui.card.weather.${forecastType}`
                            )}
                          </ha-tab-group-tab>`
                      )}
                    </ha-tab-group>`
                  : nothing
              }
              <div
                class=${classMap({
                  forecast: true,
                  dragging: this._dragScrollController.scrolling,
                })}
              >
                ${
                  forecast?.length
                    ? this._groupForecastByDay(forecast).map((dayForecast) => {
                        const showDayHeader = hourly || dayNight;
                        return html`
                          <div class="forecast-day">
                            ${
                              showDayHeader
                                ? html`<div class="forecast-day-header">
                                    ${formatDateWeekdayShort(
                                      new Date(dayForecast[0].datetime),
                                      this._i18n.locale,
                                      this._config
                                    )}
                                  </div>`
                                : nothing
                            }
                            <div class="forecast-day-content">
                              ${dayForecast.map((item) =>
                                this._showValue(item.templow) ||
                                this._showValue(item.temperature)
                                  ? html`
                                      <div class="forecast-item">
                                        <div
                                          class="forecast-item-label ${
                                            showDayHeader ? "" : "no-header"
                                          }"
                                        >
                                          ${
                                            hourly
                                              ? formatTime(
                                                  new Date(item.datetime),
                                                  this._i18n.locale,
                                                  this._config
                                                )
                                              : dayNight
                                                ? html`<div class="daynight">
                                                    ${
                                                      item.is_daytime !== false
                                                        ? this._i18n.localize(
                                                            "ui.card.weather.day"
                                                          )
                                                        : this._i18n.localize(
                                                            "ui.card.weather.night"
                                                          )
                                                    }
                                                  </div>`
                                                : formatDateWeekdayShort(
                                                    new Date(item.datetime),
                                                    this._i18n.locale,
                                                    this._config
                                                  )
                                          }
                                        </div>
                                        ${
                                          this._showValue(item.condition)
                                            ? html`
                                                <div
                                                  class="forecast-image-icon"
                                                >
                                                  ${getWeatherStateIcon(
                                                    item.condition!,
                                                    this,
                                                    !(
                                                      item.is_daytime ||
                                                      item.is_daytime ===
                                                        undefined
                                                    )
                                                  )}
                                                </div>
                                              `
                                            : nothing
                                        }
                                        <div class="temp">
                                          ${
                                            this._showValue(item.temperature)
                                              ? html`${formatNumber(
                                                  item.temperature,
                                                  this._i18n.locale
                                                )}°`
                                              : "—"
                                          }
                                        </div>
                                        <div class="templow">
                                          ${
                                            this._showValue(item.templow)
                                              ? html`${formatNumber(
                                                  item.templow!,
                                                  this._i18n.locale
                                                )}°`
                                              : nothing
                                          }
                                        </div>
                                      </div>
                                    `
                                  : nothing
                              )}
                            </div>
                          </div>
                        `;
                      })
                    : html`<div class="loading">
                        <ha-spinner size="medium"></ha-spinner>
                      </div>`
                }
              </div>
            `
          : nothing
      }
      ${
        this.stateObj.attributes.attribution
          ? html`
              <div class="attribution">
                ${this.stateObj.attributes.attribution}
              </div>
            `
          : nothing
      }
    `;
  }

  private _handleForecastTypeChanged(ev: CustomEvent): void {
    this._forecastType = ev.detail.name;
  }

  static get styles(): CSSResultGroup {
    return [
      weatherSVGStyles,
      css`
        ha-svg-icon {
          color: var(--state-icon-color);
          margin-left: var(--ha-space-2);
          margin-inline-start: var(--ha-space-2);
          margin-inline-end: initial;
        }

        .section {
          margin: var(--ha-space-4) 0 var(--ha-space-2) 0;
          font-size: 1.2em;
        }

        ha-tab-group-tab {
          flex: 1;
        }

        ha-tab-group-tab::part(base) {
          width: 100%;
          justify-content: center;
        }

        .flex {
          display: flex;
          height: 32px;
          align-items: center;
        }
        .flex > div:last-child {
          direction: ltr;
        }

        .main {
          flex: 1;
          margin-left: var(--ha-space-6);
          margin-inline-start: var(--ha-space-6);
          margin-inline-end: initial;
        }

        .attribution {
          text-align: center;
          margin-top: var(--ha-space-4);
        }

        .time-ago,
        .attribute {
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }

        .attribution,
        .templow,
        .daynight,
        .attribute,
        .time-ago {
          color: var(--secondary-text-color);
        }

        .content {
          display: flex;
          flex-wrap: nowrap;
          justify-content: space-between;
          align-items: center;
          margin-bottom: var(--ha-space-4);
        }

        .icon-image {
          display: flex;
          align-items: center;
          min-width: 64px;
          margin-right: var(--ha-space-4);
          margin-inline-end: var(--ha-space-4);
          margin-inline-start: initial;
        }

        .icon-image > * {
          flex: 0 0 64px;
          height: 64px;
        }

        .weather-icon {
          --mdc-icon-size: 64px;
        }

        .info {
          display: flex;
          justify-content: space-between;
          flex-grow: 1;
          overflow: hidden;
        }

        .temp-attribute {
          text-align: var(--float-end);
        }

        .temp-attribute .temp {
          position: relative;
          margin-right: var(--ha-space-6);
          direction: ltr;
        }

        .temp-attribute .temp span {
          position: absolute;
          font-size: var(--ha-font-size-2xl);
          top: 1px;
        }

        .state,
        .temp-attribute .temp {
          font-size: var(--ha-font-size-3xl);
          line-height: var(--ha-line-height-condensed);
        }

        .attribute {
          font-size: var(--ha-font-size-m);
          line-height: 1;
        }

        .name-state {
          overflow: hidden;
          padding-right: var(--ha-space-3);
          padding-inline-end: var(--ha-space-3);
          padding-inline-start: initial;
          width: 100%;
        }

        .state {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }

        .forecast {
          display: flex;
          justify-content: space-around;
          padding: var(--ha-space-4);
          padding-bottom: 0px;
          overflow-x: auto;
          scrollbar-color: var(--scrollbar-thumb-color) transparent;
          scrollbar-width: thin;
          mask-image: linear-gradient(
            90deg,
            transparent 0%,
            black 5%,
            black 94%,
            transparent 100%
          );
          user-select: none;
          cursor: grab;
        }

        .forecast.dragging {
          cursor: grabbing;
        }

        .forecast.dragging * {
          pointer-events: none;
        }

        .forecast-day {
          display: flex;
          flex-direction: column;
        }

        .forecast-day-header {
          position: sticky;
          top: 0;
          left: 0;
          color: var(--primary-text-color);
          z-index: 1;
          padding: 0 var(--ha-space-3) var(--ha-space-1) var(--ha-space-3);
          width: fit-content;
          width: 40px;
          text-align: center;
          font-weight: var(--ha-font-weight-semi-bold);
        }

        .forecast-day-content {
          display: flex;
          flex-direction: row;
        }

        .forecast-item {
          text-align: center;
          padding: 0 var(--ha-space-3);
        }

        .forecast-item-label {
          font-size: var(--ha-font-size-m);
          color: var(--secondary-text-color);
        }

        .forecast-item-label.no-header {
          color: var(--primary-text-color);
        }

        .forecast .icon,
        .forecast .temp {
          margin: var(--ha-space-1) 0;
        }

        .forecast .temp {
          font-size: var(--ha-font-size-l);
        }

        .forecast-image-icon {
          padding-top: var(--ha-space-1);
          padding-bottom: var(--ha-space-1);
          display: flex;
          justify-content: center;
        }

        .forecast-image-icon > * {
          width: 40px;
          height: 40px;
          --mdc-icon-size: 40px;
        }

        .forecast-icon {
          --mdc-icon-size: 40px;
        }

        .forecast .loading {
          display: flex;
          justify-content: center;
          align-items: center;
          height: 120px;
        }
      `,
    ];
  }

  private _showValue(item: number | string | undefined): boolean {
    return typeof item !== "undefined" && item !== null;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "more-info-weather": MoreInfoWeather;
  }
}
