;; scittle-run filter + 行の内訳 over the embedded industry rows (no build step).
;; marketplace-page.cljs と同じ形。ここが答えるのは「どの ISIC コードが、
;; どの状態で、どれだけのスコアか」だけで、数は 1 つも計算しない
;; （全部 scripts/generate-industries.cljs が埋め込んだ値をそのまま出す。
;; bp → % の割り算だけは表示のためにここでやる）。
(ns industries.page)


;; ── 言語 ───────────────────────────────────────────────────────────────
;; このページの markup は生成時に訳されている（cloud-itonami.site.i18n）が、
;; **行と内訳はここが実行時に組み立てる**ので、その語はここに要る。
;; 文書が `<html lang>` で名乗っている言語をそのまま読む —— ページ本文と
;; 表の中で言語が食い違うのが、この作業が直している当の欠陥なので、
;; locale の出所は 1 つにする。英語が既定で、未知の言語は英語に落ちる。
(def ^:private messages
  {"en" {:implemented "Implemented"
         :spec "Spec only"
         :unmeasured "Not measured"
         :unmeasured-title "the maturity scan did not include this repo (this is not a score of zero)"
         :tier-axes "Seven axes (straight from observation)"
         :tier-folded "Folded values"
         :dt-maturity "maturity"
         :dt-effective "after propagation"
         :dt-kind "kind"
         :dt-layer "layer"
         :note-folded (str "Maturity weights the seven axes, and the weights are a judgement "
                           "(they differ by kind of repo). Post-propagation and band are model "
                           "output, not observation.")
         :note-unmeasured (str "This repo is not in the maturity scan, so there is no breakdown. "
                               "<strong>That is not-measured, not a score of zero</strong> "
                               "(it is excluded from the mean as well).")
         :axis-substrate "implementation size"
         :axis-test "tests"
         :axis-governed "governor parts"
         :axis-ingest "sources cited"
         :axis-docs "documentation"
         :axis-surface "public surface"
         :axis-fresh "recency"}
   "ja" {:implemented "実装済み"
         :spec "spec のみ"
         :unmeasured "未計測"
         :unmeasured-title "maturity scan にこの repo が無い（スコア 0 ではない）"
         :tier-axes "7 軸（観測から直接）"
         :tier-folded "畳んだ値"
         :dt-maturity "成熟度"
         :dt-effective "依存伝播後"
         :dt-kind "種類"
         :dt-layer "層"
         :note-folded (str "成熟度は 7 軸に重みを掛けたもので、重みは判断"
                           "（repo の種類ごとに違う）。依存伝播後と band はモデルの"
                           "出力で、観測ではありません。")
         :note-unmeasured (str "この repo は maturity scan に入っていないので"
                               "内訳がありません。<strong>スコア 0 ではなく未計測</strong>です"
                               "（平均の分母からも外してあります）。")
         :axis-substrate "実装量"
         :axis-test "テスト"
         :axis-governed "governor 部品"
         :axis-ingest "出典引用"
         :axis-docs "文書"
         :axis-surface "公開面"
         :axis-fresh "更新の新しさ"}})

(def ^:private t
  (let [tag (.toLowerCase (or (.getAttribute js/document.documentElement "lang") "en"))]
    (merge (get messages "en") (get messages tag))))

(def entries
  (js->clj (js/JSON.parse (.-textContent (js/document.getElementById "industries-data")))
           :keywordize-keys true))

(def by-id (into {} (map (juxt :id identity)) entries))

(defn- esc [s]
  (-> (str s) (.replaceAll "&" "&amp;") (.replaceAll "<" "&lt;") (.replaceAll ">" "&gt;")))

(defn- state-label [e]
  (if (= "implemented" (:state e)) (:implemented t) (:spec t)))

;; **未計測を 0 と書かない。** own が nil の行は「—」+ muted で出す。
(defn- score-cell [e]
  (if (some? (:own e))
    (str "<td class=\"num\">" (esc (:own e)) "</td>")
    (str "<td class=\"num muted\" title=\"" (esc (:unmeasured-title t)) "\">"
         (esc (:unmeasured t)) "</td>")))

;; ── 内訳 ───────────────────────────────────────────────────────────────
;; 軸の並びは itonami-maturity の重みテーブルと同じ順。日本語の見出しは
;; ページ本文の「読み方」と同じ語を使う（同じものを 2 通りに呼ばない）。
(def axis-labels
  [[:substrate (:axis-substrate t)] [:test (:axis-test t)]
   [:governed (:axis-governed t)] [:ingest (:axis-ingest t)]
   [:docs (:axis-docs t)] [:surface (:axis-surface t)] [:fresh (:axis-fresh t)]])

(defn- meter-html
  "section カードと同じ .meter/.track/.fill を使う。同じ量を 2 通りの見た目で
  出すと、別の指標に見える。bp(0-10000) → %。"
  [label bp]
  (let [pct (js/Math.round (/ bp 100))]
    (str "<div class=\"meter\"><div class=\"meter-top\"><span>" (esc label)
         "</span><b>" pct "%</b></div>"
         ;; 0% でも 1% 幅は描く（track の中に何も無いと『未計測』と紛れる）。
         ;; 未計測の行はそもそもこの関数を通らない。
         "<div class=\"track\"><span class=\"fill\" style=\"width:"
         (max 1 pct) "%\"></span></div></div>")))

(defn- links-html [e]
  (let [ls (cond-> []
             (:repo e) (conj (str "<a href=\"" (esc (:repo e)) "\">repo</a>"))
             (:demo e) (conj (str "<a href=\"" (esc (:demo e)) "\">demo</a>")))]
    (if (seq ls)
      (str "<p class=\"detail-links\">" (apply str (interpose " " ls)) "</p>")
      "")))

(defn- detail-html [e]
  (str
   "<tr class=\"detail\" id=\"d-" (esc (:id e)) "\"><td colspan=\"7\">"
   (if (:axes e)
     (str
      "<div class=\"detail-grid\">"
      "<div><p class=\"detail-tier\">" (esc (:tier-axes t)) "</p>"
      "<div class=\"detail-axes\">"
      (apply str (map (fn [[k label]] (meter-html label (get (:axes e) k 0))) axis-labels))
      "</div></div>"
      "<div><p class=\"detail-tier\">" (esc (:tier-folded t)) "</p><dl class=\"detail-meta\">"
      "<dt>" (esc (:dt-maturity t)) "</dt><dd>" (esc (:own e)) "</dd>"
      "<dt>" (esc (:dt-effective t)) "</dt><dd>" (esc (or (:effective e) "—")) "</dd>"
      "<dt>band</dt><dd>" (esc (or (:band e) "—")) "</dd>"
      "<dt>" (esc (:dt-kind t)) "</dt><dd>" (esc (or (:kind e) "—")) "</dd>"
      "<dt>" (esc (:dt-layer t)) "</dt><dd>" (esc (or (:layer e) "—")) "</dd>"
      "</dl>"
      ;; note は <strong> を含むので esc しない（この 2 本だけは markup）。
      "<p class=\"detail-note\">" (:note-folded t) "</p>"
      (links-html e)
      "</div></div>")
     (str "<p class=\"detail-note\">" (:note-unmeasured t) "</p>"
          (links-html e)))
   "</td></tr>"))

(defn- row-html [e]
  (str "<tr><td class=\"num\">"
       "<button type=\"button\" class=\"rowtoggle\" aria-expanded=\"false\""
       " aria-controls=\"d-" (esc (:id e)) "\" data-id=\"" (esc (:id e)) "\">"
       (esc (:id e)) "</button></td>"
       "<td class=\"num\">" (esc (or (:section e) "—")) "</td>"
       "<td>" (if (:repo e)
                (str "<a href=\"" (esc (:repo e)) "\">" (esc (:name e)) "</a>")
                (esc (:name e))) "</td>"
       "<td class=\"state\">" (state-label e) "</td>"
       (score-cell e)
       "<td class=\"num" (if (:band e) "" " muted") "\">" (esc (or (:band e) "—")) "</td>"
       "<td>" (if (:demo e) (str "<a href=\"" (esc (:demo e)) "\">demo</a>") "—") "</td></tr>"))

(defn- matches? [q sec state e]
  (and (or (= q "") (.includes (.toLowerCase (str (:id e) " " (:name e))) q))
       (or (= sec "") (= sec (:section e)))
       (case state
         "" true
         "implemented" (= "implemented" (:state e))
         "spec" (= "spec" (:state e))
         "measured" (some? (:own e))
         "unmeasured" (nil? (:own e))
         true)))

(defn- render! []
  (let [q (.toLowerCase (.-value (js/document.getElementById "q")))
        sec (.-value (js/document.getElementById "sec"))
        state (.-value (js/document.getElementById "state"))
        hits (filter (partial matches? q sec state) entries)]
    ;; 絞り込みを変えると tbody を作り直すので、開いていた内訳は閉じる。
    ;; 行の同一性を保って開閉だけ残す実装もできるが、651 行を差分更新する
    ;; 価値より、状態が 1 箇所（DOM）に閉じている方が壊れにくい。
    (set! (.-innerHTML (js/document.getElementById "rows")) (apply str (map row-html hits)))
    (set! (.-hidden (js/document.getElementById "empty")) (boolean (seq hits)))))

;; 開閉は tbody への委譲。再描画のたびに 651 個の listener を張り直さない。
(defn- toggle! [btn]
  (let [id (.getAttribute btn "data-id")
        open? (= "true" (.getAttribute btn "aria-expanded"))
        row (.closest btn "tr")
        existing (js/document.getElementById (str "d-" id))]
    (if open?
      (do (.setAttribute btn "aria-expanded" "false")
          (when existing (.remove existing)))
      (do (.setAttribute btn "aria-expanded" "true")
          (when-not existing
            (.insertAdjacentHTML row "afterend" (detail-html (get by-id id))))))))

(.addEventListener (js/document.getElementById "rows") "click"
                   (fn [ev]
                     (when-let [btn (.closest (.-target ev) "button.rowtoggle")]
                       (toggle! btn))))

(doseq [id ["q" "sec" "state"]]
  (.addEventListener (js/document.getElementById id)
                     (if (= id "q") "input" "change")
                     render!))
(render!)
