// IndieVertical — store: persistent state, actions, hash router, game merge helpers.

const IV_KEY = "indievertical:v1";
const StoreCtx = React.createContext(null);

function ivClone(o) { return JSON.parse(JSON.stringify(o)); }

function ivDefaultState() {
  return {
    v: 1,
    role: "visitor", // "visitor" | "dev" | "scout"
    shortlist: {},   // slug -> { note, savedAt }
    threads: ivClone(IV_SEED_THREADS),
    projects: ivClone(IV_SEED_PROJECTS),
  };
}

function ivLoad() {
  try {
    const raw = localStorage.getItem(IV_KEY);
    if (raw) {
      const s = JSON.parse(raw);
      if (s && s.v === 1 && s.projects && s.threads) return s;
    }
  } catch (e) { /* fall through */ }
  return ivDefaultState();
}

// ---------- game accessors (merge catalog + owned project state) ----------
function ivGetGame(state, slug) {
  const cat = IV_CATALOG[slug];
  const proj = state.projects[slug];
  if (!cat && !proj) return null;
  const base = cat
    ? { ...cat }
    : {
        slug, art: (proj && proj.art) || "art-newa", genres: [], platforms: ["PC"],
        about: [], members: [], comps: [], links: [], wants: [], pastWork: null, shots: null,
        studio: "Tanglewood Games", teamSize: "2 to 5 people", location: "Porto, Portugal",
        trailerLabel: "", traction: {}, open: true,
      };
  if (proj) {
    const f = proj.fields;
    base.owned = true;
    base.projStatus = proj.status;
    base.stats = proj.stats;
    base.updated = proj.updated;
    base.art = proj.art || base.art;
    if (f.keyArtUrl) base.keyArtUrl = f.keyArtUrl;
    base.name = f.title || "Untitled project";
    base.hook = f.hook || "";
    base.status = f.devStatus;
    base.deal = f.dealType;
    base.demo = !!f.demoUrl;
    base.trailerUrl = f.trailerUrl;
    base.targetRelease = f.targetRelease;
    if (f.aboutTeam) base.teamAbout = f.aboutTeam;
    if (f.description) base.about = f.description.split(/\n\n+/);
    else if (!cat) base.about = [];
    base.funding = f.fundingValue;
    base.fundingGated = f.fundingGated;
    base.contact = f.contactValue;
    base.contactGated = f.contactGated;
    base.traction = { wishlists: f.tractionW, discord: f.tractionD, social: f.tractionS };
    base.tractionGated = f.tractionGated;
    base.socials = f.socials;
    if (f.comps !== proj.initialComps || !cat) {
      base.comps = (f.comps || "").split(",").map((s) => s.trim()).filter(Boolean).map((t) => ({ t, d: "" }));
    }
  } else {
    base.fundingGated = true;
    base.contactGated = true;
  }
  return base;
}

function ivAllGames(state) {
  const list = [];
  Object.keys(state.projects).forEach((slug) => {
    if (!IV_CATALOG[slug]) {
      const p = state.projects[slug];
      if (p.status === "published" || p.status === "pending") list.push(ivGetGame(state, slug));
    }
  });
  IV_CATALOG_ORDER.forEach((slug) => list.push(ivGetGame(state, slug)));
  return list;
}

function ivThreadsFor(state, role) {
  if (role === "dev") return state.threads.filter((t) => t.devId === IV_USERS.dev.id);
  if (role === "scout") return state.threads.filter((t) => t.scoutId === IV_USERS.scout.id);
  return [];
}

function ivUnreadCount(state, role) {
  const side = role === "dev" ? "dev" : role === "scout" ? "scout" : null;
  if (!side) return 0;
  return ivThreadsFor(state, role).filter((t) => t.unreadFor === side && !t.archived).length;
}

// ---------- provider ----------
function StoreProvider({ children }) {
  const [state, setState] = React.useState(ivLoad);
  const [toast, setToast] = React.useState(null);
  const toastTimer = React.useRef(null);

  React.useEffect(() => {
    try { localStorage.setItem(IV_KEY, JSON.stringify(state)); } catch (e) { /* ignore */ }
  }, [state]);

  const api = React.useMemo(() => {
    const up = (fn) => setState((s) => { const n = ivClone(s); fn(n); return n; });
    const showToast = (msg) => {
      setToast(msg);
      if (toastTimer.current) clearTimeout(toastTimer.current);
      toastTimer.current = setTimeout(() => setToast(null), 2600);
    };
    return {
      showToast,
      setRole(role, quiet) {
        up((s) => { s.role = role; });
        if (!quiet) {
          const who = role === "dev" ? "Tanglewood Games (developer)" : role === "scout" ? "Maren Holt (scout)" : null;
          showToast(who ? "Now viewing as " + who : "Now viewing as a visitor");
        }
      },
      logout() { up((s) => { s.role = "visitor"; }); showToast("Logged out"); },
      resetDemo() { localStorage.removeItem(IV_KEY); location.hash = "#/"; location.reload(); },

      toggleShortlist(slug, name) {
        let added = false;
        up((s) => {
          if (s.shortlist[slug]) delete s.shortlist[slug];
          else { s.shortlist[slug] = { note: "", savedAt: IV_TODAY }; added = true; }
        });
        showToast(added ? name + " saved to your shortlist" : name + " removed from your shortlist");
      },
      setNote(slug, note) { up((s) => { if (s.shortlist[slug]) s.shortlist[slug].note = note; }); },
      removeShortlist(slug) { up((s) => { delete s.shortlist[slug]; }); },

      markThreadRead(id, side) {
        setState((s) => {
          const t = s.threads.find((x) => x.id === id);
          if (!t || t.unreadFor !== side) return s;
          const n = ivClone(s);
          const nt = n.threads.find((x) => x.id === id);
          nt.unreadFor = null;
          const other = side === "dev" ? "scout" : "dev";
          nt.messages.forEach((m) => { if (m.from === other && !m.readDate) m.readDate = IV_TODAY; });
          return n;
        });
      },
      sendMessage(id, text, side) {
        up((s) => {
          const t = s.threads.find((x) => x.id === id);
          if (!t) return;
          t.messages.push({ from: side, text, date: IV_TODAY });
          t.unreadFor = side === "dev" ? "scout" : "dev";
          t.archived = false;
        });
      },
      toggleArchive(id) {
        up((s) => { const t = s.threads.find((x) => x.id === id); if (t) t.archived = !t.archived; });
      },
      // Scout messaging a game's developer. Returns thread id.
      openThreadForGame(game) {
        let id = null;
        setState((s) => {
          const existing = s.threads.find((t) => t.game === game.slug && t.scoutId === IV_USERS.scout.id);
          if (existing) { id = existing.id; return s; }
          const n = ivClone(s);
          id = "t-" + Date.now();
          n.threads.unshift({
            id, game: game.slug, gameName: game.name,
            devId: game.owned ? IV_USERS.dev.id : (game.studio || "studio").toLowerCase().replace(/[^a-z]+/g, "-"),
            devName: game.studio || "The developer",
            scoutId: IV_USERS.scout.id, scoutName: IV_USERS.scout.name, scoutOrg: IV_USERS.scout.org,
            unreadFor: null, archived: false, messages: [],
          });
          return n;
        });
        return id;
      },

      updateProject(slug, patch) {
        up((s) => {
          const p = s.projects[slug];
          if (!p) return;
          Object.assign(p.fields, patch);
          p.updated = IV_TODAY;
        });
      },
      setProjectArt(slug, art) { up((s) => { const p = s.projects[slug]; if (p) { p.art = art; p.updated = IV_TODAY; } }); },
      setProjectKeyArt(slug, dataUrl, name) {
        up((s) => {
          const p = s.projects[slug];
          if (!p) return;
          p.fields.keyArtUrl = dataUrl;
          p.fields.keyArtName = name;
          p.updated = IV_TODAY;
        });
      },
      createProject() {
        let slug = null;
        setState((s) => {
          const n = ivClone(s);
          const count = Object.keys(n.projects).length;
          slug = "new-project-" + Date.now().toString(36);
          n.projects[slug] = {
            slug, status: "draft", updated: IV_TODAY,
            art: IV_ART_POOL[count % IV_ART_POOL.length],
            stats: { views: 0, saves: 0 }, initialComps: "",
            fields: {
              title: "", hook: "", keyArtName: "", keyArtUrl: "", description: "", aboutTeam: "",
              trailerUrl: "", devStatus: "Concept", targetRelease: "", dealType: "Seeking Publisher",
              demoUrl: "", fundingValue: "", fundingGated: true,
              tractionW: "", tractionD: "", tractionS: "", tractionGated: false,
              comps: "", contactValue: "", contactGated: true,
              socials: { x: "", bluesky: "", instagram: "", tiktok: "", facebook: "", linkedin: "", linktree: "", other: "" },
            },
          };
          return n;
        });
        return slug;
      },
      publishProject(slug) {
        up((s) => { const p = s.projects[slug]; if (p) { p.status = "pending"; p.updated = IV_TODAY; } });
        showToast("Published! Your public fields are live while we review.");
      },
      approveProject(slug) {
        up((s) => { const p = s.projects[slug]; if (p) p.status = "published"; });
        showToast("Review complete — your page is fully live.");
      },
    };
  }, []);

  return (
    <StoreCtx.Provider value={{ state, api }}>
      {children}
      {toast && <div className="toast">{toast}</div>}
    </StoreCtx.Provider>
  );
}

function useStore() { return React.useContext(StoreCtx); }

// ---------- hash router ----------
function ivParseHash() {
  let h = location.hash || "#/";
  if (!h.startsWith("#/")) h = "#/" + h.replace(/^#/, "");
  const parts = h.slice(2).split("/").map(decodeURIComponent).filter((p) => p !== "");
  return { parts, page: parts[0] || "home" };
}

function useRoute() {
  const [route, setRoute] = React.useState(ivParseHash);
  React.useEffect(() => {
    const onHash = () => setRoute(ivParseHash());
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);
  return route;
}

function ivGo(path) { location.hash = path; }

Object.assign(window, {
  StoreProvider, useStore, useRoute, ivGo,
  ivGetGame, ivAllGames, ivThreadsFor, ivUnreadCount, ivClone,
});
