// ds-prompt.mjs — render the per-load "design-system prompt" (_ds_prompt.md)
// that import-design-system.mjs drops into _ds/<slug>/. It mirrors the web
// product's injected design-mode prompt, adapted to the portable layout:
// binding + scope, a bundle-first wiring section (the pinned React UMD tags,
// every stylesheet in the @import closure, the bundle, and how the page's own
// JSX runs through Babel standalone), the source-tree pointer, the full guide
// inlined, the first lines of each component's *.prompt.md
// (<ds-prompt-excerpts> — those files are not bound into _ds/), the per-component
// prop contracts with full enum value lists (<ds-component-props>), and the
// exact var(--*) token allowlist.
// Project-relative `_ds/<slug>/` paths for the runtime copy; the recorded
// `sourcePath` for the system's source (mocks/ui_kits/component source).
//
// Pure string assembly — reads and writes nothing. Reused by the importer and
// any future regen script/skill.

const SCOPE =
  'Scope: the design system is a visual style reference only. Its guide may describe ' +
  'example products, brands, or people unrelated to the user and to the subject of this ' +
  'conversation. Never treat anything in the design system as a fact about the user, their ' +
  'work, or the topic.';

// Pinned page-scaffold tags — keep in sync with system-prompt.md ("React + Babel").
// The prompt must stand alone for whoever reopens the project, so the exact tags
// ride here rather than a pointer to the skill docs.
const REACT_UMD_TAGS = [
  '<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>',
  '<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>',
];
const BABEL_TAG =
  '<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>';

// Destructure-example names — prefer idiomatic PascalCase component exports so a
// constant-style export (ICON_NAMES) never becomes the usage sample.
export function sampleComponentNames(names, n = 2) {
  const list = Array.isArray(names) ? names.filter(Boolean) : [];
  const components = list.filter((x) => /^[A-Z][A-Za-z0-9]*$/.test(x) && /[a-z]/.test(x));
  return (components.length ? components : list).slice(0, n);
}

// First lines of a component's `*.prompt.md` — the one-sentence "what & when"
// plus its first JSX example as a COMPLETE fenced block (an excerpt cut mid-block
// would render an empty ```jsx``` fence, which is worse than no example). Blank
// lines don't count toward maxLines, so a fence that follows the prose is always
// reached; an overlong block is truncated but re-closed so the fence stays balanced.
export function extractPromptExcerpt(text, maxLines = 5, maxBlockLines = 14) {
  const lines = String(text ?? '').split(/\r?\n/);
  while (lines.length && !lines[0].trim()) lines.shift();

  const out = [];
  let prose = 0;
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (/^\s*```/.test(line)) {
      out.push(line);
      let used = 0;
      let closed = false;
      for (i += 1; i < lines.length; i++) {
        if (/^\s*```/.test(lines[i])) { out.push(lines[i]); closed = true; break; }
        if (used >= maxBlockLines) break;
        out.push(lines[i]); used += 1;
      }
      if (!closed) out.push('```');
      break;
    }
    if (!line.trim()) { if (out.length) out.push(line); continue; }
    if (prose >= maxLines) break;
    out.push(line); prose += 1;
  }

  while (out.length && !out[out.length - 1].trim()) out.pop();
  if (out.length >= 2 && /^\s*```/.test(out[out.length - 1]) && /^\s*```/.test(out[out.length - 2])) {
    out.pop(); out.pop();
    while (out.length && !out[out.length - 1].trim()) out.pop();
  }
  return out.join('\n');
}

// one-line type display for non-enum props: collapse whitespace, fold function
// signatures, cap length — value lists never pass through here
function compactType(type) {
  const t = String(type ?? '').replace(/\s+/g, ' ').trim();
  if (/^\(.*\)\s*=>/.test(t)) return 'function';
  return t.length > 40 ? `${t.slice(0, 37)}…` : t;
}

export function renderDsPrompt({
  name,
  slug,
  namespace,
  globalCssPaths = [],
  componentNames = [],
  componentPrompts = [],
  componentProps = [],
  readme = '',
  tokenNames = [],
  sourcePath = '',
  hasBundle = false,
} = {}) {
  const dsName = name || slug || 'this';
  const out = [];
  out.push(
    '<!-- Generated by import-design-system.mjs — regenerate by re-running the import. Do not hand-edit. -->',
  );
  out.push('');
  out.push(
    `[Design System] This project uses the **${dsName}** design system. This is a binding ` +
    "choice for visual style — every visual must follow it. Don't invent colors, type, " +
    'spacing, or components not grounded here.',
  );
  out.push('');
  out.push(SCOPE);

  // wiring — bundle-first when the system exports components, stylesheet-led otherwise
  const links = globalCssPaths.map((p) => `<link rel="stylesheet" href="_ds/${slug}/${p}">`);
  const composable = hasBundle && namespace && componentNames.length > 0;
  const block = [];
  if (hasBundle) {
    block.push('<!-- React first — the bundle calls React.createElement -->');
    block.push(...REACT_UMD_TAGS);
  }
  block.push(...links);
  if (hasBundle) block.push(`<script src="_ds/${slug}/_ds_bundle.js"></script>`);

  if (block.length) {
    out.push('');
    if (composable) {
      out.push(
        '**Loading the bundle is how you use this design system.** Every page you build must ' +
        `load \`_ds/${slug}/_ds_bundle.js\` and compose with the components it exports on ` +
        `\`window.${namespace}\` — do not recreate those components from scratch or restyle ` +
        'raw HTML to look like them. The bundle calls `React.createElement`, so `window.React` ' +
        'and `window.ReactDOM` must load first — the pinned tags below provide them. Add ' +
        'these once:',
      );
    } else {
      out.push(
        '**Loading the stylesheet(s) is how you use this design system.** Every page you build ' +
        'must load the stylesheet(s) below — they carry the design tokens (colors, type, ' +
        "spacing) and the system's classes. Add these once:",
      );
    }
    out.push('');
    out.push('```html');
    out.push(...block);
    out.push('```');
    out.push('');
    out.push(
      "With several design systems bound, keep each system's stylesheets together and load " +
      "the PRIMARY system's `<link>`s LAST so its tokens win.",
    );
    out.push('');
    if (composable) {
      const sampleNames = sampleComponentNames(componentNames);
      const sample = sampleNames.join(', ');
      out.push(
        'The bundle is plain compiled JS — load it with a regular `<script>` (no ' +
        '`type="text/babel"` or `type="module"`). Every component is then available on ' +
        `\`window.${namespace}\` — e.g. \`const { ${sample} } = window.${namespace};\`. ` +
        'The linked stylesheet(s) carry the design tokens (colors, type, spacing) — style ' +
        "against those via `var(--*)` and the design system's classes rather than guessing values.",
      );
      out.push('');
      out.push(
        'Your own page code stays separate from the bundle: write it in ' +
        '`<script type="text/babel">` blocks, with Babel standalone loaded once (pinned tag ' +
        'below) to transpile your JSX in the browser —',
      );
      out.push('');
      out.push('```html');
      out.push(BABEL_TAG);
      out.push('<script type="text/babel">');
      out.push(`  const { ${sample} } = window.${namespace};`);
      out.push(`  ReactDOM.createRoot(document.getElementById('root')).render(<${sampleNames[0]} />);`);
      out.push('</script>');
      out.push('```');
      out.push('');
      out.push(
        'In text/babel blocks, never give a top-level binding a window-global name — `status`, ' +
        '`name`, `open`, `close`, `top`, `self`, `parent`, `origin`, `event`, `length`, ' +
        '`location`, `history`, `screen`, `scroll`, `stop`, `print`, `focus`, `blur`, `frames`, ' +
        '`closed`, `opener`. Babel injects the transpiled code as a classic script (top-level ' +
        'const/let become var), so `const status = …` writes `window.status`, which coerces or ' +
        'misbehaves, and the page dies with an error the console may never show. Use a longer ' +
        'name (`statusBadge`, `openItems`).',
      );
    } else if (hasBundle) {
      out.push(
        'The bundle is plain compiled JS — load it with a regular `<script>` (no ' +
        '`type="text/babel"` or `type="module"`) after `window.React`/`window.ReactDOM`. It ' +
        "currently exports no components — this system's value is its stylesheets; style " +
        'against the tokens via `var(--*)` and its classes rather than guessing values.',
      );
    } else {
      out.push(
        "Style against the tokens via `var(--*)` and the design system's classes rather than " +
        'guessing values.',
      );
    }
  }

  // source-tree pointer + full guide, inlined (raw — this is a .md file, not embedded HTML)
  const hasReadme = Boolean(readme && readme.trim());
  if (sourcePath) {
    out.push('');
    out.push(
      'Everything beyond this runtime copy — mocks to fork, specimens, ui_kits, component ' +
      `source — lives in the design system's source tree at \`${sourcePath}/\`; read from ` +
      `there for anything not under \`_ds/${slug}/\`.` +
      (hasReadme ? " Its guide is reproduced below so you don't need to fetch it:" : ''),
    );
  } else if (hasReadme) {
    out.push('');
    out.push("The design system's guide is reproduced below so you don't need to fetch it:");
  }
  if (hasReadme) {
    out.push('');
    out.push('<design-system-guide>');
    out.push(readme.trim());
    out.push('</design-system-guide>');
  }

  // per-component usage excerpts — the *.prompt.md files are not bound into _ds/
  if (componentPrompts.length) {
    out.push('');
    out.push(
      "Per-component usage notes follow — the first lines of each component's `*.prompt.md`. " +
      `These are NOT bound into \`_ds/${slug}/\`, so they're reproduced here` +
      (sourcePath ? ' (full files live in the source tree above):' : ':'),
    );
    out.push('');
    out.push('<ds-prompt-excerpts>');
    componentPrompts.forEach(({ relPath, excerpt }, i) => {
      if (i > 0) out.push('');
      out.push(`### ${relPath}`);
      out.push(excerpt);
    });
    out.push('</ds-prompt-excerpts>');
  }

  // per-component prop contracts — names are exhaustive, enum value lists are
  // complete and never truncated; `*` marks the declared @default
  const propRows = (Array.isArray(componentProps) ? componentProps : [])
    .filter((c) => c && c.kind !== 'constant' && Array.isArray(c.props) && c.props.length);
  if (propRows.length) {
    out.push('');
    out.push(
      'Prop contracts per component (from each `.d.ts`). Prop names are exhaustive — do not ' +
      'invent props. Enum props list every allowed value; `*` marks the default. Never pass a ' +
      'value outside these lists:',
    );
    out.push('');
    out.push('<ds-component-props>');
    for (const c of propRows) {
      const parts = c.props.map((p) => {
        if (Array.isArray(p.values) && p.values.length) {
          const vals = p.values.map((v) => (p.default === v ? `${v}*` : v)).join(' | ');
          return `${p.name}: ${vals}`;
        }
        const t = compactType(p.type) || 'any';
        return p.default !== undefined ? `${p.name}: ${t} (default ${p.default})` : `${p.name}: ${t}`;
      });
      out.push(`${c.name} — ${parts.join(' · ')}`);
    }
    out.push('</ds-component-props>');
  }

  // token allowlist — the enforceable guardrail
  const names = Array.isArray(tokenNames)
    ? [...new Set(tokenNames.filter(Boolean))].sort()
    : [];
  if (names.length) {
    out.push('');
    out.push(
      `CSS tokens: the exact \`--*\` names are defined in the stylesheet(s) linked above. ` +
      `The ${names.length} custom properties they define are:`,
    );
    out.push('');
    out.push(names.join(', '));
    out.push('');
    out.push(
      'Only use `var(--*)` names from that list. Never guess a token name — an unresolved ' +
      '`var()` silently falls back to the browser default.',
    );
  }

  return out.join('\n') + '\n';
}

export default renderDsPrompt;
