// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Common from '../../../core/common/common.js';
import * as Host from '../../../core/host/host.js';
import * as i18n from '../../../core/i18n/i18n.js';
import type * as Platform from '../../../core/platform/platform.js';
import * as SDK from '../../../core/sdk/sdk.js';
import * 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 requestHeadersViewStyles from './RequestHeadersView.css.js';

const {render, html} = Lit;

const UIStrings = {
  /**
   * @description Section header for a list of the main aspects of a direct socket connection
   */
  general: 'General',
  /**
   * @description Section header for a list of the main aspects of a direct socket connection
   */
  options: 'Options',
  /**
   * @description Section header for a list of the main aspects of a direct socket connection
   */
  openInfo: 'Open Info',
  /**
   * @description Text in Connection info View of the Network panel
   */
  type: 'DirectSocket Type',
  /**
   * @description Text in Connection info View of the Network panel
   */
  errorMessage: 'Error message',
  /**
   * @description Text in Connection info View of the Network panel
   */
  status: 'Status',
  /**
   * @description Text in Connection info View of the Network panel
   */
  directSocketTypeTcp: 'TCP',
  /**
   * @description Text in Connection info View of the Network panel
   */
  directSocketTypeUdpConnected: 'UDP (connected)',
  /**
   * @description Text in Connection info View of the Network panel
   */
  directSocketTypeUdpBound: 'UDP (bound)',
  /**
   * @description Text in Connection info View of the Network panel
   */
  directSocketStatusOpening: 'Opening',
  /**
   * @description Text in Connection info View of the Network panel
   */
  directSocketStatusOpen: 'Open',
  /**
   * @description Text in Connection info View of the Network panel
   */
  directSocketStatusClosed: 'Closed',
  /**
   * @description Text in Connection info View of the Network panel
   */
  directSocketStatusAborted: 'Aborted',
  /**
   * @description Text in Connection info View of the Network panel
   */
  joinedMulticastGroups: 'joinedMulticastGroups',

} as const;

const str_ = i18n.i18n.registerUIStrings('panels/network/components/DirectSocketConnectionView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

function getDirectSocketTypeString(type: SDK.NetworkRequest.DirectSocketType): Platform.UIString.LocalizedString {
  switch (type) {
    case SDK.NetworkRequest.DirectSocketType.TCP:
      return i18nString(UIStrings.directSocketTypeTcp);
    case SDK.NetworkRequest.DirectSocketType.UDP_BOUND:
      return i18nString(UIStrings.directSocketTypeUdpBound);
    case SDK.NetworkRequest.DirectSocketType.UDP_CONNECTED:
      return i18nString(UIStrings.directSocketTypeUdpConnected);
  }
}

function getDirectSocketStatusString(status: SDK.NetworkRequest.DirectSocketStatus): Platform.UIString.LocalizedString {
  switch (status) {
    case SDK.NetworkRequest.DirectSocketStatus.OPENING:
      return i18nString(UIStrings.directSocketStatusOpening);
    case SDK.NetworkRequest.DirectSocketStatus.OPEN:
      return i18nString(UIStrings.directSocketStatusOpen);
    case SDK.NetworkRequest.DirectSocketStatus.CLOSED:
      return i18nString(UIStrings.directSocketStatusClosed);
    case SDK.NetworkRequest.DirectSocketStatus.ABORTED:
      return i18nString(UIStrings.directSocketStatusAborted);
  }
}

export const CATEGORY_NAME_GENERAL = 'general';
export const CATEGORY_NAME_OPTIONS = 'options';
export const CATEGORY_NAME_OPEN_INFO = 'open-info';

export interface ViewInput {
  socketInfo: SDK.NetworkRequest.DirectSocketInfo;
  openCategories: string[];
  onSummaryKeyDown: (event: KeyboardEvent, categoryName: string) => void;
  onToggleCategory: (event: Event, categoryName: string) => void;
  onCopyRow: () => void;
}

export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;

export const DEFAULT_VIEW: View = (input, _output, target) => {
  function isCategoryOpen(name: string): boolean {
    return input.openCategories.includes(name);
  }

  function renderCategory(
      name: string, title: Common.UIString.LocalizedString, content: Lit.LitTemplate): Lit.TemplateResult {
    // clang-format off
      return html`
        <details
          class="direct-socket-category"
          ?open=${isCategoryOpen(name)}
          @toggle=${(e: Event) => input.onToggleCategory(e, name)}
          jslog=${VisualLogging.sectionHeader(name).track({click: true})}
          aria-label=${title}
        >
          <summary
            class="header"
            @keydown=${(e: KeyboardEvent) => input.onSummaryKeyDown(e, name)}
          >
            <div class="header-grid-container">
              <div>
                ${title}
              </div>
              <div class="hide-when-closed"></div>
            </div>
          </summary>
          ${content}
        </details>
      `;
    // clang-format on
  }

  function renderRow(
      name: Common.UIString.LocalizedString, value: string|undefined, classNames?: string[]): Lit.LitTemplate {
    if (!value) {
      return Lit.nothing;
    }
    return html`
        <div class="row">
          <div class="header-name">${name}:</div>
          <div
            class="header-value ${classNames?.join(' ')}"
            @copy=${() => input.onCopyRow()}
          >${value}</div>
        </div>
      `;
  }

  const socketInfo: SDK.NetworkRequest.DirectSocketInfo = input.socketInfo;
  const generalContent = html`
      <div jslog=${VisualLogging.section(CATEGORY_NAME_GENERAL)}>
        ${renderRow(i18nString(UIStrings.type), getDirectSocketTypeString(socketInfo.type))}
        ${renderRow(i18nString(UIStrings.status), getDirectSocketStatusString(socketInfo.status))}
        ${renderRow(i18nString(UIStrings.errorMessage), socketInfo.errorMessage)}
        ${
      renderRow(
          i18nString(UIStrings.joinedMulticastGroups),
          socketInfo.joinedMulticastGroups ? Array.from(socketInfo.joinedMulticastGroups).join(', ') : '')}
      </div>`;

  const optionsContent = html`
      <div jslog=${VisualLogging.section(CATEGORY_NAME_OPTIONS)}>
        ${renderRow(i18n.i18n.lockedString('remoteAddress'), socketInfo.createOptions.remoteAddr)}
        ${renderRow(i18n.i18n.lockedString('remotePort'), socketInfo.createOptions.remotePort?.toString(10))}
        ${renderRow(i18n.i18n.lockedString('localAddress'), socketInfo.createOptions.localAddr)}
        ${renderRow(i18n.i18n.lockedString('localPort'), socketInfo.createOptions.localPort?.toString(10))}
        ${renderRow(i18n.i18n.lockedString('noDelay'), socketInfo.createOptions.noDelay?.toString())}
        ${renderRow(i18n.i18n.lockedString('keepAliveDelay'), socketInfo.createOptions.keepAliveDelay?.toString(10))}
        ${renderRow(i18n.i18n.lockedString('sendBufferSize'), socketInfo.createOptions.sendBufferSize?.toString(10))}
        ${
      renderRow(i18n.i18n.lockedString('receiveBufferSize'), socketInfo.createOptions.receiveBufferSize?.toString(10))}
        ${renderRow(i18n.i18n.lockedString('dnsQueryType'), socketInfo.createOptions.dnsQueryType)}
        ${
      renderRow(
          i18n.i18n.lockedString('multicastTimeToLive'), socketInfo.createOptions.multicastTimeToLive?.toString(10))}
        ${
      renderRow(i18n.i18n.lockedString('multicastLoopback'), socketInfo.createOptions.multicastLoopback?.toString())}
        ${
      renderRow(
          i18n.i18n.lockedString('multicastAllowAddressSharing'),
          socketInfo.createOptions.multicastAllowAddressSharing?.toString())}
      </div>`;

  let openInfoContent: Lit.LitTemplate = Lit.nothing;
  if (socketInfo.openInfo) {
    openInfoContent = html`
          <div jslog=${VisualLogging.section(CATEGORY_NAME_OPEN_INFO)}>
            ${renderRow(i18n.i18n.lockedString('remoteAddress'), socketInfo.openInfo.remoteAddr)}
            ${renderRow(i18n.i18n.lockedString('remotePort'), socketInfo.openInfo?.remotePort?.toString(10))}
            ${renderRow(i18n.i18n.lockedString('localAddress'), socketInfo.openInfo.localAddr)}
            ${renderRow(i18n.i18n.lockedString('localPort'), socketInfo.openInfo?.localPort?.toString(10))}
          </div>`;
  }

  // clang-format off
  render(html`
    <style>${UI.inspectorCommonStyles}</style>
    <style>${requestHeadersViewStyles}</style>
    ${renderCategory(CATEGORY_NAME_GENERAL, i18nString(UIStrings.general), generalContent)}
    ${renderCategory(CATEGORY_NAME_OPTIONS, i18nString(UIStrings.options), optionsContent)}
    ${socketInfo.openInfo ? renderCategory(CATEGORY_NAME_OPEN_INFO, i18nString(UIStrings.openInfo), openInfoContent) : Lit.nothing}
  `, target);
  // clang-format on
};

export class DirectSocketConnectionView extends UI.Widget.Widget {
  #request: Readonly<SDK.NetworkRequest.NetworkRequest>;
  #view: View;

  constructor(request: SDK.NetworkRequest.NetworkRequest, view: View = DEFAULT_VIEW) {
    super({
      jslog: `${VisualLogging.pane('connection-info').track({resize: true})}`,
      useShadowDom: true,
    });
    this.#request = request;
    this.#view = view;

    this.performUpdate();
  }

  override wasShown(): void {
    super.wasShown();
    this.#request.addEventListener(SDK.NetworkRequest.Events.TIMING_CHANGED, this.requestUpdate, this);
  }

  override willHide(): void {
    super.willHide();
    this.#request.removeEventListener(SDK.NetworkRequest.Events.TIMING_CHANGED, this.requestUpdate, this);
  }

  override performUpdate(): void {
    if (!this.#request || !this.#request.directSocketInfo) {
      return;
    }

    const openCategories = [CATEGORY_NAME_GENERAL, CATEGORY_NAME_OPTIONS, CATEGORY_NAME_OPEN_INFO].filter(value => {
      return this.#getCategorySetting(value).get();
    }, this);

    const viewInput: ViewInput = {
      socketInfo: this.#request.directSocketInfo,
      openCategories,
      onSummaryKeyDown: (event: KeyboardEvent, categoryName: string) => {
        if (!event.target) {
          return;
        }
        const summaryElement = event.target as HTMLElement;
        const detailsElement = summaryElement.parentElement as HTMLDetailsElement;
        if (!detailsElement) {
          throw new Error('<details> element is not found for a <summary> element');
        }
        let shouldBeOpen: boolean;

        switch (event.key) {
          case 'ArrowLeft':
            shouldBeOpen = false;
            break;
          case 'ArrowRight':
            shouldBeOpen = true;
            break;
          default:
            return;
        }

        if (detailsElement.open !== shouldBeOpen) {
          this.#setIsOpen(categoryName, shouldBeOpen);
        }
      },
      onToggleCategory: (event: Event, categoryName: string) => {
        const detailsElement = event.target as HTMLDetailsElement;
        this.#setIsOpen(categoryName, detailsElement.open);
      },
      onCopyRow: () => {
        Host.userMetrics.actionTaken(Host.UserMetrics.Action.NetworkPanelCopyValue);
      }
    };
    this.#view(viewInput, undefined, this.contentElement);
  }

  #setIsOpen(categoryName: string, open: boolean): void {
    const setting = this.#getCategorySetting(categoryName);
    setting.set(open);
    this.requestUpdate();
  }

  #getCategorySetting(name: string): Common.Settings.Setting<boolean> {
    return Common.Settings.Settings.instance().createSetting(
        `connection-info-${name}-category-expanded`, /* defaultValue= */ true);
  }
}
