/* global window */
// ── Общий store для двух приложений «Сигнал» ──
// localStorage + pub/sub + storage-событие для живой синхронизации web↔телефон.
// v2: жизненный цикл сигнала (новый → в работе → закрыт/отклонён, +эскалация) + журнал решений.
const STORE_KEY = "signal_demo_state_v3";

// Статусы жизненного цикла сигнала
// new       — пришёл, ещё не разобран
// in_work   — взят в работу (нельзя закрыть «молча»)
// closed    — закрыт с комментарием «что сделано»
// rejected  — отклонён с комментарием «почему»
const STATUS = {
  new:      { label: "Новый", color: "var(--text-2)" },
  in_work:  { label: "В работе", color: "var(--cyan)" },
  closed:   { label: "Закрыт", color: "var(--green)" },
  rejected: { label: "Отклонён", color: "var(--text-3)" },
};

const SEED = {
  version: 1,
  roles: ["Собственник", "Финансы", "Продажи", "Операции", "ИТ"],
  activeRole: "Собственник",
  signals: [
    { id: "s-margin", role: "Собственник", dir: "Финансы", source: "1С + Касса", severity: "red", read: false, snoozed: false,
      title: "Маржинальность снизилась на 8,4%", metric: "24,6", unit: "%", delta: "−8,4% к плану",
      cause: "Закупочные цены выросли по 12 позициям, продажные не обновлены.",
      risk: "−1,2 млн ₽ валовой прибыли в месяц", riskValue: 1.2, confidence: 82,
      action: "Пересчитать цены", owner: "Коммерческий директор",
      series: [33,32,31,32,30,31,29,30,28,27,25,24.6], target: 33, event: 8 },
    { id: "s-cash", role: "Финансы", dir: "Финансы", source: "Банк + 1С", severity: "red", read: true, snoozed: false,
      status: "in_work", journal: [{ ts: 1, action: "taken", comment: "", by: "Финансовый директор" }],
      title: "Кассовый разрыв через 18 дней", metric: "18", unit: " дн", delta: "−6 дн за неделю",
      cause: "Платежи поставщикам совпали с ростом дебиторской задолженности.",
      risk: "−2,1 млн ₽ дефицита ликвидности", riskValue: 2.1, confidence: 76,
      action: "Сдвинуть платежи", owner: "Финансовый директор",
      series: [46,50,43,54,48,58,51,62,55,66,60,70], target: 55, event: 9 },
    { id: "s-stock", role: "Операции", dir: "Склад", source: "Склад + WMS", severity: "red", read: false, snoozed: false,
      title: "Риск дефицита по ходовым позициям", metric: "9", unit: " дн", delta: "−7 дн запаса",
      cause: "Спрос растёт быстрее поставок, текущий запас на 9 дней.",
      risk: "−3,8 млн ₽ недополученной выручки", riskValue: 3.8, confidence: 88,
      action: "Ускорить закупку", owner: "Операционный директор",
      series: [38,44,41,49,45,52,48,57,51,64,58,72], target: 54, event: 9 },
    { id: "s-plan", role: "Продажи", dir: "Продажи", source: "CRM + Сайт", severity: "amber", read: false, snoozed: false,
      title: "План месяца под угрозой", metric: "91", unit: "%", delta: "−4% к прогнозу",
      cause: "Три канала дают меньше лидов, конверсия менеджеров ниже средней.",
      risk: "−4,8 млн ₽ выручки к концу месяца", riskValue: 4.8, confidence: 84,
      action: "Перераспределить лиды", owner: "Коммерческий директор",
      series: [42,45,48,52,47,50,46,43,39,36,34,31], target: 44, event: 6 },
    { id: "s-debt", role: "Финансы", dir: "Финансы", source: "1С", severity: "amber", read: false, snoozed: false,
      title: "Дебиторка выросла на 12%", metric: "18,4", unit: " млн ₽", delta: "+12% за месяц",
      cause: "Три ключевых клиента вышли за лимит отсрочки платежа.",
      risk: "Заморозка 2,2 млн ₽ в обороте", riskValue: 2.2, confidence: 79,
      action: "Запустить сбор дебиторки", owner: "Финансовый менеджер",
      series: [14,14.5,15,15.4,16,16.3,16.9,17.2,17.6,18,18.2,18.4], target: 14, event: 7 },
    { id: "s-lead", role: "Продажи", dir: "Маркетинг", source: "CRM + Реклама", severity: "amber", read: false, snoozed: false,
      title: "Поток лидов упал на 17%", metric: "−17", unit: "%", delta: "за неделю",
      cause: "Просадка в трёх рекламных каналах, рост стоимости лида.",
      risk: "−1,4 млн ₽ потенциальных продаж", riskValue: 1.4, confidence: 73,
      action: "Усилить рекламный бюджет", owner: "Маркетолог",
      series: [100,98,97,95,94,90,88,87,85,84,83,83], target: 100, event: 6 },
    { id: "s-illiquid", role: "Операции", dir: "Склад", source: "WMS", severity: "amber", read: false, snoozed: false,
      title: "Рост неликвидов на складе", metric: "32", unit: "%", delta: "+4% за месяц",
      cause: "Медленные позиции накапливаются, оборачиваемость падает.",
      risk: "Заморожено 3,1 млн ₽ в остатках", riskValue: 3.1, confidence: 70,
      action: "Запустить распродажу неликвидов", owner: "Операционный директор",
      series: [24,25,26,27,28,29,30,30,31,31,32,32], target: 22, event: 5 },
    { id: "s-it", role: "ИТ", dir: "Данные и ИТ", source: "ETL", severity: "amber", read: true, snoozed: false,
      status: "closed", journal: [{ ts: 1, action: "taken", comment: "", by: "ИТ" }, { ts: 2, action: "closed", comment: "Перезапустили загрузку, витрина обновилась — данные свежие.", by: "ИТ" }],
      title: "Сбой загрузки данных из 1С", metric: "3", unit: " ч", delta: "задержка витрины",
      cause: "Изменилась структура выгрузки, одна витрина обновилась с задержкой.",
      risk: "Часть сигналов на 3 часа устарела", riskValue: 0, confidence: 91,
      action: "Перезапустить загрузку", owner: "Инженер данных",
      series: [99.9,99.8,99.9,99.7,99.8,98.9,99.2,99.6,99.8,99.9,99.9,99.9], target: 99.5, event: 5 },
    { id: "s-growth", role: "Продажи", dir: "Продажи", source: "CRM", severity: "green", read: false, snoozed: false,
      title: "Точка роста: спрос в регионе ЮФО", metric: "+22", unit: "%", delta: "за 2 недели",
      cause: "Категория показывает устойчивый рост спроса выше прогноза.",
      risk: "Упустить +1,8 млн ₽, если не усилить", riskValue: 1.8, confidence: 80,
      action: "Усилить поставки и рекламу в ЮФО", owner: "Коммерческий директор",
      series: [30,33,36,38,43,47,52,55,58,60,62,64], target: 40, event: 4 },
    { id: "s-churn", role: "Собственник", dir: "Продажи", source: "CRM", severity: "amber", read: false, snoozed: false,
      title: "Отток крупных клиентов ускорился", metric: "4", unit: " клиента", delta: "за квартал",
      cause: "Снижение повторных заказов у сегмента ключевых клиентов.",
      risk: "−2,6 млн ₽ годовой выручки", riskValue: 2.6, confidence: 75,
      action: "Назначить встречи с клиентами", owner: "Коммерческий директор",
      series: [1,1,2,2,2,3,3,3,4,4,4,4], target: 1, event: 5 },
  ],
  tasks: [],
  notifications: [],
  // ── Показатели генерального директора (торговая компания) ──
  metrics: [
    { id: "m-rev", group: "Финансы", name: "Выручка", value: "125,4", unit: "млн ₽", delta: "+8,7% MoM", dir: "up", spark: [108,112,115,118,120,122,121,123,124,125,125,125.4] },
    { id: "m-margin", group: "Финансы", name: "Валовая маржа", value: "24,6", unit: "%", delta: "−8,4% к плану", dir: "down", spark: [33,32,31,32,30,31,29,30,28,27,25,24.6], signalId: "s-margin" },
    { id: "m-ebitda", group: "Финансы", name: "EBITDA", value: "12,3", unit: "млн ₽", delta: "+3,9% MoM", dir: "up", spark: [10.2,10.6,11,11.3,11.6,11.8,11.9,12,12.1,12.2,12.2,12.3] },
    { id: "m-cash", group: "Финансы", name: "Денежный остаток", value: "32,1", unit: "млн ₽", delta: "−3,1% MoM", dir: "warn", spark: [40,39,38,37,36,35,34,34,33,33,32.5,32.1] },
    { id: "m-gap", group: "Финансы", name: "Кассовый разрыв", value: "18", unit: "дн", delta: "−6 дн за неделю", dir: "down", spark: [46,50,43,54,48,58,51,62,55,66,60,70], signalId: "s-cash" },
    { id: "m-debt", group: "Финансы", name: "Дебиторка", value: "18,4", unit: "млн ₽", delta: "+12% MoM", dir: "warn", spark: [14,14.5,15,15.4,16,16.3,16.9,17.2,17.6,18,18.2,18.4], signalId: "s-debt" },
    { id: "m-plan", group: "Продажи", name: "Выполнение плана", value: "91", unit: "%", delta: "−4% к прогнозу", dir: "down", spark: [42,45,48,52,47,50,46,43,39,36,34,31], signalId: "s-plan" },
    { id: "m-check", group: "Продажи", name: "Средний чек", value: "84,2", unit: "тыс ₽", delta: "+2,2% MoM", dir: "up", spark: [79,80,81,80,82,82,83,83,84,84,84,84.2] },
    { id: "m-conv", group: "Продажи", name: "Конверсия воронки", value: "14,2", unit: "%", delta: "−3,1 п.п.", dir: "down", spark: [18,17.5,17,16.5,16,15.5,15.2,15,14.7,14.5,14.3,14.2] },
    { id: "m-lead", group: "Продажи", name: "Поток лидов", value: "−17", unit: "%", delta: "за неделю", dir: "down", spark: [100,98,97,95,94,90,88,87,85,84,83,83], signalId: "s-lead" },
    { id: "m-yfo", group: "Продажи", name: "Спрос ЮФО", value: "+22", unit: "%", delta: "точка роста", dir: "up", spark: [30,33,36,38,43,47,52,55,58,60,62,64], signalId: "s-growth" },
    { id: "m-stock", group: "Склад", name: "Запас ходовых", value: "9", unit: "дн", delta: "−7 дн запаса", dir: "down", spark: [38,44,41,49,45,52,48,57,51,64,58,72], signalId: "s-stock" },
    { id: "m-illiq", group: "Склад", name: "Неликвиды", value: "32", unit: "%", delta: "+4% MoM", dir: "warn", spark: [24,25,26,27,28,29,30,30,31,31,32,32], signalId: "s-illiquid" },
    { id: "m-turn", group: "Склад", name: "Оборачиваемость", value: "42", unit: "дн", delta: "+3 дн MoM", dir: "warn", spark: [38,38,39,39,40,40,41,41,42,42,42,42] },
    { id: "m-churn", group: "Клиенты", name: "Отток крупных", value: "4", unit: "клиента", delta: "за квартал", dir: "down", spark: [1,1,2,2,2,3,3,3,4,4,4,4], signalId: "s-churn" },
    { id: "m-nps", group: "Клиенты", name: "NPS", value: "38", unit: "", delta: "+2 пункта", dir: "up", spark: [30,31,32,33,34,35,36,36,37,37,38,38] },
  ],
};

// AI-ответы (скриптовые, по данным сигнала)
function aiReply(signal, q) {
  const R = {
    "Почему это произошло?": `Главная причина: ${signal.cause.toLowerCase()} Поэтому показатель отклонился от плана.`,
    "Какой риск в деньгах?": signal.riskValue > 0 ? `Текущий риск: ${signal.risk}. Без реакции в течение 14 дней ожидаемая потеря растёт примерно на ${(signal.riskValue*0.45).toFixed(1).replace('.',',')} млн ₽.` : `Прямого денежного риска нет, но ${signal.risk.toLowerCase()} — это влияет на качество остальных сигналов.`,
    "Что сделать сегодня?": `Рекомендуемое действие: ${signal.action.toLowerCase()}. Ответственный — ${signal.owner}. Контрольная точка — сегодня, поручение уйдёт в работу с отслеживанием статуса.`,
    "Кому поручить?": `Ответственный: ${signal.owner}. Поручение создаётся из сигнала и попадает в раздел «Поручения» — со сроком и контролем выполнения.`,
  };
  return R[q] || `По сигналу «${signal.title}»: ${signal.cause} Рекомендуемое действие — ${signal.action.toLowerCase()}.`;
}
function aiPrompts() { return ["Почему это произошло?", "Какой риск в деньгах?", "Что сделать сегодня?", "Кому поручить?"]; }

// ── store core ──
function normalize(st) {
  if (!st.activeFocus) st.activeFocus = "deviations"; // deviations | successes | in_work | resolved | all
  (st.signals || []).forEach((s) => {
    if (!s.status) s.status = "new";
    if (!Array.isArray(s.journal)) s.journal = [];
    if (s.escalated == null) s.escalated = false;
  });
  return st;
}
function loadState() {
  try { const raw = localStorage.getItem(STORE_KEY); if (raw) return normalize(JSON.parse(raw)); } catch (e) {}
  return normalize(JSON.parse(JSON.stringify(SEED)));
}
let state = loadState();
const listeners = new Set();
function persist() { try { localStorage.setItem(STORE_KEY, JSON.stringify(state)); } catch (e) {} }
function emit() { listeners.forEach((l) => l(state)); }
function commit() { persist(); emit(); }

// cross-view sync (две вкладки / две панели на странице)
window.addEventListener("storage", (e) => { if (e.key === STORE_KEY && e.newValue) { try { state = JSON.parse(e.newValue); emit(); } catch (err) {} } });

function logEntry(s, action, comment) {
  s.journal.push({ ts: Date.now(), action, comment: comment || "", by: state.activeRole });
}
function notify(text, owner) {
  state.notifications.unshift({ id: "n-" + Date.now() + Math.floor(Math.random()*99), text, owner: owner || "", ts: Date.now(), read: false });
}

const Store = {
  get: () => state,
  STATUS,
  subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
  reset() { this.clearData(); },
  clearData() { try { localStorage.removeItem(STORE_KEY); } catch (e) {} state = normalize(JSON.parse(JSON.stringify(SEED))); commit(); },
  setRole(role) { state.activeRole = role; commit(); },
  setFocus(f) { state.activeFocus = f; commit(); },

  // ── Жизненный цикл сигнала ──
  takeIntoWork(id) {
    const s = state.signals.find((x) => x.id === id); if (!s || s.status === "closed" || s.status === "rejected") return;
    s.status = "in_work"; s.read = true; logEntry(s, "taken");
    notify(`Сигнал взят в работу: ${s.title}`, s.owner);
    commit();
  },
  rejectSignal(id, comment) {
    const s = state.signals.find((x) => x.id === id); if (!s || !comment) return;
    s.status = "rejected"; s.read = true; logEntry(s, "rejected", comment);
    notify(`Сигнал отклонён: ${s.title}`, s.owner);
    commit();
  },
  escalateSignal(id, comment) {
    const s = state.signals.find((x) => x.id === id); if (!s) return;
    s.escalated = true; if (s.status === "new") s.status = "in_work"; s.read = true;
    logEntry(s, "escalated", comment);
    notify(`Эскалация: ${s.title}`, s.owner);
    commit();
  },
  closeSignal(id, comment) {
    const s = state.signals.find((x) => x.id === id); if (!s || !comment) return;
    if (s.status === "new") s.status = "in_work"; // нельзя закрыть «молча» новый — но если закрывают сразу, фиксируем что брали
    s.status = "closed"; s.escalated = false; s.read = true; logEntry(s, "closed", comment);
    notify(`Сигнал закрыт: ${s.title}`, s.owner);
    commit();
  },
  reopen(id) {
    const s = state.signals.find((x) => x.id === id); if (!s) return;
    s.status = "in_work"; logEntry(s, "reopened"); commit();
  },
  // derived
  isOpen(s) { return s.status === "new" || s.status === "in_work"; },
  isResolved(s) { return s.status === "closed" || s.status === "rejected"; },
  focusCounts() {
    const sg = state.signals;
    const open = sg.filter((s) => s.status === "new" || s.status === "in_work");
    return {
      deviations: open.filter((s) => s.severity !== "green").length,
      successes: open.filter((s) => s.severity === "green").length,
      in_work: sg.filter((s) => s.status === "in_work").length,
      new: sg.filter((s) => s.status === "new").length,
      resolved: sg.filter((s) => s.status === "closed" || s.status === "rejected").length,
      escalated: sg.filter((s) => s.escalated && s.status !== "closed" && s.status !== "rejected").length,
      all: sg.length,
    };
  },
  markRead(id) { const s = state.signals.find((x) => x.id === id); if (s && !s.read) { s.read = true; commit(); } },
  snooze(id) { const s = state.signals.find((x) => x.id === id); if (s) { s.snoozed = !s.snoozed; commit(); } },
  takeAction(id) {
    const s = state.signals.find((x) => x.id === id); if (!s) return;
    if (state.tasks.some((t) => t.signalId === id)) return;
    const task = { id: "t-" + Date.now() + Math.floor(Math.random()*99), signalId: id, title: s.action, signalTitle: s.title,
      owner: s.owner, severity: s.severity, status: "new", createdAt: Date.now(), source: s.source };
    state.tasks.unshift(task);
    state.notifications.unshift({ id: "n-"+Date.now(), text: `Поручение создано: ${s.action}`, owner: s.owner, ts: Date.now(), read: false });
    s.read = true;
    commit();
  },
  setTaskStatus(taskId, status) { const t = state.tasks.find((x) => x.id === taskId); if (t) { t.status = status; commit(); } },
  hasTask(signalId) { return state.tasks.some((t) => t.signalId === signalId); },
  clearNotifs() { state.notifications.forEach((n) => n.read = true); commit(); },
};

window.SignalStore = Store;
window.SignalStatus = STATUS;
window.aiReply = aiReply;
window.aiPrompts = aiPrompts;

// React hook
window.useStore = function useStore() {
  const [, force] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => Store.subscribe(() => force()), []);
  return Store;
};
