import pino from 'pino';
import {mkdtemp,readFile,unlink,rmdir} from 'node:fs/promises';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
export const levels={trace:10,debug:20,info:30,warn:40,error:50,fatal:60};
const descriptions=[
 ['levels','Уровни и порог','Отделите обычные события от диагностики. Порог info не пропускает debug.','Проверьте, почему строка диагностики не попала в выход.'],
 ['child','Контекст запроса','Свяжите начало и конец одной операции через requestId и child logger.','Найдите две строки с requestId req-42.'],
 ['redact','Секреты и redaction','Скройте заранее известные пути к токену, паролю и адресу.','Убедитесь, что учебные значения заменены, а orderId сохранён.'],
 ['errors','Ошибка как объект','Сохраните тип, сообщение и стек исключения под ключом err.','Сравните структурированный err с коротким текстом msg.'],
 ['serializer','Сериализатор заказа','Запишите только разрешённые поля вместо всего объекта пользователя.','В выходе должны остаться id, status и totalMinor.'],
 ['mixin','Контекст задания','Добавляйте сведения о задании в каждую запись одного запуска.','Проследите попытку и результат, не склеивая строки вручную.'],
 ['multistream','Два получателя','Направьте обычные события в общий поток, а ошибки также в отдельный.','Сравните общий и аварийный журналы; ошибка намеренно присутствует в обоих.'],
 ['file','JSONL в файл','Запишите две строки в настоящий временный файл и прочитайте их обратно.','Скачайте JSONL: каждая строка должна быть отдельным JSON-объектом.']
];
export const scenes=descriptions.map(([id,title,why,tryIt])=>({id,title,why,tryIt}));
function sink(){const lines=[];return {lines,stream:{write(s){lines.push(JSON.parse(String(s)))}}};}
export async function runScene(id){const out=sink(),extra=sink(); const base={base:null,timestamp:false};let logger,source;
 switch(id){
 case 'levels': source=`const log = pino({ level: 'info' });\nlog.debug({ cache: 'miss' }, 'Диагностика');\nlog.info({ orderId: 42 }, 'Заказ принят');\nlog.warn({ retryIn: 5 }, 'Повтор позже');`;logger=pino({...base,level:'info'},out.stream);logger.debug({cache:'miss'},'Диагностика');logger.info({orderId:42},'Заказ принят');logger.warn({retryIn:5},'Повтор позже');break;
 case 'child':source=`const log = pino();\nconst requestLog = log.child({ requestId: 'req-42', route: '/orders' });\nrequestLog.info('Запрос начат');\nrequestLog.info({ statusCode: 200 }, 'Запрос завершён');`;logger=pino(base,out.stream).child({requestId:'req-42',route:'/orders'});logger.info('Запрос начат');logger.info({statusCode:200},'Запрос завершён');break;
 case 'redact':source=`const log = pino({\n  redact: { paths: ['token', 'user.password', 'user.email'], censor: '[HIDDEN]' }\n});\nlog.info({ token: 'demo-token', user: { password: 'demo-password', email: 'reader@example.test' }, orderId: 42 }, 'Заказ принят');`;logger=pino({...base,redact:{paths:['token','user.password','user.email'],censor:'[HIDDEN]'}},out.stream);logger.info({token:'demo-token',user:{password:'demo-password',email:'reader@example.test'},orderId:42},'Заказ принят');break;
 case 'errors':source=`const log = pino();\ntry { throw new Error('Учебный API недоступен'); }\ncatch (err) { log.error({ err, operation: 'loadCatalog' }, 'Каталог не получен'); }`;logger=pino(base,out.stream);{const err=new Error('Учебный API недоступен');err.stack='Error: Учебный API недоступен\n    at demoOperation (example.mjs:10:3)';logger.error({err,operation:'loadCatalog'},'Каталог не получен');}break;
 case 'serializer':source=`const log = pino({ serializers: {\n  order: value => ({ id: value.id, status: value.status, totalMinor: value.totalMinor })\n}});\nlog.info({ order: { id: 42, status: 'paid', totalMinor: '12500', privateNote: 'demo-only' } }, 'Изменение заказа');`;logger=pino({...base,serializers:{order:v=>({id:v.id,status:v.status,totalMinor:v.totalMinor})}},out.stream);logger.info({order:{id:42,status:'paid',totalMinor:'12500',privateNote:'demo-only'}},'Изменение заказа');break;
 case 'mixin':source=`const job = { id: 'import-17', attempt: 2 };\nconst log = pino({ mixin: () => ({ jobId: job.id, attempt: job.attempt }) });\nlog.info({ imported: 3 }, 'Пакет импортирован');\nlog.info({ imported: 7 }, 'Импорт завершён');`;{const job={id:'import-17',attempt:2};logger=pino({...base,mixin:()=>({jobId:job.id,attempt:job.attempt})},out.stream);logger.info({imported:3},'Пакет импортирован');logger.info({imported:7},'Импорт завершён');}break;
 case 'multistream':source=`const streams = pino.multistream([\n  { level: 'info', stream: process.stdout },\n  { level: 'error', stream: pino.destination({ dest: 'errors.jsonl', sync: true }) }\n]);\nconst log = pino({ level: 'info' }, streams);\nlog.info('Сервис готов');\nlog.error({ code: 'DEMO_TIMEOUT' }, 'Учебный timeout');`;logger=pino({...base,level:'info'},pino.multistream([{level:'info',stream:out.stream},{level:'error',stream:extra.stream}]));logger.info('Сервис готов');logger.error({code:'DEMO_TIMEOUT'},'Учебный timeout');break;
 case 'file':source=`const destination = pino.destination({ dest: 'app.jsonl', sync: true });\nconst log = pino({}, destination);\nlog.info({ taskId: 'report-9' }, 'Отчёт начат');\nlog.info({ taskId: 'report-9', rows: 120 }, 'Отчёт готов');\ndestination.flushSync();\ndestination.end();`;{const dir=await mkdtemp(join(tmpdir(),'aen-pino-')),file=join(dir,'record.jsonl');let dest;try{dest=pino.destination({dest:file,sync:true});logger=pino(base,dest);logger.info({taskId:'report-9'},'Отчёт начат');logger.info({taskId:'report-9',rows:120},'Отчёт готов');dest.flushSync();await new Promise((resolve,reject)=>{dest.on('close',resolve);dest.on('error',reject);dest.end();});out.lines.push(...(await readFile(file,'utf8')).trim().split('\n').map(JSON.parse));}finally{await unlink(file).catch(()=>{});await rmdir(dir);}}break;
 default:throw new Error('Неизвестный сценарий');
 }
 return {...scenes.find(s=>s.id===id),source,records:out.lines,errorRecords:extra.lines,execution:'Настоящий Pino в Node.js. В записи отключены время, PID и hostname для воспроизводимости.'};
}
export async function runAll(){return Promise.all(scenes.map(s=>runScene(s.id)));}
