/**
 * ChunkBuilder service - Builds visualization chunks from parsed session data.
 *
 * Responsibilities:
 * - Group messages into chunks (user message + responses)
 * - Attach subagents to chunks
 * - Build waterfall chart data
 * - Calculate chunk metrics
 *
 * This module orchestrates chunk building using specialized modules:
 * - MessageClassifier: Classify messages into categories
 * - ChunkFactory: Create individual chunk objects
 * - ProcessLinker: Link subagent processes to chunks
 * - SemanticStepExtractor: Extract semantic steps from AI chunks
 * - SemanticStepGrouper: Group semantic steps for UI
 * - ToolExecutionBuilder: Build tool execution tracking
 * - SubagentDetailBuilder: Build subagent drill-down details
 * - ConversationGroupBuilder: Alternative grouping strategy
 */

import {
  type Chunk,
  type ConversationGroup,
  EMPTY_METRICS,
  type EnhancedChunk,
  isAIChunk,
  isCompactChunk,
  isSystemChunk,
  isUserChunk,
  type MessageCategory,
  type ParsedMessage,
  type Process,
  type Session,
  type SessionDetail,
  type SessionMetrics,
  type SubagentDetail,
  type TokenUsage,
} from '@main/types';
import { calculateMetrics } from '@main/utils/jsonl';
import { createLogger } from '@shared/utils/logger';

import type { WaterfallData, WaterfallItem } from '@shared/types';

const logger = createLogger('Service:ChunkBuilder');

import { classifyMessages } from '../parsing/MessageClassifier';

import {
  buildAIChunkFromBuffer,
  buildCompactChunk,
  buildSystemChunk,
  buildUserChunk,
} from './ChunkFactory';
import { buildGroups as buildConversationGroups } from './ConversationGroupBuilder';
import { buildSubagentDetail as buildSubagentDetailFn } from './SubagentDetailBuilder';

import type { SubagentResolver } from '../discovery/SubagentResolver';
import type { FileSystemProvider } from '../infrastructure/FileSystemProvider';
import type { SessionParser } from '../parsing/SessionParser';

export class ChunkBuilder {
  // ===========================================================================
  // Chunk Building
  // ===========================================================================

  /**
   * Build chunks from messages using 4-category classification.
   * Produces independent UserChunks, AIChunks, and SystemChunks.
   *
   * Categories:
   * - User: Genuine user input (creates UserChunk, renders RIGHT)
   * - System: Command output <local-command-stdout> (creates SystemChunk, renders LEFT)
   * - Hard Noise: Filtered out entirely (system metadata, caveats, reminders)
   * - AI: All other messages grouped into AIChunks (renders LEFT)
   *
   * All chunk types are INDEPENDENT - no pairing between User and AI.
   */
  buildChunks(
    messages: ParsedMessage[],
    subagents: Process[] = [],
    options?: { includeSidechain?: boolean }
  ): EnhancedChunk[] {
    const chunks: EnhancedChunk[] = [];

    // Filter to main thread messages (non-sidechain)
    const mainMessages = options?.includeSidechain
      ? messages
      : messages.filter((m) => !m.isSidechain);
    logger.debug(`Total messages: ${messages.length}, Main thread: ${mainMessages.length}`);

    // Classify each message into categories using MessageClassifier
    const classified = classifyMessages(mainMessages);

    // Log classification summary
    const categoryCounts = new Map<MessageCategory, number>();
    for (const { category } of classified) {
      categoryCounts.set(category, (categoryCounts.get(category) ?? 0) + 1);
    }
    logger.debug('Message classification:', Object.fromEntries(categoryCounts));

    // Build chunks from classification - AI chunks are INDEPENDENT
    let aiBuffer: ParsedMessage[] = [];

    for (const { message, category } of classified) {
      switch (category) {
        case 'hardNoise':
          // Skip - filtered out
          break;

        case 'compact':
          // Flush any buffered AI messages first
          if (aiBuffer.length > 0) {
            chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages));
            aiBuffer = [];
          }
          chunks.push(buildCompactChunk(message));
          break;

        case 'user':
          // Flush any buffered AI messages first
          if (aiBuffer.length > 0) {
            chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages));
            aiBuffer = [];
          }
          chunks.push(buildUserChunk(message));
          break;

        case 'system':
          // Flush any buffered AI messages first
          if (aiBuffer.length > 0) {
            chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages));
            aiBuffer = [];
          }
          chunks.push(buildSystemChunk(message));
          break;

        case 'ai':
          aiBuffer.push(message);
          break;
      }
    }

    // Flush remaining AI buffer
    if (aiBuffer.length > 0) {
      chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages));
    }

    // Log final chunk summary
    const userChunkCount = chunks.filter(isUserChunk).length;
    const aiChunkCount = chunks.filter(isAIChunk).length;
    const systemChunkCount = chunks.filter(isSystemChunk).length;
    const compactChunkCount = chunks.filter(isCompactChunk).length;
    logger.debug(
      `Created ${chunks.length} chunks: ${userChunkCount} user, ${aiChunkCount} AI, ${systemChunkCount} system, ${compactChunkCount} compact`
    );

    return chunks;
  }

  // ===========================================================================
  // Simplified Grouping Strategy (delegates to ConversationGroupBuilder)
  // ===========================================================================

  /**
   * Build conversation groups using simplified grouping strategy.
   * Groups one user message with all AI responses until the next user message.
   *
   * This is a cleaner alternative to buildChunks() that:
   * - Uses simpler time-based grouping
   * - Separates Task executions from regular tool executions
   * - Links subagents more explicitly via TaskExecution
   */
  buildGroups(messages: ParsedMessage[], subagents: Process[]): ConversationGroup[] {
    return buildConversationGroups(messages, subagents);
  }

  // ===========================================================================
  // Session Detail Building
  // ===========================================================================

  /**
   * Build a complete SessionDetail from parsed data.
   */
  buildSessionDetail(
    session: Session,
    messages: ParsedMessage[],
    subagents: Process[]
  ): SessionDetail {
    // Build chunks
    const chunks = this.buildChunks(messages, subagents);

    // Calculate overall metrics
    const metrics = calculateMetrics(messages);

    return {
      session,
      messages,
      chunks,
      processes: subagents,
      metrics,
    };
  }

  /**
   * Build waterfall chart data from chunks and resolved processes.
   */
  buildWaterfallData(chunks: Chunk[], processes: Process[]): WaterfallData {
    const items: WaterfallItem[] = [];

    for (const chunk of chunks) {
      const baseChunkItem: WaterfallItem = {
        id: chunk.id,
        label: this.getChunkLabel(chunk),
        startTime: chunk.startTime,
        endTime: chunk.endTime,
        durationMs: chunk.durationMs,
        tokenUsage: this.toTokenUsage(chunk.metrics),
        level: 0,
        type: 'chunk',
        isParallel: false,
      };
      items.push(baseChunkItem);

      if (isAIChunk(chunk)) {
        for (const toolExec of chunk.toolExecutions) {
          const endTime = toolExec.endTime ?? toolExec.startTime;
          items.push({
            id: `tool-${toolExec.toolCall.id}`,
            label: toolExec.toolCall.name,
            startTime: toolExec.startTime,
            endTime,
            durationMs:
              toolExec.durationMs ?? Math.max(endTime.getTime() - toolExec.startTime.getTime(), 0),
            tokenUsage: {
              input_tokens: 0,
              output_tokens: 0,
            },
            level: 1,
            type: 'tool',
            isParallel: false,
            parentId: chunk.id,
          });
        }

        for (const process of chunk.processes) {
          items.push({
            id: `subagent-${process.id}`,
            label: process.description || process.subagentType || process.id,
            startTime: process.startTime,
            endTime: process.endTime,
            durationMs: process.durationMs,
            tokenUsage: this.toTokenUsage(process.metrics),
            level: 1,
            type: 'subagent',
            isParallel: process.isParallel,
            parentId: chunk.id,
            metadata: {
              subagentType: process.subagentType,
              messageCount: process.messages.length,
            },
          });
        }
      }
    }

    // Add any process that was not attached to an AI chunk (defensive fallback)
    for (const process of processes) {
      const itemId = `subagent-${process.id}`;
      if (items.some((item) => item.id === itemId)) {
        continue;
      }
      items.push({
        id: itemId,
        label: process.description || process.subagentType || process.id,
        startTime: process.startTime,
        endTime: process.endTime,
        durationMs: process.durationMs,
        tokenUsage: this.toTokenUsage(process.metrics),
        level: 0,
        type: 'subagent',
        isParallel: process.isParallel,
        metadata: {
          subagentType: process.subagentType,
          messageCount: process.messages.length,
        },
      });
    }

    const sortedItems = [...items];
    sortedItems.sort((a, b) => a.startTime.getTime() - b.startTime.getTime());

    if (sortedItems.length === 0) {
      const now = new Date();
      return {
        items: [],
        minTime: now,
        maxTime: now,
        totalDurationMs: 0,
      };
    }

    const minTime = sortedItems.reduce(
      (min, item) => (item.startTime.getTime() < min.getTime() ? item.startTime : min),
      sortedItems[0].startTime
    );
    const maxTime = sortedItems.reduce(
      (max, item) => (item.endTime.getTime() > max.getTime() ? item.endTime : max),
      sortedItems[0].endTime
    );

    return {
      items: sortedItems,
      minTime,
      maxTime,
      totalDurationMs: Math.max(maxTime.getTime() - minTime.getTime(), 0),
    };
  }

  // ===========================================================================
  // Utility Methods
  // ===========================================================================

  /**
   * Get total metrics for all chunks.
   */
  getTotalChunkMetrics(chunks: (Chunk | EnhancedChunk)[]): SessionMetrics {
    if (chunks.length === 0) {
      return { ...EMPTY_METRICS };
    }

    let durationMs = 0;
    let inputTokens = 0;
    let outputTokens = 0;
    let cacheReadTokens = 0;
    let cacheCreationTokens = 0;
    let messageCount = 0;

    for (const chunk of chunks) {
      durationMs += chunk.durationMs;
      inputTokens += chunk.metrics.inputTokens;
      outputTokens += chunk.metrics.outputTokens;
      cacheReadTokens += chunk.metrics.cacheReadTokens;
      cacheCreationTokens += chunk.metrics.cacheCreationTokens;
      messageCount += chunk.metrics.messageCount;
    }

    return {
      durationMs,
      totalTokens: inputTokens + outputTokens,
      inputTokens,
      outputTokens,
      cacheReadTokens,
      cacheCreationTokens,
      messageCount,
    };
  }

  /**
   * Find chunk containing a specific message UUID.
   */
  findChunkByMessageId(
    chunks: (Chunk | EnhancedChunk)[],
    messageUuid: string
  ): Chunk | EnhancedChunk | undefined {
    return chunks.find((c) => {
      // UserChunk: check userMessage
      if (isUserChunk(c)) {
        return c.userMessage.uuid === messageUuid;
      }
      // AIChunk: check responses
      if (isAIChunk(c)) {
        return c.responses.some((r) => r.uuid === messageUuid);
      }
      return false;
    });
  }

  /**
   * Find chunk containing a specific subagent.
   * Only AIChunks have processes.
   */
  findChunkBySubagentId(
    chunks: (Chunk | EnhancedChunk)[],
    subagentId: string
  ): Chunk | EnhancedChunk | undefined {
    return chunks.find((c) => {
      if (isAIChunk(c)) {
        return c.processes.some((s: Process) => s.id === subagentId);
      }
      return false;
    });
  }

  private getChunkLabel(chunk: Chunk): string {
    switch (chunk.chunkType) {
      case 'user':
        return 'User';
      case 'ai':
        return 'Assistant';
      case 'system':
        return 'System';
      case 'compact':
        return 'Compact';
      default:
        return 'Chunk';
    }
  }

  private toTokenUsage(metrics: SessionMetrics): TokenUsage {
    return {
      input_tokens: metrics.inputTokens,
      output_tokens: metrics.outputTokens,
      cache_read_input_tokens: metrics.cacheReadTokens || undefined,
      cache_creation_input_tokens: metrics.cacheCreationTokens || undefined,
    };
  }

  // ===========================================================================
  // Subagent Detail Building (for drill-down)
  // ===========================================================================

  /**
   * Build detailed information for a specific subagent.
   * Used for drill-down modal to show subagent's internal execution.
   *
   * @param projectId - Project ID
   * @param sessionId - Parent session ID (currently unused, kept for API consistency)
   * @param subagentId - Subagent ID to load
   * @param sessionParser - SessionParser instance for parsing subagent file
   * @param subagentResolver - SubagentResolver instance for nested subagents
   * @returns SubagentDetail or null if not found
   */
  async buildSubagentDetail(
    projectId: string,
    sessionId: string,
    subagentId: string,
    sessionParser: SessionParser,
    subagentResolver: SubagentResolver,
    fsProvider: FileSystemProvider,
    projectsDir: string
  ): Promise<SubagentDetail | null> {
    // Delegate to the extracted module, passing buildChunks as a callback
    return buildSubagentDetailFn(
      projectId,
      sessionId,
      subagentId,
      sessionParser,
      subagentResolver,
      (messages, subagents) => this.buildChunks(messages, subagents, { includeSidechain: true }),
      fsProvider,
      projectsDir
    );
  }
}
