/**
 * compile.js
 * ----------------------------------------------------------------------------
 * Section 2: LangGraph 1.0 -- state, nodes, persistence, HITL.
 * 26 slides, 16:9, dark theme, code-heavy.
 *
 * Output: section2.pptx (same dir)
 * PDF + PNG preview: produced by build.sh (soffice + pdftoppm).
 *
 * Run: node compile.js
 */

'use strict';

const path = require('path');
const ds = require(path.join(__dirname, '..', '..', 'design-system.js'));
const { theme, helpers, layouts } = ds;
const { slideBase, addHeader, addCodeBlock, addCallout, addProsCons,
        addPageNumber, addSectionDivider, addSourceLine, withFallback } = helpers;

const pptxgen = require('pptxgenjs');

const pres = new pptxgen();
pres.layout = 'LAYOUT_16x9';
pres.title = 'LangChain Evolution: Section 2 -- LangGraph 1.0';
pres.subject = 'LangGraph 1.0 state, nodes, persistence, HITL';

// Override code font size for this section: more lines fit per card.
// Original sizes.code = 12 (from design-system). 10 fits ~35% more.
const CODE_FONT_SIZE = 10;
theme.sizes.code = CODE_FONT_SIZE;

// Section-wide constants
const SECTION_NUMBER = 2;
const SECTION_LABEL = 'SECTION 2';

// Slide counter for page numbers (1-based)
let n = 0;
const next = () => ++n;

// ---------------------------------------------------------------------------
// Slide 1 -- Section divider
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  addSectionDivider(s, pres, theme, {
    number: SECTION_NUMBER,
    eyebrow: SECTION_LABEL + ': LANGGRAPH 1.0',
    title: 'LangGraph 1.0: stateful runtime',
    intro: 'State, nodes, persistence, HITL. ' +
           'Production-ready durable execution: ' +
           'агенты, которые переживают падение сервера и одобряются человеком.',
  });
  // No page number on divider -- standard practice.
}

// ---------------------------------------------------------------------------
// Slide 2 -- Why LangGraph
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'WHY LANGGRAPH',
    title: 'Зачем LangGraph: что не может обычный LangChain',
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 4.5, h: 3.5,
    kind: 'info',
    title: 'LCEL хорош для',
    text: '- простых pipeline (prompt | model | parser)\n' +
          '- одного прохода без ветвлений\n' +
          '- read-only чатов без state между вызовами',
  });

  addCallout(s, pres, theme, {
    x: 5.2, y: layouts.CONTENT_TOP, w: 4.3, h: 3.5,
    kind: 'warning',
    title: 'LCEL не даёт',
    text: '- циклов и произвольной топологии графа\n' +
          '- first-class persistence и time-travel\n' +
          '- настоящей паузы на human approval\n' +
          '- multi-agent и параллельных веток (Send)\n' +
          '- восстановления после падения посреди long-run',
  });

  addSourceLine(s, pres, theme, {
    source: 'blog.langchain.com/langchain-langgraph-1dot0',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 3 -- Install
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'INSTALL',
    title: 'Установка: одна команда',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 6.0, h: 1.3,
    code: 'pip install -U langgraph',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: 2.85, w: 6.0, h: 2.0,
    code: [
      '# extras: postgres / sqlite checkpointers -- отдельные пакеты',
      'pip install -U langgraph langgraph-checkpoint-postgres',
      'pip install -U langgraph langgraph-checkpoint-sqlite',
      '',
      '# JS / TypeScript',
      'npm install @langchain/langgraph @langchain/langgraph-checkpoint',
    ].join('\n'),
  });

  addCallout(s, pres, theme, {
    x: 6.8, y: layouts.CONTENT_TOP, w: 2.7, h: 3.5,
    kind: 'success',
    title: 'Что в коробке',
    text: 'state, persistence, HITL, streaming, ToolNode, subgraph API. ' +
          'Никаких внешних сервисов кроме самого рантайма.',
  });

  addSourceLine(s, pres, theme, {
    source: 'pypi.org/project/langgraph (langgraph 1.2.6, 2026-06-18)',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 4 -- State: TypedDict basic
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STATE / 1',
    title: 'State как TypedDict -- первая итерация',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.5,
    code: [
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      '',
      'class State(TypedDict):',
      '    question: str',
      '    answer:   str',
      '    steps:    int',
      '',
      'def answer(state: State) -> dict:',
      '    return {"answer": f"echo: {state[\'question\']}", "steps": 1}',
      '',
      'builder = StateGraph(State)',
      'builder.add_node("answer", answer)',
      'builder.add_edge(START, "answer")',
      'builder.add_edge("answer", END)',
      'graph = builder.compile()',
      '',
      'print(graph.invoke({"question": "hi", "steps": 0}))',
      '# -> {\'question\': \'hi\', \'answer\': \'echo: hi\', \'steps\': 1}',
    ].join('\n'),
    highlightLines: [4, 5, 6, 17, 18, 19, 21],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 5 -- State: Annotated + add_messages reducer
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STATE / 2',
    title: 'Annotated + add_messages -- каналы с reducer',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from typing import Annotated',
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      'from langgraph.graph.message import add_messages',
      '',
      'class State(TypedDict):',
      '    # reducer add_messages склеивает списки сообщений по правилам',
      '    messages: Annotated[list, add_messages]',
      '',
      'def echo(state: State):',
      '    last = state["messages"][-1]',
      '    return {"messages": [{"role": "assistant", "content": f"echo: {last.content}"}]}',
      '',
      'g = StateGraph(State)',
      'g.add_node("echo", echo)',
      'g.add_edge(START, "echo")',
      'g.add_edge("echo", END)',
      'app = g.compile()',
      '',
      'print(app.invoke({"messages": [{"role": "user", "content": "hi"}]}))',
    ].join('\n'),
    highlightLines: [8, 12, 13],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#reducers',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 6 -- State: operator.add
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STATE / 3',
    title: 'operator.add -- аккумуляция для list-канала',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'import operator',
      'from typing import Annotated',
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      '',
      'class State(TypedDict):',
      '    # каждый узел возвращает кусок списка, operator.add склеивает',
      '    log:    Annotated[list[str], operator.add]',
      '    tokens: Annotated[int,   operator.add]',
      '',
      'def step(state: State):',
      '    return {"log": ["step-1"], "tokens": 12}',
      '',
      'def another(state: State):',
      '    return {"log": ["step-2"], "tokens": 7}',
      '',
      'g = StateGraph(State)',
      'g.add_node("a", step)',
      'g.add_node("b", another)',
      'g.add_edge(START, "a")',
      'g.add_edge("a", "b")',
      'g.add_edge("b", END)',
      'app = g.compile()',
      'print(app.invoke({"log": [], "tokens": 0}))',
      '# -> {\'log\': [\'step-1\', \'step-2\'], \'tokens\': 19}',
    ].join('\n'),
    highlightLines: [9, 10],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#reducers',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 7 -- State: dataclass + LastValue
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STATE / 4',
    title: 'dataclass + LastValue -- когда хочется типизации',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from dataclasses import dataclass, field',
      'from typing import Annotated',
      'from langgraph.graph import StateGraph, START, END',
      'from langgraph.graph.message import add_messages',
      '',
      '@dataclass',
      'class State:',
      '    question:  str = ""',
      '    # dataclass + Annotated -- каналы работают точно так же',
      '    messages:  Annotated[list, add_messages] = field(default_factory=list)',
      '    approved:  bool = False',
      '',
      'def greet(state: State):',
      '    return {"messages": [{"role": "assistant", "content": f"hi, {state.question}"}]}',
      '',
      'g = StateGraph(State)',
      'g.add_node("greet", greet)',
      'g.add_edge(START, "greet")',
      'g.add_edge("greet", END)',
      'app = g.compile()',
      'print(app.invoke(State(question="alex")))',
    ].join('\n'),
    highlightLines: [9, 10],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#dataclass',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 8 -- StateGraph: builder.compile() минимальный граф
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STATEGRAPH / 1',
    title: 'StateGraph: builder.compile() -- базовый граф',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      '',
      'class State(TypedDict):',
      '    n: int',
      '',
      'def inc(state: State):',
      '    return {"n": state["n"] + 1}',
      '',
      'builder = StateGraph(State)',
      'builder.add_node("inc", inc)        # регистрируем узел',
      'builder.add_edge(START, "inc")      # поток входа',
      'builder.add_edge("inc", END)        # поток выхода',
      '',
      'graph = builder.compile()           # компиляция -- граф готов к invoke',
      '',
      'print(graph.invoke({"n": 0}))       # -> {\'n\': 1}',
    ].join('\n'),
    highlightLines: [12, 13, 14, 17, 19],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#stategraph',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 9 -- START, END and data flow
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STATEGRAPH / 2',
    title: 'START, END и поток данных через рёбра',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      '# START и END -- это специальные sentinel-узлы, не callable',
      'from langgraph.graph import StateGraph, START, END',
      '',
      'class State(TypedDict):',
      '    text: str',
      '',
      'def upper(state: State):',
      '    return {"text": state["text"].upper()}',
      '',
      'def exclaim(state: State):',
      '    return {"text": state["text"] + "!"}',
      '',
      'g = StateGraph(State)',
      'g.add_node("upper",   upper)',
      'g.add_node("exclaim", exclaim)',
      'g.add_edge(START, "upper")         # вход в граф',
      'g.add_edge("upper",   "exclaim")   # внутреннее ребро',
      'g.add_edge("exclaim", END)         # выход из графа',
      '',
      'app = g.compile()',
      'print(app.invoke({"text": "hi"}))  # -> {\'text\': \'HI!\'}',
    ].join('\n'),
    highlightLines: [3, 14, 16, 17],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#why-langgraph',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 10 -- Nodes: sync/async
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'NODES / 1',
    title: 'Узлы: синхронные и асинхронные функции',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'import asyncio',
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      '',
      'class State(TypedDict):',
      '    out: str',
      '',
      'def sync_node(state: State):                 # обычная функция',
      '    return {"out": "sync-ok"}',
      '',
      'async def async_node(state: State):          # async тоже работает',
      '    await asyncio.sleep(0)',
      '    return {"out": "async-ok"}',
      '',
      'g = StateGraph(State)',
      'g.add_node("s", sync_node)',
      'g.add_node("a", async_node)',
      'g.add_edge(START, "s")',
      'g.add_edge("s", "a")',
      'g.add_edge("a", END)',
      'app = g.compile()',
      '',
      'print(app.invoke({"out": ""}))',
      'print(asyncio.run(app.ainvoke({"out": ""})))',
    ].join('\n'),
    highlightLines: [11, 13],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#nodes',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 11 -- Command: explicit goto
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'NODES / 2',
    title: 'Command -- узел сам решает, куда идти дальше',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from langgraph.graph import StateGraph, START, END',
      'from langgraph.types import Command',
      'from typing_extensions import TypedDict',
      'from typing import Literal',
      'class State(TypedDict):',
      '    n: int',
      'def decide(state: State) -> Command[Literal["inc", "halt"]]:',
      '    # Command(update, goto) -- обновляет state и сам выбирает узел',
      '    if state["n"] < 3:',
      '        return Command(update={"n": state["n"] + 1}, goto="inc")',
      '    return Command(update={}, goto="halt")',
      'g = StateGraph(State)',
      'g.add_node("decide", decide)',
      'g.add_node("inc",    inc)',
      'g.add_node("halt",   lambda s: s)',
      'g.add_edge(START, "decide")',
      'g.add_edge("inc", "decide")',
      'g.add_edge("halt", END)',
      'app = g.compile()',
      'print(app.invoke({"n": 0}))',
    ].join('\n'),
    highlightLines: [7, 8, 10],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#command',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 12 -- Conditional edges
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'ROUTING / 1',
    title: 'Conditional edges: routing по содержимому',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      '',
      'class State(TypedDict):',
      '    needs_tool: bool',
      '    answer: str',
      '',
      'def agent(state: State) -> dict:',
      '    return {"answer": "model-output"}',
      '',
      'def tool_node(state: State) -> dict:',
      '    return {"answer": "tool-output"}',
      '',
      'def route(state: State) -> str:',
      '    # возвращаем ключ, который есть в path_map ниже',
      '    return "tool_node" if state["needs_tool"] else END',
      '',
      'g = StateGraph(State)',
      'g.add_node("agent",     agent)',
      'g.add_node("tool_node", tool_node)',
      'g.add_edge(START, "agent")',
      'g.add_conditional_edges("agent", route, {',
      '    "tool_node": "tool_node",',
      '    END: END,',
      '})',
      'g.add_edge("tool_node", END)',
      'app = g.compile()',
      'print(app.invoke({"needs_tool": True, "answer": ""}))',
    ].join('\n'),
    highlightLines: [19, 20, 21, 22, 23],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#conditional-edges',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 13 -- Cycles
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'ROUTING / 2',
    title: 'Циклы: agentic loop без рекурсии в коде',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      '# агентный цикл: agent <-> tools, выход когда done=true',
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      '',
      'class State(TypedDict):',
      '    done: bool',
      '    iter: int',
      '',
      'def agent(state: State) -> dict:',
      '    return {"iter": state["iter"] + 1, "done": state["iter"] >= 3}',
      '',
      'def maybe_continue(state: State) -> str:',
      '    return "agent" if not state["done"] else END',
      '',
      'g = StateGraph(State)',
      'g.add_node("agent", agent)',
      'g.add_edge(START, "agent")',
      'g.add_conditional_edges("agent", maybe_continue, {"agent": "agent", END: END})',
      'app = g.compile()',
      'print(app.invoke({"done": False, "iter": 0}))',
      '# agent крутится 3 раза, потом уходит в END',
    ].join('\n'),
    highlightLines: [15, 16],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/low_level/#cycles',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 14 -- Persistence: InMemorySaver + thread_id
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'PERSISTENCE / 1',
    title: 'InMemorySaver + thread_id: stateful сессии',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from typing import Annotated',
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      'from langgraph.graph.message import add_messages',
      'from langgraph.checkpoint.memory import InMemorySaver',
      '',
      'class State(TypedDict):',
      '    messages: Annotated[list, add_messages]',
      '',
      'def echo(state: State):',
      '    last = state["messages"][-1]',
      '    return {"messages": [{"role": "assistant", "content": f"echo: {last.content}"}]}',
      '',
      'g = StateGraph(State)',
      'g.add_node("echo", echo)',
      'g.add_edge(START, "echo")',
      'g.add_edge("echo", END)',
      '',
      '# checkpointer -- обязателен для thread persistence',
      'checkpointer = InMemorySaver()',
      'app = g.compile(checkpointer=checkpointer)',
      '',
      'cfg = {"configurable": {"thread_id": "user-1"}}',
      'app.invoke({"messages": [{"role": "user", "content": "hi"}]},   cfg)',
      'app.invoke({"messages": [{"role": "user", "content": "again"}]}, cfg)',
      '# второй вызов видит все сообщения первого: thread persistence работает',
    ].join('\n'),
    highlightLines: [22, 23, 25, 26],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/persistence/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 15 -- SqliteSaver / PostgresSaver
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'PERSISTENCE / 2',
    title: 'SqliteSaver и PostgresSaver для production',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 4.5, h: 3.65,
    code: [
      '# SQLite -- файл на диске, идеален для dev / small-prod',
      'from langgraph.checkpoint.sqlite import SqliteSaver',
      '',
      'with SqliteSaver.from_conn_string("./checkpoints.db") as cp:',
      '    app = g.compile(checkpointer=cp)',
      '    cfg = {"configurable": {"thread_id": "u1"}}',
      '    app.invoke({"messages": []}, cfg)',
      '',
      '# данные переживают рестарт процесса.',
      '# Для asyncio-варианта -- aiosqlite.',
    ].join('\n'),
    highlightLines: [1, 3, 4, 5],
  });

  addCodeBlock(s, pres, theme, {
    x: 5.2, y: layouts.CONTENT_TOP, w: 4.3, h: 3.65,
    code: [
      '# Postgres -- production-grade, multi-instance',
      'from langgraph.checkpoint.postgres import PostgresSaver',
      '',
      'DB = "postgresql://user:pass@host:5432/lg"',
      'with PostgresSaver.from_conn_string(DB) as cp:',
      '    # первый запуск создаст schema',
      '    cp.setup()',
      '    app = g.compile(checkpointer=cp)',
      '    cfg = {"configurable": {"thread_id": "u1"}}',
      '    app.invoke({"messages": []}, cfg)',
    ].join('\n'),
    highlightLines: [1, 3, 6],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/persistence/#checkpointer-implementations',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 16 -- StateSnapshot
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'PERSISTENCE / 3',
    title: 'StateSnapshot -- что лежит в checkpoint',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      '# После invoke можно достать текущий snapshot:',
      'snapshot = app.get_state(cfg)',
      '',
      '# snapshot -- это StateSnapshot с полями:',
      '#   .config     -- thread_id + checkpoint_id',
      '#   .metadata   -- step, source, writes',
      '#   .values     -- текущие значения всех каналов state',
      '#   .next       -- tuple узлов, которые будут выполняться следующими',
      '#   .tasks      -- PregelTask с pending/result/error',
      '',
      'print(snapshot.next)        # ()  -- граф завершён',
      'print(snapshot.values["messages"][-1].content)',
    ].join('\n'),
    highlightLines: [2, 5, 6, 7, 8, 9],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/persistence/#state-snapshot',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 17 -- Time travel: get_state_history
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'TIME TRAVEL / 1',
    title: 'get_state_history: вся история шагов',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      '# Каждый super-step -- отдельный checkpoint в thread',
      'history = list(app.get_state_history(cfg))',
      '',
      '# history[0]  -- последний шаг (самый свежий)',
      '# history[-1] -- самый первый (начало сессии)',
      '',
      'for i, snap in enumerate(history):',
      '    print(i, snap.metadata.get("step"), snap.values.get("iter"))',
      '',
      '# replays = форк от любого прошлого snapshot:',
      'old = history[2].config',
      'app.invoke(None, old)   # переигрывает только следующие шаги',
    ].join('\n'),
    highlightLines: [2, 3, 4, 7, 10, 11],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/persistence/#time-travel',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 18 -- update_state: fork and replay
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'TIME TRAVEL / 2',
    title: 'update_state: форкнуть состояние и пойти другой веткой',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      '# patch значения канала прямо в snapshot -- создаётся новый checkpoint',
      'app.update_state(',
      '    cfg,',
      '    values={"messages": [{"role": "user", "content": "rewind"}]},',
      '    as_node="user_input",   # от имени какого узла пишем',
      ')',
      '',
      '# Дальше invoke(None, cfg) переигрывает граф с нового состояния',
      'app.invoke(None, cfg)',
      '',
      '# Типичный приём: "что если пользователь сказал не X, а Y?" --',
      '# ответвляемся, смотрим альтернативный прогон без потери истории.',
    ].join('\n'),
    highlightLines: [2, 3, 4, 5, 10, 11],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/persistence/#update-state',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 19 -- HITL: interrupt + Command(resume)
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'HITL / 1',
    title: 'interrupt + Command(resume=): пауза на человеке',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from langgraph.types import interrupt, Command',
      'from langgraph.graph import StateGraph, START, END',
      'from langgraph.checkpoint.memory import InMemorySaver',
      'from typing_extensions import TypedDict',
      'class State(TypedDict):',
      '    question: str',
      '    approved: bool',
      'def ask(state: State):',
      '    # interrupt() -- пауза. Возвращает значение из Command(resume=...)',
      '    answer = interrupt({"question": "Approve sending this message?"})',
      '    return {"approved": answer == "yes"}',
      'g = StateGraph(State)',
      'g.add_node("ask", ask)',
      'g.add_edge(START, "ask")',
      'g.add_edge("ask", END)',
      'app = g.compile(checkpointer=InMemorySaver())',
      'cfg = {"configurable": {"thread_id": "approval-1"}}',
      'app.invoke({"question": "send email", "approved": False}, cfg)   # пауза',
      'result = app.invoke(Command(resume="yes"), cfg)                  # resume',
      'print(result["approved"])  # -> True',
    ].join('\n'),
    highlightLines: [10, 17, 18, 19],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/human_in_the_loop/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 20 -- HITL: graph.invoke with config + pause
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'HITL / 2',
    title: 'Как выглядит HITL-цикл снаружи',
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 4.5, h: 3.5,
    kind: 'info',
    title: 'Серверная сторона',
    text: '1. graph.invoke(input, cfg)\n' +
          '2. Внутри узла вызван interrupt(payload)\n' +
          '3. Граф замораживается, состояние в checkpointer\n' +
          '4. Возвращается GraphInterrupt с payload\n' +
          '5. Сервер ждёт -- пользователь думает часы/дни',
  });

  addCallout(s, pres, theme, {
    x: 5.2, y: layouts.CONTENT_TOP, w: 4.3, h: 3.5,
    kind: 'success',
    title: 'Клиентская сторона',
    text: '1. UI получает payload, рисует форму\n' +
          '2. Пользователь жмёт Approve / Reject\n' +
          '3. UI делает POST /threads/{id}/resume\n' +
          '4. Сервер вызывает graph.invoke(Command(resume=answer), cfg)\n' +
          '5. Граф оживает с того же узла',
  });

  addSourceLine(s, pres, theme, {
    source: 'blog.langchain.com/langchain-langgraph-1dot0 (HITL section)',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 21 -- HITL: multi-turn approval cycle (review-node only)
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'HITL / 3',
    title: 'Multi-turn approval: узел review',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      '# Узел review -- маршрутизация по ответу человека',
      'from langgraph.types import interrupt, Command',
      'from typing_extensions import TypedDict',
      'class State(TypedDict):',
      '    plan: str',
      '    executed: bool',
      'def plan(state: State):',
      '    return {"plan": "step-A; step-B; step-C"}',
      'def review(state: State) -> Command:',
      '    # interrupt() возвращает ответ из Command(resume=...)',
      '    decision = interrupt({"plan": state["plan"]})',
      '    if decision == "approve":',
      '        return Command(goto="execute")',
      '    if decision == "abort":',
      '        return Command(goto=END)',
      '    return Command(goto="plan")  # перепланировать',
      'def execute(state: State):',
      '    return {"executed": True}',
    ].join('\n'),
    highlightLines: [9, 10, 11, 12, 13, 14, 15],
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: 4.0, w: 9.0, h: 0.65,
    kind: 'info',
    text: 'Полный пример со сборкой графа -- на следующем слайде.',
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/cycle/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 21b -- HITL: full graph assembly for multi-turn approval
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'HITL / 4',
    title: 'Multi-turn approval: сборка графа',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from langgraph.graph import StateGraph, START, END',
      'from langgraph.checkpoint.memory import InMemorySaver',
      '# plan, review, execute -- из предыдущего слайда',
      'g = StateGraph(State)',
      'g.add_node("plan",    plan)',
      'g.add_node("review",  review)',
      'g.add_node("execute", execute)',
      'g.add_edge(START, "plan")',
      'g.add_edge("plan", "review")',
      'g.add_edge("execute", END)',
      'app = g.compile(checkpointer=InMemorySaver())',
      '',
      '# Цикл: каждый review -- это пауза; Command(goto=...) -- ответвление',
      '# plan -> review -> (approve: execute | abort: END | else: plan)',
      'cfg = {"configurable": {"thread_id": "u1"}}',
      'app.invoke({"plan": "", "executed": False}, cfg)         # пауза 1',
      'app.invoke(Command(resume="approve"), cfg)               # resume -> execute',
    ].join('\n'),
    highlightLines: [5, 6, 8, 14, 15],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/cycle/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 22 -- Subgraphs
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'SUBGRAPHS',
    title: 'Subgraphs: композитность и изоляция state',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from typing_extensions import TypedDict',
      'from langgraph.graph import StateGraph, START, END',
      '',
      'class SubState(TypedDict):',
      '    internal: str',
      '',
      'def inner(state: SubState):',
      '    return {"internal": "sub-output"}',
      '',
      '# Собираем subgraph -- у него свой state',
      'sub = StateGraph(SubState)',
      'sub.add_node("inner", inner)',
      'sub.add_edge(START, "inner")',
      'sub.add_edge("inner", END)',
      'sub_compiled = sub.compile()',
      '',
      '# Вставляем как обычный узел в родительский граф',
      'class ParentState(TypedDict):',
      '    out: str',
      '',
      'parent = StateGraph(ParentState)',
      'parent.add_node("sub_block", sub_compiled)   # <- subgraph целиком',
      'parent.add_edge(START, "sub_block")',
      'parent.add_edge("sub_block", END)',
      'app = parent.compile()',
      'print(app.invoke({"out": ""}))',
    ].join('\n'),
    highlightLines: [11, 12, 13, 14, 21, 22],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/subgraphs/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 23 -- Streaming: 5 modes
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STREAMING / 1',
    title: 'Пять режимов stream_mode',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'cfg = {"configurable": {"thread_id": "u1"}}',
      '# values    -- весь state после каждого super-step',
      'for snap in app.stream({"messages": []}, cfg, stream_mode="values"):',
      '    print(snap["messages"][-1].content)',
      '# updates   -- delta: только что вернул каждый узел',
      'for upd in app.stream({"messages": []}, cfg, stream_mode="updates"):',
      '    print(upd)',
      '# events    -- низкоуровневые события (start, end, error, interrupt)',
      'for ev in app.stream({"messages": []}, cfg, stream_mode="events"):',
      '    print(ev["event"], ev["name"])',
      '# messages  -- токены LLM по мере генерации',
      'for tok, meta in app.stream({"messages": []}, cfg, stream_mode="messages"):',
      '    print(tok.content, end="|")',
      '# custom    -- только то, что узлы пишут через get_stream_writer()',
      'for chunk in app.stream({"messages": []}, cfg, stream_mode="custom"):',
      '    print(chunk)',
    ].join('\n'),
    highlightLines: [3, 6, 9, 12, 15],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/streaming/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 24 -- Custom streaming with get_stream_writer
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STREAMING / 2',
    title: 'Custom stream: пишем из узла как хотим',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from langgraph.graph import StateGraph, START, END',
      'from langgraph.config import get_stream_writer',
      'from typing_extensions import TypedDict',
      '',
      'class State(TypedDict):',
      '    total: int',
      '',
      'def progress(state: State):',
      '    writer = get_stream_writer()      # доступен только во время выполнения узла',
      '    for i in range(3):',
      '        writer({"progress": i, "phase": "thinking"})',
      '    return {"total": 3}',
      '',
      'g = StateGraph(State)',
      'g.add_node("progress", progress)',
      'g.add_edge(START, "progress")',
      'g.add_edge("progress", END)',
      'app = g.compile()',
      '',
      '# в UI -- только custom-чанки, без промежуточного state',
      'for chunk in app.stream({"total": 0}, stream_mode="custom"):',
      '    print(chunk)',
    ].join('\n'),
    highlightLines: [10, 11, 12, 19, 20],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/streaming/#custom',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 25 -- Tool calling: tools + LLM binding
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'TOOL CALLING / 1',
    title: 'Tool calling: tools + LLM binding',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from langchain_openai import ChatOpenAI',
      'from langchain.tools import tool',
      'from langgraph.graph.message import add_messages',
      'from typing import Annotated',
      'from typing_extensions import TypedDict',
      '@tool',
      'def add(a: int, b: int) -> int:',
      '    "Add two numbers."',
      '    return a + b',
      'tools = [add]',
      'llm = ChatOpenAI(model="gpt-4o-mini").bind_tools(tools)',
      'class State(TypedDict):',
      '    messages: Annotated[list, add_messages]',
      'def agent(state: State):',
      '    return {"messages": [llm.invoke(state["messages"])]}',
      'def route(state: State) -> str:',
      '    last = state["messages"][-1]',
      '    return "tools" if getattr(last, "tool_calls", None) else END',
    ].join('\n'),
    highlightLines: [11, 17],
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: 4.0, w: 9.0, h: 0.65,
    kind: 'info',
    text: 'Сборка графа и запуск -- на следующем слайде.',
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/how-tos/tool-calling/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 25b -- Tool calling: graph assembly + run
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'TOOL CALLING / 2',
    title: 'Tool calling: сборка графа и запуск',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'from langgraph.graph import StateGraph, START, END',
      'from langgraph.prebuilt import ToolNode',
      '# tools, llm, agent, route -- из предыдущего слайда',
      'g = StateGraph(State)',
      'g.add_node("agent", agent)',
      'g.add_node("tools", ToolNode(tools))',
      'g.add_edge(START, "agent")',
      'g.add_conditional_edges("agent", route, {"tools": "tools", END: END})',
      'g.add_edge("tools", "agent")',
      'app = g.compile()',
      '',
      '# Цикл: agent решает -> tools исполняет -> agent снова читает результат',
      'result = app.invoke({',
      '    "messages": [{"role": "user", "content": "What is 2 + 3?"}]',
      '})',
      'print(result["messages"][-1].content)',
    ].join('\n'),
    highlightLines: [6, 10, 13, 14],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/how-tos/tool-calling/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 26 -- LangGraph Studio
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'STUDIO',
    title: 'LangGraph Studio: визуальный debugger',
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 4.5, h: 3.0,
    kind: 'info',
    title: 'Что это',
    text: 'Desktop IDE и web-UI для отладки графов. ' +
          'Показывает граф, каждый super-step, ' +
          'state на каждом шаге, время на узел.',
  });

  addCallout(s, pres, theme, {
    x: 5.2, y: layouts.CONTENT_TOP, w: 4.3, h: 3.0,
    kind: 'success',
    title: 'Что умеет',
    text: '- запускать граф интерактивно\n' +
          '- ставить breakpoints на узлах\n' +
          '- модифицировать state вручную\n' +
          '- редактировать узлы и перезапускать\n' +
          '- экспорт trace в LangSmith',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: 4.2, w: 9.0, h: 0.8,
    code: [
      '# запуск: langgraph dev  -- поднимает Studio на http://localhost:8123',
    ].join('\n'),
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/langgraph_studio/',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 27 -- Deploy через LangGraph Platform
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'PLATFORM',
    title: 'Deploy через LangGraph Platform',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 1.3,
    code: [
      '# langgraph.json -- declarative config',
      '{',
      '  "graphs": {"agent": "./agent.py:graph"},',
      '  "env":  "./.env",',
      '  "python_version": "3.11"',
      '}',
    ].join('\n'),
    highlightLines: [2],
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: 2.8, w: 9.0, h: 2.0,
    code: [
      '# CLI: локальный dev-сервер и deploy',
      'langgraph dev       # локальный API + Studio',
      'langgraph up        # Docker-compose stack (Redis + API + Studio)',
      'langgraph deploy    # пуш в LangGraph Platform (managed)',
    ].join('\n'),
    highlightLines: [3],
  });

  addSourceLine(s, pres, theme, {
    source: 'langchain-ai.github.io/langgraph/concepts/langgraph_platform/ (managed: scaling, queue, persistent threads, observability)',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 28 -- What's new in 1.0
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'NEW IN 1.0',
    title: 'Что нового в LangGraph 1.0: четыре фичи',
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 4.5, h: 1.55,
    kind: 'success',
    title: 'Durable execution',
    text: 'Состояние персистится автоматически. Падение сервера посреди long-run -- ' +
          'граф восстанавливается ровно с точки остановки.',
  });

  addCallout(s, pres, theme, {
    x: 5.2, y: layouts.CONTENT_TOP, w: 4.3, h: 1.55,
    kind: 'success',
    title: 'HITL first-class',
    text: 'interrupt() -- стабильный API, multi-day approval workflows без костылей.',
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: 3.1, w: 4.5, h: 1.5,
    kind: 'info',
    title: 'Built-in persistence',
    text: 'InMemorySaver / SqliteSaver / PostgresSaver -- first-class контракты, ' +
          'а не отдельный набор фич.',
  });

  addCallout(s, pres, theme, {
    x: 5.2, y: 3.1, w: 4.3, h: 1.5,
    kind: 'warning',
    title: 'Breaking changes',
    text: '- langgraph.prebuilt.create_react_agent -> langchain.agents.create_agent\n' +
          '- MemorySaver -> InMemorySaver (новый alias)\n' +
          '- semver: стабильно до 2.0',
  });

  addSourceLine(s, pres, theme, {
    source: 'changelog.langchain.com/announcements/langgraph-1-0-is-now-generally-available (bonus 1.2: fault tolerance middleware -- retries / timeouts / error handlers)',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 29 -- TypeScript analogue
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'TYPESCRIPT',
    title: '@langchain/langgraph -- JS/TS-аналог',
  });

  addCodeBlock(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.65,
    code: [
      'import { StateGraph, START, END, Annotation } from "@langchain/langgraph";',
      'import { MemorySaver }               from "@langchain/langgraph-checkpoint";',
      '',
      'const State = Annotation.Root({',
      '  messages: Annotation({',
      '    reducer: (a, b) => a.concat(b),',
      '    default: () => [],',
      '  }),',
      '});',
      '',
      'const g = new StateGraph(State)',
      '  .addNode("echo", (s) => ({',
      '    messages: [{ role: "assistant", content: "echo: " + s.messages.at(-1).content }],',
      '  }))',
      '  .addEdge(START, "echo")',
      '  .addEdge("echo", END);',
      '',
      'const app = g.compile({ checkpointer: new MemorySaver() });',
      'const cfg = { configurable: { thread_id: "t1" } };',
      'const result = await app.invoke({ messages: [{ role: "user", content: "hi" }] }, cfg);',
    ].join('\n'),
    highlightLines: [1, 2, 19, 21],
  });

  addSourceLine(s, pres, theme, {
    source: 'github.com/langchain-ai/langgraphjs',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 30 -- pros/cons: LangGraph vs LCEL
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'WHEN TO USE',
    title: 'Когда LangGraph, когда остаться на LCEL',
  });

  addProsCons(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 9.0, h: 3.5,
    pros: [
      'Агент крутится в цикле (tool calls + retry)',
      'Нужна пауза на human approval / review',
      'Long-running workflow переживает рестарт',
      'Сложная топология: ветвления, merge, map-reduce',
      'Multi-agent: subgraphs + Send',
      'Time-travel и replay для отладки',
    ],
    cons: [
      'Простой pipeline prompt | model | parser',
      'Один проход без state между вызовами',
      'Read-only чат без persistence',
      'Быстрый прототип без долгоживущего state',
      'Команда не готова к concepts: channels/reducers/Send',
    ],
  });

  addSourceLine(s, pres, theme, {
    source: 'blog.langchain.com/langchain-langgraph-1dot0',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Slide 31 -- Bridge to Deep Agents
// ---------------------------------------------------------------------------
{
  const s = pres.addSlide();
  slideBase(s, pres, theme);
  addHeader(s, pres, theme, {
    section: SECTION_LABEL,
    sectionNumber: SECTION_NUMBER,
    eyebrow: 'BRIDGE TO SECTION 3',
    title: 'Мостик к Deep Agents',
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: layouts.CONTENT_TOP, w: 4.5, h: 1.8,
    kind: 'info',
    title: 'Что мы только что разобрали',
    text: '- State, nodes, edges, persistence\n' +
          '- HITL через interrupt + Command(resume)\n' +
          '- Subgraphs, streaming, ToolNode\n' +
          '- Deploy через LangGraph Platform',
  });

  addCallout(s, pres, theme, {
    x: 5.2, y: layouts.CONTENT_TOP, w: 4.3, h: 1.8,
    kind: 'success',
    title: 'Что дальше (Section 3)',
    text: 'Deep Agents -- это готовая архитектура поверх LangGraph: ' +
          'planning tool, filesystem, subagents, middleware. ' +
          'create_deep_agent -- одна функция вместо сотни строк boilerplate.',
  });

  addCallout(s, pres, theme, {
    x: 0.5, y: 3.4, w: 9.0, h: 1.3,
    kind: 'warning',
    title: 'Связь',
    text: 'create_deep_agent из deepagents==0.6.11 -- это обёртка, которая собирает граф LangGraph ' +
          'с planning tool, файловой системой и subagents. Внутри всё, что мы видели: ' +
          'StateGraph, Command, interrupt, subgraphs.',
  });

  addSourceLine(s, pres, theme, {
    source: 'github.com/langchain-ai/deepagents (README)',
  });
  addPageNumber(s, pres, theme, next());
}

// ---------------------------------------------------------------------------
// Build
// ---------------------------------------------------------------------------
const outFile = path.join(__dirname, 'section2.pptx');
pres.writeFile({ fileName: outFile }).then(function (file) {
  console.log('Wrote: ' + file);
}).catch(function (err) {
  console.error('ERROR:', err);
  process.exit(1);
});
