// 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 SDK from '../../../core/sdk/sdk.js';
import type * as Protocol from '../../../generated/protocol.js';
import * as Marked from '../../../third_party/marked/marked.js';
import type * as MarkdownView from '../../../ui/components/markdown_view/MarkdownView.js';
import * as Lit from '../../../ui/lit/lit.js';
import * as PanelsCommon from '../../common/common.js';

import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js';

const {html} = Lit.StaticHtml;
const {until} = Lit.Directives;

export class StylingAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
  constructor(
      private mainFrameId = '',
  ) {
    super();
  }

  #renderTableFromJson(data: Array<Record<string, string>>): Lit.LitTemplate|null {
    if (!Array.isArray(data) || data.length === 0 || typeof data[0] !== 'object' || data[0] === null) {
      return null;
    }

    const headers = Object.keys(data[0]);
    const requiredKeys = ['Problem', 'Element', 'NodeId', 'Details'];
    if (!requiredKeys.every(key => headers.includes(key))) {
      return null;  // Not the expected JSON structure
    }

    const problemIndex = headers.indexOf('Problem');
    if (problemIndex > -1) {
      const problemHeader = headers.splice(problemIndex, 1);
      headers.unshift(...problemHeader);
    }

    return html`
      <table style="width: 100%;">
        <thead>
          <tr>
            ${headers.map(header => html`<th style="text-align: left;">${header === 'NodeId' ? '' : header}</th>`)}
          </tr>
        </thead>
        <tbody>
          ${data.flatMap(row => {
      return html`
            <tr>
              ${headers.map(header => {
        if (header === 'NodeId') {
          return html`<td>${this.#renderLinkifiedText(row[header])}</td>`;
        }
        if (header === 'Details') {
          // eslint-disable-next-line @devtools/no-a-tags-in-lit
          return html`<td><a href="#" @click=${this.#toggleDetailsRow}>Details</a></td>`;
        }
        return html`<td>${row[header]}</td>`;
      })}
            </tr>
            <tr class="details-row" style="display: none;">
              <td colspan=${headers.length} style="background-color: #f0f0f0; padding: 1em;">
                <devtools-markdown-view .data=${{
        tokens: Marked.Marked.lexer(row['Details']),
        renderer: new StylingAgentMarkdownRenderer(this.mainFrameId),
      } as MarkdownView.MarkdownViewData}></devtools-markdown-view>
              </td>
            </tr>
          `;
    })}
        </tbody>
      </table>
      <br><div>To investigate these problems, please click one of the provided links (above), to set as context, and ask me further questions about the problem.</div>
    `;
  }

  override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
    if (token.type === 'code') {
      try {
        const data = JSON.parse(token.text);
        const table = this.#renderTableFromJson(data);
        if (table) {
          return table;
        }
      } catch {
        // Not a JSON object, fallback to default rendering.
      }
    }

    if (token.type === 'link' && token.href.startsWith('#')) {
      let nodeId = undefined;
      if (token.href.startsWith('#node-')) {
        nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId;
      } else if (token.href.startsWith('#')) {
        // So often does it ignore requests to prepend nodes with node-, frustratingly.
        nodeId = Number(token.href.replace('#', '')) as Protocol.DOM.BackendNodeId;
      }

      if (nodeId) {
        return html`<span>${
            until(this.#linkifyNode(nodeId, token.text).then(node => node || token.text), token.text)}</span>`;
      }
    }

    return super.templateForToken(token);
  }

  #toggleDetailsRow(e: Event): void {
    e.preventDefault();
    const link = e.target as HTMLAnchorElement;
    const currentRow = link.closest('tr');
    if (!currentRow) {
      return;
    }
    const detailsRow = currentRow.nextElementSibling as HTMLTableRowElement | null;
    if (detailsRow?.classList.contains('details-row')) {
      if (detailsRow.style.display === 'none') {
        detailsRow.style.display = 'table-row';
        link.textContent = 'Hide';
      } else {
        detailsRow.style.display = 'none';
        link.textContent = 'Details';
      }
    }
  }

  #renderLinkifiedText(text: string): Lit.LitTemplate {
    if (text.indexOf(',') === -1) {
      const nodeId = Number(text) as Protocol.DOM.BackendNodeId;
      if (isNaN(nodeId)) {
        // Not a number, return as is.
        return html`${text}`;
      }
      return this.#renderSingleLink(nodeId);
    }

    // Check for comma separated list.
    const nodeIdsStr = text.split(',').map(s => s.trim()).filter(Boolean);
    return html`${nodeIdsStr.map(idStr => {
      const nodeId = Number(idStr) as Protocol.DOM.BackendNodeId;
      if (isNaN(nodeId)) {
        return html`<div>${idStr}</div>`;
      }
      return html`<div>${this.#renderSingleLink(nodeId)}</div>`;
    })}`;
  }

  #renderSingleLink(nodeId: Protocol.DOM.BackendNodeId): Lit.LitTemplate {
    const label = `link`;
    return html`<span>${until(this.#linkifyNode(nodeId, label).then(node => node || label), label)}</span>`;
  }

  async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Lit.LitTemplate|undefined> {
    if (backendNodeId === undefined) {
      return;
    }

    const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
    const domModel = target?.model(SDK.DOMModel.DOMModel);
    if (!domModel) {
      return undefined;
    }
    const domNodesMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
    const node = domNodesMap?.get(backendNodeId);
    if (!node) {
      return;
    }

    if (node.frameId() !== this.mainFrameId) {
      return;
    }

    const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label});
    return linkedNode;
  }
}
