// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable @devtools/no-lit-render-outside-of-view*/

import '../../../../ui/kit/kit.js';
import '../../../../ui/legacy/legacy.js';

import * as i18n from '../../../../core/i18n/i18n.js';
import * as Platform from '../../../../core/platform/platform.js';
import type * as Protocol from '../../../../generated/protocol.js';
import * as Buttons from '../../../../ui/components/buttons/buttons.js';
import * as Input from '../../../../ui/components/input/input.js';
import type * as UI from '../../../../ui/legacy/legacy.js';
import * as Lit from '../../../../ui/lit/lit.js';
import * as VisualLogging from '../../../../ui/visual_logging/visual_logging.js';
import * as EmulationUtils from '../utils/utils.js';

import userAgentClientHintsFormStyles from './userAgentClientHintsForm.css.js';

const {html} = Lit;

const UIStrings = {
  /**
   * @description Title for user agent client hints form
   */
  title: 'User agent client hints',
  /**
   * @description Heading for user agent section.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   */
  useragent: 'User agent (Sec-CH-UA)',
  /**
   * @description Heading for full-version-list section.
   */
  fullVersionList: 'Full version list (Sec-CH-UA-Full-Version-List)',
  /**
   * @description ARIA label for a form with properties for a single brand in a brand list. The form includes a brand name input field, a version
   * input field and a delete icon. Brand refer to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   */
  brandProperties: 'User agent properties',
  /**
   * @description Input field placeholder for brands browser name.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   */
  brandName: 'Brand',
  /**
   * @description Aria label for brands browser name input field.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   * @example {index} PH1
   */
  brandNameAriaLabel: 'Brand {PH1}',
  /**
   * @description Input field placeholder for significant brand version.
   * Brands here relate to different browser brands/vendors like Google Chrome (v89), Microsoft Edge (v92) etc.
   */
  significantBrandVersionPlaceholder: 'Significant version (e.g. 87)',
  /**
   * @description Input field placeholder for brand version.
   * Brands here relate to different browser brands/vendors like Google Chrome (v89), Microsoft Edge (v92) etc.
   */
  brandVersionPlaceholder: 'Version (e.g. 87.0.4280.88)',
  /**
   * @description Aria label for brands browser version input field.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   * @example {index} PH1
   */
  brandVersionAriaLabel: 'Version {PH1}',
  /**
   * @description Button title for adding another brand in brands section to client hints.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   */
  addBrand: 'Add Brand',
  /**
   * @description Tooltip and aria label for delete icon for deleting browser brand from brands user agent section.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   */
  brandUserAgentDelete: 'Delete brand from user agent section',
  /**
   * @description Tooltip and aria label for delete icon for deleting user agent from brands full version list.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   */
  brandFullVersionListDelete: 'Delete brand from full version list',

  /**
   * @description Heading for the form factors section.
   */
  formFactorsTitle: 'Form Factors (Sec-CH-UA-Form-Factors)',
  /**
   * @description ARIA label for the group of form factor checkboxes.
   */
  formFactorsGroupAriaLabel: 'Available Form Factors',
  /**
   * @description Form factor option: Desktop.
   */
  formFactorDesktop: 'Desktop',
  /**
   * @description Form factor option: Automotive.
   */
  formFactorAutomotive: 'Automotive',
  /**
   * @description Form factor option: Mobile.
   */
  formFactorMobile: 'Mobile',
  /**
   * @description Form factor option: Tablet.
   */
  formFactorTablet: 'Tablet',
  /**
   * @description Form factor option: XR.
   */
  formFactorXR: 'XR',
  /**
   * @description Form factor option: EInk.
   */
  formFactorEInk: 'EInk',
  /**
   * @description Form factor option: Watch.
   */
  formFactorWatch: 'Watch',

  /**
   * @description Label for full browser version input field.
   */
  fullBrowserVersion: 'Full browser version (Sec-CH-UA-Full-Version)',
  /**
   * @description Placeholder for full browser version input field.
   */
  fullBrowserVersionPlaceholder: 'Full browser version (e.g. 87.0.4280.88)',
  /**
   * @description Label for platform heading section, platform relates to OS like Android, Windows etc.
   */
  platformLabel: 'Platform (Sec-CH-UA-Platform / Sec-CH-UA-Platform-Version)',
  /**
   * @description Platform row, including platform name and platform version input field.
   */
  platformProperties: 'Platform properties',
  /**
   * @description Version for platform input field, platform relates to OS like Android, Windows etc.
   */
  platformVersion: 'Platform version',
  /**
   * @description Placeholder for platform name input field, platform relates to OS like Android, Windows etc.
   */
  platformPlaceholder: 'Platform (e.g. Android)',
  /**
   * @description Label for architecture (Eg: x86, x64, arm) input field.
   */
  architecture: 'Architecture (Sec-CH-UA-Arch)',
  /**
   * @description Placeholder for architecture (Eg: x86, x64, arm) input field.
   */
  architecturePlaceholder: 'Architecture (e.g. x86)',
  /**
   * @description Device model row, including device model input field and mobile checkbox
   */
  deviceProperties: 'Device properties',
  /**
   * @description Label for Device Model input field.
   */
  deviceModel: 'Device model (Sec-CH-UA-Model)',
  /**
   * @description Label for Mobile phone checkbox.
   */
  mobileCheckboxLabel: 'Mobile',
  /**
   * @description Label for button to submit client hints form in DevTools.
   */
  update: 'Update',
  /**
   * @description Field Error message in the Device settings pane that shows that the entered value has characters that can't be represented in the corresponding User Agent Client Hints
   */
  notRepresentable: 'Not representable as structured headers string.',
  /**
   * @description Hover text for info icon which explains user agent client hints.
   */
  userAgentClientHintsInfo:
      'User agent client hints are an alternative to the user agent string that identify the browser and the device in a more structured way with better privacy accounting.',
  /**
   * @description Success message when brand row is successfully added in client hints form.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   */
  addedBrand: 'Added brand row',
  /**
   * @description Success message when brand row is successfully deleted in client hints form.
   * Brands here relate to different browser brands/vendors like Google Chrome, Microsoft Edge etc.
   */
  deletedBrand: 'Deleted brand row',
  /**
   * @description Text that is usually a hyperlink to more documentation
   */
  learnMore: 'Learn more',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/settings/emulation/components/UserAgentClientHintsForm.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

export class ClientHintsChangeEvent extends Event {
  static readonly eventName = 'clienthintschange';
  constructor() {
    super(ClientHintsChangeEvent.eventName);
  }
}

export class ClientHintsSubmitEvent extends Event {
  static readonly eventName = 'clienthintssubmit';
  detail: {value: Protocol.Emulation.UserAgentMetadata};

  constructor(value: Protocol.Emulation.UserAgentMetadata) {
    super(ClientHintsSubmitEvent.eventName);
    this.detail = {value};
  }
}

export interface UserAgentClientHintsFormData {
  metaData?: Protocol.Emulation.UserAgentMetadata;
  showMobileCheckbox?: boolean;
  showSubmitButton?: boolean;
}

const DEFAULT_METADATA = {
  brands: [
    {
      brand: '',
      version: '',
    },
  ],
  fullVersionList: [
    {
      brand: '',
      version: '',
    },
  ],
  fullVersion: '',
  platform: '',
  platformVersion: '',
  architecture: '',
  model: '',
  mobile: false,
  formFactors: []
};

export const ALL_PROTOCOL_FORM_FACTORS: string[] = [
  UIStrings.formFactorDesktop,
  UIStrings.formFactorAutomotive,
  UIStrings.formFactorMobile,
  UIStrings.formFactorTablet,
  UIStrings.formFactorXR,
  UIStrings.formFactorEInk,
  UIStrings.formFactorWatch,
] as const;

/**
 * Component for user agent client hints form, it is used in device settings panel
 * and network conditions panel. It is customizable through showMobileCheckbox and showSubmitButton.
 */
export class UserAgentClientHintsForm extends HTMLElement {
  readonly #shadow = this.attachShadow({mode: 'open'});

  #isFormOpened = false;
  #isFormDisabled = false;
  #metaData: Protocol.Emulation.UserAgentMetadata = DEFAULT_METADATA;
  #showMobileCheckbox = false;
  #showSubmitButton = false;
  #useragentModifiedAriaMessage = '';

  set value(data: UserAgentClientHintsFormData) {
    const {metaData = DEFAULT_METADATA, showMobileCheckbox = false, showSubmitButton = false} = data;
    this.#metaData = {
      ...this.#metaData,
      ...metaData,
    };
    this.#showMobileCheckbox = showMobileCheckbox;
    this.#showSubmitButton = showSubmitButton;
    this.#render();
  }

  get value(): UserAgentClientHintsFormData {
    return {
      metaData: this.#metaData,
    };
  }

  set disabled(disableForm: boolean) {
    this.#isFormDisabled = disableForm;
    this.#isFormOpened = false;
    this.#render();
  }

  get disabled(): boolean {
    return this.#isFormDisabled;
  }

  #handleTreeExpand = (event: KeyboardEvent): void => {
    if (event.code === 'Space' || event.code === 'Enter' || event.code === 'ArrowLeft' || event.code === 'ArrowRight') {
      event.consume(true);
      this.#handleTreeClick(event.code);
    }
  };

  #handleTreeClick = (key: string): void => {
    if (this.#isFormDisabled) {
      return;
    }
    if ((key === 'ArrowLeft' && !this.#isFormOpened) || (key === 'ArrowRight' && this.#isFormOpened)) {
      return;
    }
    this.#isFormOpened = !this.#isFormOpened;
    this.#render();
  };

  #handleUseragentInputChange = (value: string, index: number, brandInputType: 'brandName'|'brandVersion'): void => {
    const updatedUseragent = this.#metaData.brands?.map((browserBrand, brandIndex) => {
      if (brandIndex === index) {
        const {brand, version} = browserBrand;
        if (brandInputType === 'brandName') {
          return {
            brand: value,
            version,
          };
        }
        return {
          brand,
          version: value,
        };
      }
      return browserBrand;
    });
    this.#metaData = {
      ...this.#metaData,
      brands: updatedUseragent,
    };
    this.dispatchEvent(new ClientHintsChangeEvent());
    this.#render();
  };

  #handleFullVersionListInputChange =
      (value: string, index: number, brandInputType: 'brandName'|'brandVersion'): void => {
        const fullVersionList = this.#metaData.fullVersionList?.map((browserBrand, brandIndex) => {
          if (brandIndex === index) {
            const {brand, version} = browserBrand;
            if (brandInputType === 'brandName') {
              return {
                brand: value,
                version,
              };
            }
            return {
              brand,
              version: value,
            };
          }
          return browserBrand;
        });
        this.#metaData = {
          ...this.#metaData,
          fullVersionList,
        };
        this.dispatchEvent(new ClientHintsChangeEvent());
        this.#render();
      };

  #handleUseragentDelete = (index: number): void => {
    const {brands = []} = this.#metaData;
    brands.splice(index, 1);
    this.#metaData = {
      ...this.#metaData,
      brands,
    };
    this.dispatchEvent(new ClientHintsChangeEvent());
    this.#useragentModifiedAriaMessage = i18nString(UIStrings.deletedBrand);
    this.#render();

    // after deleting a brand row, focus on next Brand input if available,
    // otherwise focus on the "Add Brand" button
    let nextFocusElement = this.shadowRoot?.getElementById(`ua-brand-${index + 1}-input`);
    if (!nextFocusElement) {
      nextFocusElement = this.shadowRoot?.getElementById('add-brand-button');
    }
    (nextFocusElement as HTMLElement)?.focus();
  };

  #handleFullVersionListDelete = (index: number): void => {
    const {fullVersionList = []} = this.#metaData;
    fullVersionList.splice(index, 1);
    this.#metaData = {
      ...this.#metaData,
      fullVersionList,
    };
    this.dispatchEvent(new ClientHintsChangeEvent());
    this.#useragentModifiedAriaMessage = i18nString(UIStrings.deletedBrand);
    this.#render();

    // after deleting a brand row, focus on next Brand input if available,
    // otherwise focus on the "Add Brand" button
    let nextFocusElement = this.shadowRoot?.getElementById(`fvl-brand-${index + 1}-input`);
    if (!nextFocusElement) {
      nextFocusElement = this.shadowRoot?.getElementById('add-fvl-brand-button');
    }
    (nextFocusElement as HTMLElement)?.focus();
  };

  #handleAddUseragentBrandClick = (): void => {
    const {brands} = this.#metaData;
    this.#metaData = {
      ...this.#metaData,
      brands: [
        ...(Array.isArray(brands) ? brands : []),
        {
          brand: '',
          version: '',
        },
      ],
    };
    this.dispatchEvent(new ClientHintsChangeEvent());
    this.#useragentModifiedAriaMessage = i18nString(UIStrings.addedBrand);
    this.#render();
    const brandInputElements = this.shadowRoot?.querySelectorAll('.ua-brand-name-input');
    if (brandInputElements) {
      const lastBrandInputElement = Array.from(brandInputElements).pop();
      if (lastBrandInputElement) {
        (lastBrandInputElement as HTMLInputElement).focus();
      }
    }
  };

  #handleAddUseragentBrandKeyPress = (event: KeyboardEvent): void => {
    if (event.code === 'Space' || event.code === 'Enter') {
      event.preventDefault();
      this.#handleAddUseragentBrandClick();
    }
  };

  #handleAddFullVersionListBrandClick = (): void => {
    const {fullVersionList} = this.#metaData;
    this.#metaData = {
      ...this.#metaData,
      fullVersionList: [
        ...(Array.isArray(fullVersionList) ? fullVersionList : []),
        {
          brand: '',
          version: '',
        },
      ],
    };
    this.dispatchEvent(new ClientHintsChangeEvent());
    this.#useragentModifiedAriaMessage = i18nString(UIStrings.addedBrand);
    this.#render();
    const brandInputElements = this.shadowRoot?.querySelectorAll('.fvl-brand-name-input');
    if (brandInputElements) {
      const lastBrandInputElement = Array.from(brandInputElements).pop();
      if (lastBrandInputElement) {
        (lastBrandInputElement as HTMLInputElement).focus();
      }
    }
  };

  #handleAddFullVersionListBrandKeyPress = (event: KeyboardEvent): void => {
    if (event.code === 'Space' || event.code === 'Enter') {
      event.preventDefault();
      this.#handleAddFullVersionListBrandClick();
    }
  };

  #handleFormFactorCheckboxChange = (formFactorValue: string, isChecked: boolean): void => {
    let currentFormFactors = [...(this.#metaData.formFactors || [])];
    if (isChecked) {
      if (!currentFormFactors.includes(formFactorValue)) {
        currentFormFactors.push(formFactorValue);
      }
    } else {
      currentFormFactors = currentFormFactors.filter(ff => ff !== formFactorValue);
    }
    this.#metaData = {
      ...this.#metaData,
      formFactors: currentFormFactors,
    };
    this.dispatchEvent(new ClientHintsChangeEvent());
    this.#render();
  };

  #handleInputChange = (stateKey: keyof Protocol.Emulation.UserAgentMetadata, value: string|boolean): void => {
    if (stateKey in this.#metaData) {
      this.#metaData = {
        ...this.#metaData,
        [stateKey]: value,
      };
      this.#render();
    }
    this.dispatchEvent(new ClientHintsChangeEvent());
  };

  #handleSubmit = (event: Event): void => {
    event.preventDefault();
    if (this.#showSubmitButton) {
      this.dispatchEvent(new ClientHintsSubmitEvent(this.#metaData));
      this.#render();
    }
  };

  #renderInputWithLabel(
      label: string, placeholder: string, value: string,
      stateKey: keyof Protocol.Emulation.UserAgentMetadata): Lit.TemplateResult {
    const handleInputChange = (event: KeyboardEvent): void => {
      const value = (event.target as HTMLInputElement).value;
      this.#handleInputChange(stateKey, value);
    };
    return html`
      <label class="full-row label input-field-label-container">
        ${label}
        <input
          class="input-field"
          type="text"
          @input=${handleInputChange}
          .value=${value}
          placeholder=${placeholder}
          jslog=${
        VisualLogging.textField().track({change: true}).context(Platform.StringUtilities.toKebabCase(stateKey))}
          />
      </label>
    `;
  }

  #renderPlatformSection(): Lit.TemplateResult {
    const {platform, platformVersion} = this.#metaData;
    const handlePlatformNameChange = (event: KeyboardEvent): void => {
      const value = (event.target as HTMLInputElement).value;
      this.#handleInputChange('platform', value);
    };
    const handlePlatformVersionChange = (event: KeyboardEvent): void => {
      const value = (event.target as HTMLInputElement).value;
      this.#handleInputChange('platformVersion', value);
    };
    return html`
      <span class="full-row label">${i18nString(UIStrings.platformLabel)}</span>
      <div class="full-row brand-row" aria-label=${i18nString(UIStrings.platformProperties)} role="group">
        <input
          class="input-field half-row"
          type="text"
          @input=${handlePlatformNameChange}
          .value=${platform}
          placeholder=${i18nString(UIStrings.platformPlaceholder)}
          aria-label=${i18nString(UIStrings.platformLabel)}
          jslog=${VisualLogging.textField('platform').track({
      change: true,
    })}
        />
        <input
          class="input-field half-row"
          type="text"
          @input=${handlePlatformVersionChange}
          .value=${platformVersion}
          placeholder=${i18nString(UIStrings.platformVersion)}
          aria-label=${i18nString(UIStrings.platformVersion)}
          jslog=${VisualLogging.textField('platform-version').track({
      change: true,
    })}
        />
      </div>
    `;
  }

  #renderDeviceModelSection(): Lit.TemplateResult {
    const {model, mobile} = this.#metaData;
    const handleDeviceModelChange = (event: KeyboardEvent): void => {
      const value = (event.target as HTMLInputElement).value;
      this.#handleInputChange('model', value);
    };
    const handleMobileChange = (event: KeyboardEvent): void => {
      const value = (event.target as HTMLInputElement).checked;
      this.#handleInputChange('mobile', value);
    };
    const mobileCheckboxInput = this.#showMobileCheckbox ? html`
      <label class="mobile-checkbox-container">
        <input type="checkbox" @input=${handleMobileChange} .checked=${mobile}
          jslog=${VisualLogging.toggle('mobile').track({
      click: true,
    })}
        />
        ${i18nString(UIStrings.mobileCheckboxLabel)}
      </label>
    ` :
                                                           Lit.nothing;
    return html`
      <span class="full-row label">${i18nString(UIStrings.deviceModel)}</span>
      <div class="full-row brand-row" aria-label=${i18nString(UIStrings.deviceProperties)} role="group">
        <input
          class="input-field ${this.#showMobileCheckbox ? 'device-model-input' : 'full-row'}"
          type="text"
          @input=${handleDeviceModelChange}
          .value=${model}
          placeholder=${i18nString(UIStrings.deviceModel)}
          jslog=${VisualLogging.textField('model').track({
      change: true,
    })}
        />
        ${mobileCheckboxInput}
      </div>
    `;
  }

  #renderUseragent(): Lit.TemplateResult {
    const {
      brands =
          [
            {
              brand: '',
              version: '',
            },
          ],
    } = this.#metaData;
    const brandElements = brands.map((brandRow, index) => {
      const {brand, version} = brandRow;
      const handleDeleteClick = (): void => {
        this.#handleUseragentDelete(index);
      };
      const handleKeyPress = (event: KeyboardEvent): void => {
        if (event.code === 'Space' || event.code === 'Enter') {
          event.preventDefault();
          handleDeleteClick();
        }
      };
      const handleBrandChange = (event: KeyboardEvent): void => {
        const value = (event.target as HTMLInputElement).value;
        this.#handleUseragentInputChange(value, index, 'brandName');
      };
      const handleVersionChange = (event: KeyboardEvent): void => {
        const value = (event.target as HTMLInputElement).value;
        this.#handleUseragentInputChange(value, index, 'brandVersion');
      };
      return html`
        <div class="full-row brand-row" aria-label=${i18nString(UIStrings.brandProperties)} role="group">
          <input
            class="input-field ua-brand-name-input"
            type="text"
            @input=${handleBrandChange}
            .value=${brand}
            id="ua-brand-${index + 1}-input"
            placeholder=${i18nString(UIStrings.brandName)}
            aria-label=${i18nString(UIStrings.brandNameAriaLabel, {
        PH1: index + 1,
      })}
            jslog=${VisualLogging.textField('brand-name').track({
        change: true,
      })}
          />
          <input
            class="input-field"
            type="text"
            @input=${handleVersionChange}
            .value=${version}
            placeholder=${i18nString(UIStrings.significantBrandVersionPlaceholder)}
            aria-label=${i18nString(UIStrings.brandVersionAriaLabel, {
        PH1: index + 1,
      })}
            jslog=${VisualLogging.textField('brand-version').track({
        change: true,
      })}
          />
          <devtools-icon name="bin"
            title=${i18nString(UIStrings.brandUserAgentDelete)}
            class="medium delete-icon"
            tabindex="0"
            role="button"
            @click=${handleDeleteClick}
            @keypress=${handleKeyPress}
            aria-label=${i18nString(UIStrings.brandUserAgentDelete)}
          >
          </devtools-icon>
        </div>
      `;
    });
    return html`
      <span class="full-row label">${i18nString(UIStrings.useragent)}</span>
      ${brandElements}
      <div
        class="add-container full-row"
        role="button"
        tabindex="0"
        id="add-brand-button"
        aria-label=${i18nString(UIStrings.addBrand)}
        @click=${this.#handleAddUseragentBrandClick}
        @keypress=${this.#handleAddUseragentBrandKeyPress}
      >
        <devtools-icon
          aria-hidden="true" name="plus" class="medium">
        </devtools-icon>
        ${i18nString(UIStrings.addBrand)}
      </div>
    `;
  }

  #renderFullVersionList(): Lit.TemplateResult {
    const {
      fullVersionList =
          [
            {
              brand: '',
              version: '',
            },
          ],
    } = this.#metaData;
    const elements = fullVersionList.map((brandRow, index) => {
      const {brand, version} = brandRow;
      const handleDeleteClick = (): void => {
        this.#handleFullVersionListDelete(index);
      };
      const handleKeyPress = (event: KeyboardEvent): void => {
        if (event.code === 'Space' || event.code === 'Enter') {
          event.preventDefault();
          handleDeleteClick();
        }
      };
      const handleBrandChange = (event: KeyboardEvent): void => {
        const value = (event.target as HTMLInputElement).value;
        this.#handleFullVersionListInputChange(value, index, 'brandName');
      };
      const handleVersionChange = (event: KeyboardEvent): void => {
        const value = (event.target as HTMLInputElement).value;
        this.#handleFullVersionListInputChange(value, index, 'brandVersion');
      };
      return html`
        <div
          class="full-row brand-row"
          aria-label=${i18nString(UIStrings.brandProperties)}
          jslog=${VisualLogging.section('full-version')}
          role="group">
          <input
            class="input-field fvl-brand-name-input"
            type="text"
            @input=${handleBrandChange}
            .value=${brand}
            id="fvl-brand-${index + 1}-input"
            placeholder=${i18nString(UIStrings.brandName)}
            aria-label=${i18nString(UIStrings.brandNameAriaLabel, {
        PH1: index + 1,
      })}
            jslog=${VisualLogging.textField('brand-name').track({
        change: true,
      })}
          />
          <input
            class="input-field"
            type="text"
            @input=${handleVersionChange}
            .value=${version}
            placeholder=${i18nString(UIStrings.brandVersionPlaceholder)}
            aria-label=${i18nString(UIStrings.brandVersionAriaLabel, {
        PH1: index + 1,
      })}
            jslog=${VisualLogging.textField('brand-version').track({
        change: true,
      })}
          />
          <devtools-icon name="bin" 
            title=${i18nString(UIStrings.brandFullVersionListDelete)}
            class="medium delete-icon"
            tabindex="0"
            role="button"
            @click=${handleDeleteClick}
            @keypress=${handleKeyPress}
            aria-label=${i18nString(UIStrings.brandFullVersionListDelete)}
          >
          </devtools-icon>
        </div>
      `;
    });
    return html`
      <span class="full-row label">${i18nString(UIStrings.fullVersionList)}</span>
      ${elements}
      <div
        class="add-container full-row"
        role="button"
        tabindex="0"
        id="add-fvl-brand-button"
        aria-label=${i18nString(UIStrings.addBrand)}
        @click=${this.#handleAddFullVersionListBrandClick}
        @keypress=${this.#handleAddFullVersionListBrandKeyPress}
      >
        <devtools-icon name="plus" class="medium"
          aria-hidden="true">
        </devtools-icon>
        ${i18nString(UIStrings.addBrand)}
      </div>
    `;
  }

  #renderFormFactorsSection(): Lit.TemplateResult {
    const checkboxElements = ALL_PROTOCOL_FORM_FACTORS.map(ffValue => {
      const isChecked = this.#metaData.formFactors?.includes(ffValue) || false;
      const labelStringId = `formFactor${ffValue}` as keyof typeof UIStrings;
      const label = i18nString(UIStrings[labelStringId]);

      return html`
        <label class="form-factor-checkbox-label">
          <input
            type="checkbox"
            .checked=${isChecked}
            value=${ffValue}
            jslog=${VisualLogging.toggle(Platform.StringUtilities.toKebabCase(ffValue)).track({
        click: true
      })}
            @change=${
          (e: Event) => this.#handleFormFactorCheckboxChange(ffValue, (e.target as HTMLInputElement).checked)}
          />
          ${label}
        </label>
      `;
    });

    return html`
      <span class="full-row label" jslog=${VisualLogging.sectionHeader('form-factors')}>
        ${i18nString(UIStrings.formFactorsTitle)}
      </span>
      <div class="full-row form-factors-checkbox-group" role="group" aria-label=${
        i18nString(UIStrings.formFactorsGroupAriaLabel)}>
        ${checkboxElements}
      </div>
    `;
  }

  #render(): void {
    const {fullVersion, architecture} = this.#metaData;
    const useragentSection = this.#renderUseragent();
    const fullVersionListSection = this.#renderFullVersionList();
    const fullBrowserInput = this.#renderInputWithLabel(
        i18nString(UIStrings.fullBrowserVersion), i18nString(UIStrings.fullBrowserVersionPlaceholder),
        fullVersion || '', 'fullVersion');
    const formFactorsSection = this.#renderFormFactorsSection();
    const platformSection = this.#renderPlatformSection();
    const architectureInput = this.#renderInputWithLabel(
        i18nString(UIStrings.architecture), i18nString(UIStrings.architecturePlaceholder), architecture,
        'architecture');
    const deviceModelSection = this.#renderDeviceModelSection();
    // clang-format off
    const submitButton = this.#showSubmitButton ? html`
      <devtools-button
        .variant=${Buttons.Button.Variant.OUTLINED}
        .type=${'submit'}
      >
        ${i18nString(UIStrings.update)}
      </devtools-button>
    ` : Lit.nothing;
    // clang-format on

    // clang-format off
    const output = html`
      <style>${Input.checkboxStyles}</style>
      <style>${userAgentClientHintsFormStyles}</style>
      <section class="root">
        <div class="tree-title">
          <div
            role=button
            @click=${this.#handleTreeClick}
            tabindex=${this.#isFormDisabled ? '-1' : '0'}
            @keydown=${this.#handleTreeExpand}
            aria-expanded=${this.#isFormOpened}
            aria-controls=form-container
            aria-disabled=${this.#isFormDisabled}
            aria-label=${i18nString(UIStrings.title)}
            jslog=${VisualLogging.toggleSubpane().track({click: true})}>
            <devtools-icon name=triangle-right></devtools-icon>
            <devtools-icon name=triangle-down></devtools-icon>
            ${i18nString(UIStrings.title)}
          </div>
          <devtools-icon tabindex=${this.#isFormDisabled ? '-1' : '0'} class=info-icon name=info aria-label=${i18nString(UIStrings.userAgentClientHintsInfo)} title=${i18nString(UIStrings.userAgentClientHintsInfo)}></devtools-icon>
          <devtools-link
           tabindex=${this.#isFormDisabled ? '-1' : '0'}
           href="https://web.dev/user-agent-client-hints/"
           class="link"
           aria-label=${i18nString(UIStrings.learnMore)}
           jslogcontext="learn-more"
          >
            ${i18nString(UIStrings.learnMore)}
          </devtools-link>
        </div>
        <form
          id="form-container"
          class="form-container ${this.#isFormOpened ? '' : 'hide-container'}"
          @submit=${this.#handleSubmit}
        >
          ${useragentSection}
          <hr class="section-separator">
          ${fullVersionListSection}
          <hr class="section-separator">
          ${fullBrowserInput}
          <hr class="section-separator">
          ${formFactorsSection}
          <hr class="section-separator">
          ${platformSection}
          <hr class="section-separator">
          ${architectureInput}
          <hr class="section-separator">
          ${deviceModelSection}
          ${submitButton}
        </form>
        <div aria-live="polite" aria-label=${this.#useragentModifiedAriaMessage}></div>
      </section>
    `;
    // clang-format on
    Lit.render(output, this.#shadow, {host: this});
  }

  validate = (): UI.ListWidget.ValidatorResult => {
    for (const [metaDataKey, metaDataValue] of Object.entries(this.#metaData)) {
      if (metaDataKey === 'brands' || metaDataKey === 'fullVersionList') {
        // for structured fields, check each individual brand/version
        const isBrandValid = this.#metaData.brands?.every(({brand, version}) => {
          const brandNameResult = EmulationUtils.UserAgentMetadata.validateAsStructuredHeadersString(
              brand, i18nString(UIStrings.notRepresentable));
          const brandVersionResult = EmulationUtils.UserAgentMetadata.validateAsStructuredHeadersString(
              version, i18nString(UIStrings.notRepresentable));
          return brandNameResult.valid && brandVersionResult.valid;
        });
        if (!isBrandValid) {
          return {valid: false, errorMessage: i18nString(UIStrings.notRepresentable)};
        }
      } else if (metaDataKey === 'formFactors') {
        const formFactors = metaDataValue as string[] | undefined;
        if (formFactors) {
          for (const ff of formFactors) {
            if (!ALL_PROTOCOL_FORM_FACTORS.includes(ff)) {
              return {
                valid: false,
                errorMessage: i18nString(UIStrings.notRepresentable) + ` (Invalid form factor: ${ff})`,
              };
            }
            const ffError = EmulationUtils.UserAgentMetadata.validateAsStructuredHeadersString(
                ff, i18nString(UIStrings.notRepresentable));
            if (!ffError.valid) {
              return ffError;
            }
          }
        }
      } else {
        // otherwise, validate the value as a string
        const metaDataError = EmulationUtils.UserAgentMetadata.validateAsStructuredHeadersString(
            metaDataValue, i18nString(UIStrings.notRepresentable));
        if (!metaDataError.valid) {
          return metaDataError;
        }
      }
    }
    return {valid: true};
  };
}

customElements.define('devtools-user-agent-client-hints-form', UserAgentClientHintsForm);

declare global {
  interface HTMLElementTagNameMap {
    'devtools-user-agent-client-hints-form': UserAgentClientHintsForm;
  }
}
