// ==========================================================================
// STORE — React Context wrapping the API layer.
// All UI reads from useStore(); all writes go through actions that hit api.
//
// URL-hash routing:
//   #/t/:threadId          → thread (default view: chat + artifacts)
//   #/t/:threadId/brief    → (same thread, focused on brief artifact)
//   #/agents               → scheduled intelligence panel
//   #/agents/:agentId      → agent detail + latest briefing
// ==========================================================================

const StoreContext = React.createContext(null);
const useStore = () => React.useContext(StoreContext);

// ---- hash router ----
function useHashRoute() {
  const [hash, setHash] = React.useState(() => window.location.hash || '#/');
  React.useEffect(() => {
    const onChange = () => setHash(window.location.hash || '#/');
    window.addEventListener('hashchange', onChange);
    return () => window.removeEventListener('hashchange', onChange);
  }, []);
  const parts = hash.replace(/^#\/?/, '').split('/').filter(Boolean);
  const route = {
    raw: hash,
    name: parts[0] || 'home',        // 'home' | 't' | 'agents' | 'briefings'
    threadId: parts[0] === 't' ? parts[1] : null,
    threadView: parts[0] === 't' ? (parts[2] || 'chat') : null,
    agentId: parts[0] === 'agents' ? parts[1] : null,
    briefingId: parts[0] === 'briefings' ? parts[1] : null,
  };
  const navigate = (to) => { window.location.hash = to.startsWith('#') ? to : '#' + to; };
  return { route, navigate };
}

function StoreProvider({ children }) {
  const { route, navigate } = useHashRoute();

  const [me, setMe] = React.useState(null);
  const [threads, setThreads] = React.useState([]);
  const [agents, setAgents] = React.useState([]);
  const [messagesByThread, setMessagesByThread] = React.useState({});
  const [artifactsByThread, setArtifactsByThread] = React.useState({});
  const [streaming, setStreaming] = React.useState({ active: false, threadId: null, skills: [], activeSkillIndex: -1, partial: '', steps: [], plan: null, receipts: [], clarifier: null, activity: [], todos: [] });
  const [uploadsInFlight, setUploadsInFlight] = React.useState({}); // {tempId: {progress, name}}
  const [toast, setToast] = React.useState(null);
  // Briefing-management state. List for the table view + cache by id for the drawer.
  const [briefings, setBriefings] = React.useState([]);  // summaries for list
  const [briefingsById, setBriefingsById] = React.useState({});  // full records by id
  const [briefingFilter, setBriefingFilter] = React.useState({ status: null, owner: null });

  // --- bootstrap ---
  React.useEffect(() => {
    (async () => {
      const u = await api.me();
      setMe(u);
      const ts = await api.listThreads();
      setThreads(ts);
      const ag = await api.listAgents();
      setAgents(ag);
      // default route: latest thread
      if (route.name === 'home' && ts.length) navigate(`/t/${ts[0].id}`);
    })();
    // eslint-disable-next-line
  }, []);

  // --- load thread details when route changes to a thread ---
  React.useEffect(() => {
    if (!route.threadId) return;
    if (messagesByThread[route.threadId] && artifactsByThread[route.threadId]) return;
    (async () => {
      const [msgs, arts] = await Promise.all([
        api.listMessages(route.threadId),
        api.listArtifacts(route.threadId),
      ]);
      setMessagesByThread(m => ({ ...m, [route.threadId]: msgs }));
      setArtifactsByThread(a => ({ ...a, [route.threadId]: arts }));
    })();
  }, [route.threadId]);

  // --- derived ---
  // Auto-load briefings whenever we land on the briefings route or filter changes.
  React.useEffect(() => {
    if (route.name !== 'briefings') return;
    (async () => {
      try {
        const data = await api.listBriefings(briefingFilter);
        setBriefings(data.items || []);
      } catch (e) { console.error(e); }
    })();
  }, [route.name, briefingFilter.status, briefingFilter.owner]);

  // Auto-load briefing detail when an id is in the route.
  React.useEffect(() => {
    if (!route.briefingId) return;
    (async () => {
      try {
        const rec = await api.getBriefing(route.briefingId);
        setBriefingsById(map => ({ ...map, [route.briefingId]: rec }));
      } catch (e) { console.error(e); }
    })();
  }, [route.briefingId]);

  const currentThread = route.threadId ? threads.find(t => t.id === route.threadId) : null;
  const currentMessages = route.threadId ? (messagesByThread[route.threadId] || []) : [];
  const currentArtifacts = route.threadId ? (artifactsByThread[route.threadId] || []) : [];

  // --- actions ---
  const actions = {
    async createThread(opts = {}) {
      const t = await api.createThread({ title: 'New thread', ...opts });
      setThreads(x => [t, ...x]);
      navigate(`/t/${t.id}`);
      return t;
    },

    // Spin up an empty draft briefing — the row appears in the table
    // immediately, then the user lands on a full /briefings/<id> page
    // where the CD agent runs intake against the reserved thread_id.
    // When the marketing brief is emitted, the same record is updated
    // (via thread_id lookup in store.upsert_from_brief).
    async createBriefing() {
      const t = await api.createThread({ title: 'New briefing' });
      setThreads(x => [t, ...x]);
      const rec = await api.createBriefing({
        thread_id: t.id,
        title: 'New briefing',
        actor: (me && (me.email || me.name)) || 'system',
      });
      setBriefings(x => [
        {
          id: rec.id, thread_id: rec.thread_id, title: rec.title,
          status: rec.status, owner: rec.owner, assignees: rec.assignees,
          created_at: rec.created_at, updated_at: rec.updated_at,
          comment_count: 0, subtitle: '',
          creative_approved_by: null, business_approved_by: null,
        },
        ...x,
      ]);
      setBriefingsById(map => ({ ...map, [rec.id]: rec }));
      navigate(`/briefings/${rec.id}`);
      return rec;
    },

    async deleteThread(id) {
      await api.deleteThread(id);
      setThreads(x => x.filter(t => t.id !== id));
      setMessagesByThread(m => { const n = { ...m }; delete n[id]; return n; });
      setArtifactsByThread(a => { const n = { ...a }; delete n[id]; return n; });
      const remaining = threads.filter(t => t.id !== id);
      navigate(remaining.length ? `/t/${remaining[0].id}` : '/');
    },

    async renameThread(id, title) {
      const t = await api.renameThread(id, title);
      setThreads(x => x.map(y => y.id === id ? t : y));
    },

    async uploadFiles(fileList) {
      if (!route.threadId) return;
      const parsed = [];
      for (const f of fileList) {
        const tempId = 'tmp_' + Math.random().toString(36).slice(2,8);
        setUploadsInFlight(s => ({ ...s, [tempId]: { name: f.name, progress: 0.3 } }));
        try {
          const result = await api.uploadAndParse(f);
          parsed.push(result);
          setUploadsInFlight(s => { const n = { ...s }; delete n[tempId]; return n; });
        } catch (e) {
          setUploadsInFlight(s => { const n = { ...s }; delete n[tempId]; return n; });
          setToast({ kind: 'error', text: `Failed to parse ${f.name}` });
        }
      }
      if (!parsed.length) return;
      // Append a user message carrying the attachments
      const userMsg = await api.appendMessage(route.threadId, {
        role: 'user',
        content: `Attached ${parsed.length} file${parsed.length>1?'s':''}`,
        attachments: parsed,
      });
      setMessagesByThread(m => ({ ...m, [route.threadId]: [...(m[route.threadId]||[]), userMsg] }));
      // Kick the assistant
      await actions.sendToAssistant('', parsed);
    },

    async sendMessage(text) {
      if (!route.threadId || !text.trim()) return;
      const userMsg = await api.appendMessage(route.threadId, { role: 'user', content: text });
      setMessagesByThread(m => ({ ...m, [route.threadId]: [...(m[route.threadId]||[]), userMsg] }));
      await actions.sendToAssistant(text, []);
    },

    async sendToAssistant(text, attachments, options = {}) {
      const threadId = route.threadId;
      setStreaming({ active: true, threadId, skills: [], activeSkillIndex: -1, partial: '', steps: [], plan: null, receipts: [], clarifier: null, activity: [], todos: [] });

      const onEvent = (ev) => {
        if (ev.type === 'plan') {
          // Server emits this on plan_research tool call AND every time
          // step statuses advance. We replace the whole plan each time.
          setStreaming(s => ({ ...s, plan: ev.plan }));
        } else if (ev.type === 'step.start') {
          setStreaming(s => ({ ...s, steps: [...s.steps, { id: ev.step.id, text: '', tools: [], done: false, isFinal: false }] }));
        } else if (ev.type === 'step.end') {
          setStreaming(s => ({
            ...s,
            steps: s.steps.map(st => st.id === ev.step.id ? { ...st, done: true, isFinal: !!ev.step.isFinal } : st),
          }));
        } else if (ev.type === 'step.delta') {
          // Append text to the in-flight step (last in the list).
          setStreaming(s => {
            if (!s.steps.length) return s;
            const last = s.steps.length - 1;
            const updated = [...s.steps];
            updated[last] = { ...updated[last], text: updated[last].text + (ev.text || '') };
            return { ...s, steps: updated };
          });
        } else if (ev.type === 'skill.start') {
          // Append tool to the current (last) step's tool list, AND keep the
          // legacy top-level skills rail working.
          setStreaming(s => {
            const next = { ...s, skills: [...s.skills, ev.skill].filter((v,i,a)=>a.indexOf(v)===i), activeSkillIndex: ev.index };
            if (s.steps.length) {
              const last = s.steps.length - 1;
              next.steps = [...s.steps];
              next.steps[last] = {
                ...next.steps[last],
                tools: [...(next.steps[last].tools || []), { slug: ev.skill, name: ev.skill, description: '' }],
              };
            }
            return next;
          });
        } else if (ev.type === 'skill.done') {
          setStreaming(s => ({ ...s, activeSkillIndex: ev.index + 1 }));
        } else if (ev.type === 'receipt') {
          setStreaming(s => ({ ...s, receipts: [...(s.receipts || []), ev.receipt] }));
        } else if (ev.type === 'clarifier') {
          setStreaming(s => ({ ...s, clarifier: ev.clarifier }));
        } else if (ev.type === 'content.delta') {
          setStreaming(s => ({ ...s, partial: ev.text }));
        } else if (ev.type === 'todos') {
          // Live TodoWrite update — replace the streaming todos array.
          setStreaming(s => ({ ...s, todos: Array.isArray(ev.todos) ? ev.todos : [] }));
        } else if (ev.type === 'activity') {
          // New activity entry from the agent (tool call, subagent transition,
          // artifact). Append to the live trail so the InlineStreamingRow can
          // render it before the message persists.
          setStreaming(s => ({ ...s, activity: [...(s.activity || []), ev.entry] }));
        } else if (ev.type === 'activity.update') {
          // Tool result hydration — replace the matching entry by tool_use_id
          // with a fresh object reference so React re-renders.
          setStreaming(s => ({
            ...s,
            activity: (s.activity || []).map(e =>
              e.kind === 'tool' && e.tool_use_id === ev.entry.tool_use_id
                ? { ...ev.entry }
                : e
            ),
          }));
        } else if (ev.type === 'artifact') {
          setArtifactsByThread(a => ({
            ...a,
            [threadId]: [...(a[threadId]||[]), ev.artifact],
          }));
        } else if (ev.type === 'artifact.update') {
          // Critic warnings (or any post-emit enrichment) attach to an
          // existing artifact. Replace by id with a new object reference so
          // React re-renders the BriefCard.
          setArtifactsByThread(a => {
            const list = a[threadId] || [];
            const next = list.map(x => x.id === ev.artifact.id ? { ...ev.artifact } : x);
            return { ...a, [threadId]: next };
          });
        } else if (ev.type === 'done') {
          // noop — final message added below
        }
      };

      try {
        const { message } = await api.chat({ threadId, userText: text, attachments, onEvent, ...options });
        setMessagesByThread(m => ({ ...m, [threadId]: [...(m[threadId]||[]), message] }));
      } catch (e) {
        console.error(e);
        setToast({ kind: 'error', text: 'Something went wrong.' });
      } finally {
        setStreaming({ active: false, threadId: null, skills: [], activeSkillIndex: -1, partial: '', steps: [], plan: null, receipts: [], clarifier: null, activity: [], todos: [] });
      }
    },

    async submitClarifier(clarifierAnswers, originalText) {
      // Append a synthetic user message summarizing the answers
      const threadId = route.threadId;
      const summary = Object.entries(clarifierAnswers)
        .map(([k, v]) => `${k.replace(/_/g,' ')}: ${v}`).join(' · ');

      // Mark the most recent clarifier message as answered (locks the UI form)
      setMessagesByThread(m => {
        const list = [...(m[threadId] || [])];
        for (let i = list.length - 1; i >= 0; i--) {
          if (list[i].role === 'assistant' && list[i].awaitingClarifier) {
            list[i] = { ...list[i], awaitingClarifier: false, clarifierAnswers };
            break;
          }
        }
        return { ...m, [threadId]: list };
      });

      const userMsg = await api.appendMessage(threadId, {
        role: 'user',
        content: summary,
        isClarifierReply: true,
      });
      setMessagesByThread(m => ({ ...m, [threadId]: [...(m[threadId]||[]), userMsg] }));
      // Kick the assistant, passing the original prompt and the clarifier answers
      await actions.sendToAssistant(originalText, [], { clarifierAnswers });
    },

    async regenerateVariant(variantId, instructions) {
      const threadId = route.threadId;
      // Find variant in any artifact in this thread
      const arts = artifactsByThread[threadId] || [];
      let target = null;
      for (const a of arts) {
        if (a.kind === 'variant-matrix') {
          const found = a.variants.find(v => v.id === variantId);
          if (found) { target = { artifact: a, variant: found }; break; }
        }
      }
      if (!target) return;
      const updated = await api.regenerateVariant(target.variant, instructions);
      setArtifactsByThread(a => ({
        ...a,
        [threadId]: (a[threadId]||[]).map(ar => {
          if (ar.id !== target.artifact.id) return ar;
          return { ...ar, variants: ar.variants.map(v => v.id === variantId ? updated : v) };
        }),
      }));
      setToast({ kind: 'ok', text: 'Variant refined.' });
    },

    async createAgent(payload) {
      const agent = await api.createScheduledAgent(payload);
      setAgents(a => [...a, agent]);
      return agent;
    },

    async toggleAgent(id) {
      const updated = await api.toggleAgent(id);
      setAgents(a => a.map(x => x.id === id ? updated : x));
    },

    async deleteAgent(id) {
      await api.deleteAgent(id);
      setAgents(a => a.filter(x => x.id !== id));
    },

    async approveProposal(proposalId) {
      const res = await api.approveProposal(proposalId);
      setToast({ kind: 'ok', text: 'Proposal approved. Brief queued to production.' });
      return res;
    },

    // ── Briefings management ────────────────────────────────────────────
    setBriefingFilter,

    async loadBriefings() {
      try {
        const data = await api.listBriefings(briefingFilter);
        setBriefings(data.items || []);
      } catch (e) {
        console.error('loadBriefings failed', e);
        setToast({ kind: 'error', text: 'Failed to load briefings.' });
      }
    },

    async loadBriefing(id) {
      if (!id) return null;
      try {
        const rec = await api.getBriefing(id);
        setBriefingsById(map => ({ ...map, [id]: rec }));
        return rec;
      } catch (e) {
        console.error('loadBriefing failed', e);
        setToast({ kind: 'error', text: 'Failed to load briefing.' });
        return null;
      }
    },

    async transitionBriefing(id, to, reason) {
      try {
        const rec = await api.transitionBriefing(id, {
          to, actor: (me && me.name) || 'demo-user', reason,
        });
        setBriefingsById(map => ({ ...map, [id]: rec }));
        // Refresh the list so the table reflects the new status.
        const data = await api.listBriefings(briefingFilter);
        setBriefings(data.items || []);
        setToast({ kind: 'ok', text: `Briefing → ${to.replace(/_/g, ' ')}` });
        return rec;
      } catch (e) {
        console.error('transitionBriefing failed', e);
        setToast({ kind: 'error', text: e.message || 'Transition failed.' });
        throw e;
      }
    },

    async patchBriefing(id, patch) {
      try {
        const rec = await api.patchBriefing(id, {
          ...patch, actor: (me && me.name) || 'demo-user',
        });
        setBriefingsById(map => ({ ...map, [id]: rec }));
        const data = await api.listBriefings(briefingFilter);
        setBriefings(data.items || []);
        return rec;
      } catch (e) {
        console.error('patchBriefing failed', e);
        setToast({ kind: 'error', text: e.message || 'Update failed.' });
        throw e;
      }
    },

    async commentOnBriefing(id, body) {
      try {
        await api.commentBriefing(id, {
          author: (me && me.name) || 'demo-user', body,
        });
        // Reload the full record so comments and activity refresh together.
        const rec = await api.getBriefing(id);
        setBriefingsById(map => ({ ...map, [id]: rec }));
        return rec;
      } catch (e) {
        console.error('commentOnBriefing failed', e);
        setToast({ kind: 'error', text: e.message || 'Comment failed.' });
        throw e;
      }
    },

    // Dev reset — wipes server-side SessionState + every inku.* localStorage
    // key, then forces a hard reload so the UI rebuilds from a fresh state.
    async resetAll() {
      const result = await api.resetAll();
      setToast({
        kind: 'ok',
        text: `Reset complete — server cleared ${result.server_cleared} session(s). Reloading…`,
      });
      // Brief delay so the toast can flash before reload.
      setTimeout(() => { window.location.reload(); }, 400);
    },

    navigate,
    setToast,
  };

  // toast auto-dismiss
  React.useEffect(() => {
    if (!toast) return;
    const t = setTimeout(() => setToast(null), 2800);
    return () => clearTimeout(t);
  }, [toast]);

  const value = {
    me, route, navigate,
    threads, agents,
    currentThread, currentMessages, currentArtifacts,
    streaming, uploadsInFlight, toast,
    briefings, briefingsById, briefingFilter,
    ...actions,
  };

  return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>;
}

window.StoreProvider = StoreProvider;
window.useStore = useStore;
