// Tiny global store with subscribe / setState / snapshot.
// Holds lanes (Gantt ledger) and per-client tasks/touchpoints so changes
// in the drawer reflect on the home page (Gantt, calendar, alerts panel, KPIs).

(function(){
  const listeners = new Set();
  const STORAGE_KEY = "mcc.store.v1";
  // Slices we persist to localStorage. `lanes` is intentionally excluded —
  // those are driven by the live Operations Roadmap API now, not user-edited state.
  const PERSISTED_KEYS = [
    "tasksByClient", "touchpointsByClient", "contactsByClient",
    "companyOverrides", "companyIdentity", "addedCompanies",
    "team", "hiring", "internalTasks",
  ];

  const defaultState = () => ({
    lanes: [],
    tasksByClient: {},
    touchpointsByClient: {},
    contactsByClient: {},
    companyOverrides: {},   // key = "Name:segment" — per-engagement (MRR, services, currentProject, etc.)
    companyIdentity: {},    // key = "Name"          — shared across segments (champion, logo, health, displayName)
    addedCompanies: { md: [], mgmt: [] }, // [{ name, record }] per segment
    team: null,
    hiring: null,
    internalTasks: null,
  });

  const loadPersisted = () => {
    try {
      const raw = localStorage.getItem(STORAGE_KEY);
      if (!raw) return {};
      const parsed = JSON.parse(raw);
      const out = {};
      for (const k of PERSISTED_KEYS) if (k in parsed) out[k] = parsed[k];
      return out;
    } catch (e) {
      console.warn("[Store] failed to read localStorage:", e);
      return {};
    }
  };

  let state = { ...defaultState(), ...loadPersisted() };

  // ===== Server sync (Upstash Redis via /api/store) =====
  // localStorage stays as the offline cache + first-paint source. The server is
  // the source of truth across browsers/devices. On boot we attempt a server
  // read; if the server is empty AND localStorage has data, we push localStorage
  // up (= automatic migration from per-browser to shared storage).
  let _syncTimer = null;
  let _serverSeen = false; // have we successfully read the server yet
  const SYNC_DEBOUNCE_MS = 800;

  const persistLocal = () => {
    try {
      const slice = {};
      for (const k of PERSISTED_KEYS) slice[k] = state[k];
      localStorage.setItem(STORAGE_KEY, JSON.stringify(slice));
    } catch (e) {
      console.warn("[Store] failed to write localStorage:", e);
    }
  };

  const scheduleServerPush = () => {
    if (!window.authFetch) return;
    clearTimeout(_syncTimer);
    _syncTimer = setTimeout(async () => {
      try {
        const slice = {};
        for (const k of PERSISTED_KEYS) slice[k] = state[k];
        const r = await window.authFetch("/api/store", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(slice),
        });
        if (!r.ok) console.warn("[Store] server push failed:", r.status);
      } catch (e) {
        console.warn("[Store] server push error:", e);
      }
    }, SYNC_DEBOUNCE_MS);
  };

  const persist = () => {
    persistLocal();
    scheduleServerPush();
  };

  // Pull from server when the page mounts (and after sign-in completes).
  // Replaces state if server has data; pushes up if server is empty but local isn't.
  async function pullFromServer() {
    if (!window.authFetch) return;
    try {
      const r = await window.authFetch("/api/store");
      if (!r.ok) {
        // 401 = not signed in yet; will retry when auth changes
        // 503 = KV not configured (user hasn't set it up); we stay in localStorage-only mode
        return;
      }
      const body = await r.json();
      _serverSeen = true;
      const serverData = body.data;
      if (serverData && typeof serverData === "object" && Object.keys(serverData).length > 0) {
        // Server has data — replace local state with it (server is source of truth)
        state = { ...defaultState(), ...serverData };
        persistLocal(); // refresh localStorage cache
        listeners.forEach(fn => { try { fn(state); } catch (e) { console.error(e); } });
      } else {
        // Server is empty — push localStorage up if we have anything worth pushing.
        const localHasData = PERSISTED_KEYS.some(k => {
          const v = state[k];
          if (v == null) return false;
          if (Array.isArray(v)) return v.length > 0;
          if (typeof v === "object") return Object.keys(v).length > 0;
          return true;
        });
        if (localHasData) scheduleServerPush();
      }
    } catch (e) {
      console.warn("[Store] pull from server failed:", e);
    }
  }
  // First pull happens after a short delay so auth + window.authFetch are wired up.
  setTimeout(pullFromServer, 500);
  // Also re-pull whenever the tab regains focus — picks up edits made by teammates.
  window.addEventListener("focus", pullFromServer);

  const Store = {
    get(){ return state; },
    set(patch){
      state = typeof patch === "function" ? patch(state) : { ...state, ...patch };
      persist();
      listeners.forEach(fn => { try{ fn(state); } catch(e){ console.error(e); } });
    },
    subscribe(fn){ listeners.add(fn); return ()=>listeners.delete(fn); },
    // Wipe persisted overrides — useful for debugging or "reset to mock data".
    resetPersisted(){
      try { localStorage.removeItem(STORAGE_KEY); } catch {}
      state = defaultState();
      listeners.forEach(fn => { try{ fn(state); } catch(e){ console.error(e); } });
    },

    // ===== Lanes (Gantt) =====
    setLanes(lanesOrFn){
      const next = typeof lanesOrFn === "function" ? lanesOrFn(state.lanes) : lanesOrFn;
      Store.set({ lanes: next });
    },
    findLaneByClient(name, segment){
      // Prefer exact lane name match
      return state.lanes.find(l => l.name === name && (segment ? l.segment === segment : true))
          || state.lanes.find(l => l.name === name);
    },
    addBarToClient(clientName, segment, bar){
      // Ensure a lane exists; create one if missing.
      let lane = Store.findLaneByClient(clientName, segment);
      const newBar = { id: "b" + Date.now() + Math.random().toString(36).slice(2,5), ...bar };
      Store.setLanes(lanes => {
        if(lane){
          return lanes.map(l => l.id === lane.id ? { ...l, bars:[...l.bars, newBar] } : l);
        }
        const newLane = {
          id: "l-" + clientName.toLowerCase().replace(/[^a-z0-9]/g,"") + "-" + Date.now(),
          name: clientName,
          segment: segment || "md",
          bars: [newBar],
        };
        return [...lanes, newLane];
      });
      return newBar;
    },
    updateBarById(barId, patch){
      Store.setLanes(lanes => lanes.map(l => ({
        ...l,
        bars: l.bars.map(b => b.id === barId ? { ...b, ...patch } : b),
      })));
    },
    deleteBarById(barId){
      Store.setLanes(lanes => lanes.map(l => ({
        ...l,
        bars: l.bars.filter(b => b.id !== barId),
      })));
    },

    // ===== Tasks per client =====
    keyFor(name, segment){ return name + ":" + (segment || "md"); },
    getTasks(name, segment){
      return state.tasksByClient[Store.keyFor(name, segment)] || null;
    },
    setTasks(name, segment, tasksOrFn){
      const k = Store.keyFor(name, segment);
      const cur = state.tasksByClient[k] || [];
      const next = typeof tasksOrFn === "function" ? tasksOrFn(cur) : tasksOrFn;
      Store.set({ tasksByClient: { ...state.tasksByClient, [k]: next } });
    },

    // ===== Touchpoints per client =====
    getTouchpoints(name, segment){
      return state.touchpointsByClient[Store.keyFor(name, segment)] || null;
    },
    setTouchpoints(name, segment, tpsOrFn){
      const k = Store.keyFor(name, segment);
      const cur = state.touchpointsByClient[k] || [];
      const next = typeof tpsOrFn === "function" ? tpsOrFn(cur) : tpsOrFn;
      Store.set({ touchpointsByClient: { ...state.touchpointsByClient, [k]: next } });
    },

    // ===== Company overrides =====
    getCompanyOverride(name, segment){
      return state.companyOverrides[Store.keyFor(name, segment)] || null;
    },
    // ===== Contacts =====
    getContacts(name, segment){
      return state.contactsByClient[Store.keyFor(name, segment)] || null;
    },
    setContacts(name, segment, listOrFn){
      const k = Store.keyFor(name, segment);
      const cur = state.contactsByClient[k] || [];
      const next = typeof listOrFn === "function" ? listOrFn(cur) : listOrFn;
      Store.set({ contactsByClient: { ...state.contactsByClient, [k]: next } });
    },

    setCompanyOverride(name, segment, patch){
      const k = Store.keyFor(name, segment);
      const cur = state.companyOverrides[k] || {};
      Store.set({ companyOverrides: { ...state.companyOverrides, [k]: { ...cur, ...patch } } });
    },

    // ===== Shared identity (across segments) =====
    // Use this for fields that are intrinsic to the company itself: champion,
    // logo, health, display name. Edits propagate to every segment the company
    // appears in (e.g. monday.com on both MD and Mgmt).
    getCompanyIdentity(name){
      return state.companyIdentity[name] || null;
    },
    setCompanyIdentity(name, patch){
      const cur = state.companyIdentity[name] || {};
      const merged = { ...cur };
      // Only overwrite keys present in patch (and not undefined) so the caller can
      // pass partial updates without nuking unrelated fields.
      for (const [k, v] of Object.entries(patch)) {
        if (v !== undefined) merged[k] = v;
      }
      Store.set({ companyIdentity: { ...state.companyIdentity, [name]: merged } });
    },

    // ===== Companies added at runtime via "Add MD/Mgmt client" =====
    getAddedCompanies(segment){
      return (state.addedCompanies && state.addedCompanies[segment]) || [];
    },
    addCompany(segment, name, record){
      const cur = state.addedCompanies || { md: [], mgmt: [] };
      const list = cur[segment] || [];
      if (list.some(c => c.name === name)) return; // already present
      Store.set({
        addedCompanies: { ...cur, [segment]: [...list, { name, record }] },
      });
    },
    removeCompany(segment, name){
      const cur = state.addedCompanies || { md: [], mgmt: [] };
      const list = cur[segment] || [];
      Store.set({
        addedCompanies: { ...cur, [segment]: list.filter(c => c.name !== name) },
      });
    },

    // ===== Internal collections (Team, Hiring, Internal Tasks) =====
    getCollection(key, seed){
      if(state[key] == null) Store.set({ [key]: [...seed] });
      return state[key] || [];
    },
    setCollection(key, listOrFn){
      const cur = state[key] || [];
      const next = typeof listOrFn === "function" ? listOrFn(cur) : listOrFn;
      Store.set({ [key]: next });
    },
    addToCollection(key, item){
      Store.setCollection(key, list => [...list, { _id: "x" + Date.now() + Math.random().toString(36).slice(2,5), ...item }]);
    },
    updateInCollection(key, idx, patch){
      Store.setCollection(key, list => list.map((it, i) => i === idx ? { ...it, ...patch } : it));
    },
    removeFromCollection(key, idx){
      Store.setCollection(key, list => list.filter((_, i) => i !== idx));
    },

    // ===== Aggregates (computed) =====
    totalOpenTasks(){
      let n = 0;
      Object.values(state.tasksByClient).forEach(list => list.forEach(t => { if(!t.done) n++; }));
      return n;
    },
    overdueTasks(){
      let n = 0;
      Object.values(state.tasksByClient).forEach(list => list.forEach(t => { if(!t.done && t.status === "overdue") n++; }));
      return n;
    },
  };

  window.Store = Store;

  // Hook: subscribe a component to the store
  window.useStore = function useStore(selector){
    const [, force] = React.useReducer(x => x + 1, 0);
    const sel = selector || ((s)=>s);
    React.useEffect(()=>{
      return Store.subscribe(() => force());
    }, []);
    return sel(state);
  };
})();
