// Copyright 2026 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 i18n from '../../../core/i18n/i18n.js';
import type * as SDK from '../../../core/sdk/sdk.js';
import {
  type ContextDetail,
  ConversationContext,
  type ConversationSuggestions,
} from '../agents/AiAgent.js';

const UIStringsNotTranslate = {
  /**
   * @description Heading text for context details of DevTools AI Agent.
   */
  dataUsed: 'Data used',
} as const;

const lockedString = i18n.i18n.lockedString;

export class DOMNodeContext extends ConversationContext<SDK.DOMModel.DOMNode> {
  #node: SDK.DOMModel.DOMNode;

  constructor(node: SDK.DOMModel.DOMNode) {
    super();
    this.#node = node;
  }

  override getURL(): string {
    const ownerDocument = this.#node.ownerDocument;
    if (!ownerDocument) {
      // The node is detached from a document.
      return 'detached';
    }
    return ownerDocument.documentURL;
  }

  getItem(): SDK.DOMModel.DOMNode {
    return this.#node;
  }

  override getTitle(): string {
    throw new Error('Not implemented');
  }

  override async getSuggestions(): Promise<ConversationSuggestions|undefined> {
    const layoutProps = await this.#node.domModel().cssModel().getLayoutPropertiesFromComputedStyle(this.#node.id);

    if (!layoutProps) {
      return;
    }

    if (layoutProps.isFlex) {
      return [
        {title: 'How can I make flex items wrap?', jslogContext: 'flex-wrap'},
        {title: 'How do I distribute flex items evenly?', jslogContext: 'flex-distribute'},
        {title: 'What is flexbox?', jslogContext: 'flex-what'},
      ];
    }
    if (layoutProps.isSubgrid) {
      return [
        {title: 'Where is this grid defined?', jslogContext: 'subgrid-where'},
        {title: 'How to overwrite parent grid properties?', jslogContext: 'subgrid-override'},
        {title: 'How do subgrids work? ', jslogContext: 'subgrid-how'},
      ];
    }
    if (layoutProps.isGrid) {
      return [
        {title: 'How do I align items in a grid?', jslogContext: 'grid-align'},
        {title: 'How to add spacing between grid items?', jslogContext: 'grid-gap'},
        {title: 'How does grid layout work?', jslogContext: 'grid-how'},
      ];
    }
    if (layoutProps.hasScroll) {
      return [
        {title: 'How do I remove scrollbars for this element?', jslogContext: 'scroll-remove'},
        {title: 'How can I style a scrollbar?', jslogContext: 'scroll-style'},
        {title: 'Why does this element scroll?', jslogContext: 'scroll-why'},
      ];
    }
    if (layoutProps.containerType) {
      return [
        {title: 'What are container queries?', jslogContext: 'container-what'},
        {title: 'How do I use container-type?', jslogContext: 'container-how'},
        {title: 'What\'s the container context for this element?', jslogContext: 'container-context'},
      ];
    }

    return;
  }

  override async getPromptDetails(): Promise<string|null> {
    return `# Inspected element

${await this.describe()}`;
  }

  override async getUserFacingDetails(): Promise<[ContextDetail, ...ContextDetail[]]|null> {
    return [
      {
        title: lockedString(UIStringsNotTranslate.dataUsed),
        text: await this.describe(),
      },
    ];
  }

  async describe(): Promise<string> {
    const element = this.#node;
    let output = `* Element's uid is ${element.backendNodeId()}.
* Its selector is \`${element.simpleSelector()}\``;
    const childNodes = await element.getChildNodesPromise();
    if (childNodes) {
      const textChildNodes = childNodes.filter(childNode => childNode.nodeType() === Node.TEXT_NODE);
      const elementChildNodes = childNodes.filter(childNode => childNode.nodeType() === Node.ELEMENT_NODE);
      switch (elementChildNodes.length) {
        case 0:
          output += '\n* It doesn\'t have any child element nodes';
          break;
        case 1:
          output += `\n* It only has 1 child element node: \`${elementChildNodes[0].simpleSelector()}\``;
          break;
        default:
          output += `\n* It has ${elementChildNodes.length} child element nodes: ${
              elementChildNodes.map(node => `\`${node.simpleSelector()}\` (uid=${node.backendNodeId()})`).join(', ')}`;
      }

      switch (textChildNodes.length) {
        case 0:
          output += '\n* It doesn\'t have any child text nodes';
          break;
        case 1:
          output += '\n* It only has 1 child text node';
          break;
        default:
          output += `\n* It has ${textChildNodes.length} child text nodes`;
      }
    }

    if (element.nextSibling) {
      const elementOrNodeElementNodeText = element.nextSibling.nodeType() === Node.ELEMENT_NODE ?
          `an element (uid=${element.nextSibling.backendNodeId()})` :
          'a non element';
      output += `\n* It has a next sibling and it is ${elementOrNodeElementNodeText} node`;
    }

    if (element.previousSibling) {
      const elementOrNodeElementNodeText = element.previousSibling.nodeType() === Node.ELEMENT_NODE ?
          `an element (uid=${element.previousSibling.backendNodeId()})` :
          'a non element';
      output += `\n* It has a previous sibling and it is ${elementOrNodeElementNodeText} node`;
    }

    if (element.isInShadowTree()) {
      output += '\n* It is in a shadow DOM tree.';
    }

    const parentNode = element.parentNode;
    if (parentNode) {
      const parentChildrenNodes = await parentNode.getChildNodesPromise();
      output += `\n* Its parent's selector is \`${parentNode.simpleSelector()}\` (uid=${parentNode.backendNodeId()})`;
      const elementOrNodeElementNodeText = parentNode.nodeType() === Node.ELEMENT_NODE ? 'an element' : 'a non element';
      output += `\n* Its parent is ${elementOrNodeElementNodeText} node`;
      if (parentNode.isShadowRoot()) {
        output += '\n* Its parent is a shadow root.';
      }
      if (parentChildrenNodes) {
        const childElementNodes =
            parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === Node.ELEMENT_NODE);
        switch (childElementNodes.length) {
          case 0:
            break;
          case 1:
            output += '\n* Its parent has only 1 child element node';
            break;
          default:
            output += `\n* Its parent has ${childElementNodes.length} child element nodes: ${
                childElementNodes.map(node => `\`${node.simpleSelector()}\` (uid=${node.backendNodeId()})`)
                    .join(', ')}`;
            break;
        }

        const siblingTextNodes = parentChildrenNodes.filter(siblingNode => siblingNode.nodeType() === Node.TEXT_NODE);
        switch (siblingTextNodes.length) {
          case 0:
            break;
          case 1:
            output += '\n* Its parent has only 1 child text node';
            break;
          default:
            output += `\n* Its parent has ${siblingTextNodes.length} child text nodes: ${
                siblingTextNodes.map(node => `\`${node.simpleSelector()}\``).join(', ')}`;
            break;
        }
      }
    }

    return output.trim();
  }
}
