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`<ha-spinner size="medium"></ha-spinner>`}
            </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 ha-spinner {
          height: 120px;
        }
      `,
    ];
  }

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

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