//375px 未満は JS で viewport を固定する
// =============================
(function () {
  // <meta name="viewport"> タグを取得（ビューポートの設定を変更するため）
  const viewport = document.querySelector('meta[name="viewport"]');

  // 画面幅に応じて viewport の content 属性を切り替える関数
  function switchViewport() {
    // 画面の外枠の幅（ブラウザのスクロールバーなどを含む）をチェック
    const value =
      window.outerWidth > 375
        ? // 375px より広い場合は、通常のレスポンシブ表示（端末幅に合わせる）
          "width=device-width,initial-scale=1"
        : // 375px 以下の狭い画面では、幅を固定（375px に強制）
          "width=375";

    // すでに設定されている content と異なる場合のみ変更を加える
    if (viewport.getAttribute("content") !== value) {
      viewport.setAttribute("content", value);
    }
  }

  // ウィンドウサイズが変更されたときに switchViewport を実行する
  addEventListener("resize", switchViewport, false);

  // 初回読み込み時にも現在の幅に応じて viewport を設定する
  switchViewport();
})();

// =============================
// ハンバーガーメニュー
// =============================
// メニューを開いている時、背景が動かないように固定する

const CLASS = "is-checked";
let savedScrollY = 0; // スクロール位置を保存

// 背景を固定/解除する関数
const backgroundFix = (isOpen) => {
  const scrollingElement =
    "scrollingElement" in document
      ? document.scrollingElement
      : document.documentElement;

  if (isOpen) {
    // 現在のスクロール位置を保存
    savedScrollY = scrollingElement.scrollTop;
    // CSS変数にスクロール位置を設定（マイナス値）
    document.body.style.setProperty("--scroll-y", `${savedScrollY * -1}px`);
    document.body.classList.add("is-menu-open");
  } else {
    document.body.classList.remove("is-menu-open");
    // スクロール位置を元に戻す
    window.scrollTo(0, savedScrollY);
    document.body.style.removeProperty("--scroll-y");
  }
};

const $hamburger = jQuery("#js-drawer-button");
const $menu = jQuery("#js-drawer-content");
const $focusTrap = jQuery("#js-focus-trap");
const $firstLink = jQuery(".js-header-link").first();
const $logoSecondary = jQuery(".header__logo-secondary a");

const closeMenu = () => {
  // 読み取り操作を先に実行（リフローを最小化）
  const hamburgerEl = $hamburger[0];

  // 書き込み操作をrequestAnimationFrameでバッチ化
  requestAnimationFrame(() => {
    $hamburger.removeClass(CLASS).attr({
      "aria-expanded": "false",
      "aria-haspopup": "menu",
    });
    $menu.removeClass(CLASS);
    backgroundFix(false);

    // フォーカス操作は最後に実行
    requestAnimationFrame(() => {
      hamburgerEl.focus();
    });
  });
};

const openMenu = () => {
  // 読み取り操作を先に実行
  const logoSecondaryEl = $logoSecondary.length ? $logoSecondary[0] : null;
  const hamburgerEl = $hamburger[0];

  // 書き込み操作をrequestAnimationFrameでバッチ化
  requestAnimationFrame(() => {
    $hamburger
      .addClass(CLASS)
      .attr("aria-expanded", "true")
      .removeAttr("aria-haspopup");
    $menu.addClass(CLASS);
    backgroundFix(true);

    // フォーカス操作は最後に実行
    requestAnimationFrame(() => {
      if (logoSecondaryEl) {
        logoSecondaryEl.focus();
      } else {
        hamburgerEl.focus();
      }
    });
  });
};

$hamburger.on("click", function (e) {
  e.preventDefault();
  // 読み取り操作を先に実行
  const isOpen = $hamburger.hasClass(CLASS);
  // 書き込み操作はrequestAnimationFrameで実行
  requestAnimationFrame(() => {
    isOpen ? closeMenu() : openMenu();
  });
});

jQuery(window).on("keydown", (e) => {
  // 読み取り操作を先に実行
  const isOpen = $hamburger.hasClass(CLASS);
  if (e.key === "Escape" && isOpen) {
    // 書き込み操作はrequestAnimationFrameで実行
    requestAnimationFrame(() => {
      closeMenu();
    });
  }
});

// フォーカストラップ：メニュー内でキーボード操作を閉じ込める
$focusTrap.on("focus", () => {
  $hamburger.focus();
});

// ページ内リンクをクリックした時にメニューを閉じる
jQuery('#js-drawer-content a[href^="#"]').on("click", closeMenu);

// =============================
// リンク先にスクロール（強制リフロー軽減: offset().top の読み取りを rAF 内で実行）
// =============================
jQuery('a[href^="#"]').on("click", function (e) {
  const id = jQuery(this).attr("href");
  e.preventDefault();
  const speed = 300;
  const target = id === "#" ? jQuery("html") : jQuery(id);
  requestAnimationFrame(() => {
    const position = target.offset().top;
    jQuery("html, body").animate({ scrollTop: position }, speed, "swing");
  });
});

// =============================
// 要素が画面に入った時に見出しを表示
// =============================
const intersectionObserver = new IntersectionObserver(function (entries) {
  entries.forEach(function (entry) {
    if (entry.isIntersecting) {
      entry.target.classList.add("is-in-view");
    } else {
      // entry.target.classList.remove("is-in-view");
    }
  });
});

const inViewItems = document.querySelectorAll(".js-in-view");
inViewItems.forEach(function (inViewItem) {
  intersectionObserver.observe(inViewItem);
});

// =============================
// ページトップに戻るボタン（強制リフロー軽減のため rAF でスロットル）
// =============================
// scrollTop の読み取りはレイアウトを発生させるため、rAF 内で1フレームに1回だけ実行する。

let pagetopScrollScheduled = false;
jQuery(window).on("scroll", function () {
  if (pagetopScrollScheduled) return;
  pagetopScrollScheduled = true;
  requestAnimationFrame(function () {
    pagetopScrollScheduled = false;
    if (100 < jQuery(window).scrollTop()) {
      jQuery("#js-pagetop").addClass("is-show");
    } else {
      jQuery("#js-pagetop").removeClass("is-show");
    }
  });
});

// =============================
// モーダル
// =============================

const modalEl = jQuery("#js-about-modal")[0];
const $modalEl = jQuery(modalEl);
let lastFocusedElement = null;
let modalTriggerButton = null;

// モーダル内でフォーカス可能な要素を取得
const getFocusableInModal = () =>
  jQuery(modalEl)
    .find(
      'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])',
    )
    .filter(":visible");

// モーダルを閉じてフォーカスを元の要素に戻す
const closeModal = () => {
  if (!modalEl || !modalEl.open) return;

  if (typeof modalEl.close === "function") {
    modalEl.close();
  } else {
    jQuery(modalEl).removeAttr("open");
  }

  if (modalTriggerButton) {
    jQuery(modalTriggerButton).attr("aria-expanded", "false");
  }

  if (lastFocusedElement && typeof lastFocusedElement.focus === "function") {
    lastFocusedElement.focus();
  }
};

// モーダルを開く
jQuery(".js-modal-open").on("click", function (e) {
  e.preventDefault();

  // 開く直前のフォーカス位置を保存
  lastFocusedElement = document.activeElement;
  modalTriggerButton = this;

  jQuery(this).attr("aria-expanded", "true");

  if (!modalEl) return;

  if (typeof modalEl.showModal === "function") {
    modalEl.showModal();
  } else {
    jQuery(modalEl).attr("open", "open");
  }

  // モーダル内の先頭フォーカス可能要素へフォーカスを移動
  const $focusable = getFocusableInModal();
  const $first = $focusable.first();

  if ($first.length) {
    $first.trigger("focus");
  } else {
    jQuery(modalEl).attr("tabindex", "-1").trigger("focus");
  }
});

// 閉じるボタンでモーダルを閉じる
jQuery(".js-modal-close").on("click", function (e) {
  e.preventDefault();

  closeModal();
});

// Escキー（cancelイベント）で閉じる
$modalEl.on("cancel", function (e) {
  e.preventDefault();
  closeModal();
});

// ============================================
// アコーディオン機能
// ============================================
// FAQセクションのアコーディオン（開閉可能な質問と回答）を実装します。
// クリックでスムーズに開閉するアニメーションを提供します。

// DOMContentLoadedイベント: HTMLの読み込みが完了したときに発生するイベント
// このタイミングで実行することで、HTML要素が確実に存在する状態で処理を開始できます
// addEventListenerメソッド: 要素やドキュメントにイベントリスナーを登録する
// - 第1引数: イベント名（"DOMContentLoaded"）
// - 第2引数: イベント発生時に実行する関数（アロー関数）
document.addEventListener("DOMContentLoaded", () => {
  setUpAccordion();
});

/**
 * 既存のtransitionendイベントリスナーをクリーンアップする関数
 *
 * なぜ必要か: 連続でクリックされた場合、前回のtransitionendイベントリスナーが
 * 残っている可能性があります。これを削除して、イベントリスナーの重複登録を防ぎます。
 *
 * @param {HTMLElement} content - コンテンツ要素（開閉される回答部分の要素）
 *
 * @paramについて:
 * - @param: JSDocのタグ（関数のパラメータを説明するための記号）
 *   関数の引数（パラメータ）について説明する際に使用します
 *
 * - {HTMLElement}: 型注釈（このパラメータがどんな型のデータかを示す）
 *   HTML要素を表すオブジェクトの型です
 *   JavaScriptでは型が明示されませんが、このコメントで型を示すことで、
 *   コードの可読性が向上し、開発ツール（IDE）での補完やエラーチェックに役立ちます
 *   - HTMLElement: HTML要素全般を表す型
 *   - 例: <div>、<span>、<p>などの要素がこれに該当します
 *
 * - content: パラメータ名（関数の引数の名前）
 *   この関数を呼び出すときに渡す引数の名前です
 *   関数内では「content」という名前でこの引数にアクセスできます
 *
 * 具体例:
 *   cleanupTransitionHandler(contentElement);
 *   ↑この「contentElement」が「content」パラメータとして渡されます
 *
 * 処理の流れ:
 * 1. content._transitionHandler: 独自のプロパティとして保存したハンドラー関数への参照を取得
 *    （このプロパティはsetupTransitionHandler関数で設定されます）
 * 2. 既存のハンドラーがあれば、removeEventListenerメソッドで削除
 * 3. _transitionHandlerプロパティをnullに設定してクリア
 */
const cleanupTransitionHandler = (content) => {
  // _transitionHandler: 要素に独自に追加したプロパティ（後で削除するために保存しておく参照）
  const existingHandler = content._transitionHandler;
  if (existingHandler) {
    // removeEventListenerメソッド: 登録したイベントリスナーを削除する
    // - 第1引数: イベント名（"transitionend"）
    // - 第2引数: 削除するハンドラー関数（登録した時と同じ関数を指定する必要がある）
    content.removeEventListener("transitionend", existingHandler);
    // プロパティをnullに設定してクリア
    content._transitionHandler = null;
  }
};

/**
 * アニメーションを開始する関数（リフローとrequestAnimationFrameの処理）
 *
 * この関数は、CSSのtransitionアニメーションを確実に動作させるために必要な処理を行います。
 *
 * @param {HTMLElement} content - コンテンツ要素（アニメーション対象の要素）
 *
 * @paramについて:
 * - @param: JSDocのタグ（関数のパラメータを説明するための記号）
 * - {HTMLElement}: 型注釈（このパラメータがHTMLElement型のオブジェクトであることを示す）
 *   HTML要素（<div>、<span>など）を表す型です
 * - content: パラメータ名（この関数内で使用する引数の名前）
 *
 * @param {Function} callback - アニメーション開始時に実行するコールバック関数
 *                              （この中でstyle.heightなどを変更してアニメーションを開始します）
 *
 * @paramについて（2つ目のパラメータ）:
 * - @param: JSDocのタグ
 * - {Function}: 型注釈（このパラメータがFunction型、つまり関数であることを示す）
 *   実行可能な関数を渡すことを意味します
 * - callback: パラメータ名（コールバック関数を表す一般的な名前）
 *   この関数内で呼び出される関数です
 *
 * 具体例:
 *   startAnimation(contentElement, () => {
 *     contentElement.style.height = "100px";
 *   });
 *   ↑「contentElement」が「content」パラメータ、「() => {...}」が「callback」パラメータとして渡されます
 *
 * 処理の流れ:
 * 1. リフロー（再レンダリング）を強制的に発生させる
 * 2. requestAnimationFrameを二重ネストで実行して、ブラウザの描画タイミングを待つ
 * 3. コールバック関数を実行してアニメーションを開始
 *
 * なぜリフローが必要か:
 * - ブラウザは複数のスタイル変更をまとめて処理するため、
 *   直前のstyle.heightの変更が認識されない場合があります
 * - offsetHeightプロパティにアクセスすると、ブラウザが要素のレイアウトを再計算します
 * - これにより、直前のスタイル変更が確実にブラウザに認識されます
 *
 * なぜrequestAnimationFrameを二重ネストするか:
 * - 1回だけでは、スタイル変更とアニメーション開始が同じフレームで処理される可能性があります
 * - 二重ネストすることで、ブラウザの描画サイクルを確実に待ちます
 * - これにより、アニメーションが正常に動作します
 *
 * @param {boolean} [skipFlush=false] - true のときは offsetHeight を読まない（呼び出し元で既にリフロー済みのとき用・強制リフロー軽減）
 */
const startAnimation = (content, callback, skipFlush = false) => {
  if (!skipFlush) {
    void content.offsetHeight; // 直前のスタイル変更を適用させるため1回だけリフロー
  }

  requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      callback();
    });
  });
};

/**
 * transitionendイベントハンドラーを設定する関数
 *
 * CSSのtransitionアニメーションが完了したときに実行される処理を設定します。
 * アニメーション完了後の後処理（クリーンアップ）を行うために使用されます。
 *
 * @param {HTMLElement} content - コンテンツ要素（アニメーション対象の要素）
 *
 * @paramについて:
 * - @param: JSDocのタグ（関数のパラメータを説明するための記号）
 * - {HTMLElement}: 型注釈（このパラメータがHTMLElement型のオブジェクトであることを示す）
 *   HTML要素を表す型です
 * - content: パラメータ名（この関数内で使用する引数の名前）
 *
 * @param {Function} onComplete - アニメーション完了時に実行するコールバック関数
 *                                （インラインスタイルの削除などを行う）
 *
 * @paramについて（2つ目のパラメータ）:
 * - @param: JSDocのタグ
 * - {Function}: 型注釈（このパラメータがFunction型、つまり関数であることを示す）
 * - onComplete: パラメータ名（完了時に実行される関数であることを示す名前）
 *   "on"は「〜のときに」、"Complete"は「完了」を意味し、「完了時に実行される関数」という意味になります
 *
 * 具体例:
 *   setupTransitionHandler(contentElement, () => {
 *     contentElement.style.height = "auto";
 *   });
 *   ↑「contentElement」が「content」パラメータ、「() => {...}」が「onComplete」パラメータとして渡されます
 *
 * 処理の流れ:
 * 1. transitionendイベントが発生したときに実行されるハンドラー関数を作成
 * 2. イベントがcontent要素のheightプロパティの変化によるものか確認
 * 3. 条件が一致したら、onCompleteコールバックを実行
 * 4. イベントリスナーを削除してメモリリークを防止
 * 5. ハンドラー関数への参照をcontent._transitionHandlerに保存（後で削除できるように）
 */
const setupTransitionHandler = (content, onComplete) => {
  // transitionendイベントハンドラー: CSSトランジション（アニメーション）が完了したときに実行される関数
  const handler = (e) => {
    // e.target: イベントが発生した要素
    // e.propertyName: アニメーションが完了したプロパティ名（"height"、"opacity"など）
    // 複数のプロパティがアニメーションしている場合、すべてのtransitionendイベントが発生するため、
    // 対象の要素とプロパティをチェックして、適切なタイミングでのみ処理を実行します
    if (e.target === content && e.propertyName === "height") {
      // アニメーション完了後の処理を実行（コールバック関数）
      onComplete();

      // イベントリスナーを削除（メモリリーク防止）
      content.removeEventListener("transitionend", handler);
      // 独自プロパティをクリア
      content._transitionHandler = null;
    }
  };

  // addEventListenerメソッド: 要素にイベントリスナーを登録
  // - 第1引数: イベント名（"transitionend"）
  // - 第2引数: イベント発生時に実行する関数（handler）
  content.addEventListener("transitionend", handler);

  // 後で削除できるように、ハンドラー関数への参照を保存
  // _transitionHandler: 独自のプロパティとして保存（cleanupTransitionHandler関数で使用）
  content._transitionHandler = handler;
};

/**
 * アコーディオン機能の初期設定を行う関数
 *
 * 処理の流れ:
 * 1. ページ内のすべてのFAQアイテムを取得
 * 2. 各アイテムにクリックイベントを設定
 * 3. 最初のアイテムを初期状態で開いた状態にする（HTMLのopen属性がある場合）
 * 4. クリック時に開閉を切り替え、スムーズなアニメーションを実現
 */
const setUpAccordion = () => {
  // querySelectorAllメソッド: 指定したCSSセレクタに一致するすべての要素を取得する
  // - document: HTMLドキュメント全体を表すオブジェクト
  // - ".faq__item": CSSクラス名faq__itemを持つすべての要素
  // - 戻り値: NodeList（配列のようなオブジェクト）で、見つかったすべての要素が含まれる
  const details = document.querySelectorAll(".faq__item");

  // CSSクラス名の定数定義
  // 定数として定義することで、クラス名を変更する際に1箇所修正するだけで済みます
  const IS_OPENED_CLASS = "is-opened"; // アコーディオンが開いている状態を示すクラス（親要素に適用）

  // forEachメソッド: 配列やNodeListの各要素に対して順番に処理を実行する
  // - element: 現在処理中の要素（この場合は1つ1つの.faq__item要素）
  //   配列やNodeListの中の各要素が順番に代入されます
  //   例: 1回目のループでは1つ目の.faq__item、2回目では2つ目の.faq__item...
  // - index: 現在処理中の要素のインデックス（番号）
  //   配列やNodeListの中での位置を示す数値（0から始まる）
  //   例: 1つ目の要素なら0、2つ目の要素なら1、3つ目の要素なら2...
  //   用途: 最初の要素（index === 0）だけを初期表示で開く処理などに使用
  details.forEach((element, index) => {
    // querySelectorメソッド: 指定した要素の子孫要素の中から、CSSセレクタに一致する最初の1つの要素を取得する
    // - element: 親要素（現在処理中の.faq__item要素）
    // - ".faq__question": CSSクラス名faq__questionを持つ要素を検索
    // - 戻り値: 見つかった要素（見つからない場合はnull）
    // - 重要: 複数存在する場合は、最初の1つだけを返す
    const summary = element.querySelector(".faq__question"); // クリック可能な質問部分
    const content = element.querySelector(".faq__answer"); // 開閉される回答部分

    // 必要な要素が存在しない場合は処理をスキップ（エラー防止）
    // return文: 関数の実行を途中で終了する
    // この場合、forEeachのループから抜けて次の要素の処理に進みます
    if (!summary || !content) return;

    // ============================================
    // 初期状態の設定
    // ============================================
    // 最初のアコーディオン（index === 0）で、HTMLにopen属性がある場合のみ
    // 初期表示時に開いた状態にします
    //
    // hasAttributeメソッド: HTML要素が指定した属性を持っているか確認する
    // - 第1引数: 属性名（"open"）
    // - 戻り値: 属性があればtrue、なければfalse
    if (index === 0 && element.hasAttribute("open")) {
      // 初期状態で open 属性が付いている要素にはクラスだけ同期させる
      element.classList.add(IS_OPENED_CLASS);
    }

    // ============================================
    // クリックイベントの設定
    // ============================================
    // addEventListenerメソッド: 要素にイベントリスナー（イベントが発生したときに実行する関数）を追加する
    // - 第1引数: イベント名（"click" → マウスクリックまたはタッチ時のイベント）
    // - 第2引数: イベント発生時に実行する関数（アロー関数）
    //   event: イベントオブジェクト（イベントに関する情報が含まれる）
    summary.addEventListener("click", (event) => {
      // preventDefaultメソッド: デフォルトの動作をキャンセルする
      // <details>要素の場合、デフォルトで開閉動作があるため、それを無効化して
      // 独自のアニメーション処理を実行できるようにします
      event.preventDefault();

      // classList.containsメソッド: 要素が指定したクラスを持っているか確認する
      // - 第1引数: 確認するクラス名
      // - 戻り値: クラスがあればtrue、なければfalse
      // isOpen: アコーディオンが開いているかどうかの状態
      const isOpen = element.hasAttribute("open");

      if (isOpen) {
        // ============================================
        // 閉じる処理
        // ============================================
        // classList.removeメソッド: 指定したクラス名を要素から削除する
        // - 第1引数: 削除するクラス名
        element.classList.remove(IS_OPENED_CLASS);
        // open属性も即座に削除して、CSSの[open]セレクタが効かないようにする
        element.removeAttribute("open");
        // インラインスタイルは使わない
        content.style.height = "";
        content.style.opacity = "";
      } else {
        // ============================================
        // 開く処理
        // ============================================
        element.classList.add(IS_OPENED_CLASS);
        element.setAttribute("open", "true");
      }
    });
  });
};

// =============================
// ギャラリースライダー（強制リフロー軽減のため遅延初期化）
// =============================
// Swiper は初期化時に offsetWidth 等を読むためリフローが発生する。
// ギャラリーがビューポートに入るまで初期化を遅らせ、初回描画の負荷を減らす。

const galleryEl = document.querySelector("#js-gallery-swiper");

const initGallerySwiper = () => {
  // 既に Swiper が読み込まれている場合はそのまま初期化
  if (window.Swiper) {
    // eslint-disable-next-line no-undef
    new Swiper("#js-gallery-swiper", {
      spaceBetween: 82,
      loop: true,
      pagination: {
        el: "#js-gallery-pagination",
      },
      navigation: {
        nextEl: "#js-gallery-next",
        prevEl: "#js-gallery-prev",
      },
    });
    return;
  }

  // Swiper 本体をまだ読み込んでいない場合は、ここで遅延ロード
  const script = document.createElement("script");
  script.src = "./js/lib/swiper-bundle.min.js";
  script.async = true;
  script.onload = () => {
    // eslint-disable-next-line no-undef
    new Swiper("#js-gallery-swiper", {
      spaceBetween: 82,
      loop: true,
      pagination: {
        el: "#js-gallery-pagination",
      },
      navigation: {
        nextEl: "#js-gallery-next",
        prevEl: "#js-gallery-prev",
      },
    });
  };
  document.head.appendChild(script);
};

if (galleryEl) {
  const galleryObserver = new IntersectionObserver(
    (entries) => {
      const entry = entries[0];
      if (!entry || !entry.isIntersecting) return;
      galleryObserver.disconnect();

      initGallerySwiper();
    },
    { rootMargin: "100px", threshold: 0 },
  );
  galleryObserver.observe(galleryEl);
}

// =============================
// お問い合わせフォーム（エラー通知・アクセシビリティ）
// =============================
(function () {
  const form = document.getElementById("contact-form");
  const errorsBlock = document.getElementById("contact-errors");
  const errorsTitle = document.getElementById("contact-errors-title");
  const errorsList = document.getElementById("contact-errors-list");

  const fieldMessages = {
    "your-name": "お名前は必須です。入力してください。",
    "your-email": "メールアドレスは必須です。正しい形式で入力してください。",
    "your-topic": "お問い合わせ項目を選択してください。",
    "your-message": "お問い合わせ内容は必須です。入力してください。",
    "your-privacy": "プライバシーポリシーに同意のうえ、チェックしてください。",
  };

  function clearErrors() {
    if (!errorsBlock || !errorsList) return;
    errorsBlock.hidden = true;
    errorsList.innerHTML = "";
    form.querySelectorAll("[aria-invalid]").forEach(function (el) {
      el.setAttribute("aria-invalid", "false");
    });
    form.querySelectorAll(".form-field__error").forEach(function (el) {
      el.textContent = "";
    });
    form
      .querySelectorAll(".form-field, .form-text, .form-textarea")
      .forEach(function (el) {
        el.classList.remove("is-error");
      });
    form.querySelectorAll(".form-checkbox__input").forEach(function (el) {
      el.classList.remove("is-error");
    });
  }

  function showErrors(invalidFields) {
    if (!errorsBlock || !errorsTitle || !errorsList) return;
    clearErrors();
    if (invalidFields.length === 0) return;

    errorsTitle.textContent =
      "このフォームには" + invalidFields.length + "件のエラーがあります";
    errorsList.innerHTML = "";

    invalidFields.forEach(function (item) {
      const li = document.createElement("li");
      const a = document.createElement("a");
      a.href = "#" + item.id;
      a.textContent =
        fieldMessages[item.id] || item.id + "にエラーがあります。";
      a.id = item.id + "_errorlink";
      li.appendChild(a);
      errorsList.appendChild(li);

      const descEl = document.getElementById(item.id + "-error");
      if (descEl) {
        descEl.textContent = fieldMessages[item.id] || "入力に誤りがあります。";
      }

      const control = item.element;
      if (control) {
        control.setAttribute("aria-invalid", "true");
        control.classList.add("is-error");
        if (control.closest(".form-field")) {
          control.closest(".form-field").setAttribute("aria-invalid", "true");
          control.closest(".form-field").classList.add("is-error");
        }
      }
    });

    errorsBlock.hidden = false;
    const firstId = invalidFields[0].id;
    let firstEl = document.getElementById(firstId);
    if (firstId === "your-topic") {
      const firstRadio = form.querySelector('input[name="お問い合わせ項目"]');
      firstEl = firstRadio || firstEl;
    }
    if (firstEl && typeof firstEl.focus === "function") {
      firstEl.focus();
    }
  }

  function validateForm() {
    const invalidFields = [];
    const topicFieldset = form.querySelector(
      'fieldset[aria-describedby="your-topic-error"]',
    );
    const topicRadios = form.querySelectorAll('input[name="お問い合わせ項目"]');
    let topicChecked = false;
    topicRadios.forEach(function (r) {
      if (r.checked) topicChecked = true;
    });
    if (!topicChecked && topicFieldset) {
      invalidFields.push({ id: "your-topic", element: topicFieldset });
    }

    ["your-name", "your-email", "your-message"].forEach(function (id) {
      const el = form.querySelector("#" + id);
      if (el && !el.checkValidity()) {
        invalidFields.push({ id: id, element: el });
      }
    });

    const privacyEl = form.querySelector("#your-privacy");
    if (privacyEl && privacyEl.required && !privacyEl.checked) {
      invalidFields.push({ id: "your-privacy", element: privacyEl });
    }

    return invalidFields;
  }

  if (form) {
    form.addEventListener("submit", function (e) {
      const invalidFields = validateForm();
      if (invalidFields.length > 0) {
        e.preventDefault();
        showErrors(invalidFields);
        return false;
      }
      clearErrors();
    });
  }
})();

