// 이 파일이 하는 일: "배열 메서드로 데이터 요리하기" 코스 — for문으로 데이터를 다루던
// 학생이 map·filter·reduce·sort·flatMap이라는 '주방 칼'을 손에 쥐고, API 응답을 화면에
// 딱 맞는 모양으로 썰고 볶는 실전 감각을 익히는 7개 섹션의 정적 학습 페이지.
// 최종 목표는 실습 — 주문 내역 JSON 200건에서 '고객별 총 구매액 TOP 5'와 '월별 매출
// 집계'를 배열 메서드만으로 만들어 내는 것이다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

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

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

const CODE_FOR_VS_METHOD = `// 같은 요리: "만원 이상 주문의 고객 이름만 뽑기"

// ① for문 — '어떻게' 할지를 한 줄씩 지시하는 명령형
const names = [];
for (let i = 0; i < orders.length; i++) {
  if (orders[i].amount >= 10000) {
    names.push(orders[i].customer);
  }
}

// ② 배열 메서드 — '무엇을' 원하는지만 말하는 선언형
const names2 = orders
  .filter((o) => o.amount >= 10000)   // 만원 이상만 골라서
  .map((o) => o.customer);            // 고객 이름으로 바꿔 줘

// 읽는 사람은 filter·map이라는 '동사'만 보고도
// "골라서 → 모양을 바꾼다"는 의도를 3초 만에 파악합니다.
// for문은 i, push, 조건문을 전부 따라 읽어야 같은 결론에 닿아요.`;

const CODE_MAP_SHAPE = `// 서버가 주는 모양 그대로의 API 응답 (스네이크 케이스, 원 단위 숫자)
const res = [
  { order_id: 1, customer_name: '김수습', total_price: 42000 },
  { order_id: 2, customer_name: '이신입', total_price: 18000 },
];

// 화면이 원하는 모양으로 '변환' — map은 요소마다 함수를 적용한 새 배열
const rows = res.map((o) => ({
  id: o.order_id,
  name: o.customer_name,
  price: o.total_price.toLocaleString() + '원',   // 42000 → "42,000원"
}));

// 원본 res는 그대로 남아 있어요. map은 항상 '새 배열'을 돌려줍니다.
// 서버 모양(res)과 화면 모양(rows)을 분리해 두면 서로를 망가뜨리지 않죠.`;

const CODE_MAP_MISTAKE = `// 흔한 실수 ①: 중괄호 블록을 열었는데 return을 빼먹음
const bad = res.map((o) => { o.order_id });        // [undefined, undefined] !
const ok1 = res.map((o) => { return o.order_id }); // [1, 2]
const ok2 = res.map((o) => o.order_id);            // 한 줄이면 return 생략 가능

// 흔한 실수 ②: 객체를 바로 돌려줄 땐 소괄호로 감싸기
const ok3 = res.map((o) => ({ id: o.order_id }));  // ( ) 없으면 블록으로 오해받음

// 두 번째 인자 index — 순위표 만들 때 요긴
const ranked = ['김수습', '이신입'].map((n, i) => (i + 1) + '위 ' + n);
// ['1위 김수습', '2위 이신입']`;

const CODE_FILTER_FIND = `// filter: 조건에 맞는 '전부' — 결과는 항상 배열 (0개여도 빈 배열)
const paid = orders.filter((o) => o.status === 'PAID');

// find: 조건에 맞는 '첫 하나' — 결과는 요소 하나 (없으면 undefined)
const target = orders.find((o) => o.order_id === 17);

// some: 하나라도 있나요? / every: 전부 그런가요? — 결과는 true/false
const hasRefund = orders.some((o) => o.status === 'REFUND');
const allPaid   = orders.every((o) => o.status === 'PAID');

// 용도 감각:
//   목록 화면에 뿌린다        → filter (배열이 필요하니까)
//   상세 화면에 하나 띄운다   → find   (한 건이면 충분하니까)
//   경고 배지를 띄울지 판단   → some / every (예/아니오만 필요하니까)`;

const CODE_REDUCE_SUM = `// reduce: 배열 전체를 '값 하나'로 접는다(fold)
const total = orders.reduce((acc, o) => acc + o.amount, 0);
//                            │    │                    └ 초기값: 누산기의 출발점
//                            │    └ 지금 처리 중인 요소
//                            └ 누산기(accumulator): 지금까지 접어 온 중간 결과

// 동작을 풀어 보면: 0 → 0+42000 → 42000+18000 → ... 마지막 값이 total
const avg = total / orders.length;   // 평균은 합계 ÷ 개수`;

const CODE_REDUCE_GROUP = `// 배열을 '객체'로 접기 — 고객별 총 구매액 (실습 과제의 심장)
const byCustomer = orders.reduce((acc, o) => {
  acc[o.customer] = (acc[o.customer] || 0) + o.amount;
  return acc;              // 다음 바퀴에 넘길 누산기를 반드시 돌려준다!
}, {});                    // 초기값: 빈 객체 — 여기에 키를 하나씩 채워 나간다

// 결과: { '김수습': 130000, '이신입': 87000, '박대리': 254000, ... }

// 같은 요령으로 '월별 매출 집계' — 날짜 문자열에서 월만 잘라 키로 쓴다
const byMonth = orders.reduce((acc, o) => {
  const month = o.ordered_at.slice(0, 7);     // '2026-07-13' → '2026-07'
  acc[month] = (acc[month] || 0) + o.amount;
  return acc;
}, {});
// 결과: { '2026-05': 1200000, '2026-06': 980000, '2026-07': 1430000 }`;

const CODE_REDUCE_BUG = `// 초기값을 빼먹으면? '첫 요소'가 누산기의 출발점이 되어 버립니다.
[3, 5, 7].reduce((acc, n) => acc + n);       // 15 — 숫자 합계라 우연히 정상

orders.reduce((acc, o) => acc + o.amount);   // 앗! 첫 바퀴의 acc가 '주문 객체'
// → "[object Object]18000..." 같은 괴상한 문자열이 만들어집니다.

// 빈 배열이면 아예 에러:
[].reduce((a, b) => a + b);      // TypeError: Reduce of empty array
[].reduce((a, b) => a + b, 0);   // 0 — 초기값이 있으면 안전하게 0 반환

// 결론: reduce의 초기값은 '생략 가능'이 아니라 '항상 쓰는 것'으로 외우세요.`;

const CODE_SORT_COMPARE = `// 비교 함수 (a, b) => 리턴값의 약속:
//   음수 → a가 앞으로 / 양수 → b가 앞으로 / 0 → 순서 유지

[5, 1, 30].sort();                  // [1, 30, 5] ?! — 문자열로 비교하는 함정
                                    // '30' < '5' (첫 글자 '3' < '5')
[5, 1, 30].sort((a, b) => a - b);   // [1, 5, 30] — 숫자 오름차순
[5, 1, 30].sort((a, b) => b - a);   // [30, 5, 1] — 내림차순

// 문자열(특히 한글)은 localeCompare가 정석
names.sort((a, b) => a.localeCompare(b, 'ko'));

// 날짜는 Date로 바꾸면 빼기 연산이 타임스탬프 차이가 되어 숫자처럼 정렬
logs.sort((a, b) => new Date(a.ordered_at) - new Date(b.ordered_at));`;

const CODE_SORT_MUTATE = `// sort는 '원본을 바꾸는' 몇 안 되는 메서드 — map·filter와 성격이 다르다!
const prices = [30, 10, 20];
const sorted = prices.sort((a, b) => a - b);
console.log(prices);   // [10, 20, 30] — 원본까지 바뀌어 버렸다
console.log(sorted === prices);   // true — 같은 배열을 가리키는 중

// 방어법 ①: toSorted — 원본은 두고 '새 배열'을 돌려주는 신형 메서드
const s1 = prices.toSorted((a, b) => a - b);

// 방어법 ②: 스프레드로 복사본을 만든 뒤 그 복사본을 정렬
const s2 = [...prices].sort((a, b) => a - b);

// React에서 state 배열을 그냥 sort하면 원본이 몰래 바뀌어
// "화면이 갱신 안 돼요" 류의 미스터리 버그가 됩니다. 복사가 습관!`;

const CODE_PIPELINE = `// 실습 과제 절반 완성: '고객별 총 구매액 TOP 5 랭킹'
// 재료(주문 200건)가 파이프라인을 타고 완성 접시가 되는 과정

const top5 = Object.entries(          // ③ 객체 → [이름, 합계] 쌍의 배열로
    orders
      .filter((o) => o.status === 'PAID')   // ① 결제 완료 주문만 골라서
      .reduce((acc, o) => {                 // ② 고객별 합계 객체로 접고
        acc[o.customer] = (acc[o.customer] || 0) + o.amount;
        return acc;
      }, {})
  )
  .map((pair) => ({ name: pair[0], sum: pair[1] }))  // ④ 화면용 객체로 변환
  .sort((a, b) => b.sum - a.sum)                     // ⑤ 큰 금액부터 정렬
  .slice(0, 5);                                      // ⑥ 앞의 5명만 잘라 내기

// [{ name: '박대리', sum: 254000 }, { name: '김수습', sum: 130000 }, ...]`;

const CODE_PIPELINE_DEBUG = `// 체인이 길어지면 중간 결과에 '이름'을 붙여 끊어 두세요.
const paidOrders  = orders.filter((o) => o.status === 'PAID');
const sumByName   = paidOrders.reduce((acc, o) => {
  acc[o.customer] = (acc[o.customer] || 0) + o.amount;
  return acc;
}, {});
const rankingRows = Object.entries(sumByName)
  .map((pair) => ({ name: pair[0], sum: pair[1] }))
  .sort((a, b) => b.sum - a.sum);
const top5 = rankingRows.slice(0, 5);

// 장점 ①: 어디서 틀렸는지 console.log(sumByName)으로 바로 확인
// 장점 ②: paidOrders, rankingRows라는 이름 자체가 주석 역할
// 감각: 한 체인이 4~5단계를 넘거나, 중간 모양이 머리에 안 그려지면 쪼갠다.`;

const CODE_FLATMAP = `// 실전 데이터는 중첩되어 있다 — 주문 '안에' 상품 배열이 또 있는 모양
const orders = [
  { id: 1, items: [{ name: '키보드', qty: 1 }, { name: '마우스', qty: 2 }] },
  { id: 2, items: [{ name: '모니터', qty: 1 }] },
];

// map만 쓰면 '배열의 배열'이 되어 버린다
orders.map((o) => o.items);
// [ [키보드, 마우스], [모니터] ]  ← 두 겹. 화면에 바로 못 뿌림

// flatMap = map 하고 나서 '한 겹' 펼치기(flat)
orders.flatMap((o) => o.items);
// [ 키보드, 마우스, 모니터 ]     ← 한 겹. 바로 쓸 수 있는 평평한 목록

// 펼친 뒤엔 배운 칼들이 전부 그대로 통한다 — 전체 판매 수량:
orders.flatMap((o) => o.items).reduce((acc, it) => acc + it.qty, 0);  // 4`;

const CODE_OPTIONAL = `// 서버 응답엔 '없을 수도 있는' 값이 흔하다 — undefined 방어가 필수
const first = orders.find((o) => o.customer === '박대리');
console.log(first.amount);    // 박대리 주문이 없으면? first는 undefined
                              // → TypeError로 화면 전체가 하얗게 터진다!

// 옵셔널 체이닝 ?. — 왼쪽이 없으면 에러 대신 조용히 undefined
console.log(first?.amount);                     // undefined — 앱은 계속 산다

// ??(널 병합)와 짝지으면 기본값까지 한 줄
const city  = user?.address?.city ?? '미입력';  // 중첩 필드도 안전하게
const items = order.items ?? [];                // 배열 필드는 '빈 배열'로 방어
items.map((it) => it.name);                     // 빈 배열이면 그냥 빈 결과`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다(React 입문 섹션 2). 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: '왜 메서드인가' },
  { n: 2, label: 'map' },
  { n: 3, label: 'filter·find' },
  { n: 4, label: 'reduce' },
  { n: 5, label: 'sort와 불변성' },
  { n: 6, label: '체이닝' },
  { n: 7, label: '중첩 데이터' },
];

export default function Lv2ArrayDataWranglingPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 중급 과정</div>
        <h1>배열 메서드로<br />데이터 요리하기 🔪</h1>
        <p>
          실전 웹 개발의 절반은 <strong>데이터 다듬기</strong>예요. 서버가 주는 JSON은 날것의
          재료일 뿐, 화면에 올리려면 골라내고(filter), 모양을 바꾸고(map), 집계하고(reduce),
          줄을 세워야(sort) 하죠. 이 코스에서는 for문 대신{' '}
          <strong>배열 메서드</strong>라는 잘 벼린 칼들로 — 주문 내역 200건을{' '}
          <strong>TOP 5 랭킹과 월별 매출표</strong>로 요리하는 실전 기술을 익힙니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 50분</span>
          <span className="chip">실습: 주문 200건 → TOP 5 랭킹·월별 집계</span>
          <span className="chip">선수 지식: JS 기초 문법·함수</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="for문 대신 메서드를 쓰는 이유" sub="'어떻게'가 아니라 '무엇을' 말하는 코드">
        <p>
          기초 과정에서 배운 for문으로도 모든 데이터 작업은 <strong>가능</strong>해요. 그런데
          실무 코드를 열어 보면 for문 대신 <span className="icode">map</span>·
          <span className="icode">filter</span>·<span className="icode">reduce</span>가 압도적으로
          많습니다. 왜일까요? 답은 "누가 읽는 코드인가"에 있어요. for문은 인덱스 변수, 조건문,
          push까지 <strong>어떻게(how)</strong> 할지를 전부 지시하지만, 배열 메서드는{' '}
          <strong>무엇을(what)</strong> 원하는지만 선언합니다.
        </p>
        <Code>{CODE_FOR_VS_METHOD}</Code>
        <p>
          두 코드는 결과가 완전히 같아요. 하지만 두 번째 코드를 읽는 동료는{' '}
          <span className="icode">filter</span>와 <span className="icode">map</span>이라는 단어만
          보고도 <strong>"골라서, 바꾸는구나"</strong>를 3초 만에 압니다. 코드는 쓰는 시간보다{' '}
          <strong>읽히는 시간이 훨씬 긴</strong> 물건이라, 이 3초의 차이가 쌓이면 팀 전체의
          속도가 됩니다.
        </p>
        <div className="tip">
          <b>비유</b> for문이 "칼을 들고, 45도로 눕혀서, 3mm 간격으로 내리치세요"라는{' '}
          <strong>동작 지시서</strong>라면, 배열 메서드는 "채 썰어 주세요"라는{' '}
          <strong>주문서</strong>예요. 주방(자바스크립트 엔진)이 알아서 손을 움직이고, 우리는
          요리의 의도만 말하면 됩니다. 이 코스의 목표는 상황마다 어떤 주문서를 내밀지 아는
          감각이에요.
        </div>
      </Section>

      <Section n={2} title="map — 모양 바꾸기" sub="API 응답을 화면용 데이터로 변환하는 기본 칼">
        <p>
          <span className="icode">map</span>은 배열의 <strong>모든 요소에 같은 함수를 적용해
          새 배열을 만드는</strong> 메서드예요. 개수는 그대로, 모양만 바뀝니다. 가장 흔한
          실전 용도는 <strong>서버 모양 → 화면 모양 변환</strong>이에요. 서버는{' '}
          <span className="icode">order_id</span>·<span className="icode">total_price</span> 같은
          자기 사정에 맞는 필드를 주지만, 화면은 "42,000원"처럼 다듬어진 값을 원하니까요.
        </p>
        <Code>{CODE_MAP_SHAPE}</Code>
        <p>
          여기서 원칙 하나를 새기세요. <strong>map은 원본을 절대 건드리지 않습니다.</strong>{' '}
          변환 결과는 언제나 <strong>새 배열</strong>이고, <span className="icode">res</span>는
          받았을 때 모습 그대로 남아요. 원본이 살아 있으면 "다른 화면에서는 원 단위 숫자가
          필요한데?" 같은 요구가 와도 다시 요리할 수 있죠. 이 <strong>불변성 감각</strong>은
          섹션 5의 sort에서 다시 시험대에 오릅니다.
        </p>
        <Code>{CODE_MAP_MISTAKE}</Code>
        <div className="tip">
          <b>디버깅 신호</b> map의 결과가 <span className="icode">[undefined, undefined, ...]</span>
          라면 십중팔구 <strong>반환 누락</strong>이에요. 화살표 함수에서 중괄호{' '}
          <span className="icode">{'{ }'}</span>를 여는 순간 "여기부터는 블록"이라{' '}
          <span className="icode">return</span>을 직접 써야 하고, 객체를 바로 돌려주고 싶으면
          소괄호로 <span className="icode">{'({ ... })'}</span>처럼 감싸야 합니다. 둘 다 자바스크립트
          문법이 놓은 덫이라, 알고 있으면 5초, 모르면 한 시간짜리 버그예요.
        </div>
      </Section>

      <Section n={3} title="filter와 find — 골라내기" sub="전부가 필요한가, 첫 하나면 충분한가">
        <p>
          <span className="icode">filter</span>는 <strong>조건 함수</strong> 하나를 받아, 그
          함수가 <span className="icode">true</span>를 돌려준 요소들만 모은 <strong>새 배열</strong>을
          만들어요. "결제 완료 주문만", "만원 이상만", "이번 달 것만" — 목록 화면의 온갖
          필터가 전부 이 한 줄입니다.
        </p>
        <Code>{CODE_FILTER_FIND}</Code>
        <p>
          <span className="icode">filter</span>와 <span className="icode">find</span>는 겉이
          닮아서 자주 헷갈려요. 구분 기준은 <strong>결과의 자료형</strong>입니다.{' '}
          <span className="icode">filter</span>는 조건에 맞는 <strong>전부</strong>를 담은{' '}
          <strong>배열</strong>(하나도 없으면 빈 배열 <span className="icode">[]</span>),{' '}
          <span className="icode">find</span>는 조건에 맞는 <strong>첫 하나</strong>인{' '}
          <strong>요소 자체</strong>(없으면 <span className="icode">undefined</span>)를 돌려줘요.
          목록엔 filter, 상세 조회엔 find — 손에 익혀 두세요.
        </p>
        <p>
          한편 "있는지 <strong>여부만</strong> 궁금할" 때가 있어요. 환불 건이 하나라도 있으면
          경고 배지를 띄운다든가요. 그럴 땐 배열을 만들 필요 없이{' '}
          <span className="icode">some</span>(하나라도 만족?)과{' '}
          <span className="icode">every</span>(전부 만족?)가 <span className="icode">true/false</span>
          로 바로 답해 줍니다.
        </p>
        <div className="tip">
          <b>find의 함정 예고</b> <span className="icode">find</span>가 못 찾으면{' '}
          <span className="icode">undefined</span>를 돌려준다고 했죠? 그 undefined에 점(.)을 찍는
          순간 앱이 터집니다. 이 방어법은 섹션 7의 <strong>옵셔널 체이닝</strong>에서 제대로
          다뤄요 — 일단 "find 결과는 없을 수도 있다"만 기억해 두세요.
        </div>
      </Section>

      <Section n={4} title="reduce — 집계의 만능 칼" sub="배열 하나를 값 하나로 '접는' 기술">
        <p>
          map과 filter는 배열을 넣으면 배열이 나와요. 그런데 실습 과제인 "고객별{' '}
          <strong>총 구매액</strong>"이나 "월별 <strong>매출 합계</strong>"는 배열이 아니라{' '}
          <strong>접힌 결과 하나</strong>가 필요하죠. 그 접기(fold)를 담당하는 칼이{' '}
          <span className="icode">reduce</span>입니다. 누산기(accumulator)라는 그릇을 들고
          배열을 한 바퀴 돌면서, 요소를 하나씩 그릇에 <strong>누적</strong>해 나가요.
        </p>
        <Code>{CODE_REDUCE_SUM}</Code>
        <p>
          진짜 힘은 누산기가 <strong>숫자일 필요가 없다</strong>는 데서 나옵니다. 빈 객체{' '}
          <span className="icode">{'{}'}</span>를 초기값으로 주면, 배열을 돌며 객체에 키를 채우는{' '}
          <strong>그룹핑</strong>이 돼요. 실습 과제의 두 집계가 모두 이 패턴 하나로 풀립니다.
        </p>
        <Code>{CODE_REDUCE_GROUP}</Code>
        <p>
          그리고 reduce에서 가장 자주 터지는 버그 — <strong>초기값 누락</strong>입니다. 초기값을
          생략하면 자바스크립트는 <strong>배열의 첫 요소</strong>를 누산기의 출발점으로 삼아요.
          숫자 배열의 합계라면 우연히 맞지만, 객체 배열이라면 첫 바퀴의 누산기가{' '}
          <strong>주문 객체 자체</strong>가 되어 엉뚱한 결과가 나옵니다.
        </p>
        <Code>{CODE_REDUCE_BUG}</Code>
        <div className="tip">
          <b>읽는 요령</b> reduce가 어렵게 느껴지면 <strong>초기값부터</strong> 읽으세요. 마지막
          인자가 <span className="icode">0</span>이면 "아, 숫자로 접는구나(합계)",{' '}
          <span className="icode">{'{}'}</span>면 "객체로 접는구나(그룹핑)",{' '}
          <span className="icode">[]</span>면 "새 배열을 조립하는구나". 초기값이 결과물의
          자료형을 미리 알려 주는 예고편이에요.
        </div>
      </Section>

      <Section n={5} title="sort와 불변성 함정" sub="비교 함수의 약속, 그리고 원본을 지키는 법">
        <p>
          랭킹을 만들려면 줄 세우기가 필요하죠. <span className="icode">sort</span>는{' '}
          <strong>비교 함수</strong> <span className="icode">(a, b) =&gt; 리턴값</span>을 받는데,
          약속은 딱 세 가지예요. <strong>음수면 a가 앞</strong>, <strong>양수면 b가 앞</strong>,
          0이면 그대로. 그래서 숫자 오름차순은 <span className="icode">a - b</span>, 내림차순은{' '}
          <span className="icode">b - a</span> 한 줄로 끝납니다.
        </p>
        <Code>{CODE_SORT_COMPARE}</Code>
        <p>
          비교 함수를 <strong>생략하면</strong> 모든 요소를 <strong>문자열로 바꿔</strong>{' '}
          사전순 비교합니다. 그래서 <span className="icode">[5, 1, 30]</span>이{' '}
          <span className="icode">[1, 30, 5]</span>가 되는 유명한 함정이 생겨요. 숫자 정렬엔
          비교 함수 필수, 한글 문자열엔 <span className="icode">localeCompare</span>, 날짜는{' '}
          <span className="icode">new Date()</span>로 바꿔 숫자처럼 — 세 가지 요령을 세트로
          기억하세요.
        </p>
        <p>
          더 무서운 함정은 따로 있어요. map·filter가 새 배열을 돌려주는 것과 달리,{' '}
          <span className="icode">sort</span>는 <strong>원본 배열 자체를 재배열</strong>합니다.
          정렬 한 번 했을 뿐인데 다른 화면의 순서까지 바뀌어 있는 미스터리의 범인이 대개
          이 녀석이에요.
        </p>
        <Code>{CODE_SORT_MUTATE}</Code>
        <div className="tip">
          <b>방어 습관</b> 원본을 지키려면 <span className="icode">toSorted</span>(정렬된{' '}
          <strong>새 배열</strong>을 돌려주는 신형 메서드)를 쓰거나, 구형 환경까지 생각하면{' '}
          <span className="icode">[...arr].sort(...)</span>처럼 <strong>스프레드 복사 후 정렬</strong>
          하세요. 같은 이유로 원본을 바꾸는 <span className="icode">reverse</span>·
          <span className="icode">splice</span>에도 <span className="icode">toReversed</span>·
          <span className="icode">toSpliced</span>라는 안전한 짝이 있습니다.
        </div>
      </Section>

      <Section n={6} title="체이닝으로 파이프라인 만들기" sub="filter → reduce → sort를 잇는 데이터 가공 라인">
        <p>
          칼을 하나씩 익혔으니 이제 <strong>요리 라인</strong>을 만들 차례예요. 배열 메서드는
          결과가 다시 배열(또는 접힌 값)이라, 점(.)으로 <strong>이어 붙일</strong> 수 있습니다.
          재료가 컨베이어 벨트를 타고 공정을 하나씩 통과하는 그림이죠. 실습 과제의 절반인{' '}
          <strong>TOP 5 랭킹</strong>이 정확히 이 파이프라인입니다.
        </p>
        <Code>{CODE_PIPELINE}</Code>
        <p>
          중간에 낯선 얼굴이 하나 있죠? reduce로 접은 결과는 <strong>객체</strong>라서 배열
          메서드를 바로 못 씁니다. <span className="icode">Object.entries()</span>가 객체를{' '}
          <span className="icode">[키, 값]</span> 쌍의 <strong>배열</strong>로 되돌려 주는 다리
          역할을 해요. 객체 ↔ 배열을 오가는 이 왕복이 실전 집계 코드의 단골 패턴입니다.
        </p>
        <p>
          다만 체이닝에도 <strong>과유불급</strong>이 있어요. 한 체인이 너무 길어지면 중간에
          어떤 모양의 데이터가 흐르는지 머리로 따라가기 어려워집니다. 그럴 땐 중간 결과를
          변수로 끊고 <strong>이름</strong>을 붙이세요.
        </p>
        <Code>{CODE_PIPELINE_DEBUG}</Code>
        <div className="tip">
          <b>쪼개는 기준</b> ① 체인이 4~5단계를 넘어갈 때, ② 중간 데이터의 모양이 머리에
          그려지지 않을 때, ③ 같은 중간 결과를 <strong>두 군데 이상</strong>에서 쓸 때 — 이
          셋 중 하나라도 걸리면 변수나 함수로 쪼갤 타이밍이에요. 멋져 보이는 한 줄보다,
          내일의 내가 읽을 수 있는 다섯 줄이 낫습니다.
        </div>
      </Section>

      <Section n={7} title="실전 — 중첩 데이터 다루기" sub="flatMap과 옵셔널 체이닝으로 진짜 API 응답 상대하기">
        <p>
          연습용 데이터는 평평하지만, 진짜 API 응답은 <strong>객체 안에 배열, 배열 안에 객체</strong>가
          겹겹이 쌓여 있어요. 주문 하나에 상품이 여러 개 담기는 것처럼요. 이 중첩을 다루는
          두 가지 필수 도구로 코스를 마무리합니다.
        </p>
        <p>
          첫째, <strong><span className="icode">flatMap</span></strong>. "모든 주문의 상품을
          한 줄로 나열하고 싶다"에서 map만 쓰면 <strong>배열의 배열</strong>이라는 두 겹 결과가
          나와요. flatMap은 map을 하고 나서 <strong>한 겹을 펼쳐(flat)</strong>, 바로 쓸 수 있는
          평평한 배열을 만들어 줍니다.
        </p>
        <Code>{CODE_FLATMAP}</Code>
        <p>
          둘째, <strong>옵셔널 체이닝 <span className="icode">?.</span></strong>. 섹션 3에서
          예고했죠 — <span className="icode">find</span>가 못 찾으면{' '}
          <span className="icode">undefined</span>고, 중첩 응답에는 없을 수도 있는 필드가
          흔합니다. undefined에 점을 찍는 순간 <span className="icode">TypeError</span>로 화면이
          통째로 하얗게 되니, <span className="icode">?.</span>로 방어벽을 세워야 해요.
        </p>
        <Code>{CODE_OPTIONAL}</Code>
        <div className="tip">
          <b>이제 실습입니다</b> 주문 내역 JSON 200건을 내려받아 두 접시를 완성해 보세요.
          ① <strong>고객별 총 구매액 TOP 5</strong> — 섹션 6의 파이프라인(filter → reduce →
          Object.entries → map → sort → slice)을 그대로, ② <strong>월별 매출 집계</strong> —
          섹션 4의 <span className="icode">slice(0, 7)</span> 그룹핑 패턴을 그대로 쓰면 됩니다.
          단, for문·while문은 <strong>금지</strong>예요. 칼질은 전부 배열 메서드로!
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🔪 여기까지 왔다면</h3>
          <p className="muted">
            이제 날것의 JSON 앞에서 막막해하는 대신, <strong>골라서(filter) → 모양 바꾸고(map)
            → 접고(reduce) → 줄 세우는(sort)</strong> 요리 순서가 머리에 그려질 거예요. 원본을
            지키는 불변성 감각, 초기값·반환 누락 같은 덫의 위치, 중첩 데이터를 펼치고 방어하는
            요령까지 — 화면에 올릴 데이터를 스스로 다듬는 손을 갖췄습니다. 그런데 이 재료들,
            애초에 <strong>서버가 어떤 모양으로 주느냐</strong>도 누군가의 설계죠. 다음{' '}
            <Link to="/learn/lv2-rest-api-design"><strong>REST API 설계: 주소와 동사의 문법</strong></Link>{' '}
            코스에서, 이번엔 데이터를 <strong>건네는 쪽</strong>의 문법을 배워 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
