// 이 파일이 하는 일: "REST API 설계: 주소와 동사의 문법" 코스 — 코드를 짜기 전에
// API 명세부터 쓰는 이유에서 출발해, URL 설계·HTTP 메서드·상태 코드·바디 설계·
// 쿼리스트링·문서화까지 7개 섹션으로 안내하는 정적 학습 페이지. 프레이밍은
// "API는 프론트와 백엔드 사이의 계약서" — 명세를 먼저 합의하면 두 팀이 동시에
// 달릴 수 있고, 나중에 싸울 일이 사라진다는 감각을 심어 준다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 예제 코드 상수들 ──

const CODE_CONTRACT = `프론트엔드 개발자                        백엔드 개발자
(화면을 만든다)                          (데이터를 다룬다)
      |                                        |
      |            ┌──────────────┐            |
      └──────────▶ |  API 명세서   | ◀──────────┘
                   |  (계약서)     |
                   └──────────────┘
        "글 목록은 GET /posts 로 주세요"
        "응답엔 id, title, createdAt 이 있어요"

핵심: 명세가 먼저 합의되면 두 사람은 서로를 기다리지 않아요.
프론트는 명세대로 가짜 데이터(mock)를 만들어 화면을 짜고,
백엔드는 명세대로 서버를 짭니다. 마지막에 붙이면? 딱 맞습니다.
명세 없이 시작하면 — "필드 이름이 왜 달라요?" 싸움이 시작되죠.`;

const CODE_BAD_URLS = `나쁜 URL (동사가 주소에 들어감)          좋은 URL (명사 + 메서드)
──────────────────────────────────────────────────────────────
GET  /getAllPosts                →  GET    /posts
POST /createPost                 →  POST   /posts
POST /deletePost?id=3            →  DELETE /posts/3
GET  /getCommentsOfPost/3        →  GET    /posts/3/comments
POST /likePost/3                 →  POST   /posts/3/likes

규칙 세 줄 요약:
① URL에는 명사만, 그것도 복수형으로 (/posts, /comments)
② "무엇을 할지"는 URL이 아니라 HTTP 메서드가 말한다
③ 소속 관계는 경로 계층으로 (/posts/3/comments = 3번 글의 댓글들)`;

const CODE_HIERARCHY = `GET /posts                  글 전체 목록
GET /posts/3                3번 글 하나
GET /posts/3/comments       3번 글에 달린 댓글 목록
GET /posts/3/comments/12    3번 글의 12번 댓글 하나

읽는 법: 왼쪽에서 오른쪽으로 갈수록 "큰 서랍 → 작은 서랍".
폴더 경로(posts/3/comments)와 똑같은 감각이에요.
단, 계층이 3단을 넘어가면(/a/1/b/2/c/3/d…) 오히려 읽기 어려워지니
그땐 /comments/12 처럼 최상위 리소스로 바로 접근하는 걸 고려합니다.`;

const CODE_METHODS = `메서드    뜻            예시                     멱등성
──────────────────────────────────────────────────────────
GET      조회           GET /posts/3              O (몇 번 읽어도 같음)
POST     생성           POST /posts               X (보낼 때마다 새 글!)
PUT      전체 교체       PUT /posts/3              O (같은 걸로 계속 덮음)
PATCH    부분 수정       PATCH /posts/3            상황에 따라 다름
DELETE   삭제           DELETE /posts/3           O (이미 없어도 없는 상태)

멱등성(idempotent) = "같은 요청을 두 번 보내도 결과가 한 번과 같은가?"
네트워크가 끊겨 재시도할 때, 멱등한 요청은 안심하고 다시 보낼 수 있어요.
POST가 멱등하지 않다는 것 — 결제 버튼 두 번 클릭 사고의 근원입니다.`;

const CODE_PUT_PATCH = `// 원래 데이터: { "title": "제목", "content": "본문", "tags": ["일상"] }

// PUT — 전체 교체: 보낸 것"만" 남는다
PUT /posts/3
{ "title": "새 제목" }
// 결과: { "title": "새 제목" }  ← content와 tags가 사라졌다!

// PATCH — 부분 수정: 보낸 것"만" 바뀐다
PATCH /posts/3
{ "title": "새 제목" }
// 결과: { "title": "새 제목", "content": "본문", "tags": ["일상"] }

// 그래서 "제목만 고치는 기능"엔 PATCH가 맞습니다.
// PUT을 쓰려면 클라이언트가 나머지 필드까지 전부 다시 보내야 해요.`;

const CODE_STATUS = `코드   이름                  언제 쓰나                      누구 잘못?
─────────────────────────────────────────────────────────────────
200   OK                   조회·수정 성공, 돌려줄 바디 있음      -
201   Created              POST로 새 리소스가 만들어짐          -
204   No Content           성공했는데 돌려줄 바디가 없음         -
                           (DELETE 성공이 대표 사례)
400   Bad Request          요청 형식이 틀림 (필수 필드 누락 등)   클라이언트
401   Unauthorized         "누구세요?" — 로그인이 안 됨          클라이언트
403   Forbidden            "누군지는 아는데 권한이 없어요"        클라이언트
404   Not Found            그런 리소스가 없음                  클라이언트
409   Conflict             현재 상태와 충돌 (중복 아이디 등)      클라이언트
500   Internal Server Error 서버 코드가 터짐                   서버(우리)

4xx = "요청이 잘못됐어요" (클라이언트가 고쳐야 함)
5xx = "서버가 잘못했어요" (우리가 고쳐야 함) — 이 경계가 디버깅의 나침반.`;

const CODE_LIST_BODY = `// GET /posts?page=2&limit=10 의 응답 — 목록엔 '포장지'를 씌운다
{
  "items": [
    { "id": 11, "title": "열한 번째 글", "likeCount": 3, "createdAt": "2026-07-18T09:00:00Z" },
    { "id": 12, "title": "열두 번째 글", "likeCount": 0, "createdAt": "2026-07-19T14:30:00Z" }
  ],
  "page": 2,          // 지금 몇 페이지인지
  "limit": 10,        // 한 페이지에 몇 개인지
  "total": 57         // 전체가 몇 개인지 → 프론트가 "6페이지 중 2페이지"를 그릴 수 있다
}

// 배열을 맨바깥에 그냥 [ ... ] 로 보내면? 나중에 total을 추가할 자리가 없어요.
// 처음부터 객체로 감싸 두면 필드를 더해도 기존 코드가 안 깨집니다.`;

const CODE_ERROR_BODY = `// 에러 응답도 '형식'을 통일한다 — 성공만 설계하면 반쪽짜리 명세
// 400 Bad Request
{
  "code": "VALIDATION_FAILED",       // 기계가 읽는 에러 코드 (분기 처리용)
  "message": "title은 1자 이상 100자 이하여야 합니다."  // 사람이 읽는 설명
}

// 409 Conflict
{
  "code": "DUPLICATE_LIKE",
  "message": "이미 좋아요를 누른 글입니다."
}

// 프론트 입장에서: 어떤 에러든 code로 분기하고 message를 띄우면 끝.
// 어떤 API는 문자열, 어떤 API는 객체... 형식이 제각각이면 프론트가 웁니다.`;

const CODE_QUERY = `# 검색·필터·정렬·페이지 — 전부 쿼리스트링의 일
GET /posts?keyword=여행                검색: '여행'이 들어간 글
GET /posts?author=kim&tag=일상         필터: kim이 쓴 '일상' 태그 글 (AND 조합)
GET /posts?sort=-createdAt             정렬: 최신순 (- 는 내림차순 관례)
GET /posts?sort=likeCount              정렬: 좋아요 적은 순 (오름차순)
GET /posts?page=2&limit=10             페이지: 2페이지, 10개씩

# 조합도 자유롭다 — 쿼리는 서로 독립적인 '옵션'이니까
GET /posts?tag=일상&sort=-likeCount&page=1&limit=5

# 선택 기준 한 줄:
# 경로(/posts/3)  = 자원의 "주소" — 이게 없으면 대상 자체가 특정 안 됨
# 쿼리(?sort=..)  = 자원을 보는 "방식" — 없어도 기본값으로 동작해야 함`;

const CODE_SPEC_TABLE = `미니 블로그 API 명세서 v1                              (실습에서 완성할 표)
──────────────────────────────────────────────────────────────────────
메서드   경로                        설명            성공     주요 실패
GET     /api/v1/posts               글 목록(페이지)   200     400(잘못된 page)
POST    /api/v1/posts               글 작성          201     400, 401
GET     /api/v1/posts/:id           글 상세          200     404
PATCH   /api/v1/posts/:id           글 수정          200     401, 403, 404
DELETE  /api/v1/posts/:id           글 삭제          204     401, 403, 404
GET     /api/v1/posts/:id/comments  댓글 목록        200     404(글 없음)
POST    /api/v1/posts/:id/comments  댓글 작성        201     400, 401, 404
POST    /api/v1/posts/:id/likes     좋아요           201     401, 404, 409(중복)
DELETE  /api/v1/posts/:id/likes     좋아요 취소       204     401, 404

표 한 장이지만 — 메서드·경로·상태 코드·실패 시나리오까지
이 코스에서 배운 전부가 이 안에 들어 있습니다.`;

const CODE_OPENAPI = `# OpenAPI(Swagger) — 명세를 '기계가 읽는 형식'으로 쓰면 생기는 일
# 주의: OpenAPI의 경로 파라미터는 {id} 중괄호 표기예요 (:id는 Express 코드 쪽 문법)
paths:
  /posts/{id}:
    get:
      summary: 글 상세 조회
      responses:
        "200":
          description: 조회 성공
        "404":
          description: 글이 존재하지 않음

# 이렇게 YAML로 적어 두면 도구가 알아서:
# ① 사람이 보기 좋은 문서 페이지를 자동 생성 (Swagger UI)
# ② 브라우저에서 바로 "Try it out" — 요청을 쏴 볼 수 있는 버튼
# ③ 프론트용 API 호출 코드 뼈대까지 자동 생성
# 우리가 표로 쓰던 명세의 '전산화 버전'이라고 보면 됩니다.`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: 'API는 계약이다' },
  { n: 2, label: 'URL 설계' },
  { n: 3, label: 'HTTP 메서드' },
  { n: 4, label: '상태 코드' },
  { n: 5, label: '바디 설계' },
  { n: 6, label: '쿼리스트링' },
  { n: 7, label: '명세서와 문서화' },
];

export default function Lv2RestApiDesignPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 중급 과정</div>
        <h1>📐 REST API 설계<br />: 주소와 동사의 문법</h1>
        <p>
          기능부터 코딩하고 싶은 마음, 잠깐만 눌러 두세요. 실전 웹 서비스는{' '}
          <strong>프론트와 백엔드 두 사람이 동시에</strong> 만듭니다. 둘이 싸우지 않으려면
          코드보다 먼저 써야 하는 게 있어요 — 바로 <strong>API 명세, 즉 계약서</strong>입니다.
          이 코스에서는 URL이라는 '주소'와 HTTP 메서드라는 '동사'로 그 계약서를 쓰는
          문법을 익힙니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 55분</span>
          <span className="chip">실습: 미니 블로그 API 명세서 작성 · 짝 리뷰</span>
          <span className="chip">선수 지식: HTTP 요청/응답 기초</span>
        </div>
      </div>

      {/* 섹션 바로가기 */}
      <nav className="pill-nav">
        {SECTIONS.map((s) => (
          <a key={s.n} href={`#sec-${s.n}`}>
            <b>{s.n}</b>
            {s.label}
          </a>
        ))}
      </nav>

      <Section n={1} title="API는 계약이다" sub="코드보다 명세가 먼저인 이유">
        <p>
          웹 서비스는 크게 두 덩어리예요. 화면을 그리는 <strong>프론트엔드</strong>, 데이터를
          보관하고 처리하는 <strong>백엔드</strong>. 이 둘은 서로의 코드를 보지 않습니다. 대신{' '}
          <strong>API</strong>라는 창구로만 대화하죠. "이 주소로 이렇게 요청하면, 이런 모양의
          응답을 준다" — 이 약속의 목록이 <strong>API 명세</strong>이고, 성격은 정확히{' '}
          <strong>계약서</strong>입니다.
        </p>
        <Code>{CODE_CONTRACT}</Code>
        <p>
          명세를 먼저 쓰면 개발이 빨라지는 원리는 단순해요. 계약서가 확정되는 순간{' '}
          <strong>프론트와 백엔드가 서로를 기다릴 필요가 없어집니다.</strong> 프론트는 명세에
          적힌 응답 모양대로 가짜 데이터를 만들어 화면을 완성하고, 백엔드는 명세대로 진짜
          서버를 만들죠. 마지막 날 둘을 연결하면 — 계약대로 만들었으니 맞물립니다. 반대로
          명세 없이 각자 출발하면, 연결하는 날 "필드 이름이 다르다", "그런 주소 없는데요"가
          쏟아져요.
        </p>
        <p>
          그 계약서를 쓰는 문법으로 오늘날 사실상 표준이 된 것이 <strong>REST</strong>
          (REpresentational State Transfer)입니다. 특별한 라이브러리나 프로토콜이 아니라,{' '}
          <strong>HTTP가 원래 가진 재료</strong>(URL, 메서드, 상태 코드)를 정직하게 쓰자는{' '}
          <strong>설계 스타일</strong>이에요. 재료가 이미 모든 브라우저·서버·언어에 들어 있으니
          별도 학습 비용이 거의 없고, 그래서 어디서나 통합니다.
        </p>
        <div className="tip">
          <b>비유</b> API 명세는 <strong>식당의 메뉴판</strong>이에요. 손님(프론트)은 주방(백엔드)
          내부를 몰라도 메뉴판만 보면 주문할 수 있죠. 메뉴판이 정확하면 주방이 바빠도 홀은
          돌아갑니다. 이 코스의 목표는 "읽기 좋은 메뉴판을 쓰는 법"입니다.
        </div>
      </Section>

      <Section n={2} title="리소스 중심 URL 설계" sub="URL에는 명사, 행동은 메서드로">
        <p>
          REST의 첫 번째 문법은 이거예요. <strong>URL은 '자원(리소스)의 주소'다.</strong> 글,
          댓글, 좋아요, 사용자 — 서비스가 다루는 데이터 덩어리 하나하나가 리소스입니다. 주소는
          그 리소스를 가리키기만 하고, "가져와라 / 만들어라 / 지워라" 같은 <strong>행동은
          다음 섹션의 HTTP 메서드</strong>가 맡아요. 그래서 URL에는 <strong>명사만, 복수형으로</strong>{' '}
          씁니다.
        </p>
        <Code>{CODE_BAD_URLS}</Code>
        <p>
          왜 복수형(<span className="icode">/posts</span>)일까요? <span className="icode">/posts</span>는
          '글들이 모인 서랍'이고, <span className="icode">/posts/3</span>은 '그 서랍의 3번 글'—
          하나의 규칙으로 목록과 개별 항목을 모두 표현할 수 있기 때문입니다. 단수·복수를
          섞으면(<span className="icode">/post</span>, <span className="icode">/comments</span>)
          쓰는 사람이 매번 "여긴 뭐였지?"를 기억해야 해요. <strong>일관성이 곧 사용성</strong>입니다.
        </p>
        <p>
          리소스끼리 소속 관계가 있으면 <strong>경로의 계층</strong>으로 드러냅니다. "3번 글에
          달린 댓글들"은 <span className="icode">/posts/3/comments</span> — 폴더 경로를 읽듯
          자연스럽게 읽히죠.
        </p>
        <Code>{CODE_HIERARCHY}</Code>
        <div className="tip">
          <b>스스로 점검하는 법</b> 설계한 URL에서 <strong>동사를 찾아보세요.</strong>{' '}
          <span className="icode">get</span>, <span className="icode">create</span>,{' '}
          <span className="icode">delete</span>, <span className="icode">update</span> 같은 단어가
          URL 안에 보인다면 십중팔구 메서드가 할 일을 URL이 뺏은 겁니다. 동사를 지우고,
          그 뜻을 메서드로 옮기세요.
        </div>
      </Section>

      <Section n={3} title="HTTP 메서드의 정확한 의미" sub="같은 주소, 다른 동사 — 그리고 멱등성">
        <p>
          URL이 명사라면 <strong>HTTP 메서드는 동사</strong>입니다. 같은{' '}
          <span className="icode">/posts/3</span>이라도 <span className="icode">GET</span>이면
          "읽어 줘", <span className="icode">DELETE</span>면 "지워 줘"가 되죠. 다섯 동사의
          용도를 정확히 구분하는 것이 이 섹션의 전부입니다.
        </p>
        <Code>{CODE_METHODS}</Code>
        <p>
          가장 자주 헷갈리는 짝이 <strong>PUT과 PATCH</strong>예요. 둘 다 '수정'이지만 방식이
          다릅니다. <span className="icode">PUT</span>은 <strong>전체 교체</strong> — 리소스를
          보낸 내용으로 통째로 갈아 끼웁니다. <span className="icode">PATCH</span>는{' '}
          <strong>부분 수정</strong> — 보낸 필드만 바꾸고 나머지는 그대로 둡니다. 이 차이를
          모르고 PUT에 제목만 담아 보내면, 본문과 태그가 증발하는 사고가 납니다.
        </p>
        <Code>{CODE_PUT_PATCH}</Code>
        <p>
          표의 마지막 열, <strong>멱등성(idempotency)</strong>도 짚고 갑시다. "같은 요청을 두 번
          보내도 결과가 한 번 보낸 것과 같은가?"라는 질문이에요. <span className="icode">GET</span>은
          몇 번을 읽어도 데이터가 변하지 않고, <span className="icode">DELETE</span>는 두 번째엔
          이미 없지만 '없는 상태'라는 결과는 같으니 멱등합니다. 반면{' '}
          <span className="icode">POST</span>는 보낼 때마다 <strong>새 리소스가 하나 더</strong>{' '}
          생기니 멱등하지 않아요.
        </p>
        <div className="warn">
          <b>멱등성이 실무에서 중요한 순간</b> 네트워크는 종종 끊깁니다. 응답을 못 받은
          클라이언트가 "재시도해도 될까?"를 판단할 때 기준이 멱등성이에요. 멱등한 요청은
          안심하고 다시 보내면 되지만, 멱등하지 않은 POST를 무심코 재시도하면 —
          글이 두 번 등록되고, 결제가 두 번 나갑니다. "저장 버튼 연타" 사고의 정체가
          바로 이것입니다.
        </div>
      </Section>

      <Section n={4} title="상태 코드 제대로 고르기" sub="숫자 세 자리에 담긴 책임 소재">
        <p>
          응답의 첫인상은 <strong>상태 코드</strong>입니다. 바디를 열어 보기 전에, 숫자 세 자리가
          "성공했는지, 실패했다면 <strong>누구 잘못</strong>인지"를 먼저 말해 주죠. 이 분류만
          정확해도 디버깅 시간이 절반으로 줄어듭니다.
        </p>
        <Code>{CODE_STATUS}</Code>
        <p>
          성공 3형제부터 구분해 볼게요. <strong>200</strong>은 "성공했고, 결과물을 바디에
          담았다" — 조회·수정의 기본값. <strong>201</strong>은 "성공했는데, 그 결과{' '}
          <strong>새로운 것이 만들어졌다</strong>" — POST 성공 전용. <strong>204</strong>는
          "성공했지만 돌려줄 내용이 없다" — 삭제 성공이 대표적이에요. 지운 것을 다시 보여 줄
          이유는 없으니까요.
        </p>
        <p>
          4xx 형제들은 전부 <strong>"요청이 잘못됐다"</strong>는 뜻인데, 이유가 다 다릅니다.{' '}
          <strong>400</strong>은 요청 자체의 형식 오류(필수 필드 누락, 타입 틀림).{' '}
          <strong>401</strong>은 "누구신지 모르겠어요"(로그인 필요), <strong>403</strong>은
          "누군지는 알겠는데 이건 못 하세요"(남의 글 삭제 시도). <strong>404</strong>는 그런
          리소스가 없음, <strong>409</strong>는 현재 상태와 충돌 — 이미 좋아요를 누른 글에 또
          좋아요를 누를 때가 딱 409입니다.
        </p>
        <div className="warn">
          <b>500은 서버의 사과문</b> <strong>500</strong>은 "당신 잘못이 아니라 우리 서버 코드가
          터졌습니다"라는 뜻이에요. 그래서 클라이언트 실수(잘못된 입력, 없는 글 요청)까지
          전부 500으로 뭉뚱그리면 안 됩니다. 프론트 입장에선 "내가 고칠 문제인지(4xx),
          기다릴 문제인지(5xx)"를 구분할 수 없게 되고, 서버 개발자는 진짜 버그 알림이
          가짜 알림에 파묻혀 버려요. <strong>4xx로 걸러낼 수 있는 건 전부 4xx로.</strong>
        </div>
        <div className="tip">
          <b>외우는 요령</b> 401과 403이 늘 헷갈린다면 이렇게 기억하세요. 401은{' '}
          <strong>"출입증이 없네요"</strong>(Unauthorized = 인증 안 됨), 403은{' '}
          <strong>"출입증은 있는데 이 방은 못 들어가요"</strong>(Forbidden = 권한 없음).
          문 앞의 문제냐, 문 안의 문제냐의 차이입니다.
        </div>
      </Section>

      <Section n={5} title="요청·응답 바디 설계" sub="필드 이름·목록 포장·에러 형식의 일관성">
        <p>
          주소와 동사, 상태 코드까지 정했으면 이제 <strong>내용물(JSON 바디)</strong>의 규칙을
          정할 차례예요. 여기서 중요한 건 화려함이 아니라 <strong>일관성</strong>입니다. 딱
          세 가지만 팀 규칙으로 못 박으면 됩니다.
        </p>
        <p>
          <strong>첫째, 필드 네이밍 규칙.</strong> JSON 필드 이름은{' '}
          <span className="icode">camelCase</span>(<span className="icode">likeCount</span>,{' '}
          <span className="icode">createdAt</span>)로 통일하는 것이 자바스크립트 진영의
          관례예요. 중요한 건 어느 쪽을 고르느냐보다 <strong>한 API 안에서 절대 섞지 않는 것</strong>
          입니다. <span className="icode">created_at</span>과 <span className="icode">likeCount</span>가
          한 응답에 공존하는 순간, 프론트 코드엔 "이 필드는 어느 표기였더라?" 하는 지뢰가
          깔립니다.
        </p>
        <p>
          <strong>둘째, 목록 응답에는 포장지를.</strong> 목록을 배열로만 덜렁 보내지 말고,
          객체로 감싸 <strong>페이지네이션 정보</strong>를 함께 담습니다.
        </p>
        <Code>{CODE_LIST_BODY}</Code>
        <p>
          <span className="icode">page</span>·<span className="icode">limit</span>·
          <span className="icode">total</span> 세 필드만 있으면 프론트는 "전체 6페이지 중
          2페이지" 같은 UI를 그릴 수 있어요. 글이 5만 개가 되어도 서버는 한 번에 10개만
          보내면 되니 성능도 지켜집니다.
        </p>
        <p>
          <strong>셋째, 에러 응답도 형식을 통일.</strong> 성공 응답만 설계하는 건 반쪽짜리
          계약서예요. 실패했을 때 바디가 어떤 모양인지도 계약에 들어가야 프론트가 에러 처리를
          한 가지 코드로 끝낼 수 있습니다.
        </p>
        <Code>{CODE_ERROR_BODY}</Code>
        <div className="tip">
          <b>code와 message의 역할 분담</b> <span className="icode">code</span>는{' '}
          <strong>기계</strong>가 읽습니다 — 프론트가 "이 코드면 로그인 화면으로 보내자"처럼
          분기하는 데 쓰죠. <span className="icode">message</span>는 <strong>사람</strong>이
          읽습니다 — 화면에 그대로 띄워도 어색하지 않은 문장으로요. 하나는 분기용, 하나는
          표시용 — 둘을 겸직시키지 마세요.
        </div>
      </Section>

      <Section n={6} title="쿼리스트링 활용" sub="검색·필터·정렬 — 그리고 경로 vs 쿼리 선택 기준">
        <p>
          "여행 키워드로 검색하고, 최신순으로 정렬해서, 2페이지를 보여 줘" — 이런 요구를
          전부 URL 경로로 만들 순 없습니다. 조합이 무한하니까요. 이때 쓰는 것이{' '}
          <strong>쿼리스트링</strong>, 주소 뒤에 <span className="icode">?이름=값</span> 형태로
          붙는 <strong>옵션들</strong>입니다.
        </p>
        <Code>{CODE_QUERY}</Code>
        <p>
          <span className="icode">?sort=-createdAt</span>의 <strong>마이너스(-) 기호</strong>는
          "내림차순"을 뜻하는 널리 쓰이는 관례예요. <span className="icode">sort=createdAt</span>이면
          오래된 순, <span className="icode">sort=-createdAt</span>이면 최신순. 파라미터 하나로
          방향까지 표현되니 <span className="icode">order=desc</span> 같은 파라미터를 따로 둘
          필요가 없어 간결합니다. 물론 이것도 관례일 뿐 — <strong>어느 쪽을 쓰든 명세에 적고
          전체 API에서 통일</strong>하는 것이 본질이에요.
        </p>
        <p>
          그럼 <strong>경로 파라미터와 쿼리 파라미터는 언제 무엇을</strong> 쓸까요? 기준은 한
          줄입니다. <strong>그 값이 없으면 대상 자체가 특정되지 않는가?</strong> 3번 글의 댓글은{' '}
          <span className="icode">3</span>이 없으면 아예 성립하지 않으니{' '}
          <strong>경로</strong>(<span className="icode">/posts/3/comments</span>)로. 정렬·페이지는
          없어도 "기본 정렬, 1페이지"로 동작할 수 있으니 <strong>쿼리</strong>로. 즉 경로는
          자원의 <strong>주소</strong>, 쿼리는 자원을 보는 <strong>방식</strong>입니다.
        </p>
        <div className="tip">
          <b>직접 확인해 보기</b> 지금 쓰는 쇼핑몰이나 검색 사이트에서 검색을 한 뒤{' '}
          <strong>주소창을 관찰</strong>해 보세요. <span className="icode">?query=...</span>,{' '}
          <span className="icode">&sort=...</span>, <span className="icode">&page=...</span>가
          그대로 보일 거예요. 여러분이 매일 쓰는 서비스가 이미 이 문법으로 말하고 있습니다.
        </div>
      </Section>

      <Section n={7} title="명세서 작성과 문서화" sub="표 한 장으로 계약을 완성하기">
        <p>
          이제 배운 것을 전부 모아 <strong>계약서 본문</strong>을 씁니다. 거창한 도구는
          필요 없어요. <strong>메서드 · 경로 · 설명 · 성공 코드 · 주요 실패</strong> 다섯 열짜리
          표 하나면, 프론트와 백엔드가 같은 그림을 보고 일할 수 있습니다. 이 코스의 실습이
          바로 이 표를 완성하는 것이고요.
        </p>
        <Code>{CODE_SPEC_TABLE}</Code>
        <p>
          경로에 붙은 <strong><span className="icode">/v1</span></strong>이 보이시나요? API에{' '}
          <strong>버전</strong>을 붙이는 이유는 계약서라는 성격 그 자체에 있습니다. 서비스가
          자라면 응답 구조를 바꿔야 할 날이 반드시 와요. 그런데 이미 v1 계약을 믿고 만들어진
          프론트가 세상에 돌고 있다면? 기존 계약을 깨는 대신 <span className="icode">/v2</span>라는{' '}
          <strong>새 계약서</strong>를 나란히 열고, 옛 사용자가 이사 올 시간을 주는 겁니다.
          붙이는 시점은 <strong>첫 배포 때부터</strong> — 나중에 붙이려면 그게 바로 첫 번째
          계약 파기가 되니까요.
        </p>
        <p>
          표가 커지면 사람 손으로 관리하기 버거워지는데, 그때 등장하는 것이{' '}
          <strong>Swagger(OpenAPI)</strong>입니다. 명세를 기계가 읽을 수 있는 형식(YAML/JSON)으로
          쓰면, 문서 페이지 생성부터 "바로 쏴 보기" 버튼까지 도구가 대신해 줘요.
        </p>
        <Code>{CODE_OPENAPI}</Code>
        <div className="tip">
          <b>실습 진행법 — 짝 리뷰가 핵심</b> ① 위 양식으로 미니 블로그(글·댓글·좋아요)
          명세서를 각자 완성하세요. ② 짝과 서로 바꿔 읽으며 <strong>빈틈을 찾습니다</strong> —
          "없는 글에 댓글을 달면 몇 번이죠?", "좋아요를 두 번 누르면요?", "삭제 성공은 200인가요
          204인가요?" ③ 답이 막히는 곳이 바로 계약서의 구멍이에요. 실제 현장의 API 리뷰가
          정확히 이 대화로 이루어집니다.
        </div>
        <div className="warn">
          <b>명세는 살아 있는 문서</b> 한 번 쓰고 끝이 아니에요. 구현하다 보면 반드시 명세를
          고칠 일이 생깁니다. 그때 <strong>코드만 바꾸고 명세를 안 바꾸는 것</strong>이 최악의
          습관이에요 — 거짓말하는 계약서는 없느니만 못합니다. "명세를 먼저 고치고, 상대에게
          알리고, 코드를 바꾼다" — 이 순서를 몸에 붙이세요.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>📐 여기까지 왔다면</h3>
          <p className="muted">
            이제 여러분은 코드를 한 줄도 짜기 전에 <strong>서비스의 뼈대를 표 한 장으로 설계</strong>할
            수 있게 됐어요. 명사로 된 주소, 동사인 메서드, 책임 소재를 말하는 상태 코드,
            일관된 바디, 옵션을 담는 쿼리 — 그리고 그 전부를 담은 계약서까지. 그런데 계약서는
            결국 <strong>이행</strong>되어야 의미가 있죠. 여러분이 방금 쓴 미니 블로그 명세서를
            진짜로 응답하는 서버로 만들어 볼 차례입니다. 다음{' '}
            <Link to="/learn/lv2-express-crud"><strong>Express로 CRUD API 만들기</strong></Link>{' '}
            강좌에서, 이 계약서를 코드로 이행하며 "명세대로 만든다"는 감각을 손에 익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
