// Global state — context + reducer.
// Drives:  view (route), spaces, sessions, docs, history, modal stack, mobile nav.

const AppCtx = React.createContext(null);
const useApp = () => React.useContext(AppCtx);

// View shape:
//   { type: 'default' } — default chat
//   { type: 'chat', spaceId, sessionId }
//   { type: 'explore', spaceId }
//   { type: 'doc', docId, edit?, fromCrossSpace? }
//   { type: 'search', global? }
//   { type: 'history', detailId? }
//
// modal: null | { type, props }

function initialState() {
  // Pre-compute markdown for every doc so editor always has a string to work with.
  const docs = DOCS_INIT.map(d => {
    const md = typeof d.markdown === "string" ? d.markdown : blocksToMd(d.body || []);
    return { ...d, markdown: md };
  });
  // Default chat is no longer a single volatile log — it's a list of sessions
  // just like a space, but in the "기본" bucket. Seed with one session containing
  // the existing demo messages.
  const defaultSessions = [
    { id: "d-1", title: "잡담 — 오늘 회의", when: "방금", active: true, messages: [...DEFAULT_CHAT_INIT] },
    { id: "d-2", title: "산책하면서 떠오른 메모", when: "어제",   active: false, messages: [
      { id: "p1", role: "user", text: "오늘 산책하면서 떠오른 거 정리 안 하고 그냥 흘려보낼래." },
      { id: "p2", role: "ai",   text: "알겠어요. 정리할 만한 흐름 나오면 알려드릴게요." },
    ]},
    { id: "d-3", title: "어제 본 영화 잡담",     when: "그제",   active: false, messages: [
      { id: "m1", role: "user", text: "어제 본 영화 좀 별로였는데 뭐가 별로였는지 정리는 잘 안 됨." },
      { id: "m2", role: "ai",   text: "그럼 그냥 두실래요?" },
      { id: "m3", role: "user", text: "응 그냥 두자" },
    ]},
  ];
  return {
    view: { type: "default" },
    prevView: null,
    spaces: [...SPACES_INIT],
    sessions: JSON.parse(JSON.stringify(SESSIONS_INIT)),
    defaultSessions,
    docs,
    history: [...HISTORY_INIT],
    modal: null,
    mobileStack: ["shell"],
    sidebarOpen: false,
    flashDocIds: new Set(),
    flashHistoryIds: new Set(),
    sessionsOpen: false,
    genericIdx: 0,
    authed: true,        // demo starts authenticated; toggle via 로그아웃
    user: { name: "준수", email: "junsoo@example.com", initial: "준", plan: "free" },
    userMenuOpen: false,
  };
}

function reducer(state, a) {
  switch (a.type) {
    case "GO": {
      // Track previous view so views like search/history can offer a back button.
      // Skip recording when target is the same as current (no-op nav).
      const prev = state.view.type !== a.view.type || JSON.stringify(state.view) !== JSON.stringify(a.view) ? state.view : state.prevView;
      return { ...state, view: a.view, prevView: prev, sessionsOpen: false, sidebarOpen: false };
    }
    case "GO_BACK": {
      if (!state.prevView) return { ...state, view: { type: "default" }, prevView: null };
      return { ...state, view: state.prevView, prevView: null, sessionsOpen: false, sidebarOpen: false };
    }
    case "GO_SPACE_CHAT": {
      // Pick the active session of a space (or first one)
      const ss = state.sessions[a.spaceId] || [];
      const active = ss.find(s => s.active) || ss[0];
      if (!active) {
        // empty space — bounce to empty-space onboarding instead
        return { ...state, view: { type: "empty", spaceId: a.spaceId }, sidebarOpen: false };
      }
      return { ...state, view: { type: "chat", spaceId: a.spaceId, sessionId: active.id }, sidebarOpen: false, sessionsOpen: false };
    }
    case "GO_SPACE": {
      // Sidebar click on a space — land on the dashboard (Explore) first.
      // Empty spaces still go to onboarding.
      const ss = state.sessions[a.spaceId] || [];
      const docs = state.docs.filter(d => d.spaceId === a.spaceId);
      if (ss.length === 0 && docs.length === 0) {
        return { ...state, view: { type: "empty", spaceId: a.spaceId }, sidebarOpen: false };
      }
      return { ...state, view: { type: "explore", spaceId: a.spaceId }, sidebarOpen: false, sessionsOpen: false };
    }
    case "TOGGLE_SESSIONS": return { ...state, sessionsOpen: !state.sessionsOpen };
    case "CLOSE_SESSIONS":  return { ...state, sessionsOpen: false };
    case "OPEN_SIDEBAR":    return { ...state, sidebarOpen: true };
    case "CLOSE_SIDEBAR":   return { ...state, sidebarOpen: false };

    case "TOGGLE_USER_MENU": return { ...state, userMenuOpen: !state.userMenuOpen };
    case "CLOSE_USER_MENU":  return { ...state, userMenuOpen: false };
    case "LOGOUT":  return { ...state, authed: false, userMenuOpen: false, view: { type: "default" } };
    case "LOGIN":   return { ...state, authed: true, view: { type: "default" } };
    case "UPDATE_USER": return { ...state, user: { ...state.user, ...a.patch } };
    case "SET_PLAN":    return { ...state, user: { ...state.user, plan: a.plan } };

    case "OPEN_MODAL":  return { ...state, modal: a.modal, sessionsOpen: false };
    case "CLOSE_MODAL": return { ...state, modal: null };

    case "MOBILE_PUSH":  return { ...state, mobileStack: [...state.mobileStack, a.entry] };
    case "MOBILE_POP":   return { ...state, mobileStack: state.mobileStack.slice(0, -1) };

    case "PICK_SESSION": {
      const ss = (state.sessions[a.spaceId] || []).map(s => ({ ...s, active: s.id === a.sessionId }));
      return {
        ...state,
        sessions: { ...state.sessions, [a.spaceId]: ss },
        view: { type: "chat", spaceId: a.spaceId, sessionId: a.sessionId },
        sessionsOpen: false,
      };
    }
    case "NEW_SESSION": {
      const spaceId = a.spaceId;
      const id = "s-" + Date.now();
      const ss = (state.sessions[spaceId] || []).map(s => ({ ...s, active: false }));
      const next = { id, title: a.title || "새 세션", turns: 0, when: "방금", active: true, docsTouched: 0, messages: [] };
      return {
        ...state,
        sessions: { ...state.sessions, [spaceId]: [next, ...ss] },
        view: { type: "chat", spaceId, sessionId: id },
        sessionsOpen: false,
      };
    }

    case "SEND_DEFAULT": {
      const ds = state.defaultSessions.map(s => ({ ...s }));
      const activeId = a.sessionId || ds.find(s => s.active)?.id || ds[0]?.id;
      const i = ds.findIndex(s => s.id === activeId);
      if (i < 0) return state;
      const userMsg = { id: "u-" + Date.now(), role: "user", text: a.text };
      const replyText = GENERIC_DEFAULT_REPLIES[state.genericIdx % GENERIC_DEFAULT_REPLIES.length];
      const aiMsg = { id: "a-" + Date.now() + 1, role: "ai", text: replyText };
      ds[i].messages = [...ds[i].messages, userMsg, aiMsg];
      ds[i].when = "방금";
      return { ...state, defaultSessions: ds, genericIdx: state.genericIdx + 1 };
    }

    case "RENAME_SESSION": {
      if (a.kind === "default") {
        const ds = state.defaultSessions.map(s => s.id === a.sessionId ? { ...s, title: a.title } : s);
        return { ...state, defaultSessions: ds };
      } else {
        const list = (state.sessions[a.spaceId] || []).map(s => s.id === a.sessionId ? { ...s, title: a.title } : s);
        return { ...state, sessions: { ...state.sessions, [a.spaceId]: list } };
      }
    }

    case "DELETE_SESSION": {
      // a: { kind: "default" | "space", spaceId?, sessionId }
      if (a.kind === "default") {
        const ds = state.defaultSessions.filter(s => s.id !== a.sessionId);
        // if we deleted the active one, activate the first remaining (if any)
        if (ds.length && !ds.some(s => s.active)) ds[0].active = true;
        const view = state.view;
        let next = view;
        if (view.type === "default" && (view.sessionId === a.sessionId || !view.sessionId)) {
          next = { type: "default", sessionId: ds[0]?.id };
        }
        return { ...state, defaultSessions: ds, view: next };
      } else {
        const list = (state.sessions[a.spaceId] || []).filter(s => s.id !== a.sessionId);
        if (list.length && !list.some(s => s.active)) list[0].active = true;
        const view = state.view;
        let next = view;
        if (view.type === "chat" && view.spaceId === a.spaceId && view.sessionId === a.sessionId) {
          next = list.length
            ? { type: "chat", spaceId: a.spaceId, sessionId: list[0].id }
            : { type: "empty", spaceId: a.spaceId };
        }
        return { ...state, sessions: { ...state.sessions, [a.spaceId]: list }, view: next };
      }
    }

    case "NEW_DEFAULT_SESSION": {
      const id = "d-" + Date.now();
      const ds = state.defaultSessions.map(s => ({ ...s, active: false }));
      const next = { id, title: a.title || "새 잡담", when: "방금", active: true, messages: [] };
      return {
        ...state,
        defaultSessions: [next, ...ds],
        view: { type: "default", sessionId: id },
      };
    }

    case "PICK_DEFAULT_SESSION": {
      const ds = state.defaultSessions.map(s => ({ ...s, active: s.id === a.sessionId }));
      return {
        ...state,
        defaultSessions: ds,
        view: { type: "default", sessionId: a.sessionId },
      };
    }

    case "TOGGLE_AUTO_ORGANIZE": {
      const ss = (state.sessions[a.spaceId] || []).map(s =>
        s.id === a.sessionId ? { ...s, autoOrganize: s.autoOrganize === false ? true : false } : s
      );
      return { ...state, sessions: { ...state.sessions, [a.spaceId]: ss } };
    }

    case "SEND_SPACE": {
      // Find session; append user msg
      const spaceId = a.spaceId, sessionId = a.sessionId;
      const ss = (state.sessions[spaceId] || []).map(s => ({ ...s }));
      const i = ss.findIndex(s => s.id === sessionId);
      if (i < 0) return state;
      const userMsg = { id: "u-" + Date.now(), role: "user", text: a.text };
      // If auto-organize is OFF for this session, behave like default chat (no reflections).
      const autoOff = ss[i].autoOrganize === false;
      // pick a script
      const script = autoOff ? null : (SCRIPTS[spaceId] || []).find(s => s.match.some(m => a.text.includes(m)));
      const generic = autoOff
        ? { reply: GENERIC_DEFAULT_REPLIES[state.genericIdx % GENERIC_DEFAULT_REPLIES.length], reflections: [] }
        : GENERIC_SPACE_REPLIES[state.genericIdx % GENERIC_SPACE_REPLIES.length];
      const chosen = script || generic;
      const reflections = (chosen.reflections || []).map(r => {
        const d = state.docs.find(x => x.id === r.docId);
        return { ...r, doc: d ? d.title : r.docId };
      });
      userMsg.reflections = reflections.length ? reflections : undefined;
      const aiMsg = { id: "a-" + Date.now() + 1, role: "ai", text: chosen.reply };
      ss[i].messages = [...ss[i].messages, userMsg, aiMsg];
      ss[i].turns = (ss[i].turns || 0) + 1;
      ss[i].when = "방금";
      ss[i].docsTouched = (ss[i].docsTouched || 0) + reflections.length;

      // Apply reflections to docs (mark autoReflected + freshly added block)
      const docs = state.docs.map(d => ({ ...d }));
      const flash = new Set(state.flashDocIds);
      const newHistory = [];
      reflections.forEach(r => {
        const di = docs.findIndex(x => x.id === r.docId);
        if (di < 0) return;
        const d = docs[di];
        d.autoReflected = true;
        d.updated = "방금";
        if (r.kind === "update") {
          const md = typeof d.markdown === "string" ? d.markdown : blocksToMd(d.body || []);
          const fresh = `\n\n> ✻ 방금 자동 반영 — ${r.change || "변경"}`;
          d.markdown = md + fresh;
        }
        flash.add(d.id);
        newHistory.unshift({
          id: "h-" + Date.now() + "-" + r.docId,
          time: "방금", spaceId, action: r.kind, docId: r.docId,
          title: d.title, detail: r.change || (r.kind === "create" ? "새 문서 생성" : "변경"),
          undoable: true, why: "사용자 메시지에 명확한 변경 의도가 담겨 있었어요.",
        });
      });

      const flashHist = new Set(state.flashHistoryIds);
      newHistory.forEach(h => flashHist.add(h.id));

      return {
        ...state,
        sessions: { ...state.sessions, [spaceId]: ss },
        docs,
        history: [...newHistory, ...state.history],
        flashDocIds: flash,
        flashHistoryIds: flashHist,
        genericIdx: state.genericIdx + 1,
      };
    }

    case "UNDO_HISTORY": {
      const h = state.history.find(x => x.id === a.id);
      if (!h || !h.undoable) return state;
      // Mark as undone; remove the appended reflection from doc markdown
      const history = state.history.map(x => x.id === a.id ? { ...x, undone: true } : x);
      const docs = state.docs.map(d => {
        if (d.id !== h.docId) return d;
        if (h.action === "update") {
          const md = typeof d.markdown === "string" ? d.markdown : blocksToMd(d.body || []);
          // strip the specific 방금 자동 반영 line if we can identify it; otherwise strip all
          const escaped = (h.detail || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
          const re = new RegExp(`\\n+> ✻ 방금 자동 반영 — ${escaped}\\s*$`, "m");
          let next = md.replace(re, "");
          if (next === md) {
            // fallback: strip the last 자동 반영 quote line
            next = md.replace(/\n+> ✻ 방금 자동 반영[^\n]*$/m, "");
          }
          return { ...d, markdown: next, autoReflected: false };
        }
        if (h.action === "create") {
          return null;
        }
        return d;
      }).filter(Boolean);
      return { ...state, history, docs };
    }

    case "TOGGLE_LOCK": {
      const docs = state.docs.map(d => d.id === a.docId ? { ...d, locked: !d.locked } : d);
      return { ...state, docs };
    }

    case "EDIT_DOC": {
      const docs = state.docs.map(d => {
        if (d.id !== a.docId) return d;
        const next = { ...d, updated: "방금" };
        if (typeof a.markdown === "string") next.markdown = a.markdown;
        if (a.body) next.body = a.body;
        return next;
      });
      return { ...state, docs };
    }

    case "RENAME_DOC": {
      const docs = state.docs.map(d => d.id === a.docId ? { ...d, title: a.title } : d);
      return { ...state, docs };
    }

    case "NEW_DOC": {
      const id = "doc-" + Date.now();
      const doc = {
        id, title: a.title || "새 문서", spaceId: a.spaceId,
        updated: "방금", markdown: "",
        isNew: true, tags: [], size: "0자",
      };
      return { ...state, docs: [doc, ...state.docs], view: { type: "doc", docId: id, edit: true } };
    }

    case "NEW_SPACE": {
      const id = "sp-" + Date.now();
      const sp = { id, name: a.name || "새 공간", dot: a.color || "#5C8AA8", description: a.description || "" };
      return {
        ...state,
        spaces: [...state.spaces, sp],
        sessions: { ...state.sessions, [id]: [] },
        view: { type: "empty", spaceId: id },
      };
    }

    case "EDIT_SPACE": {
      const spaces = state.spaces.map(s => s.id === a.id
        ? { ...s, ...(a.name !== undefined ? { name: a.name } : {}), ...(a.color !== undefined ? { dot: a.color } : {}), ...(a.description !== undefined ? { description: a.description } : {}) }
        : s);
      return { ...state, spaces };
    }

    case "DELETE_SPACE": {
      const spaces = state.spaces.filter(s => s.id !== a.id);
      const docs = state.docs.filter(d => d.spaceId !== a.id);
      const sessions = { ...state.sessions };
      delete sessions[a.id];
      const view = state.view.spaceId === a.id ? { type: "default" } : state.view;
      return { ...state, spaces, docs, sessions, view, modal: null };
    }

    case "MOVE_SESSION": {
      // Move a session from one space to another (or from default chat → space)
      // For simplicity, when moving default chat: create a new session in target space with the messages.
      if (a.fromSpaceId === "__default__") {
        const id = "s-" + Date.now();
        const ss = (state.sessions[a.toSpaceId] || []).map(s => ({ ...s, active: false }));
        // Pull the currently active default session
        const src = state.defaultSessions.find(s => s.active) || state.defaultSessions[0];
        if (!src) return state;
        const newSession = {
          id, title: a.title || src.title || "기본 채팅에서 옮겨옴",
          turns: src.messages.length,
          when: "방금", active: true, docsTouched: 0,
          messages: src.messages.map(m => ({ ...m })),
        };
        // Remove the moved session from defaultSessions; activate the next one
        const remaining = state.defaultSessions.filter(s => s.id !== src.id);
        if (remaining.length && !remaining.some(s => s.active)) remaining[0].active = true;
        return {
          ...state,
          sessions: { ...state.sessions, [a.toSpaceId]: [newSession, ...ss] },
          defaultSessions: remaining,
          view: { type: "chat", spaceId: a.toSpaceId, sessionId: id },
          modal: null,
        };
      } else {
        // space → space (real move)
        const fromList = (state.sessions[a.fromSpaceId] || []).filter(s => s.id !== a.sessionId);
        const session = (state.sessions[a.fromSpaceId] || []).find(s => s.id === a.sessionId);
        if (!session) return state;
        const toList = [{ ...session, active: true, when: "방금" }, ...(state.sessions[a.toSpaceId] || []).map(s => ({ ...s, active: false }))];
        return {
          ...state,
          sessions: { ...state.sessions, [a.fromSpaceId]: fromList, [a.toSpaceId]: toList },
          view: { type: "chat", spaceId: a.toSpaceId, sessionId: session.id },
          modal: null,
        };
      }
    }

    case "CLEAR_FLASH": {
      return { ...state, flashDocIds: new Set(), flashHistoryIds: new Set() };
    }

    default:
      return state;
  }
}

function AppProvider({ children }) {
  const [state, dispatch] = React.useReducer(reducer, undefined, initialState);
  // shortcut helper to look up things
  const helpers = React.useMemo(() => ({
    getSpace: (id) => state.spaces.find(s => s.id === id),
    getDoc:   (id) => state.docs.find(d => d.id === id),
    getSession: (spaceId, sessionId) => (state.sessions[spaceId] || []).find(s => s.id === sessionId),
    docsForSpace: (spaceId) => state.docs.filter(d => d.spaceId === spaceId),
    spaceDocCount: (spaceId) => state.docs.filter(d => d.spaceId === spaceId).length,
  }), [state]);

  // Keyboard shortcuts
  React.useEffect(() => {
    const onKey = (e) => {
      const k = e.key.toLowerCase();
      if ((e.metaKey || e.ctrlKey) && k === "k") {
        e.preventDefault();
        dispatch({ type: "GO", view: { type: "search" } });
      } else if (e.key === "Escape") {
        if (state.modal) dispatch({ type: "CLOSE_MODAL" });
        else if (state.sessionsOpen) dispatch({ type: "CLOSE_SESSIONS" });
        else if (state.sidebarOpen) dispatch({ type: "CLOSE_SIDEBAR" });
        else if (state.view.type === "search") dispatch({ type: "GO_BACK" });
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [state.modal, state.sessionsOpen, state.sidebarOpen, state.view]);

  return <AppCtx.Provider value={{ state, dispatch, ...helpers }}>{children}</AppCtx.Provider>;
}

// Responsive: a hook that returns "mobile" | "tablet" | "desktop"
function useBreakpoint() {
  const [bp, setBp] = React.useState(() => bpFor(window.innerWidth));
  React.useEffect(() => {
    const onResize = () => setBp(bpFor(window.innerWidth));
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);
  return bp;
}
function bpFor(w) {
  if (w < 720) return "mobile";
  if (w < 1024) return "tablet";
  return "desktop";
}

Object.assign(window, { AppCtx, useApp, AppProvider, useBreakpoint });
