import type { ShikiTransformer } from '@shikijs/types' import type { ElementContent, RootContent } from 'hast' import { isElement } from 'hast-util-is-element' import { CODE_PROPERTY_CONTAINER, CODE_PROPERTY_TITLE } from './constants' import { parseTitleString } from './shiki-meta-title' const name = 'starlight-theme-nova-shiki-transformer-container' /** * A transformer to wrap code blocks in a container. */ export function transformerContainer(): ShikiTransformer { return { name: name, root(node) { if (node.children.length === 0) { throw new Error(`[${name}] Expected at least one child`) } const hasPre = node.children.some((child) => isElement(child, 'pre')) if (!hasPre) { throw new Error( `[${name}] Expected a
 element in the root node but got ${JSON.stringify(node)}`,
        )
      }

      const title = parseTitleString(this.options.meta?.__raw || '')

      const children: ElementContent[] = normalizeContent(node.children)

      if (title) {
        children.unshift({
          type: 'element',
          tagName: 'div',
          properties: {
            class:
              'hidden border-b border-solid border-b-(--sl-color-gray-5) bg-(--sl-color-bg) px-4 py-2 font-mono text-sm text-(--sl-color-gray-3) [div[data-nova-code-container][data-nova-code-title]_&]:block',
          },
          children: [
            {
              type: 'text',
              value: title,
            },
          ],
        })
      }

      node.children = [
        {
          type: 'element',
          tagName: 'div',
          properties: {
            [CODE_PROPERTY_CONTAINER]: '',
            [CODE_PROPERTY_TITLE]: title || undefined,
            class:
              'not-content relative overflow-hidden [&_pre]:relative [&_nova-code-copy-button]:opacity-0 [&:hover_nova-code-copy-button]:opacity-100 border border-solid border-(--sl-color-gray-5) rounded-md',
          },
          children: [
            ...children,
            {
              type: 'element',
              tagName: 'nova-code-copy-button',
              properties: {
                type: 'button',
                'data-code': this.source,
                title: 'Copy code',
                'aria-label': 'Copy code',
                class:
                  'absolute top-2 right-2 m-0 size-6 rounded border border-solid border-(--sl-color-gray-5) bg-gray-100/30 p-1 text-black backdrop-blur-sm transition hover:bg-gray-200/50 active:scale-90 dark:bg-gray-600/30 dark:text-white hover:dark:bg-gray-500/50',
              },
              children: [],
            },
          ],
        },
      ]

      return node
    },
  }
}

// Shiki only emits elements and text here, but Sätteri augments the `hast`
// module so `RootContent` also includes flow-only nodes (e.g. `mdxjsEsm`).
// Narrow back to `ElementContent`, dropping the `doctype` that can't be a child.
function normalizeContent(children: RootContent[]): ElementContent[] {
  return children.filter(
    (child): child is ElementContent => child.type !== 'doctype',
  )
}