/**
 * 중앙화된 로깅 시스템
 *
 * MCP 스펙 준수:
 * - MCP 모드: mcpLogger.logServer() 사용 (MCP 스펙 준수)
 * - 일반 모드: stderr.write() 사용
 * - PII 마스킹: 모든 로그 메시지와 메타데이터에 자동 적용
 * - 일관된 logger 이름: MCP 모드에서 'server' 사용
 *
 * 참조:
 * - MCP 스펙: https://spec.modelcontextprotocol.io/specification/server/#logging
 * - PRD 0019: 보안 강화 (Phase 1) - PII 마스킹 강화
 */
import { PIIMasker } from './pii-masker.js';
// MCP 모드에서 사용할 mcpLogger (순환 의존성은 실제로 발생하지 않음)
import { mcpLogger } from '../../server/mcp-logger.js';
/**
 * 로깅 메타데이터 검증
 *
 * @param meta 메타데이터 객체
 * @returns 검증 결과
 */
export function validateLogMetadata(meta) {
    const errors = [];
    const warnings = [];
    if (!meta) {
        return { valid: true, errors, warnings };
    }
    // slot 값 검증
    if (meta.slot !== undefined) {
        if (typeof meta.slot !== 'string' || !['A', 'B', 'C'].includes(meta.slot)) {
            errors.push(`Invalid slot value: ${meta.slot}. Must be 'A', 'B', or 'C'.`);
        }
    }
    // agentId 형식 검증 (선택적)
    if (meta.agentId !== undefined && typeof meta.agentId !== 'string') {
        warnings.push(`agentId should be a string, got ${typeof meta.agentId}`);
    }
    // memoryId 형식 검증 (선택적)
    if (meta.memoryId !== undefined && typeof meta.memoryId !== 'string') {
        warnings.push(`memoryId should be a string, got ${typeof meta.memoryId}`);
    }
    // traceId 형식 검증 (선택적)
    if (meta.traceId !== undefined && typeof meta.traceId !== 'string') {
        warnings.push(`traceId should be a string, got ${typeof meta.traceId}`);
    }
    // requestId 형식 검증 (선택적)
    if (meta.requestId !== undefined && typeof meta.requestId !== 'string') {
        warnings.push(`requestId should be a string, got ${typeof meta.requestId}`);
    }
    return {
        valid: errors.length === 0,
        errors,
        warnings
    };
}
/**
 * MCP 모드 감지
 * MCP 서버는 stdio를 통해 실행되므로 stdin.isTTY와 stdout.isTTY가 모두 false입니다.
 */
/**
 * MCP 모드 감지
 * MCP 서버는 stdio를 사용하므로 stdin/stdout이 TTY가 아님
 *
 * 왜 이 함수가 중요한가?
 * - MCP 모드와 일반 모드를 정확히 구분하여 중복 로그 출력 방지
 * - 잘못된 감지는 중복 로그의 원인이 될 수 있음
 */
function isMCPMode() {
    // 환경 변수로 명시적으로 제어 가능
    if (process.env.MCP_MODE === 'true') {
        return true;
    }
    if (process.env.MCP_MODE === 'false') {
        return false;
    }
    // 자동 감지: stdio가 TTY가 아니면 MCP 모드
    return process.stdin.isTTY === false && process.stdout.isTTY === false;
}
function safeStringify(value) {
    try {
        return typeof value === 'string' ? value : JSON.stringify(value);
    }
    catch (error) {
        return `[unserializable: ${error.message}]`;
    }
}
function formatTime(date = new Date()) {
    return date.toISOString();
}
/**
 * PII 마스킹을 적용한 로그 메시지 생성
 *
 * PRD 0019: 보안 강화 (Phase 1) - PII 마스킹 강화
 * 모든 로그 메시지와 메타데이터에 PII 마스킹을 자동으로 적용합니다.
 */
function buildLogMessage(level, message, meta) {
    // 메시지 문자열의 PII 마스킹
    const maskedMessage = PIIMasker.mask(message).masked;
    const parts = [formatTime(), level.toUpperCase(), maskedMessage];
    if (meta && Object.keys(meta).length > 0) {
        // 메타데이터는 이미 PIIMasker.maskObject로 마스킹되었으므로 직렬화만 수행
        const serializedMeta = safeStringify(meta);
        parts.push(serializedMeta);
    }
    return parts.join(' | ');
}
/**
 * 일반 모드에서 stderr로 로그 출력
 */
function logToStderr(level, message, meta) {
    // PII 마스킹 적용 (중첩 객체도 깊이 마스킹)
    const maskedMeta = meta ? PIIMasker.maskObject(meta) : undefined;
    const logMessage = buildLogMessage(level, message, maskedMeta);
    // undefined가 넘어가면 Node가 문자열 "undefined"를 출력하므로 항상 문자열만 전달
    const line = typeof logMessage === 'string' ? `${logMessage}\n` : '\n';
    if (line)
        process.stderr.write(line);
}
/**
 * 중복 로그 출력 방지 플래그
 * 왜 필요한가? 동일한 로그가 여러 번 출력되는 것을 방지
 */
let isLoggingInProgress = false;
/**
 * MCP 모드에서 mcpLogger를 사용하여 로그 출력
 * mcpLogger.logServer는 동기 함수이므로 동기적으로 호출 가능
 *
 * MCP 스펙 준수:
 * - PII 마스킹: 모든 로그 메시지와 메타데이터에 자동 적용 (MCP 스펙 필수)
 * - logger 이름: 'server' 사용 (일관된 logger 이름)
 * - notifications/message 형식: mcpLogger.logServer()가 자동으로 처리
 *
 * 구현 완료:
 * - Rate limiting: 로그 전송 빈도 제한 (logging-rate-limiter.ts)
 * - 컨텍스트 포함: agentId, slot, memoryId, traceId 등 (logging-helpers.ts)
 *
 * 중복 로그 방지:
 * - isLoggingInProgress 플래그로 동시 호출 방지
 */
function logWithMCPLogger(level, message, meta) {
    // 중복 로그 방지: 이미 로깅 중이면 무시
    // 왜 필요한가? 동일한 로그가 여러 번 출력되는 것을 방지
    if (isLoggingInProgress) {
        return;
    }
    try {
        isLoggingInProgress = true;
        // PII 마스킹 적용 (MCP 스펙 필수: 자격 증명, 내부 시스템 세부사항 포함 금지)
        const maskedMessage = PIIMasker.mask(message).masked;
        // 중첩 객체도 깊이 마스킹하기 위해 PIIMasker.maskObject 사용
        const maskedMeta = meta ? PIIMasker.maskObject(meta) : undefined;
        // mcpLogger.logServer는 동기 함수이므로 동기적으로 호출
        // MCP 스펙: notifications/message 형식 준수 (mcpLogger 내부에서 처리)
        mcpLogger.logServer(level, maskedMessage, maskedMeta);
    }
    finally {
        isLoggingInProgress = false;
    }
}
/**
 * 중앙화된 로거 인스턴스
 *
 * MCP 모드와 일반 모드를 자동으로 감지하여 적절한 로깅 방식을 사용합니다.
 * 모든 로그 메시지와 메타데이터에 PII 마스킹이 자동으로 적용됩니다.
 *
 * 중복 로그 방지:
 * - MCP 모드와 일반 모드가 동시에 작동하지 않도록 보장
 * - isMCPMode()가 정확히 감지되도록 개선
 *
 * 사용법:
 * ```typescript
 * import { logger } from './logger.js';
 *
 * logger.info('Operation completed', { operationId: 'op123' });
 * logger.error('Failed to process request', { error: 'timeout' });
 * ```
 *
 * MCP 모드:
 * - mcpLogger.logServer()를 사용하여 MCP 프로토콜 준수
 * - logger 이름: 'server'
 * - notifications/message 형식 자동 처리
 *
 * 일반 모드:
 * - stderr.write()를 사용하여 표준 에러 출력
 * - 구조화된 텍스트 형식 (ISO 8601 타임스탬프 + 레벨 + 메시지 + JSON 메타데이터)
 */
/**
 * CLI 모드에서 로그 억제 (REQ-IO-4, AC8).
 * MEMENTO_CLI_QUIET=1 이면 stdout/stderr에 로그를 남기지 않음.
 */
export function isCliQuiet() {
    return process.env.MEMENTO_CLI_QUIET === '1';
}
export const logger = {
    debug(message, meta) {
        if (isCliQuiet())
            return;
        // 중복 로그 방지: MCP 모드와 일반 모드가 동시에 작동하지 않도록 보장
        const mcpMode = isMCPMode();
        if (mcpMode) {
            logWithMCPLogger('debug', message, meta);
        }
        else {
            logToStderr('debug', message, meta);
        }
    },
    info(message, meta) {
        if (isCliQuiet())
            return;
        const mcpMode = isMCPMode();
        if (mcpMode) {
            logWithMCPLogger('info', message, meta);
        }
        else {
            logToStderr('info', message, meta);
        }
    },
    warn(message, meta) {
        if (isCliQuiet())
            return;
        const mcpMode = isMCPMode();
        if (mcpMode) {
            logWithMCPLogger('warn', message, meta);
        }
        else {
            logToStderr('warn', message, meta);
        }
    },
    error(message, meta) {
        if (isCliQuiet())
            return;
        const mcpMode = isMCPMode();
        if (mcpMode) {
            logWithMCPLogger('error', message, meta);
        }
        else {
            logToStderr('error', message, meta);
        }
    }
};
//# sourceMappingURL=logger.js.map