// note-feed.jsx — note（ブログ）の最新記事を自動表示
//
// 仕組み（取得の優先順位）:
//  1) note-rss.php … 当社サーバー側の取得窓口（最優先・最安定）。
//                    note の RSS をサーバーが代理取得し、タイトル/リンク/日付/
//                    抜粋/アイキャッチ画像 を JSON で返す。外部サービス非依存。
//  2) 外部CORSプロキシ … サーバー窓口が無い環境（プレビュー等）向けの保険。
//  3) localStorage キャッシュ … 一度表示できた内容を24時間保持。
//  note 公式には「最新記事の自動表示ウィジェット」が無いため、RSSを取得して
//  当サイト独自デザインのカードで描画しています。

const NOTE_USERNAME = "skyidea";
const NOTE_PROFILE_URL = "https://note.com/" + NOTE_USERNAME;
const NOTE_RSS_URL = "https://note.com/" + NOTE_USERNAME + "/rss";
const NOTE_PHP_ENDPOINT = "note-rss.php"; // 同一ドメインのサーバー窓口
const NOTE_ARTICLE_COUNT = 4;

// --- ユーティリティ ---------------------------------------------------------

function stripHtmlAndTruncate(html, max) {
  if (!html) return "";
  const tmp = document.createElement("div");
  tmp.innerHTML = html;
  let text = (tmp.textContent || tmp.innerText || "").replace(/\s+/g, " ").trim();
  if (text.length > max) text = text.slice(0, max).trim() + "…";
  return text;
}

function formatDate(str) {
  if (!str) return "";
  const d = new Date(String(str).replace(" ", "T"));
  if (isNaN(d.getTime())) return "";
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}.${m}.${day}`;
}

// --- キャッシュ（取得済みデータを localStorage に保持） ----------------------
// 一度でも取得に成功すれば、プロキシ不調時でも前回内容（画像含む）を即表示できる。
const NOTE_CACHE_KEY = "skyidea_note_cache_v3";
const NOTE_CACHE_TTL = 1000 * 60 * 60 * 24; // 24時間

// 旧バージョンのキャッシュは破棄
try {
  localStorage.removeItem("skyidea_note_cache_v1");
  localStorage.removeItem("skyidea_note_cache_v2");
} catch (e) {}

function readNoteCache() {
  try {
    const raw = localStorage.getItem(NOTE_CACHE_KEY);
    if (!raw) return null;
    const obj = JSON.parse(raw);
    if (!obj || !Array.isArray(obj.articles) || obj.articles.length === 0) return null;
    if (Date.now() - (obj.ts || 0) > NOTE_CACHE_TTL) return null;
    return obj.articles;
  } catch (e) {
    return null;
  }
}

function writeNoteCache(articles) {
  try {
    if (!articles || !articles.length) return;
    localStorage.setItem(NOTE_CACHE_KEY, JSON.stringify({ articles, ts: Date.now() }));
  } catch (e) {}
}

// --- 取得: 当社サーバー窓口（note-rss.php・最優先／画像つき） ---------------

async function fetchWithTimeout(url, ms) {
  const ctrl = new AbortController();
  const id = setTimeout(() => ctrl.abort(), ms);
  try {
    return await fetch(url, { signal: ctrl.signal });
  } finally {
    clearTimeout(id);
  }
}

async function fetchFromPhp() {
  const res = await fetchWithTimeout(
    NOTE_PHP_ENDPOINT + "?t=" + Math.floor(Date.now() / 60000),
    9000
  );
  if (!res.ok) throw new Error("note-rss.php HTTP " + res.status);
  const json = await res.json();
  if (json.status !== "ok" || !Array.isArray(json.items) || json.items.length === 0) {
    throw new Error("note-rss.php status: " + (json.status || "?"));
  }
  return json.items.slice(0, NOTE_ARTICLE_COUNT).map((it) => ({
    title: it.title || "（無題）",
    link: it.link,
    date: it.date || formatDate(it.pubDate),
    excerpt: it.excerpt || "",
    image: it.image || ""
  }));
}

// --- 取得: テキスト（rss2json・高速） ---------------------------------------

async function fetchTextItems() {
  const api =
    "https://api.rss2json.com/v1/api.json?rss_url=" + encodeURIComponent(NOTE_RSS_URL);
  const res = await fetch(api);
  if (!res.ok) throw new Error("rss2json HTTP " + res.status);
  const json = await res.json();
  if (json.status !== "ok" || !Array.isArray(json.items)) {
    throw new Error("rss2json status: " + (json.status || "?"));
  }
  return json.items.slice(0, NOTE_ARTICLE_COUNT).map((it) => ({
    title: it.title || "（無題）",
    link: it.link,
    date: formatDate(it.pubDate),
    excerpt: stripHtmlAndTruncate(it.description || it.content, 100),
    image: ""
  }));
}

// --- 取得: 生XML（画像つき / フォールバックにも使用） ------------------------
//
// note-rss.php が無い環境（プレビュー等）向けの保険。複数のCORSプロキシを
// 順に試し、media:thumbnail（アイキャッチ画像）を含む生RSSを取得する。

// プロキシ候補（上から順に試行）。応答が安定しているものを優先。
const XML_PROXIES = [
  (u) => "https://test.cors.workers.dev/?" + u,
  (u) => "https://api.allorigins.win/raw?url=" + encodeURIComponent(u),
];

function parseRssXml(xml) {
  const doc = new DOMParser().parseFromString(xml, "text/xml");
  const nodes = Array.from(doc.querySelectorAll("item")).slice(0, NOTE_ARTICLE_COUNT);
  if (nodes.length === 0) throw new Error("no items in RSS");
  return nodes.map((node) => {
    const get = (tag) => {
      const el = node.getElementsByTagName(tag)[0];
      return el ? el.getAttribute("url") || el.textContent || "" : "";
    };
    return {
      title: get("title") || "（無題）",
      link: get("link"),
      date: formatDate(get("pubDate")),
      excerpt: stripHtmlAndTruncate(get("description") || get("content:encoded"), 100),
      image: get("media:thumbnail") || get("media:content") || ""
    };
  });
}

async function fetchXmlItems() {
  let lastErr;
  for (const makeUrl of XML_PROXIES) {
    try {
      const res = await fetchWithTimeout(makeUrl(NOTE_RSS_URL), 9000);
      if (!res.ok) throw new Error("HTTP " + res.status);
      const xml = await res.text();
      return parseRssXml(xml);
    } catch (e) {
      lastErr = e;
    }
  }
  throw lastErr || new Error("all xml proxies failed");
}

// --- noteアイコン -----------------------------------------------------------

function NoteMark() {
  return (
    <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
      <path
        fill="currentColor"
        d="M3 5.4C3 4.07 4.07 3 5.4 3h9.46c.86 0 1.5.27 2.2.95l3.06 3.02c.62.62.88 1.2.88 2.16V18.6c0 1.33-1.07 2.4-2.4 2.4H5.4A2.4 2.4 0 0 1 3 18.6V5.4Zm3.6 3.9c0-.5.4-.78.95-.78h7.7c.55 0 .9.3.9.78 0 .47-.35.77-.9.77h-7.7c-.55 0-.95-.3-.95-.77Zm0 4.06c0-.48.4-.78.95-.78h5.2c.56 0 .9.3.9.78 0 .47-.34.78-.9.78h-5.2c-.55 0-.95-.31-.95-.78Z"
      />
    </svg>
  );
}

// --- カード（サムネイル全面・全カード同サイズ） -----------------------------

function NoteCard({ article }) {
  // note のアイキャッチ画像にはタイトルが含まれるため、画像がある場合は
  // サムネイルのみを表示（シャドウ・タイトル等は重複になるので出さない）。
  // 画像が取得できなかった時だけ、情報が伝わるようタイトルを表示する。
  return (
    <a className="note-card" href={article.link} target="_blank" rel="noopener noreferrer">
      {article.image ? (
        <img className="note-card-img" src={article.image} alt={article.title} />
      ) : (
        <React.Fragment>
          <div className="note-card-img note-card-img--ph" aria-hidden="true">
            <NoteMark />
          </div>
          <div className="note-card-overlay">
            {article.date && <span className="note-card-date">{article.date}</span>}
            <h4 className="note-card-title">{article.title}</h4>
          </div>
        </React.Fragment>
      )}
    </a>
  );
}

function NoteCardSkeleton() {
  return (
    <div className="note-card note-card--skeleton" aria-hidden="true">
      <div className="sk-block"></div>
    </div>
  );
}

// --- セクション本体 ---------------------------------------------------------

function NoteFeed() {
  const [state, setState] = React.useState("loading"); // loading | ready | error
  const [articles, setArticles] = React.useState([]);

  React.useEffect(() => {
    let alive = true;

    // 0) キャッシュがあれば即時表示（取得失敗時でも内容を維持）
    const cached = readNoteCache();
    if (cached) {
      setArticles(cached);
      setState("ready");
    }

    // 既存カードの画像をリンク単位で保持してマージ
    const keepImages = (items, prev) => {
      const imgByLink = {};
      (prev || []).forEach((p) => { if (p.image) imgByLink[p.link] = p.image; });
      return items.map((a) =>
        !a.image && imgByLink[a.link] ? { ...a, image: imgByLink[a.link] } : a
      );
    };

    // 1) 最優先: 当社サーバー窓口（note-rss.php）。画像つきで1回で揃う。
    fetchFromPhp()
      .then((items) => {
        if (!alive) return;
        setArticles(items);
        writeNoteCache(items);
        setState("ready");
      })
      .catch(() => {
        // 2) 窓口が無い/失敗 → 外部プロキシでフォールバック
        if (!alive) return;
        const textP = fetchTextItems();
        const xmlP = fetchXmlItems();

        // 2a) まず速い方（rss2json・テキスト）で描画
        textP
          .then((items) => {
            if (!alive) return;
            setArticles((prev) => {
              const merged = keepImages(items, prev);
              writeNoteCache(merged);
              return merged;
            });
            setState("ready");
          })
          .catch(() => {});

        // 2b) XML が取れたら実画像を反映
        xmlP
          .then((xmlItems) => {
            if (!alive) return;
            setArticles((prev) => {
              const base = prev && prev.length ? prev : xmlItems;
              const byLink = {};
              xmlItems.forEach((x) => { if (x.link) byLink[x.link] = x; });
              const merged = base.map((a) => {
                const x = byLink[a.link];
                return x && x.image ? { ...a, image: x.image } : a;
              });
              writeNoteCache(merged);
              return merged;
            });
            setState("ready");
          })
          .catch(() => {});

        // 2c) すべて失敗し、キャッシュも無ければエラー表示
        Promise.allSettled([textP, xmlP]).then((rs) => {
          if (!alive) return;
          const allFailed = rs.every((r) => r.status === "rejected");
          if (allFailed && !cached) {
            console.warn("[note-feed] all feeds failed and no cache");
            setState("error");
          }
        });
      });

    return () => { alive = false; };
  }, []);

  return (
    <section className="section section-soft note-section" id="note">
      <div className="container">
        <div className="section-head">
          <div className="meta">
            <span className="section-num">JOURNAL</span>
            <span className="eyebrow">NOTE / COLUMN</span>
          </div>
          <div>
            <h2 className="sr-only">note 最新記事</h2>
            <p className="intro">
              DX / AX、情報セキュリティ、BCP に関する知見やニュースへの所感を、
              ブログサービス「note」で随時発信しています。最新の記事を自動で表示しています。
            </p>
          </div>
        </div>

        <div className="note-feed-wrap">
          {state === "loading" && (
            <div className="note-grid" style={{ "--note-cols": NOTE_ARTICLE_COUNT }}>
              {Array.from({ length: NOTE_ARTICLE_COUNT }).map((_, i) => (
                <NoteCardSkeleton key={i} />
              ))}
            </div>
          )}

          {state === "ready" && (
            <div className="note-grid" style={{ "--note-cols": Math.min(articles.length || NOTE_ARTICLE_COUNT, 4) }}>
              {articles.map((a, i) => (
                <NoteCard article={a} key={a.link || i} />
              ))}
            </div>
          )}

          {state === "error" && (
            <div className="note-fallback">
              <NoteMark />
              <p>
                最新記事の自動読み込みに失敗しました。
                お手数ですが、note のページから直接ご覧ください。
              </p>
              <a className="note-fallback-cta" href={NOTE_PROFILE_URL} target="_blank" rel="noopener noreferrer">
                note で記事を見る<span className="arrow">→</span>
              </a>
            </div>
          )}

          <div className="note-foot">
            <a className="note-alllink" href={NOTE_PROFILE_URL} target="_blank" rel="noopener noreferrer">
              <span className="note-alllink-mark"><NoteMark /></span>
              note のすべての記事を見る
              <span className="arrow">→</span>
            </a>
          </div>
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { NoteFeed });
