/**
 * Metadata extraction utilities for parsing first messages and session context from JSONL files.
 */

import { isCommandOutputContent, sanitizeDisplayContent } from '@shared/utils/contentSanitizer';
import { createLogger } from '@shared/utils/logger';
import * as readline from 'readline';

import { LocalFileSystemProvider } from '../services/infrastructure/LocalFileSystemProvider';
import { type ChatHistoryEntry, isTextContent, type UserEntry } from '../types';

import type { FileSystemProvider } from '../services/infrastructure/FileSystemProvider';

const logger = createLogger('Util:metadataExtraction');

const defaultProvider = new LocalFileSystemProvider();

const JSONL_HEAD_TIMEOUT_MS = 2000;
const JSONL_HEAD_MAX_BYTES = 256 * 1024;
const JSONL_HEAD_MAX_LINES = 400;

interface MessagePreview {
  text: string;
  timestamp: string;
  isCommand: boolean;
}

function byteLen(chunk: string): number {
  return Buffer.byteLength(chunk, 'utf8');
}

/**
 * Extract CWD (current working directory) from the first entry.
 * Used to get the actual project path from encoded directory names.
 */
export async function extractCwd(
  filePath: string,
  fsProvider: FileSystemProvider = defaultProvider
): Promise<string | null> {
  if (!(await fsProvider.exists(filePath))) {
    return null;
  }

  try {
    const stat = await fsProvider.stat(filePath);
    if (!stat.isFile()) {
      return null;
    }
  } catch {
    return null;
  }

  const fileStream = fsProvider.createReadStream(filePath, { encoding: 'utf8' });
  let bytes = 0;
  let timedOut = false;
  const timer = setTimeout(() => {
    timedOut = true;
    fileStream.destroy();
  }, JSONL_HEAD_TIMEOUT_MS);
  fileStream.on('data', (chunk: string) => {
    bytes += byteLen(chunk);
    if (bytes > JSONL_HEAD_MAX_BYTES) {
      fileStream.destroy();
    }
  });
  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity,
  });

  try {
    let lines = 0;
    for await (const line of rl) {
      if (++lines > JSONL_HEAD_MAX_LINES) {
        break;
      }
      if (!line.trim()) continue;

      let entry: ChatHistoryEntry;
      try {
        entry = JSON.parse(line) as ChatHistoryEntry;
      } catch {
        continue;
      }
      // Only conversational entries have cwd
      if ('cwd' in entry && entry.cwd) {
        rl.close();
        fileStream.destroy();
        return entry.cwd;
      }
    }
  } catch (error) {
    if (!timedOut) {
      logger.debug(`Error extracting cwd from ${filePath}:`, error);
    }
  } finally {
    clearTimeout(timer);
    rl.close();
    fileStream.destroy();
  }

  return null;
}

/**
 * Extract a lightweight title preview from the first user message.
 * For command-style sessions, falls back to a slash-command label.
 */
export async function extractFirstUserMessagePreview(
  filePath: string,
  fsProvider: FileSystemProvider = defaultProvider,
  maxLines: number = 200
): Promise<{ text: string; timestamp: string } | null> {
  const safeMaxLines = Math.max(1, maxLines);
  try {
    const stat = await fsProvider.stat(filePath);
    if (!stat.isFile()) {
      return null;
    }
  } catch {
    return null;
  }

  const fileStream = fsProvider.createReadStream(filePath, { encoding: 'utf8' });
  let bytes = 0;
  let timedOut = false;
  const timer = setTimeout(() => {
    timedOut = true;
    fileStream.destroy();
  }, JSONL_HEAD_TIMEOUT_MS);
  fileStream.on('data', (chunk: string) => {
    bytes += byteLen(chunk);
    if (bytes > JSONL_HEAD_MAX_BYTES) {
      fileStream.destroy();
    }
  });
  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity,
  });

  let commandFallback: { text: string; timestamp: string } | null = null;
  let linesRead = 0;

  try {
    for await (const line of rl) {
      if (linesRead++ >= safeMaxLines) {
        break;
      }

      const trimmed = line.trim();
      if (!trimmed) continue;

      let entry: ChatHistoryEntry;
      try {
        entry = JSON.parse(trimmed) as ChatHistoryEntry;
      } catch {
        continue;
      }

      if (entry.type !== 'user') {
        continue;
      }

      const preview = extractPreviewFromUserEntry(entry);
      if (!preview) {
        continue;
      }

      if (!preview.isCommand) {
        return { text: preview.text, timestamp: preview.timestamp };
      }

      if (!commandFallback) {
        commandFallback = { text: preview.text, timestamp: preview.timestamp };
      }
    }
  } catch (error) {
    if (!timedOut) {
      logger.debug(`Error extracting first user preview from ${filePath}:`, error);
    }
    return commandFallback;
  } finally {
    clearTimeout(timer);
    rl.close();
    fileStream.destroy();
  }

  return commandFallback;
}

function extractPreviewFromUserEntry(entry: UserEntry): MessagePreview | null {
  const timestamp = entry.timestamp ?? new Date().toISOString();
  const message = entry.message;
  if (!message) {
    return null;
  }

  const content = message.content;
  if (typeof content === 'string') {
    if (isCommandOutputContent(content) || content.startsWith('[Request interrupted by user')) {
      return null;
    }

    if (content.startsWith('<command-name>')) {
      return {
        text: extractCommandName(content),
        timestamp,
        isCommand: true,
      };
    }

    const sanitized = sanitizeDisplayContent(content).trim();
    if (!sanitized) {
      return null;
    }

    return {
      text: sanitized.substring(0, 500),
      timestamp,
      isCommand: false,
    };
  }

  if (!Array.isArray(content)) {
    return null;
  }

  const textContent = content
    .filter(isTextContent)
    .map((block) => block.text)
    .join(' ')
    .trim();
  if (!textContent || textContent.startsWith('[Request interrupted by user')) {
    return null;
  }

  if (textContent.startsWith('<command-name>')) {
    return {
      text: extractCommandName(textContent),
      timestamp,
      isCommand: true,
    };
  }

  const sanitized = sanitizeDisplayContent(textContent).trim();
  if (!sanitized) {
    return null;
  }

  return {
    text: sanitized.substring(0, 500),
    timestamp,
    isCommand: false,
  };
}

function extractCommandName(content: string): string {
  const commandMatch = /<command-name>\/([^<]+)<\/command-name>/.exec(content);
  return commandMatch?.[1] ? `/${commandMatch[1]}` : '/command';
}
