/**
 * Built-in stage: paginate
 * Splits content into pages by character size with navigation instructions.
 * When an LLM is available, generates descriptive page titles; otherwise
 * falls back to generic "Page N" labels.
 *
 * Config:
 *   pageSize: number (chars per page, default 8000)
 *   previewChars: number (chars per page sent to LLM for title generation, default 300)
 */
import type { StageHandler, StageContext, Section } from '../types.js';

const handler: StageHandler = async (content, ctx) => {
  const pageSize = (ctx.config.pageSize as number | undefined) ?? 8000;

  // Don't paginate small content
  if (content.length <= pageSize) {
    return { content };
  }

  const pages = splitPages(content, pageSize);

  if (pages.length <= 1) {
    return { content };
  }

  const titles = await generatePageTitles(pages, ctx);

  const sections: Section[] = pages.map((page, i) => ({
    id: `page-${i + 1}`,
    title: titles[i] ?? `Page ${i + 1}`,
    content: page,
  }));

  const toc = sections.map((s, i) =>
    `[${s.id}] ${s.title} (${pages[i]!.length} chars)`,
  ).join('\n');

  return {
    content: `Content split into ${sections.length} pages (${content.length} total chars):\n${toc}\n\nUse section parameter to read a specific page.`,
    sections,
  };
};

/**
 * Generate descriptive titles for each page using LLM.
 * Falls back to generic "Page N" titles if LLM is unavailable or fails.
 */
async function generatePageTitles(pages: string[], ctx: StageContext): Promise<string[]> {
  const fallback = pages.map((_, i) => `Page ${i + 1}`);

  if (!ctx.llm.available()) {
    return fallback;
  }

  const previewChars = (ctx.config.previewChars as number | undefined) ?? 300;
  const cacheKey = `paginate-titles:${ctx.cache.hash(ctx.originalContent)}:${pages.length}`;

  try {
    const cached = await ctx.cache.getOrCompute(cacheKey, async () => {
      const previews = pages.map((page, i) => {
        const preview = page.slice(0, previewChars).trim();
        return `--- Page ${i + 1} (${page.length} chars) ---\n${preview}`;
      }).join('\n\n');

      const DEFAULT_PROMPT = `Generate exactly {{pageCount}} short descriptive titles (max 60 chars each) for the following {{pageCount}} pages. Return ONLY a JSON array of {{pageCount}} strings. No markdown, no explanation.`;
      const template = await ctx.getSystemPrompt('llm-paginate-titles', DEFAULT_PROMPT);
      const prompt = template.replaceAll('{{pageCount}}', String(pages.length));

      const result = await ctx.llm.complete(
        `${prompt}\n\n${previews}`,
        { maxTokens: pages.length * 30 },
      );

      // Parse JSON array from response, pad/truncate to match page count
      const match = result.match(/\[[\s\S]*\]/);
      if (!match) throw new Error('No JSON array in response');
      const raw = JSON.parse(match[0]) as string[];
      if (!Array.isArray(raw) || raw.length === 0) {
        throw new Error('Empty or invalid title array');
      }
      // Pad with generic titles if model returned fewer, truncate if more
      const titles = pages.map((_, i) =>
        (i < raw.length && typeof raw[i] === 'string' && raw[i]!.trim())
          ? raw[i]!.trim().slice(0, 80)
          : `Page ${i + 1}`,
      );
      return JSON.stringify(titles);
    });

    return JSON.parse(cached) as string[];
  } catch (err) {
    ctx.log.warn(`Smart page titles failed, using generic: ${(err as Error).message}`);
    return fallback;
  }
}

function splitPages(content: string, pageSize: number): string[] {
  const pages: string[] = [];
  let offset = 0;

  while (offset < content.length) {
    let end = Math.min(offset + pageSize, content.length);
    // Try to break at a newline boundary
    if (end < content.length) {
      const lastNewline = content.lastIndexOf('\n', end);
      if (lastNewline > offset) {
        end = lastNewline + 1;
      }
    }
    pages.push(content.slice(offset, end));
    offset = end;
  }

  return pages;
}

export default handler;
