/**
 * Built-in stage: summarize-tree
 * Recursive summarization with structural summaries for programmatic content
 * and LLM summaries for prose. Creates a navigable hierarchy.
 *
 * Expects input from section-split (sections in StageResult), or operates
 * on raw content if no prior stage produced sections.
 *
 * Config:
 *   maxSummaryTokens: number (per-section summary length, default 200)
 *   maxGroupSize: number (group N sections before summarizing group, default 5)
 *   maxDepth: number (max nesting levels, default 3)
 *   leafIsFullContent: boolean (leaf drill-down returns raw content, default true)
 */
import type { StageHandler, Section } from '../types.js';
import { detectContentType } from '../content-type.js';

const handler: StageHandler = async (content, ctx) => {
  const maxTokens = (ctx.config.maxSummaryTokens as number | undefined) ?? 200;
  const maxGroup = (ctx.config.maxGroupSize as number | undefined) ?? 5;
  const maxDepth = (ctx.config.maxDepth as number | undefined) ?? 3;

  // If content is small, just return it unchanged
  if (content.length < 2000) {
    return { content };
  }

  // Parse sections from structured content (section-split output)
  // The pipeline executor passes sections from prior stages; for now we
  // parse from the content if it looks like a section-split ToC, or
  // treat the whole content as one section.
  const sections = parseSectionsFromContent(ctx.originalContent);

  if (sections.length <= 1) {
    // Single block — try to summarize directly
    if (!ctx.llm.available()) {
      return { content };
    }

    const summary = await cachedSummarize(ctx, ctx.originalContent, maxTokens);
    return {
      content: summary + '\n\nUse section parameter with id "full" to read the complete content.',
      sections: [{ id: 'full', title: 'Full Content', content: ctx.originalContent }],
    };
  }

  // Build the summary tree
  const tree = await buildTree(sections, ctx, { maxTokens, maxGroup, maxDepth, depth: 0 });

  // Format top-level ToC
  const toc = tree.map((s) => {
    const childHint = s.children?.length
      ? `\n    → ${s.children.length} sub-sections available`
      : '';
    return `[${s.id}] ${s.title}${childHint}`;
  }).join('\n');

  return {
    content: `${tree.length} sections:\n${toc}\n\nUse section parameter to read details.`,
    sections: tree,
  };
};

interface TreeOpts {
  maxTokens: number;
  maxGroup: number;
  maxDepth: number;
  depth: number;
}

async function buildTree(
  sections: Section[],
  ctx: import('../types.js').StageContext,
  opts: TreeOpts,
): Promise<Section[]> {
  const result: Section[] = [];

  for (const section of sections) {
    const contentType = detectContentType(section.content);
    let summary: string;

    if (contentType === 'json' || contentType === 'yaml' || contentType === 'xml') {
      // Structural summary — no LLM needed
      summary = structuralSummary(section.content, contentType);
    } else if (ctx.llm.available()) {
      // LLM summary for prose/code
      summary = await cachedSummarize(ctx, section.content, opts.maxTokens);
    } else {
      // No LLM — use first line as summary
      summary = (section.content.split('\n')[0] ?? '').slice(0, 200);
    }

    const node: Section = {
      id: section.id,
      title: (summary.split('\n')[0] ?? '').slice(0, 100),
      content: section.content,
    };

    // If section is large and we haven't hit max depth, recursively split
    if (section.content.length > 5000 && opts.depth < opts.maxDepth) {
      const subSections = splitIntoSubSections(section.content);
      if (subSections.length > 1) {
        node.children = await buildTree(subSections, ctx, {
          ...opts,
          depth: opts.depth + 1,
        });
        // Replace content with sub-summary for non-leaf nodes
        const childToc = node.children.map((c) => `  [${c.id}] ${c.title}`).join('\n');
        node.content = `${summary}\n\n${node.children.length} sub-sections:\n${childToc}`;
      }
    }

    result.push(node);
  }

  // If too many sections at this level, group them
  if (result.length > opts.maxGroup && ctx.llm.available()) {
    return groupSections(result, ctx, opts);
  }

  return result;
}

async function cachedSummarize(
  ctx: import('../types.js').StageContext,
  content: string,
  maxTokens: number,
): Promise<string> {
  const key = `summary:${ctx.cache.hash(content)}:${maxTokens}`;
  return ctx.cache.getOrCompute(key, async () => {
    const DEFAULT_PROMPT = `Summarize the following in about {{maxTokens}} tokens. Preserve all items marked MUST, REQUIRED, or CRITICAL verbatim. Be specific — mention names, IDs, counts, key values.`;
    const template = await ctx.getSystemPrompt('llm-summarize', DEFAULT_PROMPT);
    const prompt = template.replaceAll('{{maxTokens}}', String(maxTokens));

    return ctx.llm.complete(
      `${prompt}\n\n${content}`,
      { maxTokens },
    );
  });
}

function structuralSummary(content: string, type: string): string {
  try {
    if (type === 'json') {
      const parsed = JSON.parse(content) as unknown;
      if (Array.isArray(parsed)) {
        const sample = parsed.slice(0, 3).map((item) => {
          const obj = item as Record<string, unknown>;
          const label = obj.name ?? obj.label ?? obj.id ?? obj.type;
          return label ? String(label) : JSON.stringify(item).slice(0, 50);
        });
        const suffix = parsed.length > 3 ? `, ... +${parsed.length - 3} more` : '';
        return `JSON array (${parsed.length} items): ${sample.join(', ')}${suffix}`;
      }
      if (parsed && typeof parsed === 'object') {
        const keys = Object.keys(parsed as object);
        const suffix = keys.length > 5 ? `, ... +${keys.length - 5} more` : '';
        return `JSON object (${keys.length} keys): ${keys.slice(0, 5).join(', ')}${suffix}`;
      }
    }
  } catch {
    // Fall through
  }
  return `${type} content (${content.length} chars)`;
}

function parseSectionsFromContent(content: string): Section[] {
  const contentType = detectContentType(content);

  if (contentType === 'json') {
    try {
      const parsed = JSON.parse(content) as unknown;
      if (Array.isArray(parsed) && parsed.length > 1) {
        return parsed.map((item, i) => {
          const obj = item as Record<string, unknown>;
          const label = String(obj.label ?? obj.name ?? obj.id ?? `item-${i}`);
          return {
            id: label.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
            title: label,
            content: JSON.stringify(item, null, 2),
          };
        });
      }
      if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
        const entries = Object.entries(parsed as Record<string, unknown>);
        if (entries.length > 1) {
          return entries.map(([key, value]) => ({
            id: key.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
            title: key,
            content: JSON.stringify(value, null, 2),
          }));
        }
      }
    } catch {
      // Fall through
    }
  }

  if (contentType === 'prose') {
    const parts = content.split(/^(#{1,4}\s+.+)$/m);
    if (parts.length > 2) {
      const sections: Section[] = [];
      let title = 'Introduction';
      let body = '';
      for (const part of parts) {
        const m = /^#{1,4}\s+(.+)$/.exec(part);
        if (m) {
          if (body.trim()) {
            sections.push({
              id: title.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
              title,
              content: body.trim(),
            });
          }
          title = m[1]!.trim();
          body = '';
        } else {
          body += part;
        }
      }
      if (body.trim()) {
        sections.push({
          id: title.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
          title,
          content: body.trim(),
        });
      }
      return sections;
    }
  }

  return [{ id: 'content', title: 'Content', content }];
}

function splitIntoSubSections(content: string): Section[] {
  const contentType = detectContentType(content);
  const sections = parseSectionsFromContent(content);
  if (sections.length > 1) return sections;

  // Fall back to paragraph splitting for prose
  if (contentType === 'prose') {
    const paragraphs = content.split(/\n\n+/);
    if (paragraphs.length > 2) {
      return paragraphs.map((p, i) => ({
        id: `para-${i + 1}`,
        title: (p.split('\n')[0] ?? '').slice(0, 60),
        content: p,
      }));
    }
  }

  return [{ id: 'content', title: 'Content', content }];
}

async function groupSections(
  sections: Section[],
  ctx: import('../types.js').StageContext,
  opts: TreeOpts,
): Promise<Section[]> {
  const groups: Section[] = [];
  for (let i = 0; i < sections.length; i += opts.maxGroup) {
    const chunk = sections.slice(i, i + opts.maxGroup);
    if (chunk.length === 1) {
      groups.push(chunk[0]!);
      continue;
    }

    const groupContent = chunk.map((s) => `[${s.id}] ${s.title}`).join('\n');
    const groupId = `group-${Math.floor(i / opts.maxGroup) + 1}`;
    const groupTitle = ctx.llm.available()
      ? await cachedSummarize(ctx, groupContent, 50)
      : `Group ${Math.floor(i / opts.maxGroup) + 1} (${chunk.length} sections)`;

    groups.push({
      id: groupId,
      title: (groupTitle.split('\n')[0] ?? '').slice(0, 80),
      content: groupContent,
      children: chunk,
    });
  }
  return groups;
}

export default handler;
