'use strict';

const crypto = require('node:crypto');
const { redactPromptContent, estimateTokens } = require('./prompt-runtime');

const DEFAULT_PREVIEW_CHARS = 240;

class PromptSectionRegistry {
  constructor({ provider = '', model = '', now = () => new Date().toISOString() } = {}) {
    this.provider = provider;
    this.model = model;
    this.now = now;
    this.sections = [];
  }

  add(section = {}) {
    const normalized = normalizePromptManifestSection(section);
    this.sections.push(normalized);
    return normalized;
  }

  addMany(sections = []) {
    return sections.map((section) => this.add(section));
  }

  manifest(extra = {}) {
    return buildPromptManifest({
      provider: extra.provider || this.provider,
      model: extra.model || this.model,
      sections: this.sections,
      timestamp: extra.timestamp || this.now(),
      metadata: extra.metadata || {},
    });
  }
}

function buildCodingPromptManifest({
  systemPrompt = '',
  userTask = '',
  provider = '',
  model = '',
  runtimeMode = '',
  tools = [],
  promptCapabilities = null,
  metadata = {},
  now = () => new Date().toISOString(),
} = {}) {
  const registry = new PromptSectionRegistry({ provider, model, now });
  registry.add({
    id: 'stable.system-prompt',
    kind: 'stable',
    source: 'wall-e/coding-prompts.js',
    content: systemPrompt,
    cacheable: true,
    metadata: { runtimeMode },
  });
  registry.add({
    id: 'dynamic.user-task',
    kind: 'dynamic',
    source: 'user',
    content: userTask,
    cacheable: false,
    redactionPolicy: 'standard',
  });
  registry.add({
    id: 'dynamic.tool-catalog',
    kind: 'dynamic',
    source: 'tool-registry',
    content: toolCatalogSignature(tools),
    cacheable: false,
    metadata: { count: Array.isArray(tools) ? tools.length : 0 },
  });
  if (promptCapabilities) {
    registry.add({
      id: 'dynamic.prompt-capabilities',
      kind: 'dynamic',
      source: 'prompt-capabilities',
      content: JSON.stringify(promptCapabilities),
      cacheable: false,
      redactionPolicy: 'standard',
      metadata: {
        skills: Array.isArray(promptCapabilities.skills) ? promptCapabilities.skills.length : 0,
        mcpTools: Array.isArray(promptCapabilities.mcp?.tools) ? promptCapabilities.mcp.tools.length : 0,
      },
    });
  }
  return registry.manifest({ metadata: { runtimeMode, ...metadata } });
}

function buildPromptManifest({ provider = '', model = '', sections = [], timestamp = new Date().toISOString(), metadata = {} } = {}) {
  const normalized = sections.map(normalizePromptManifestSection);
  const stableSections = normalized.filter((section) => section.kind === 'stable');
  const dynamicSections = normalized.filter((section) => section.kind !== 'stable');
  const manifest = {
    promptManifestId: `prompt_${hashText(stableStringify({
      provider,
      model,
      sections: normalized.map((section) => ({
        id: section.id,
        hash: section.hash,
        kind: section.kind,
      })),
    })).slice(0, 24)}`,
    version: 1,
    provider: String(provider || ''),
    model: String(model || ''),
    timestamp,
    stableHash: `sha256:${hashText(stableSections.map((section) => section.hash).join('\n'))}`,
    dynamicHash: `sha256:${hashText(dynamicSections.map((section) => section.hash).join('\n'))}`,
    stableSectionCount: stableSections.length,
    dynamicSectionCount: dynamicSections.length,
    tokenEstimate: normalized.reduce((sum, section) => sum + section.tokenEstimate, 0),
    cacheBoundaryAfter: stableSections.length ? stableSections[stableSections.length - 1].id : '',
    sections: normalized.map(stripContent),
    metadata: safeJson(metadata),
  };
  return manifest;
}

function appendPromptManifest(transcript, manifest, opts = {}) {
  if (!transcript?.appendPart || !manifest) return null;
  return transcript.appendPart({
    sessionId: opts.sessionId,
    cwd: opts.cwd,
    chatSessionId: opts.chatSessionId,
    partType: 'prompt_manifest',
    data: manifest,
  });
}

function normalizePromptManifestSection(section = {}) {
  if (!section.id) throw new Error('prompt section id is required');
  if (section.hash && Object.prototype.hasOwnProperty.call(section, 'preview') && !Object.prototype.hasOwnProperty.call(section, 'content')) {
    const kind = section.kind === 'stable' ? 'stable' : 'dynamic';
    return {
      id: String(section.id),
      kind,
      source: String(section.source || 'runtime'),
      cacheable: kind === 'stable' && section.cacheable !== false,
      visibility: String(section.visibility || 'llm'),
      hash: String(section.hash),
      redactedHash: String(section.redactedHash || section.hash),
      tokenEstimate: Number.isFinite(Number(section.tokenEstimate)) ? Number(section.tokenEstimate) : 0,
      preview: String(section.preview || ''),
      metadata: safeJson(section.metadata || {}),
    };
  }
  const content = String(section.content || '');
  const redactionPolicy = section.redactionPolicy || section.redaction_policy || 'none';
  const redacted = redactPromptContent(content, redactionPolicy);
  const kind = section.kind || (section.cacheable === false ? 'dynamic' : 'stable');
  return {
    id: String(section.id),
    kind: kind === 'stable' ? 'stable' : 'dynamic',
    source: String(section.source || 'runtime'),
    cacheable: kind === 'stable' && section.cacheable !== false,
    visibility: String(section.visibility || 'llm'),
    hash: `sha256:${hashText(content)}`,
    redactedHash: `sha256:${hashText(redacted)}`,
    tokenEstimate: Number.isFinite(Number(section.tokenEstimate))
      ? Number(section.tokenEstimate)
      : estimateTokens(content),
    preview: truncate(redacted, section.previewChars || DEFAULT_PREVIEW_CHARS),
    metadata: safeJson(section.metadata || {}),
  };
}

function stripContent(section) {
  const {
    id,
    kind,
    source,
    cacheable,
    visibility,
    hash,
    redactedHash,
    tokenEstimate,
    preview,
    metadata,
  } = section;
  return { id, kind, source, cacheable, visibility, hash, redactedHash, tokenEstimate, preview, metadata };
}

function toolCatalogSignature(tools = []) {
  return (tools || []).map((tool) => {
    const schema = tool.input_schema || tool.schema || {};
    return stableStringify({
      name: tool.name,
      descriptionHash: hashText(tool.description || ''),
      required: schema.required || [],
      properties: Object.keys(schema.properties || {}).sort(),
    });
  }).join('\n');
}

function hashText(text) {
  return crypto.createHash('sha256').update(String(text || '')).digest('hex');
}

function stableStringify(value) {
  if (value === null || typeof value !== 'object') return JSON.stringify(value);
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
}

function safeJson(value) {
  try {
    return JSON.parse(JSON.stringify(value || {}));
  } catch {
    return {};
  }
}

function truncate(text, max) {
  const value = String(text || '');
  if (!max || value.length <= max) return value;
  return `${value.slice(0, Math.max(0, max - 24))}\n[prompt preview truncated]`;
}

module.exports = {
  PromptSectionRegistry,
  appendPromptManifest,
  buildCodingPromptManifest,
  buildPromptManifest,
  normalizePromptManifestSection,
  toolCatalogSignature,
};
