ALTER TABLE raw_posts
  ADD COLUMN IF NOT EXISTS publication_status TEXT NOT NULL DEFAULT 'pending',
  ADD COLUMN IF NOT EXISTS publication_error TEXT,
  ADD COLUMN IF NOT EXISTS published_at TIMESTAMPTZ,
  ADD COLUMN IF NOT EXISTS tg_publication_chat_id BIGINT,
  ADD COLUMN IF NOT EXISTS tg_publication_thread_id BIGINT,
  ADD COLUMN IF NOT EXISTS tg_publication_message_ids BIGINT[],
  ADD COLUMN IF NOT EXISTS tg_publication_url TEXT;

CREATE INDEX IF NOT EXISTS idx_raw_posts_publication_queue
ON raw_posts(publication_status, editorial_status, rewrite_status, published_at, reviewed_at, id);

CREATE INDEX IF NOT EXISTS idx_raw_posts_publication_rank
ON raw_posts(final_category_tag, final_source_tag, published_at DESC);

CREATE TABLE IF NOT EXISTS publication_runs (
  id BIGSERIAL PRIMARY KEY,
  poster TEXT NOT NULL,
  schedule_id TEXT NOT NULL,
  scheduled_for DATE NOT NULL,
  scheduled_time TEXT NOT NULL,
  planned_count INTEGER NOT NULL DEFAULT 0,
  published_count INTEGER NOT NULL DEFAULT 0,
  status TEXT NOT NULL DEFAULT 'started',
  error TEXT,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE(poster, schedule_id, scheduled_for)
);

CREATE INDEX IF NOT EXISTS idx_publication_runs_poster_date
ON publication_runs(poster, scheduled_for DESC, scheduled_time);

INSERT INTO worker_controls(name, enabled, settings_json)
VALUES ('tg-poster', FALSE, '{}'::jsonb)
ON CONFLICT (name) DO NOTHING;

INSERT INTO app_settings(key, value_json, value_type, title, description, category)
VALUES
  ('tg_poster_enabled', 'false'::jsonb, 'bool', 'TG poster enabled', 'Double safety flag for publishing accepted posts to Telegram.', 'TG Poster'),
  ('tg_poster_bot_token', '""'::jsonb, 'secret', 'TG poster bot token', 'Bot token for final Telegram publication. For test can be the same as upload bot token.', 'TG Poster'),
  ('tg_poster_chat_id', '"-1003712724327"'::jsonb, 'str', 'TG publication chat id', 'Target Telegram channel/group id. Supports chat_id:thread_id for topics.', 'TG Poster'),
  ('tg_poster_schedule_json', '[{"id":"morning","time":"10:00","count":1,"enabled":true},{"id":"evening","time":"18:00","count":1,"enabled":true}]'::jsonb, 'json', 'TG publication schedule', 'EKB time schedule rows: id, time HH:MM, count, enabled.', 'TG Poster'),
  ('tg_poster_interval_sec', '60'::jsonb, 'int', 'TG poster interval, sec', 'Pause between poster checks.', 'TG Poster'),
  ('tg_poster_media_group_max_items', '10'::jsonb, 'int', 'TG media group max items', 'Telegram media group supports 2..10 media items.', 'TG Poster'),
  ('tg_poster_caption_limit', '1024'::jsonb, 'int', 'TG caption limit', 'Telegram caption limit for media messages.', 'TG Poster'),
  ('tg_poster_message_limit', '4096'::jsonb, 'int', 'TG message limit', 'Telegram text message limit.', 'TG Poster'),
  ('tg_poster_max_attempts', '3'::jsonb, 'int', 'TG post retry attempts', 'Retries for Telegram API errors.', 'TG Poster'),
  ('tg_poster_retry_backoff_max_sec', '30'::jsonb, 'int', 'TG retry backoff max, sec', 'Max retry sleep for Telegram API errors.', 'TG Poster'),
  ('tg_poster_send_delay_sec', '1'::jsonb, 'float', 'TG send delay, sec', 'Delay between Telegram sends.', 'TG Poster'),
  ('tg_poster_text_overflow_caption', '"⬇️ Описание под медиа"'::jsonb, 'str', 'TG overflow caption', 'Caption used when full text is longer than Telegram media caption limit.', 'TG Poster'),
  ('tg_poster_recent_window', '20'::jsonb, 'int', 'Ranking recent window', 'How many published posts are checked to reduce repeated categories and sources.', 'TG Poster'),
  ('tg_poster_category_repeat_penalty', '3'::jsonb, 'float', 'Category repeat penalty', 'Ranking penalty for recently published same category.', 'TG Poster'),
  ('tg_poster_source_repeat_penalty', '4'::jsonb, 'float', 'Source repeat penalty', 'Ranking penalty for recently published same source.', 'TG Poster')
ON CONFLICT (key) DO NOTHING;

UPDATE app_settings
SET description='Обрезает исходный raw-текст перед отправкой в LLM. Итоговый rewrite должен быть компактным: Telegram caption 1024 символа минус будущая строка хэштегов.'
WHERE key='ai_writer_max_text_chars';

UPDATE app_settings
SET value_json = to_jsonb($contract$
OUTPUT SCHEMA (return array matching input order):
{"rewrites":[{"id":123,"category_id":2,"text":"готовый текст без хэштегов","notes":"короткая заметка для редактора или null"}]}

Rules:
- Input is a JSON object with key "posts" containing accepted posts.
- Input has key "categories" with objects: id, name, tag.
- Each post includes producer_name and producer_tag. producer_name can be a manufacturer, shop, or publishing source.
- Pick exactly one category from the categories list in input and return its numeric id as category_id.
- Do not add links or hashtags to rewritten text.
- Keep text compact enough for Telegram media caption: target 850 characters, hard maximum 900 characters.
- Remember the system appends category and source hashtags after text, so leave space for them.
- Return JSON only. No markdown. No text outside JSON.
- Never return an empty object. Never omit the "rewrites" key.
- Return one rewrite for every input post id.
- The "text" field must be a ready-to-publish Russian post with normal paragraph line breaks encoded as JSON string newlines.
- The "notes" field must be a short editor note in Russian or null.
$contract$::text),
updated_at = NOW()
WHERE key='ai_writer_contract';

UPDATE app_settings
SET value_json = to_jsonb(
  regexp_replace(
    value_json #>> '{}',
    '- 3-6 коротких строк или 2-5 компактных абзацев;',
    '- 3-6 коротких строк или 2-4 компактных абзаца; итоговый text должен целиться в 850 символов и не превышать 900 символов, потому что система добавит хэштеги и Telegram caption ограничен 1024 символами;',
    'g'
  )
)
WHERE key='ai_writer_prompt'
  AND (value_json #>> '{}') LIKE '%3-6 коротких строк или 2-5 компактных абзацев%';
