import {quoteExample} from '../public/scenarios.js';
const ref=name=>({$ref:'#/components/schemas/'+name});
const object=(properties,required=Object.keys(properties),extra=false)=>({type:'object',properties,required,additionalProperties:extra});
const str=(description,extra={})=>({type:'string',description,...extra});
const integer=(minimum,maximum)=>({type:'integer',minimum,maximum});
const json=(schema,description,examples)=>({description,content:{'application/json':{schema,...(examples?{examples}:{} )}}});
const query=(name,schema,description,example)=>({name,in:'query',required:false,description,schema,example});
const error=(code,message,fields={})=>({demo:true,error:{code,message,fields}});
export function createSpec(){
 const schemas={
  Ticket:object({id:integer(1,999),title:str('Краткое название работы'),status:{type:'string',enum:['new','in_progress','done']},amountMinor:integer(0,100000000),currency:{const:'RUB',type:'string'},version:integer(1,1000)}),
  Error:object({demo:{const:true,type:'boolean'},error:object({code:str('Машиночитаемый код'),message:str('Понятное описание'),fields:{type:'object',additionalProperties:{type:'string'}}}),currentVersion:integer(1,1000)},['demo','error']),
  Customer:object({name:str('Только вымышленное имя',{minLength:2,maxLength:60}),email:str('Только учебный адрес',{format:'email',maxLength:120})}),
  Item:object({title:str('Услуга',{minLength:1,maxLength:70}),quantity:integer(1,20),priceMinor:integer(0,1000000)}),
  QuoteInput:object({customer:ref('Customer'),items:{type:'array',minItems:1,maxItems:5,items:ref('Item')}}),
  QuoteResult:object({demo:{const:true,type:'boolean'},data:object({id:str('Учебный идентификатор'),totalMinor:integer(0,100000000),currency:{type:'string',const:'RUB'},items:integer(1,5),persisted:{type:'boolean',const:false}}),message:str('Результат без сохранения')}),
  Revision:object({expectedVersion:integer(1,1000000),title:str('Новый заголовок',{minLength:3,maxLength:70})}),
  GenericResult:object({demo:{type:'boolean',const:true},data:{type:'object',additionalProperties:true},message:{type:'string'}},['demo','data'])
 };
 const errors={'400':json(ref('Error'),'JSON повреждён или вместо объекта пришёл массив'), '413':json(ref('Error'),'Тело превысило допустимый размер'), '415':json(ref('Error'),'Неподдерживаемый Content-Type'), '422':json(ref('Error'),'Проверка данных не прошла',{fields:{value:error('VALIDATION_FAILED','Исправьте отмеченные поля',{'customer.email':'Нужен корректный email'})}})};
 return {
  openapi:'3.1.1',info:{title:'API-мастерская: заявки сервисной компании',version:'1.0.0',description:'Восемь практических ситуаций на настоящем PHP. Все данные вымышлены; POST/PATCH ничего не сохраняют. HTTP 429 фиксирован. Публичные токены demo-reader и demo-editor предназначены только для обучения. Не передавайте реальные персональные данные или секреты.'},servers:[{url:'./api',description:'PHP-маршруты рядом с openapi.json'}],
  tags:[{name:'Заявки',description:'Каталог, карточка, расчёт и проверка версии.'},{name:'Доступ',description:'Учебные 401/403/200 без настоящих учётных записей.'},{name:'Файлы',description:'Обработка text/plain в памяти.'},{name:'Контракт',description:'Ограничение частоты и устаревшая операция.'}],
  paths:{
   '/catalog.php':{get:{operationId:'listTickets',tags:['Заявки'],summary:'Каталог заявок с фильтром и пагинацией',description:'Всего 18 фиксированных заявок. Пустая страница — это 200 и пустой data. Значения page и limit ограничены сервером.',parameters:[query('status',{type:'string',enum:['all','new','in_progress','done'],default:'all'},'Фильтр состояния','in_progress'),query('page',{...integer(1,100),default:1},'Номер страницы',1),query('limit',{...integer(1,6),default:4},'Размер страницы',4)],responses:{'200':json(object({demo:{type:'boolean',const:true},data:{type:'array',items:ref('Ticket')},meta:object({page:integer(1,100),limit:integer(1,6),total:integer(0,18),pages:integer(0,18)})}),'Страница каталога'),'422':errors['422']}}},
   '/detail.php':{get:{operationId:'getTicket',tags:['Заявки'],summary:'Карточка по идентификатору',description:'id здесь query-параметр, чтобы пример работал на обычном PHP-хостинге без rewrite. Для REST-router аналогом будет GET /tickets/{id}.',parameters:[query('id',{...integer(1,999),default:1},'Идентификатор; 1–18 существуют',3)],responses:{'200':json(object({demo:{type:'boolean',const:true},data:ref('Ticket')}),'Карточка'),'404':json(ref('Error'),'Карточка отсутствует',{missing:{value:error('TICKET_NOT_FOUND','Учебная заявка не найдена')}}),'422':errors['422']}}},
   '/quote.php':{post:{
    operationId:'previewQuote',tags:['Заявки'],summary:'Проверить JSON и рассчитать заявку',description:'Сложение ведётся в целых копейках. 201 показывает учебный результат создания расчёта, но persisted всегда false. Лимит тела 12 КиБ. Не создаёт заказ, платёж или запись в базе.',
    requestBody:{required:true,content:{'application/json':{schema:ref('QuoteInput'),examples:{
     valid:{summary:'Корректная заявка',value:quoteExample},
     invalid:{summary:'Несколько ошибок',value:{customer:{name:'М',email:'wrong'},items:[{title:'Работа',quantity:0,priceMinor:'100'}]}}
    }}}},
    responses:{'201':json(ref('QuoteResult'),'Предпросмотр расчёта готов',{quote:{value:{demo:true,data:{id:'DEMO-Q1001',totalMinor:290000,currency:'RUB',items:2,persisted:false},message:'Предпросмотр расчёта готов. Заявка не сохранялась.'}}}),...errors}
   }},
   '/account.php':{get:{operationId:'checkDemoRole',tags:['Доступ'],summary:'Сравнить 401, 403 и разрешённое действие',description:'Для read подходит demo-reader или demo-editor; для edit — только demo-editor. Это публичные демонстрационные строки, не секреты и не production-аутентификация.',security:[{demoBearer:[]}],parameters:[query('action',{type:'string',enum:['read','edit'],default:'read'},'Демонстрационное действие','edit')],responses:{'200':json(ref('GenericResult'),'Учебная роль допускает действие'),'401':json(ref('Error'),'Токен отсутствует или неизвестен'),'403':json(ref('Error'),'Токен известен, но права не подходят'),'422':errors['422']}}},
   '/revision.php':{patch:{operationId:'previewRevision',tags:['Заявки'],summary:'Проверить ожидаемую версию изменения',description:'Фикстура всегда имеет version=3. Успешный ответ показывает version=4, но общая карточка не меняется. Для реального API нужен атомарный UPDATE с условием версии.',requestBody:{required:true,content:{'application/json':{schema:ref('Revision'),examples:{current:{value:{expectedVersion:3,title:'Проверить освещение'}},stale:{value:{expectedVersion:2,title:'Проверить освещение'}}}}}},responses:{'200':json(ref('GenericResult'),'Учебный результат изменения без сохранения'),'409':json(ref('Error'),'Конфликт версии',{conflict:{value:{...error('VERSION_CONFLICT','Данные изменились: перечитайте карточку'),currentVersion:3}}}),...errors}}},
   '/upload.php':{post:{operationId:'inspectText',tags:['Файлы'],summary:'Прочитать маленький UTF-8 текст без сохранения',description:'Тело передаётся как text/plain; это не multipart/form-data. Максимум 4096 байт, не символов. Возвращаются число байт, строк и SHA-256. Сервер не сохраняет файл.',requestBody:{required:true,content:{'text/plain':{schema:{type:'string',description:'UTF-8, от 1 до 4096 байт'},example:'Код;Задача\nA-01;Проверить щит\n'}}},responses:{'200':json(ref('GenericResult'),'Текст разобран в памяти'),'413':errors['413'],'415':errors['415'],'422':errors['422']}}},
   '/limited.php':{get:{operationId:'showRateLimit',tags:['Контракт'],summary:'Получить учебный 429 с Retry-After',description:'Фиксированный пример: ответ всегда 429, настоящего лимитера нет. Не запускайте бесконечные автоматические повторы.',responses:{'429':{...json(ref('Error'),'Учебный лимит'),headers:{'Retry-After':{description:'Рекомендуемая задержка в секундах',schema:{type:'integer',const:3}}}}}}},
   '/legacy.php':{get:{operationId:'legacyTickets',tags:['Контракт'],summary:'Устаревшая операция, которая ещё отвечает',deprecated:true,description:'Используйте catalog.php. Пометка deprecated в документе не удаляет обработчик.',responses:{'200':json(ref('GenericResult'),'Предупреждение о переходе на новый endpoint')}}}
  },components:{securitySchemes:{demoBearer:{type:'http',scheme:'bearer',description:'Только публичная строка demo-reader или demo-editor. Не вводите настоящий токен.'}},schemas}
 };
}
