/**
 * Copyright 2026 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {SlPopup} from '@shoelace-style/shoelace';
import {LitElement, TemplateResult, html, nothing} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {createRef, ref} from 'lit/directives/ref.js';
import {ROADMAP_MILESTONE_CARD_CSS} from '../css/elements/chromedash-roadmap-milestone-card-css.js';
import {Feature, ReleaseInfo} from '../js-src/cs-client.js';
import {isVerifiedWithinGracePeriod} from './utils.js';
import './chromedash-review-status-icon.js';
import {FEATURE_TYPES} from './form-field-enums.js';

const REMOVED_STATUS = ['Removed'];
const DEPRECATED_STATUS = ['Deprecated', 'No longer pursuing'];
const ORIGIN_TRIAL = ['Origin trial'];
const DEPRECATION_TRIAL = ['Deprecation trial'];
const NO_FEATURE_STRING = 'NO FEATURES ARE PLANNED FOR THIS MILESTONE YET';

export interface TemplateContent {
  channelLabel: string;
  h1Class: string;
  channelTag?: string;
  dateText: string;
  featureHeader: string;
}

@customElement('chromedash-roadmap-milestone-card')
export class ChromedashRoadmapMilestoneCard extends LitElement {
  infoPopupRef = createRef<SlPopup>();

  static styles = ROADMAP_MILESTONE_CARD_CSS;

  @property({attribute: false})
  starredFeatures = new Set<number>();
  @property({attribute: false})
  highlightFeature!: number;
  @property({attribute: false})
  templateContent!: TemplateContent;
  @property({attribute: false})
  channel?: ReleaseInfo;
  @property({type: Boolean})
  showDates = false;
  @property({type: Boolean})
  signedIn = false;
  @property({attribute: false})
  stableMilestone!: number;
  @state()
  currentDate: number = Date.now();

  /**
   *  Returns the number of days between a and b.
   */
  _dateDiffInDays(a: Date, b: Date): {days: number; future: boolean} {
    // Discard time and time-zone information.
    const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
    const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());

    const MS_PER_DAY = 1000 * 60 * 60 * 24;
    const daysDiff = Math.floor((utc2 - utc1) / MS_PER_DAY);
    return {days: Math.abs(daysDiff), future: daysDiff < 1};
  }

  _computeDate(dateStr, addYear = false) {
    const opts: Intl.DateTimeFormatOptions = {month: 'short', day: 'numeric'};
    if (addYear) {
      opts.year = 'numeric';
    }
    const date = new Date(dateStr);
    return new Intl.DateTimeFormat('en-US', opts).format(date);
  }

  _fireEvent(eventName, detail) {
    const event = new CustomEvent(eventName, {
      bubbles: true,
      composed: true,
      detail,
    });
    this.dispatchEvent(event);
  }

  _isAnyFeatureReleased() {
    for (const shippingType of this._objKeys(this.channel?.features)) {
      if (this.channel?.features[shippingType].length != 0) {
        return true;
      }
    }

    return false;
  }

  toggleStar(e) {
    e.preventDefault();
    e.stopPropagation();

    const iconEl = e.target;
    const featureId = Number(iconEl.dataset.featureId);
    const newStarred = !this.starredFeatures.has(featureId);

    this._fireEvent('star-toggle-event', {
      feature: featureId,
      doStar: newStarred,
    });
  }

  highlight(e) {
    e.preventDefault();
    e.stopPropagation();

    const iconEl = e.target.parentNode.parentNode;
    const featureId = Number(iconEl.dataset.featureId);

    this._fireEvent('highlight-feature-event', {
      feature: featureId,
    });
  }

  removeHighlight(e) {
    e.preventDefault();
    e.stopPropagation();

    this._fireEvent('highlight-feature-event', {
      feature: null,
    });
  }

  _computeDaysUntil(dateStr) {
    const date = new Date(dateStr);
    if (isNaN(date.getTime())) {
      return nothing;
    }
    const today = new Date();
    const diff = this._dateDiffInDays(date, today);
    // If stable is (or was) very recent, we don't want to display relative time
    // in small increments (e.g. "in 39 minutes"), so we show "coming soon".
    if (diff.days < 1) {
      return 'coming soon';
    }
    return html`<sl-relative-time
      class="no-squeeze"
      date="${date.toISOString()}"
    >
    </sl-relative-time>`;
  }

  _objKeys(obj) {
    if (!obj) {
      return [];
    }
    return Object.keys(obj).sort();
  }

  hidePopup(e) {
    if (
      e.relatedTarget != this.renderRoot.querySelector('#detailed-info-link')
    ) {
      this.infoPopupRef.value!.active = false;
    }
  }

  renderInfoIcon() {
    return html`
      <sl-icon-button
        name="info-circle"
        id="info-button"
        @click=${() =>
          (this.infoPopupRef.value!.active = !this.infoPopupRef.value!.active)}
        @focusout=${this.hidePopup}
      ></sl-icon-button>

      <sl-popup
        anchor="info-button"
        placement="bottom"
        strategy="fixed"
        ${ref(this.infoPopupRef)}
      >
        <div class="popup-content">
          New versions are offered to users gradually. <br />
          See
          <a
            @focusout=${this.hidePopup}
            id="detailed-info-link"
            href="https://chromiumdash.appspot.com/schedule"
            target="_blank"
            >detailed dates</a
          >.
        </div>
      </sl-popup>
    `;
  }

  renderCardHeader() {
    // Starting with M110, display Early Stable Release dates.
    if (!this.channel) {
      return nothing;
    }
    const stableStart =
      this.channel.version >= 110
        ? this.channel?.early_stable
        : this.channel?.stable_date;
    const logo = html`
      <span class="chrome-logo">
        ${
          this.templateContent.channelTag
            ? html`
                <span class="channel-tag"
                  >${this.templateContent.channelTag}</span
                >
              `
            : nothing
        }
      </span>
    `;

    return html`
      <div class="layout vertical center">
        <h1 class="channel_label">${this.templateContent.channelLabel}</h1>
        <h1
          class="chrome_version layout horizontal center ${
            this.templateContent.h1Class
          }"
        >
          ${logo} Chrome ${this.channel?.version}
        </h1>
      </div>
      ${
        this.showDates && this.channel?.earliest_beta
          ? html`
              <div class="milestone_info layout horizontal center-center">
                <h3>
                  <span class="channel_label">Beta</span> ${
                    this.templateContent.dateText
                  }
                  <span class="milestone_info-beta">
                    ${this._computeDate(this.channel?.earliest_beta)} -
                    ${this._computeDate(this.channel?.latest_beta)}
                  </span>
                </h3>
              </div>
              <div class="milestone_info layout horizontal center-center">
                <h3>
                  <span class="channel_label">Stable</span>
                  ${this._computeDaysUntil(stableStart)}
                  <span class="release-stable"
                    >(${this._computeDate(stableStart, true)})</span
                  >
                  ${this.renderInfoIcon()}
                </h3>
              </div>
            `
          : nothing
      }
    `;
  }

  /**
   * A feature is outdated if it is scheduled to ship in the next 2 milestones,
   * and its accurate_as_of date is at least 4 weeks ago.
   *
   *  @param accurateAsOf The accurate_as_of date as an ISO string.
   *  @param liveChromeVersion The Chrome milestone when a feature is live.
   */
  _isFeatureOutdated(
    accurateAsOf: string | undefined,
    liveChromeVersion: number | undefined
  ): boolean {
    if (this.stableMilestone === 0 || !liveChromeVersion) {
      return false;
    }
    // If this feature is not shipping within two upcoming milestones, return false.
    if (!(
      this.stableMilestone + 1 === liveChromeVersion ||
      this.stableMilestone + 2 === liveChromeVersion
    )) {
      return false;
    }

    const isVerified = isVerifiedWithinGracePeriod(
      accurateAsOf,
      this.currentDate
    );
    return !isVerified;
  }

  _cardFeatureItemTemplate(f, shippingType) {
    return html`
      <li
        data-feature-id="${f.id}"
        class="hbox align-top ${
          f.id == this.highlightFeature ? 'highlight' : ''
        }"
      >
        <chromedash-review-status-icon
          .feature=${f}
        ></chromedash-review-status-icon>
        <span>
          <a
            id="feature_link"
            href="/feature/${f.id}"
            @mouseenter="${this.highlight}"
            @mouseleave="${this.removeHighlight}"
          >
            ${f.name}
          </a>
          ${f.finch_urls?.map(
            url => html`
              <a
                target="_blank"
                class="experiment"
                href=${url}
                title="Deploying to some users as an experiment"
                >Exp</a
              >
            `
          )}
        </span>
        <span class="spacer"></span>
        <span class="icon_row">
          ${
            this._isFeatureOutdated(f.accurate_as_of, this.channel?.version)
              ? html`
                  <span
                    class="tooltip"
                    id="outdated-icon"
                    title="Feature outdated - last checked for overall accuracy more than four weeks ago"
                  >
                    <sl-icon
                      name="exclamation-circle-fill"
                      data-tooltip
                    ></sl-icon>
                  </span>
                `
              : nothing
          }
          ${
            DEPRECATION_TRIAL.includes(shippingType)
              ? html`
                  <span class="tooltip" title="Deprecation Trial">
                    <sl-icon
                      library="material"
                      name="extension"
                      class="experimental"
                      data-tooltip
                    ></sl-icon>
                  </span>
                `
              : nothing
          }
          ${
            REMOVED_STATUS.includes(shippingType)
              ? html`
                  <span class="tooltip" title="Removed">
                    <sl-icon
                      name="x-circle-fill"
                      class="remove"
                      data-tooltip
                    ></sl-icon>
                  </span>
                `
              : nothing
          }
          ${
            this.signedIn
              ? html`
                  <span
                    class="tooltip"
                    title="Receive an email notification when there are updates"
                  >
                    <sl-icon
                      name="${
                        this.starredFeatures.has(Number(f.id))
                          ? 'star-fill'
                          : 'star'
                      }"
                      class="pushicon"
                      data-feature-id="${f.id}"
                      @click="${this.toggleStar}"
                    >
                    </sl-icon>
                  </span>
                `
              : nothing
          }
        </span>
      </li>
    `;
  }

  renderOTFeatures(features): TemplateResult[] {
    if (!features) {
      return [];
    }

    const display: TemplateResult[] = [];
    const otFeatures = features.filter(
      f => f.feature_type_int !== FEATURE_TYPES.FEATURE_TYPE_DEPRECATION_ID[0]
    );
    const deprecationFeatures = features.filter(
      f => f.feature_type_int === FEATURE_TYPES.FEATURE_TYPE_DEPRECATION_ID[0]
    );

    if (otFeatures.length > 0) {
      display.push(html`
        <h3 class="feature_shipping_type">${ORIGIN_TRIAL[0]}</h3>
        <ul>
          ${otFeatures.map(
            f => html` ${this._cardFeatureItemTemplate(f, ORIGIN_TRIAL[0])} `
          )}
        </ul>
      `);
    }
    if (deprecationFeatures.length > 0) {
      display.push(html`
        <h3 class="feature_shipping_type">${DEPRECATION_TRIAL[0]}</h3>
        <ul>
          ${deprecationFeatures.map(
            f => html`
              ${this._cardFeatureItemTemplate(f, DEPRECATION_TRIAL[0])}
            `
          )}
        </ul>
      `);
    }

    return display;
  }

  renderCardFeatureList() {
    const featureListItems: (
      TemplateResult[] | TemplateResult | typeof nothing
    )[] = this._objKeys(this.channel?.features).map(shippingType => {
      if (shippingType === ORIGIN_TRIAL[0]) {
        return this.renderOTFeatures(this.channel?.features[shippingType]);
      } else if (this.channel?.features[shippingType].length !== 0) {
        return html`
          <h3 class="feature_shipping_type">${shippingType}</h3>
          <ul>
            ${this.channel?.features[shippingType].map(
              f => html` ${this._cardFeatureItemTemplate(f, shippingType)} `
            )}
          </ul>
        `;
      }
      return nothing;
    });

    return html`
      <div class="features_list">
        ${
          this._isAnyFeatureReleased()
            ? html`
        <div class="features_header">${this.templateContent.featureHeader}:</div>
          ${featureListItems}
          </div>`
            : html`
                <div class="features_header no_feature_released">
                  ${NO_FEATURE_STRING}
                </div>
              `
        }
      </div>
    `;
  }

  renderSkeletons() {
    return html`
      <div class="layout vertical center">
        <h1 class="channel_label">${this.templateContent.channelLabel}</h1>
        <h1 class="chrome_version layout horizontal sl-skeleton-header-container ${this.templateContent.h1Class}">
          <span class="chrome-logo"></span>
          <sl-skeleton effect="sheen"></sl-skeleton>
        </h1>
      </div>
      <div class="milestone_info layout horizontal center-center">
        <h3 class="sl-skeleton-header-container">
          <sl-skeleton effect="sheen"></sl-skeleton>
        </h3>
      </div>
      <div class="milestone_info layout horizontal center-center">
        <h3 class="sl-skeleton-header-container">
          <sl-skeleton effect="sheen"></sl-skeleton>
        </h3>
      </div>
      <div class="features_list">
        <div class="sl-skeleton-title-container">
          <sl-skeleton effect="sheen"></sl-skeleton>
        </div>
        <sl-skeleton effect="sheen"></sl-skeleton>
        <sl-skeleton effect="sheen"></sl-skeleton>
        <sl-skeleton effect="sheen"></sl-skeleton>
        <sl-skeleton effect="sheen"></sl-skeleton>
        </div>
      </div>
      <div class="features_list">
        <div class="sl-skeleton-title-container">
          <sl-skeleton effect="sheen"></sl-skeleton>
        </div>
        <sl-skeleton effect="sheen"></sl-skeleton>
        <sl-skeleton effect="sheen"></sl-skeleton>
        <sl-skeleton effect="sheen"></sl-skeleton>
        <sl-skeleton effect="sheen"></sl-skeleton>
        </div>
      </div>
    `;
  }

  render() {
    return html`
      <section class="release">
        ${
          this.channel
            ? html` ${this.renderCardHeader()} ${this.renderCardFeatureList()} `
            : html` ${this.renderSkeletons()} `
        }
      </section>
    `;
  }
}
