export const DRAFT='https://json-schema.org/draft/2020-12/schema';
const text={type:'string',minLength:1,maxLength:100};
const person={type:'object',properties:{name:text,email:{type:'string',format:'email',maxLength:254},age:{type:'integer',minimum:18,maximum:120}},required:['name','email','age'],additionalProperties:false};
const product={type:'object',properties:{sku:{type:'string',pattern:'^[A-Z][A-Z0-9-]{1,15}$'},quantity:{type:'integer',minimum:1,maximum:100},price:{type:'number',minimum:0,maximum:1000000}},required:['sku','quantity','price'],additionalProperties:false};
const def=(key,title,why,guide,shape,good,bad)=>({key,title,why,guide,schema:{$schema:DRAFT,$id:'https://example.invalid/schemas/'+key,...shape},good,bad});
export const lessons=[
 def('customer','1. Контракт клиента','Регистрация, CRM и API личного кабинета.','Сравните отсутствующее поле, пустую строку и неверный тип. required проверяет наличие, minLength — длину строки.',person,{name:'Анна',email:'anna@example.test',age:29},{name:'',email:'не почта',age:'29'}),
 def('extras','2. Неожиданные поля','Импорт и API, где лишнее поле часто означает опечатку.','Добавьте поле admin или переименуйте name. additionalProperties:false отклонит лишний ключ, а не удалит его.',{type:'object',properties:{name:text,active:{type:'boolean'}},required:['name','active'],additionalProperties:false},{name:'Сервис Север',active:true},{name:'Сервис Север',active:true,admin:true}),
 def('order','3. Позиции заказа','Проверка размера заказа и данных каждой позиции.','Посмотрите путь /items/0/quantity: это индекс первого элемента, а не номер строки JSON.',{type:'object',properties:{items:{type:'array',minItems:1,maxItems:20,items:product}},required:['items'],additionalProperties:false},{items:[{sku:'AB-10',quantity:2,price:499.5}]},{items:[{sku:'?',quantity:0,price:-10}]}),
 def('refs','4. Вложенность и $ref','Одинаковые правила адреса доставки и оплаты.','Оба адреса используют одно определение в $defs. Ссылки разрешаются локально; мастерская не скачивает внешние схемы.',{type:'object',$defs:{address:{type:'object',properties:{city:text,postcode:{type:'string',pattern:'^[0-9]{6}$'}},required:['city','postcode'],additionalProperties:false}},properties:{shipping:{$ref:'#/$defs/address'},billing:{$ref:'#/$defs/address'}},required:['shipping','billing'],additionalProperties:false},{shipping:{city:'Москва',postcode:'101000'},billing:{city:'Казань',postcode:'420000'}},{shipping:{city:'',postcode:'ABC'},billing:{city:'Казань'}}),
 def('variants','5. Один из вариантов заявки','Самовывоз и доставка требуют разных полей.','oneOf принимает ровно одну ветку. Поле kind с const делает варианты однозначными; ошибки остальных веток тоже могут попасть в отчёт.',{type:'object',oneOf:[{properties:{kind:{const:'pickup'},pointId:{type:'integer',minimum:1}},required:['kind','pointId'],additionalProperties:false},{properties:{kind:{const:'delivery'},address:text},required:['kind','address'],additionalProperties:false}]},{kind:'pickup',pointId:12},{kind:'delivery',pointId:12}),
 def('formats','6. Email, дата и время','Настройка рассылок и событий календаря.','ajv-formats проверяет синтаксис, но не существование почтового ящика. date-time требует часовой пояс, date — существующую календарную дату.',{type:'object',properties:{email:{type:'string',format:'email',maxLength:254},date:{type:'string',format:'date'},startsAt:{type:'string',format:'date-time'}},required:['email','date','startsAt'],additionalProperties:false},{email:'team@example.test',date:'2026-11-10',startsAt:'2026-11-10T10:00:00+03:00'},{email:'abc',date:'2026-02-30',startsAt:'2026-11-10 10:00'}),
 def('form','7. Ошибки рядом с полями','Форма клиента с тем же контрактом, что использует Node.js.','Меняйте поля формы: валидация покажет ошибки у каждого поля. Значение возраста преобразует сама форма, не Ajv.',person,{name:'Мария',email:'maria@example.test',age:31},{name:'',email:'ошибка',age:12}),
 def('normalize','8. Проверить или изменить?','Явный этап нормализации конфигурации перед сохранением.','Переключите режим. По умолчанию данные не меняются. В режиме нормализации копия получает defaults, числовые строки преобразуются, неизвестные поля удаляются.',{type:'object',properties:{name:text,retries:{type:'integer',minimum:0,maximum:5,default:2},enabled:{type:'boolean',default:true}},required:['name','retries','enabled'],additionalProperties:false},{name:'Импорт',retries:2,enabled:true},{name:'Импорт',retries:'3',extra:'будет удалено только в режиме нормализации'}),
 def('import','9. Пакетный импорт NDJSON','Проверка выгрузки построчно с отчётом и номерами проблемных строк.','Каждая непустая строка — отдельный JSON-объект. Ошибка одной строки не останавливает остальные. Схема компилируется один раз.',person,{name:'Иван',email:'ivan@example.test',age:24},{name:'',email:'не-email',age:15})
];
export const byKey=Object.fromEntries(lessons.map(l=>[l.key,l]));
