/**
 * 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 {LitElement, TemplateResult, css, html, nothing} from 'lit';
import {ref} from 'lit/directives/ref.js';
import {repeat} from 'lit/directives/repeat.js';
import {
  formatFeatureChanges,
  getDisabledHelpText,
  getStageValue,
  showToastMessage,
  flattenSections,
  setupScrollToHash,
  shouldShowDisplayNameField,
  renderHTMLIf,
  FieldInfo,
  closestShippingDateInfo,
  findClosestShippingDate,
  getFeatureOutdatedBanner,
  userCanEdit,
  maybeAlertOnLateChange,
} from './utils.js';
import './chromedash-form-table.js';
import './chromedash-form-field.js';
import {
  formatFeatureForEdit,
  FLAT_METADATA_FIELDS,
  FLAT_ENTERPRISE_METADATA_FIELDS,
  FORMS_BY_STAGE_TYPE,
  FLAT_TRIAL_EXTENSION_FIELDS,
} from './form-definition.js';
import {SHARED_STYLES} from '../css/shared-css.js';
import {FORM_STYLES} from '../css/forms-css.js';
import {
  STAGE_SHORT_NAMES,
  STAGE_SPECIFIC_FIELDS,
  STAGE_ENT_ROLLOUT,
} from './form-field-enums.js';
import {ALL_FIELDS} from './form-field-specs.js';
import {openAddStageDialog} from './chromedash-add-stage-dialog.js';
import {customElement, property, state} from 'lit/decorators.js';
import {Feature, User} from '../js-src/cs-client.js';

interface FormToRender {
  id: number;
  item: typeof nothing | TemplateResult;
}

@customElement('chromedash-guide-editall-page')
export class ChromedashGuideEditallPage extends LitElement {
  static get styles() {
    return [
      ...SHARED_STYLES,
      ...FORM_STYLES,
      css`
        .enterprise-help-text > *,
        .enterprise-help-text li {
          margin: revert;
          padding: revert;
        }
      `,
    ];
  }
  @property({attribute: false})
  featureId = 0;
  @property({attribute: false})
  user!: User;
  @property({type: String})
  appTitle = '';
  @property({type: Number})
  nextStageToCreateId = 0;
  @state()
  feature!: Feature;
  @state()
  loading = true;
  @state()
  submitting = false;
  @state()
  previousStageTypeRendered = 0;
  @state()
  sameTypeRendered = 0;
  @state()
  fieldValues: FieldInfo[] & {feature?: Feature} = [];
  @state()
  shippingInfo: closestShippingDateInfo = {
    // The closest milestone shipping date as an ISO string.
    closestShippingDate: '',
    hasShipped: false,
    isUpcoming: false,
  };
  @state()
  currentBetaMilestone = 0;

  connectedCallback() {
    super.connectedCallback();
    this.fetchData();
  }

  fetchData() {
    this.loading = true;
    Promise.all([
      window.csClient.getFeature(this.featureId),
      window.csClient.getChannels(),
    ])
      .then(async ([feature, channels]) => {
        this.feature = feature;
        this.currentBetaMilestone = Number(channels.beta.mstone);
        if (this.feature.name) {
          document.title = `${this.feature.name} - ${this.appTitle}`;
        }
        this.shippingInfo = await findClosestShippingDate(
          channels,
          feature.stages
        );
        this.loading = false;
      })
      .catch(() => {
        showToastMessage(
          'Some errors occurred. Please refresh the page or try again later.'
        );
      });
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    document.title = this.appTitle;
  }

  /* Add the form's event listener after Shoelace event listeners are attached
   * see more at https://github.com/GoogleChrome/chromium-dashboard/issues/2014 */
  async registerHandlers(el) {
    if (!el) return;
    await el.updateComplete;

    const hiddenTokenField = this.renderRoot.querySelector(
      'input[name=token]'
    ) as HTMLInputElement;
    hiddenTokenField.form?.addEventListener('submit', event => {
      this.handleFormSubmit(event, hiddenTokenField);
    });

    setupScrollToHash(this);
  }

  handleFormSubmit(e, hiddenTokenField) {
    e.preventDefault();
    maybeAlertOnLateChange(
      this.feature,
      this.fieldValues,
      this.currentBetaMilestone
    );
    this.submitting = true;
    const submitBody = formatFeatureChanges(this.fieldValues, this.featureId);

    // get the XSRF token and update it if it's expired before submission
    window.csClient
      .ensureTokenIsValid()
      .then(() => {
        hiddenTokenField.value = window.csClient.token;
        return window.csClient.updateFeature(submitBody);
      })
      .then(() => {
        window.location.href = this.getNextPage();
      })
      .catch(() => {
        this.submitting = false;
        showToastMessage(
          'Some errors occurred. Please refresh the page or try again later.'
        );
      });
  }

  // Handler to update form values when a field update event is fired.
  handleFormFieldUpdate(event) {
    const value = event.detail.value;
    // Index represents which form was updated.
    const index = event.detail.index;
    if (index >= this.fieldValues.length) {
      throw new Error('Out of bounds index when updating field values.');
    }
    // The field has been updated, so it is considered touched.
    this.fieldValues[index].touched = true;
    this.fieldValues[index].value = value;
  }

  getNextPage() {
    const urlParams = new URLSearchParams(window.location.search);
    const intent = urlParams.get('intent');
    if (intent === 'wpt_eval') {
      return `/feature/${this.featureId}/ai-coverage-analysis`;
    }
    return `/feature/${this.featureId}`;
  }

  handleCancelClick() {
    window.location.href = this.getNextPage();
  }

  renderSkeletons() {
    return html`
      <h3><sl-skeleton effect="sheen"></sl-skeleton></h3>
      <section class="flat_form">
        <h3><sl-skeleton effect="sheen"></sl-skeleton></h3>
        <p>
          <sl-skeleton effect="sheen"></sl-skeleton>
          <sl-skeleton effect="sheen"></sl-skeleton>
          <sl-skeleton effect="sheen"></sl-skeleton>
          <sl-skeleton effect="sheen"></sl-skeleton>
        </p>
      </section>
      <h3><sl-skeleton effect="sheen"></sl-skeleton></h3>
      <section class="flat_form">
        <h3><sl-skeleton effect="sheen"></sl-skeleton></h3>
        <p>
          <sl-skeleton effect="sheen"></sl-skeleton>
          <sl-skeleton effect="sheen"></sl-skeleton>
          <sl-skeleton effect="sheen"></sl-skeleton>
          <sl-skeleton effect="sheen"></sl-skeleton>
        </p>
      </section>
    `;
  }

  renderSubheader() {
    return html`
      <div id="subheader">
        <h2 id="breadcrumbs">
          <a href=${this.getNextPage()}>
            <sl-icon name="arrow-left"></sl-icon>
            Edit feature: ${this.loading ? 'loading...' : this.feature.name}
          </a>
        </h2>
      </div>
    `;
  }

  getStageForm(stageType) {
    return FORMS_BY_STAGE_TYPE[stageType] || null;
  }

  getHelpTextForStage(stageType) {
    switch (stageType) {
      case STAGE_ENT_ROLLOUT:
        return html`
          <section class="enterprise-help-text">
            <h3>Rollout steps</h3>
            <p>
              The enterprise release notes focus on changes to the stable
              channel. Please add a stage for each milestone where something is
              changing on the stable channel. For finch rollouts, use the
              milestone where the rollout starts.
            </p>
            <p>
              For example, you may only have a single stage where you roll out
              to 100% of users on milestone N.
            </p>
            <p>A more complex example might look like this:</p>
            <ul>
              <li>
                On milestone N-1, you introduce a flag for early testing of an
                upcoming change, or start a deprecation origin trial
              </li>
              <li>
                On milestone N, you start a finch rollout of a feature at 1% and
                introduce an enterprise policy for it
              </li>
              <li>On milestone N+3, you remove the enterprise policy</li>
            </ul>
          </section>
        `;
      default:
        return nothing;
    }
  }

  renderStageSection(
    formattedFeature,
    sectionBaseName,
    feStage,
    stageFields,
    featureFieldsDisplayed: Set<string>
  ) {
    if (!stageFields) return nothing;

    // Add a number differentiation if this stage type is the same as another stage.
    let numberDifferentiation = '';
    if (
      this.previousStageTypeRendered &&
      this.previousStageTypeRendered === feStage.stage_type
    ) {
      this.sameTypeRendered += 1;
      numberDifferentiation = ` ${this.sameTypeRendered}`;
    } else {
      this.previousStageTypeRendered = feStage.stage_type;
      this.sameTypeRendered = 1;
    }
    let sectionName = `${sectionBaseName}${numberDifferentiation}`;
    if (feStage.display_name) {
      sectionName = `${sectionBaseName}: ${feStage.display_name} `;
    }

    const formFieldEls = stageFields.map(field => {
      // Only show "display name" field if there is more than one stage of the same type.
      const featureJSONKey = ALL_FIELDS[field].name || field;
      if (
        featureJSONKey === 'display_name' &&
        !shouldShowDisplayNameField(this.feature.stages, feStage.stage_type)
      ) {
        return nothing;
      }
      let value = formattedFeature[field];
      let stageId = null;
      if (STAGE_SPECIFIC_FIELDS.has(featureJSONKey)) {
        value = getStageValue(feStage, featureJSONKey);
        stageId = feStage.id;
      } else if (featureFieldsDisplayed.has(featureJSONKey)) {
        // Don't render fields that are not stage-specific if this is
        // a stage type that is already being rendered.
        // This is to avoid repeated fields on the edit-all page.
        return nothing;
      }
      const index = this.fieldValues.length;
      this.fieldValues.push({
        name: featureJSONKey,
        touched: false,
        value,
        stageId,
      });
      featureFieldsDisplayed.add(featureJSONKey);

      return html`
        <chromedash-form-field
          name=${field}
          index=${index}
          stageId=${stageId ?? 0}
          value=${value}
          disabledReason="${getDisabledHelpText(field, feStage)}"
          .fieldValues=${this.fieldValues}
          .feature=${formattedFeature}
          ?forEnterprise=${formattedFeature.is_enterprise_feature}
          @form-field-update="${this.handleFormFieldUpdate}"
          class="${field === 'enterprise_product_category' ? 'choices' : ''}"
        >
        </chromedash-form-field>
      `;
    });
    const id =
      `${STAGE_SHORT_NAMES[feStage.stage_type] || 'metadata'}${this.sameTypeRendered}`.toLowerCase();
    const isEnterpriseFeatureRollout =
      formattedFeature.is_enterprise_feature &&
      feStage.stage_type === STAGE_ENT_ROLLOUT;
    return {
      id: feStage.id,
      item: html`
        ${renderHTMLIf(
          !isEnterpriseFeatureRollout,
          html`<h3 id="${id}">${sectionName}</h3>`
        )}
        <section class="flat_form" stage="${feStage.stage_type}">
          ${renderHTMLIf(
            feStage.stage_type === STAGE_ENT_ROLLOUT,
            html` <sl-button
              stage="${feStage.stage_type}"
              size="small"
              @click="${() => this.deleteStage(feStage)}"
            >
              Delete
            </sl-button>`
          )}
          ${formFieldEls}
        </section>
      `,
    };
  }

  /**
   * Builds the HTML elements for rendering the form sections.
   * @param {Object} formattedFeature Object describing the feature.
   * @param {Array} feStages List of stages associated with the feature.
   *
   * @return {Array} formsToRender, All HTML elements to render in the form.
   */
  getForms(formattedFeature, feStages) {
    // All features display the metadata section.
    let fieldsOnly = flattenSections(
      formattedFeature.is_enterprise_feature
        ? FLAT_ENTERPRISE_METADATA_FIELDS
        : FLAT_METADATA_FIELDS
    );
    // Keep track of the feature fields being displayed so that the same field
    // is not displayed more than once.
    const featureFieldsDisplayed = new Set<string>();

    const formsToRender: (typeof nothing | FormToRender)[] = [
      this.renderStageSection(
        formattedFeature,
        FLAT_METADATA_FIELDS.name,
        {id: -1},
        fieldsOnly,
        featureFieldsDisplayed
      ),
    ];

    let previousStageType = null;
    for (const feStage of feStages) {
      const stageForm = this.getStageForm(feStage.stage_type);
      if (!stageForm) {
        continue;
      }

      if (
        formattedFeature.is_enterprise_feature &&
        feStage.stage_type !== previousStageType
      ) {
        formsToRender.push({
          id: -2,
          item: this.getHelpTextForStage(feStage.stage_type),
        });
        previousStageType = feStage.stage_type;
      }

      fieldsOnly = flattenSections(stageForm);
      formsToRender.push(
        this.renderStageSection(
          formattedFeature,
          stageForm.name,
          feStage,
          fieldsOnly,
          featureFieldsDisplayed
        )
      );

      // If extension stages are associated with this stage,
      // render them in a separate section as well.
      const extensions = feStage.extensions || [];
      extensions.forEach(extensionStage => {
        fieldsOnly = flattenSections(FLAT_TRIAL_EXTENSION_FIELDS);
        let sectionName = FLAT_TRIAL_EXTENSION_FIELDS.name;
        if (feStage.display_name) {
          sectionName = ` ${FLAT_TRIAL_EXTENSION_FIELDS.name}: ${feStage.display_name} `;
        }
        formsToRender.push(
          this.renderStageSection(
            formattedFeature,
            sectionName,
            extensionStage,
            fieldsOnly,
            featureFieldsDisplayed
          )
        );
      });
    }

    return formsToRender;
  }

  // render the button to add a new stage. Displays for enterprise features only.
  renderAddStageButton() {
    const clickHandler = () => {
      void openAddStageDialog(
        this.feature.id,
        this.feature.feature_type_int,
        this.createNewStage.bind(this)
      );
    };
    return renderHTMLIf(
      this.feature.is_enterprise_feature,
      html` <sl-button size="small" @click="${clickHandler}">
        Add Step
      </sl-button>`
    );
  }

  // Create a stage requested on the edit all page.
  createNewStage(newStage) {
    window.csClient
      .createStage(this.featureId, {
        stage_type: {form_field_name: 'stage_type', value: newStage.stage_type},
      })
      .then(() => window.csClient.getFeature(this.featureId))
      .then(feature => {
        this.feature = feature;
      })
      .catch(() => {
        showToastMessage(
          'Some errors occurred. Please refresh the page or try again later.'
        );
      });
  }

  deleteStage(stage) {
    if (!confirm('Delete stage?')) return;

    window.csClient
      .deleteStage(this.featureId, stage.id)
      .then(() => window.csClient.getFeature(this.featureId))
      .then(feature => {
        this.feature = feature;
      })
      .catch(() => {
        showToastMessage(
          'Some errors occurred. Please refresh the page or try again later.'
        );
      });
  }

  renderForm() {
    const formattedFeature = formatFeatureForEdit(this.feature);
    this.fieldValues.feature = this.feature;
    const submitButtonTitle = this.submitting ? 'Submitting...' : 'Submit';

    const formsToRender = this.getForms(
      formattedFeature,
      this.feature.stages
    ) as FormToRender[];
    return html`
      <form name="feature_form">
        <input type="hidden" name="token" />
        <chromedash-form-table ${ref(this.registerHandlers)}>
          ${repeat(
            formsToRender,
            form => form.id,
            (_, i) => formsToRender[i].item
          )}
        </chromedash-form-table>
        ${this.renderAddStageButton()}

        <section class="final_buttons">
          <input
            class="button"
            type="submit"
            value=${submitButtonTitle}
            ?disabled=${this.submitting}
          />
          <button
            id="cancel-button"
            type="reset"
            @click=${this.handleCancelClick}
          >
            Cancel
          </button>
        </section>
      </form>
    `;
  }

  render() {
    return html`
      ${this.renderSubheader()}
      ${
        this.loading
          ? nothing
          : getFeatureOutdatedBanner(
              this.feature,
              this.shippingInfo,
              Date.now(),
              userCanEdit(this.user, this.feature.id)
            )
      }
      ${this.loading ? this.renderSkeletons() : this.renderForm()}
    `;
  }
}
