/**
 * Brand templating — single source of truth for the operator-visible brand name.
 *
 * Skills, agent templates, plugin manifests, and reference content reference
 * the brand only via the literal `{{productName}}` placeholder. This module
 * substitutes at read time from `brand.json`. Same shadow-source defect class
 * as Tasks 787/788: there is no fallback; missing or empty `productName`
 * hard-fails so the wrong brand can never reach the system prompt or the
 * operator UI silently.
 *
 * Used by both `platform/ui` (admin/public agent system-prompt assembly) and
 * `platform/plugins/admin/mcp` (the `plugin-read` MCP tool). Both call sites
 * pass the absolute file path as `sourcePath` so the diagnostic line names
 * exactly which file was substituted.
 */

import { join } from "node:path";
import { existsSync, readFileSync } from "node:fs";

const PLACEHOLDER = "{{productName}}";

let cachedProductName: string | null = null;

function brandJsonPath(): string {
  const platformRoot = process.env.MAXY_PLATFORM_ROOT;
  if (!platformRoot) {
    throw new Error(
      "[skill-loader] MAXY_PLATFORM_ROOT not set — cannot resolve brand.json",
    );
  }
  return join(platformRoot, "config", "brand.json");
}

export function getBrandProductName(): string {
  if (cachedProductName !== null) return cachedProductName;
  const path = brandJsonPath();
  if (!existsSync(path)) {
    throw new Error(`[skill-loader] brand.json missing at ${path}`);
  }
  let parsed: { productName?: unknown };
  try {
    parsed = JSON.parse(readFileSync(path, "utf-8"));
  } catch (err) {
    throw new Error(
      `[skill-loader] brand.json unreadable at ${path}: ${err instanceof Error ? err.message : String(err)}`,
    );
  }
  const value = parsed.productName;
  if (typeof value !== "string" || value.trim() === "") {
    throw new Error(
      `[skill-loader] brand.json at ${path} has missing or empty productName`,
    );
  }
  cachedProductName = value;
  return value;
}

export function substituteBrandPlaceholders(content: string, sourcePath: string): string {
  if (!content.includes(PLACEHOLDER)) return content;
  let productName: string;
  try {
    productName = getBrandProductName();
  } catch (err) {
    console.error(
      `[skill-loader] ERROR: brand.json missing — cannot resolve productName for skill ${sourcePath}`,
    );
    throw err;
  }
  // split-length counts non-overlapping literal matches without regex-escape risk.
  const occurrences = content.split(PLACEHOLDER).length - 1;
  const substituted = content.split(PLACEHOLDER).join(productName);
  console.log(
    `[skill-loader] brand-substituted productName=${productName} skill=${sourcePath} occurrences=${occurrences}`,
  );
  return substituted;
}
