import 'server-only';
import Anthropic from '@anthropic-ai/sdk';
import { CONTENT_SYSTEM_PROMPT, buildGameContentPrompt } from './prompts';
import type { GameFacts, GeneratedContent } from './types';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const MODEL = process.env.ANTHROPIC_MODEL ?? 'claude-sonnet-5';

// Forcing a tool call (instead of asking for JSON in prose) is what makes
// this reliable enough to run unattended: no markdown fences to strip, no
// "sure, here's the JSON:" preamble to fail on, and the shape is validated
// by the API itself before we ever see it.
const SAVE_CONTENT_TOOL: Anthropic.Tool = {
  name: 'save_game_content',
  description: 'Save the generated game page content in the required structure.',
  input_schema: {
    type: 'object',
    properties: {
      overview: { type: 'string' },
      gameplay: { type: 'string' },
      features: { type: 'string' },
      tipsAndTricks: { type: 'string' },
      beginnerGuide: { type: 'string' },
      howToPlay: { type: 'string' },
      alternatives: { type: 'string' },
      faqs: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            question: { type: 'string' },
            answer: { type: 'string' },
          },
          required: ['question', 'answer'],
        },
        minItems: 3,
      },
      pros: { type: 'array', items: { type: 'string' } },
      cons: { type: 'array', items: { type: 'string' } },
      metaTitle: { type: 'string' },
      metaDescription: { type: 'string' },
      keywords: { type: 'array', items: { type: 'string' } },
    },
    required: [
      'overview',
      'gameplay',
      'features',
      'tipsAndTricks',
      'beginnerGuide',
      'howToPlay',
      'alternatives',
      'faqs',
      'pros',
      'cons',
      'metaTitle',
      'metaDescription',
      'keywords',
    ],
  },
};

export class ContentGenerationError extends Error {}

export async function generateGameContent(facts: GameFacts): Promise<GeneratedContent> {
  const message = await client.messages.create({
    model: MODEL,
    max_tokens: 4000,
    system: CONTENT_SYSTEM_PROMPT,
    tools: [SAVE_CONTENT_TOOL],
    tool_choice: { type: 'tool', name: 'save_game_content' },
    messages: [{ role: 'user', content: buildGameContentPrompt(facts) }],
  });

  const toolUse = message.content.find(
    (block): block is Anthropic.ToolUseBlock => block.type === 'tool_use',
  );

  if (!toolUse) {
    throw new ContentGenerationError(`No tool_use block in response for "${facts.name}". Stop reason: ${message.stop_reason}`);
  }

  // The tool schema constrains the shape, but we still validate at runtime
  // rather than trusting a raw cast — model output is not a type guarantee.
  const content = toolUse.input as GeneratedContent;
  assertGeneratedContentShape(content, facts.name);
  return content;
}

function assertGeneratedContentShape(content: unknown, gameName: string): asserts content is GeneratedContent {
  const c = content as Partial<GeneratedContent>;
  const requiredStrings: (keyof GeneratedContent)[] = [
    'overview',
    'gameplay',
    'features',
    'tipsAndTricks',
    'beginnerGuide',
    'howToPlay',
    'alternatives',
    'metaTitle',
    'metaDescription',
  ];
  for (const field of requiredStrings) {
    if (typeof c[field] !== 'string' || (c[field] as string).trim().length === 0) {
      throw new ContentGenerationError(`Generated content for "${gameName}" is missing field: ${field}`);
    }
  }
  if (!Array.isArray(c.faqs) || c.faqs.length < 3) {
    throw new ContentGenerationError(`Generated content for "${gameName}" has fewer than 3 FAQs`);
  }
  if (!Array.isArray(c.pros) || !Array.isArray(c.cons)) {
    throw new ContentGenerationError(`Generated content for "${gameName}" is missing pros/cons arrays`);
  }
  if (!Array.isArray(c.keywords) || c.keywords.length === 0) {
    throw new ContentGenerationError(`Generated content for "${gameName}" is missing keywords`);
  }
}

/**
 * Used by the RSS pipeline (src/lib/rss/ingest.ts) to turn a feed item into
 * a short, original sentence — never the source's own wording. The model
 * only ever sees a factual gist we extract, not the source HTML/text, to
 * keep this structurally incapable of reproducing the original prose.
 */
export async function rewriteNewsExcerpt(title: string, factualGist: string): Promise<string> {
  const message = await client.messages.create({
    model: MODEL,
    max_tokens: 200,
    system:
      'You write single-sentence, original news summaries for a gaming site. Never reuse the exact phrasing given to you — restate the underlying fact in fresh wording. One sentence, under 30 words, no hype adjectives, no emoji.',
    messages: [
      {
        role: 'user',
        content: `Headline: ${title}\nCore fact to restate in your own words: ${factualGist}`,
      },
    ],
  });
  const textBlock = message.content.find((b): b is Anthropic.TextBlock => b.type === 'text');
  return textBlock?.text.trim() ?? factualGist;
}
