/**
 * Google Business Profile (GMB) — AI Prompt Builders
 *
 * Builds system + user prompts for the 3-stage profile pipeline:
 *   Stage 1 — Core listing  (description, categories, highlights, attributes, service areas)
 *   Stage 2 — Catalog       (service/product descriptions for EXISTING offerings, FAQs)
 *   Stage 3 — Engagement    (business posts, review response templates)
 *
 * Core principle: the platform is the source of truth. Anything already saved
 * (business name, contact details, address, hours, offerings, images, links) is
 * passed in as FIXED context the model must reuse verbatim — it only writes the
 * content that does not exist yet. See `GmbExistingData`.
 */

import type { PromptResult } from './introScriptPrompts';

// ─── Inputs ────────────────────────────────────────────────────

export interface GmbOffering {
  name: string;
  description?: string;
  usp?: string;
  features?: string[];
  price?: number;
  currency?: string;
  category?: string;
  audienceType?: string;
  marketingCopy?: string;
  /** Where the record came from — helps the model keep services and products apart. */
  kind?: 'product' | 'service' | 'unknown';
}

export interface GmbProfileInputs {
  // Company / Business Profile
  companyName: string;
  companyDescription?: string;
  descriptionLong?: string;
  mission?: string;
  vision?: string;
  coreValues?: string;
  usp?: string;
  stage?: string;
  teamSize?: number;
  yearFounded?: string;
  primaryIndustry?: string;
  secondaryIndustries?: string;
  businessModel?: string;
  primaryOffering?: string;
  secondaryOfferings?: string;
  pricingModel?: string;
  averageTicketSize?: string;
  targetGeography?: string;

  // Founder
  founderName?: string;
  founderDesignation?: string;
  founderBio?: string;
  founderExpertise?: string[];

  // Offerings already on record (products AND services)
  offerings?: GmbOffering[];

  // Brand strategy + brand voice
  brandArchetype?: string;
  brandPersonality?: string | string[];
  brandValues?: string | string[];
  brandPositioning?: string;
  brandVoice?: string;
  brandVoiceDos?: string[];
  brandVoiceDonts?: string[];
  brandTagline?: string;
  brandMessaging?: string;
  brandPromise?: string;

  // ICP
  icpName?: string;
  icpIndustry?: string;
  icpCompanySize?: string;
  icpLocation?: string;
  icpPainPoints?: string[];
  icpBusinessGoals?: string[];
  icpChallenges?: string[];
  icpBuyingProcess?: string;

  // Persona
  personaName?: string;
  personaJobTitle?: string;
  personaGoals?: string[];
  personaPainPoints?: string[];
  personaObjections?: string[];
  personaQuote?: string;

  // Website content
  websitePages?: { title: string; type?: string; metaDescription?: string; excerpt?: string }[];

  // Competitor analysis
  competitors?: {
    name: string;
    valueProposition?: string;
    tagline?: string;
    differentiators?: string[];
    pricingDetails?: string;
  }[];

  /** Everything already saved on the platform — reused, never regenerated. */
  existing: GmbExistingData;

  // Target counts
  faqCount?: number;
  highlightCount?: number;
  postCount?: number;
}

/**
 * The saved state of the listing. Populated fields are echoed into the prompt as
 * fixed facts; empty ones tell the model which sections it actually has to write.
 */
export interface GmbExistingData {
  businessName?: string;
  primaryPhone?: string;
  email?: string;
  website?: string;
  streetAddress?: string;
  city?: string;
  region?: string;
  postalCode?: string;
  country?: string;
  googleMapsLink?: string;
  appointmentUrl?: string;
  menuUrl?: string;
  bookingUrl?: string;
  socialProfiles?: Record<string, string>;

  description?: string;
  shortDescription?: string;
  primaryCategory?: string;
  secondaryCategories?: string[];
  highlights?: string[];
  attributes?: { name?: string; value?: string }[];
  serviceAreas?: string[];

  /** Opening hours are platform data — reused, never invented. */
  openingHours?: Record<string, unknown>;
  alwaysOpen?: boolean;

  /** Images already uploaded. Never AI-generated. */
  logoUrl?: string;
  coverPhotoUrl?: string;
  photoCount?: number;
}

// ─── Google's own field limits ─────────────────────────────────
// Google Business Profile enforces these server-side; generating past them
// means the content is silently truncated (or rejected) when it is published.

export const GMB_FIELD_LIMITS = {
  description: 750,
  shortDescription: 100,
  serviceDescription: 300,
  productDescription: 1000,
  faqAnswer: 800,
  highlight: 60,
  postBody: 1500,
  postCta: 58,
  reviewResponse: 4000,
} as const;

// ─── Anti-generic / JSON instructions ──────────────────────────

const BANNED_PHRASES = [
  'leverage', 'synergy', 'holistic', 'robust', 'cutting-edge',
  'innovative', 'seamless', 'next-gen', 'best-in-class', 'game-changing',
  'world-class', 'one-stop shop', 'unparalleled', 'passionate about',
  'we pride ourselves', 'take your business to the next level',
];

const ANTI_GENERIC_INSTRUCTION = `
CRITICAL: Write like a real local business listing, not a brochure. Every sentence must say
something only THIS business could say — name the actual offerings, the actual audience, the
actual city. Do not pad with filler adjectives.
Banned phrases: ${BANNED_PHRASES.map(p => `"${p}"`).join(', ')}.`;

const JSON_INSTRUCTION = `
Return ONLY valid JSON matching the schema. No markdown fences, no commentary, no trailing commas.
All strings must be properly escaped. Numbers must be numeric, not quoted strings.
Every key in the schema must be present — use an empty string or empty array when you genuinely
have nothing to say, never omit the key.`;

const REUSE_INSTRUCTION = `
REUSE RULES (the platform is the source of truth — this is the most important instruction):
- Anything listed under "ALREADY SAVED ON THE PLATFORM" is FINAL. Reuse it exactly as given.
- NEVER rewrite, re-word, translate, re-format or "improve" a saved value.
- NEVER produce a second version of something that already exists — no duplicates.
- Where a section is marked "ALREADY SET", return an empty string/array for it. Do not regenerate it.
- Only write the sections explicitly marked "TO GENERATE".`;

const FACTS_INSTRUCTION = `
FACTUAL DATA RULES (Google suspends profiles for false information):
- NEVER invent a phone number, email, street address, postal code, website or URL.
- NEVER invent opening hours, awards, certifications, ratings, review counts, years in business,
  client numbers, or staff credentials. If it is not in the context, do not claim it.
- Service areas must be real places derived from the saved address, target geography or ICP location.
- Highlights and attributes must be supported by the context. When in doubt, leave them out —
  a false "wheelchair accessible" or "award-winning" claim gets a listing suspended.`;

// ─── Context builder ───────────────────────────────────────────

function line(label: string, value: unknown): string | null {
  if (value === undefined || value === null) return null;
  if (Array.isArray(value)) {
    const items = value.filter(Boolean);
    return items.length ? `${label}: ${items.join(', ')}` : null;
  }
  const str = String(value).trim();
  return str ? `${label}: ${str}` : null;
}

function section(title: string, lines: (string | null)[]): string | null {
  const kept = lines.filter(Boolean) as string[];
  return kept.length ? `${title}\n${kept.map(l => `- ${l}`).join('\n')}` : null;
}

function has(value: unknown): boolean {
  if (value === undefined || value === null) return false;
  if (Array.isArray(value)) return value.length > 0;
  if (typeof value === 'object') return Object.keys(value as object).length > 0;
  return String(value).trim().length > 0;
}

/** The block of saved values the model must reuse rather than regenerate. */
export function buildExistingDataBlock(existing: GmbExistingData): string {
  const e = existing || {};
  const lines: (string | null)[] = [
    line('Business name', e.businessName),
    line('Phone', e.primaryPhone),
    line('Email', e.email),
    line('Website', e.website),
    line('Street address', e.streetAddress),
    line('City', e.city),
    line('Region/State', e.region),
    line('Postal code', e.postalCode),
    line('Country', e.country),
    line('Google Maps link', e.googleMapsLink),
    line('Appointment URL', e.appointmentUrl),
    line('Menu URL', e.menuUrl),
    line('Booking URL', e.bookingUrl),
    line(
      'Social links',
      Object.entries(e.socialProfiles || {})
        .filter(([, v]) => typeof v === 'string' && v.trim())
        .map(([k, v]) => `${k}=${v.trim()}`),
    ),
    has(e.openingHours) || e.alwaysOpen
      ? `Opening hours: ${e.alwaysOpen ? 'Open 24/7' : JSON.stringify(e.openingHours)} (SAVED — never regenerate or alter)`
      : null,
    e.logoUrl ? 'Logo: uploaded' : null,
    e.coverPhotoUrl ? 'Cover image: uploaded' : null,
    e.photoCount ? `Photos uploaded: ${e.photoCount}` : null,
  ];

  const kept = lines.filter(Boolean) as string[];
  return kept.length
    ? `ALREADY SAVED ON THE PLATFORM — reuse verbatim, never regenerate:\n${kept.map(l => `- ${l}`).join('\n')}`
    : 'ALREADY SAVED ON THE PLATFORM: (nothing saved yet)';
}

/**
 * Tells the model, section by section, what is already set and what it must
 * write. This is what keeps the AI from producing duplicate content.
 */
export function buildGenerationScope(existing: GmbExistingData): string {
  const e = existing || {};
  const rows: string[] = [
    `- Business description: ${has(e.description) ? 'ALREADY SET — return "" (a saved description exists)' : 'TO GENERATE'}`,
    `- Short description: ${has(e.shortDescription) ? 'ALREADY SET — return ""' : 'TO GENERATE'}`,
    `- Primary category: ${has(e.primaryCategory) ? `ALREADY SET to "${e.primaryCategory}" — return "" and keep it` : 'TO GENERATE (not selected yet)'}`,
    `- Secondary categories: ${has(e.secondaryCategories) ? `ALREADY SET (${(e.secondaryCategories || []).join(', ')}) — return [] unless you can add categories that are genuinely missing, in which case return ONLY the new ones` : 'TO GENERATE (recommend 2-6)'}`,
    `- Highlights: ${has(e.highlights) ? 'ALREADY SET — return [] unless genuinely missing ones can be added; return ONLY the new ones' : 'TO GENERATE'}`,
    `- Attributes: ${has(e.attributes) ? 'ALREADY SET — return [] unless genuinely missing ones can be added; return ONLY the new ones' : 'TO GENERATE'}`,
    `- Service areas: ${has(e.serviceAreas) ? 'ALREADY SET — return []' : 'TO GENERATE'}`,
    `- Opening hours: ALWAYS reuse the saved hours. NEVER generate hours — they are platform data.`,
    `- Contact details, address, links, images: ALWAYS reuse. NEVER generate.`,
  ];
  return `GENERATION SCOPE — write ONLY what is marked "TO GENERATE":\n${rows.join('\n')}`;
}

export function buildGmbCompanyContext(inputs: GmbProfileInputs): string {
  const blocks: (string | null)[] = [];

  blocks.push(section('BUSINESS PROFILE', [
    line('Business name', inputs.companyName),
    line('Description', inputs.companyDescription),
    line('Long description', inputs.descriptionLong),
    line('Mission', inputs.mission),
    line('Vision', inputs.vision),
    line('Core values', inputs.coreValues),
    line('Unique selling proposition', inputs.usp),
    line('Business stage', inputs.stage),
    line('Team size', inputs.teamSize),
    line('Founded', inputs.yearFounded),
    line('Primary industry', inputs.primaryIndustry),
    line('Secondary industries', inputs.secondaryIndustries),
    line('Business model', inputs.businessModel),
    line('Primary offering', inputs.primaryOffering),
    line('Secondary offerings', inputs.secondaryOfferings),
    line('Pricing model', inputs.pricingModel),
    line('Average ticket size', inputs.averageTicketSize),
    line('Target geography', inputs.targetGeography),
  ]));

  blocks.push(section('FOUNDER', [
    line('Name', inputs.founderName),
    line('Designation', inputs.founderDesignation),
    line('Bio', inputs.founderBio),
    line('Expertise', inputs.founderExpertise),
  ]));

  if (inputs.offerings?.length) {
    const offeringLines = inputs.offerings.slice(0, 25).map(o => {
      const bits = [`${o.name}${o.kind && o.kind !== 'unknown' ? ` [${o.kind}]` : ''}`];
      if (o.category) bits.push(`Category: ${o.category}`);
      if (o.description) bits.push(o.description);
      if (o.usp) bits.push(`USP: ${o.usp}`);
      if (o.features?.length) bits.push(`Features: ${o.features.slice(0, 8).join(', ')}`);
      if (o.price) bits.push(`Price: ${o.currency || ''}${o.price}`.trim());
      if (o.audienceType) bits.push(`Audience: ${o.audienceType}`);
      return bits.join(' | ');
    });
    blocks.push(
      'OFFERINGS ALREADY ON RECORD (these are the ONLY services/products that exist — never invent others)\n' +
      offeringLines.map(l => `- ${l}`).join('\n')
    );
  }

  blocks.push(section('BRAND STRATEGY & VOICE', [
    line('Archetype', inputs.brandArchetype),
    line('Personality', inputs.brandPersonality),
    line('Values', inputs.brandValues),
    line('Positioning', inputs.brandPositioning),
    line('Voice & tone', inputs.brandVoice),
    line('Voice — do', inputs.brandVoiceDos),
    line('Voice — do not', inputs.brandVoiceDonts),
    line('Brand promise', inputs.brandPromise),
    line('Tagline', inputs.brandTagline),
    line('Messaging', inputs.brandMessaging),
  ]));

  blocks.push(section('IDEAL CUSTOMER PROFILE', [
    line('ICP', inputs.icpName),
    line('Industry', inputs.icpIndustry),
    line('Company size', inputs.icpCompanySize),
    line('Location', inputs.icpLocation),
    line('Pain points', inputs.icpPainPoints),
    line('Business goals', inputs.icpBusinessGoals),
    line('Challenges', inputs.icpChallenges),
    line('Buying process', inputs.icpBuyingProcess),
  ]));

  blocks.push(section('BUYER PERSONA', [
    line('Persona', inputs.personaName),
    line('Job title', inputs.personaJobTitle),
    line('Goals', inputs.personaGoals),
    line('Pain points', inputs.personaPainPoints),
    line('Objections', inputs.personaObjections),
    line('Quote', inputs.personaQuote),
  ]));

  if (inputs.websitePages?.length) {
    const pageLines = inputs.websitePages.slice(0, 12).map(p => {
      const bits = [p.title];
      if (p.type) bits.push(`(${p.type})`);
      if (p.metaDescription) bits.push(p.metaDescription);
      else if (p.excerpt) bits.push(p.excerpt);
      return bits.join(' — ');
    });
    blocks.push(`WEBSITE CONTENT\n${pageLines.map(l => `- ${l}`).join('\n')}`);
  }

  if (inputs.competitors?.length) {
    const compLines = inputs.competitors.slice(0, 8).map(c => {
      const bits = [c.name];
      if (c.tagline) bits.push(c.tagline);
      if (c.valueProposition) bits.push(c.valueProposition);
      if (c.differentiators?.length) bits.push(`Differentiators: ${c.differentiators.slice(0, 5).join(', ')}`);
      return bits.join(' | ');
    });
    blocks.push(
      'COMPETITOR ANALYSIS (use to differentiate this listing — never name a competitor in the output)\n' +
      compLines.map(l => `- ${l}`).join('\n')
    );
  }

  blocks.push(buildExistingDataBlock(inputs.existing));

  const text = (blocks.filter(Boolean) as string[]).join('\n\n');
  return text || `Business name: ${inputs.companyName}`;
}

// ─── Stage 1: Core listing ─────────────────────────────────────

export function buildGmbCoreProfilePrompt(inputs: GmbProfileInputs): PromptResult {
  const highlightCount = inputs.highlightCount || 8;

  const systemPrompt = `You are a Google Business Profile specialist who writes local-listing content
optimised for Google Maps and local search ranking.

${ANTI_GENERIC_INSTRUCTION}
${REUSE_INSTRUCTION}
${FACTS_INSTRUCTION}

Produce the CORE listing sections for the business described below.

RULES PER FIELD:
- "description": ${GMB_FIELD_LIMITS.description} characters MAX (Google's hard limit). Local-SEO optimised: lead with
  what the business does and for whom, naturally include the primary category and the city/service
  area, then the differentiator, then a soft call to action. No phone numbers, no URLs, no prices —
  Google rejects those in the description. Write in the brand's voice.
- "shortDescription": ${GMB_FIELD_LIMITS.shortDescription} characters MAX, one line.
- "primaryCategory": ONE real Google Business Profile category name (e.g. "Marketing Agency",
  "Dental Clinic", "Software Company") matching the main revenue activity — it drives the whole
  listing's ranking. Return "" if a primary category is already set.
- "secondaryCategories": REAL Google category names ordered by relevance, none duplicating the
  primary or any already-set category.
- "highlights": up to ${highlightCount} short badge-style phrases, ${GMB_FIELD_LIMITS.highlight} characters MAX each — e.g.
  "Family-owned", "Free consultation", "Certified professionals", "Same-day service", "Eco-friendly",
  "24/7 support", "Award-winning", plus industry-specific ones. ONLY claims the context supports.
- "attributes": real Google Business attributes as {name, value} pairs. Draw from Google's supported
  set — e.g. "Wheelchair accessible entrance", "Parking available", "Online appointments",
  "Delivery", "Pickup", "Pet friendly", "Accepts credit cards", "Outdoor seating",
  "Same-day service", "Appointment required", "Identifies as women-owned", "LGBTQ+ friendly",
  "Free Wi-Fi", "Language spoken". Choose only those that fit this business type AND are supported
  by the context. Use "true"/"false" for yes-no attributes.
- "serviceAreas": real cities/regions/countries served, derived from the saved address, target
  geography or ICP location.
- Do NOT return opening hours, contact details, addresses, links or images — those are platform data.

${JSON_INSTRUCTION}

SCHEMA:
{
  "description": "string — max ${GMB_FIELD_LIMITS.description} chars, or \\"\\" if already set",
  "shortDescription": "string — max ${GMB_FIELD_LIMITS.shortDescription} chars, or \\"\\" if already set",
  "primaryCategory": "string — one real Google category, or \\"\\" if already set",
  "secondaryCategories": ["string — real Google categories not already set"],
  "highlights": ["string — max ${GMB_FIELD_LIMITS.highlight} chars each"],
  "attributes": [{ "name": "string", "value": "string" }],
  "serviceAreas": ["string — real cities/regions/countries"]
}`;

  const userPrompt = `Generate the core Google Business Profile listing content for this business.

${buildGenerationScope(inputs.existing)}

${buildGmbCompanyContext(inputs)}

Write for the ideal customer described above, in the brand's voice. Choose categories that match how
this business actually earns revenue, not how it describes itself aspirationally.`;

  return { systemPrompt, userPrompt, maxTokens: 6000 };
}

// ─── Stage 2: Services, products & FAQs ────────────────────────

export function buildGmbCatalogPrompt(
  inputs: GmbProfileInputs,
  coreResult?: Record<string, any>,
): PromptResult {
  const faqCount = inputs.faqCount || 15;

  const category = coreResult?.primaryCategory || inputs.existing?.primaryCategory || '';
  const areas = (coreResult?.serviceAreas?.length ? coreResult.serviceAreas : inputs.existing?.serviceAreas) || [];
  const coreContext = category || areas.length
    ? `\nLISTING CONTEXT (stay consistent with it):
- Primary category: ${category || '—'}
- Service areas: ${areas.join(', ') || '—'}
- Description: ${coreResult?.description || inputs.existing?.description || '—'}\n`
    : '';

  const offeringNames = (inputs.offerings || []).map(o => o.name).filter(Boolean);
  const offeringInstruction = offeringNames.length
    ? `The business has ${offeringNames.length} offering(s) on record: ${offeringNames.join(', ')}.
Write one entry for EACH of them, keeping the EXACT saved name. Classify each as a service or a
product and put it in the matching array. Do NOT invent additional offerings, do NOT rename any,
do NOT merge or split them.`
    : `No offerings are on record. Derive the service and product list ONLY from the business
profile's stated offerings and website content above — do not invent an unrelated catalogue.`;

  const systemPrompt = `You are a Google Business Profile specialist writing the Services, Products and
Q&A sections of a local listing.

${ANTI_GENERIC_INSTRUCTION}
${REUSE_INSTRUCTION}
${FACTS_INSTRUCTION}

${offeringInstruction}

RULES PER FIELD:
- "services": the bookable/quotable things a customer buys. Keep the saved name verbatim in "name".
  Write a NEW listing-ready description of ${GMB_FIELD_LIMITS.serviceDescription} characters MAX stating what the customer gets.
  Group under a small number of meaningful category labels. Prices ONLY when the context supplies a
  real one — otherwise price null and "priceType" "quote" or "free".
- "products": physical/packaged items, same rules; description ${GMB_FIELD_LIMITS.productDescription} characters MAX.
- If an offering already has a saved description, still write the Google-listing version in
  "description" but keep "name" identical — the saved record is not modified.
- "faqs": exactly ${faqCount} question/answer pairs written the way customers actually search and ask on
  Google ("Do you offer free consultations?", "What areas do you cover?", "How much does X cost?",
  "Do I need an appointment?", "What are your payment options?"). Answers ${GMB_FIELD_LIMITS.faqAnswer} characters MAX,
  direct, first-person plural ("We…"), answering in the first sentence. Cover pricing/quotes,
  turnaround, service area, booking, payment, what makes the business different, and the persona's
  stated objections. Never state hours, prices or contact details that are not in the context.

${JSON_INSTRUCTION}

SCHEMA:
{
  "services": [
    {
      "name": "string — the saved name, verbatim",
      "category": "string — grouping label",
      "description": "string — max ${GMB_FIELD_LIMITS.serviceDescription} chars",
      "price": null,
      "currency": "string — ISO code or empty",
      "priceType": "string — one of: fixed, from, range, quote, free",
      "isFeatured": false
    }
  ],
  "products": [
    {
      "name": "string — the saved name, verbatim",
      "category": "string",
      "description": "string — max ${GMB_FIELD_LIMITS.productDescription} chars",
      "price": null,
      "currency": "string — ISO code or empty",
      "highlights": ["string — 2-4 short selling points"],
      "isFeatured": false
    }
  ],
  "faqs": [
    {
      "question": "string — how a customer would type it",
      "answer": "string — max ${GMB_FIELD_LIMITS.faqAnswer} chars",
      "category": "string — e.g. pricing, booking, coverage, payment, general"
    }
  ]
}`;

  const userPrompt = `Write the Services, Products and FAQ sections of the Google Business Profile for this business.
${coreContext}
${buildGmbCompanyContext(inputs)}

Describe the offerings that already exist — do not replace them with a catalogue of your own.`;

  return { systemPrompt, userPrompt, maxTokens: 9000 };
}

// ─── Stage 3: Posts & review responses ─────────────────────────

export function buildGmbEngagementPrompt(
  inputs: GmbProfileInputs,
  coreResult?: Record<string, any>,
): PromptResult {
  const postCount = inputs.postCount || 10;
  const category = coreResult?.primaryCategory || inputs.existing?.primaryCategory || '';

  const systemPrompt = `You are a Google Business Profile specialist writing Google Posts and review
response templates for a local business.

${ANTI_GENERIC_INSTRUCTION}
${REUSE_INSTRUCTION}
${FACTS_INSTRUCTION}

RULES PER FIELD:
- "posts": ${postCount} ready-to-publish Google Business Posts covering ALL five types —
  "whats-new", "offer", "event", "product", "announcement" (at least one of each).
  * "body": ${GMB_FIELD_LIMITS.postBody} characters MAX; the first 100 characters must work standalone because Google
    truncates the preview. Write about things this business genuinely does or offers.
  * "callToAction": one of "book", "order", "shop", "learn-more", "sign-up", "call" — pick what the
    saved links actually support (only use "book" when a booking/appointment URL is saved).
  * "ctaText": ${GMB_FIELD_LIMITS.postCta} characters MAX.
  * "offer" posts: include "offerTitle", "offerTerms". Never invent a discount percentage,
    coupon code or expiry the context does not support — describe the offer generically
    (e.g. "first consultation at no charge") and leave "offerTerms" as a placeholder the owner fills in.
  * "event" posts: include "eventTitle". Leave dates as placeholders like "[event date]" — never
    invent a real date.
  * "suggestedImage": describe, in one line, the REAL photo the owner should attach
    (e.g. "Wide shot of the treatment room with the chair in view"). This is upload guidance for a
    human — you are NOT generating an image.
- "reviewResponses": response templates for "positive", "neutral" and "negative" reviews —
  at least 2 variants each, ${GMB_FIELD_LIMITS.reviewResponse} characters MAX. Use "[Customer Name]" and
  "[specific detail]" placeholders. Positive: thank, reinforce the specific thing praised, invite
  back. Neutral: thank, acknowledge the gap, state the concrete improvement, offer a direct contact.
  Negative: apologise without admitting legal fault, take it offline to a real saved contact
  channel, never argue, never mention a refund policy that is not in the context.
  Follow the brand voice. Never claim awards, guarantees or timelines the context does not support.

${JSON_INSTRUCTION}

SCHEMA:
{
  "posts": [
    {
      "type": "string — one of: whats-new, offer, event, product, announcement",
      "title": "string — short headline",
      "body": "string — max ${GMB_FIELD_LIMITS.postBody} chars",
      "callToAction": "string — one of: book, order, shop, learn-more, sign-up, call",
      "ctaText": "string — max ${GMB_FIELD_LIMITS.postCta} chars",
      "offerTitle": "string — offer posts only, else empty",
      "offerTerms": "string — offer posts only, else empty",
      "eventTitle": "string — event posts only, else empty",
      "suggestedImage": "string — what real photo the owner should upload"
    }
  ],
  "reviewResponses": {
    "positive": ["string — max ${GMB_FIELD_LIMITS.reviewResponse} chars"],
    "neutral":  ["string — max ${GMB_FIELD_LIMITS.reviewResponse} chars"],
    "negative": ["string — max ${GMB_FIELD_LIMITS.reviewResponse} chars"]
  }
}`;

  const userPrompt = `Write Google Business Posts and review response templates for this business.
${category ? `\nPrimary category: ${category}\n` : ''}
${buildGmbCompanyContext(inputs)}

Base every post on offerings and facts that already exist above. Where a post needs a date, price or
discount that is not on record, leave a clearly-marked placeholder for the owner to fill in.`;

  return { systemPrompt, userPrompt, maxTokens: 9000 };
}
